Template
feat: Setup fake auth backend, need to wire it up
This commit is contained in:
1 parent
5e79129cde
commit
19703ae07f
8 files changed
+151
-27
No files matched your search
+27
-2
@@ -1,6 +1,12 @@
|
|||||||
use std::sync::Arc;
|
use std::{collections::HashMap, sync::Arc};
|
||||||
|
|
||||||
use crate::{models::state::SharedState, util::config::get_app_config};
|
use crate::{
|
||||||
|
models::{
|
||||||
|
auth::{Backend, User},
|
||||||
|
state::SharedState,
|
||||||
|
},
|
||||||
|
util::config::get_app_config,
|
||||||
|
};
|
||||||
|
|
||||||
mod app;
|
mod app;
|
||||||
mod error;
|
mod error;
|
||||||
@@ -12,8 +18,27 @@ mod util;
|
|||||||
async fn main() {
|
async fn main() {
|
||||||
let config = get_app_config().expect("Unable to read app config");
|
let config = get_app_config().expect("Unable to read app config");
|
||||||
|
|
||||||
|
let mut temp_users = HashMap::new();
|
||||||
|
temp_users.insert(
|
||||||
|
1,
|
||||||
|
User {
|
||||||
|
id: 1234,
|
||||||
|
username: "Cyper".into(),
|
||||||
|
pw_hash: b"123".to_vec(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
temp_users.insert(
|
||||||
|
2,
|
||||||
|
User {
|
||||||
|
id: 12345,
|
||||||
|
username: "Luuk".into(),
|
||||||
|
pw_hash: b"1234".to_vec(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
let state = Arc::new(SharedState {
|
let state = Arc::new(SharedState {
|
||||||
config: config.clone(),
|
config: config.clone(),
|
||||||
|
backend: Backend { users: temp_users },
|
||||||
});
|
});
|
||||||
|
|
||||||
// Build app with shared state
|
// Build app with shared state
|
||||||
|
|||||||
+18
-21
@@ -1,13 +1,15 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use axum_login::{AuthUser, AuthnBackend, UserId};
|
use axum_login::{AuthUser, AuthnBackend, UserId};
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
// User model - whatever type for `id` is used here needs to be reflected
|
// User model - whatever type for `id` is used here needs to be reflected
|
||||||
// in `AuthUser::Id` type value as well
|
// in `AuthUser::Id` type value as well
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct User {
|
pub struct User {
|
||||||
id: i32,
|
pub id: i32,
|
||||||
pw_hash: Vec<u8>,
|
pub username: String,
|
||||||
|
pub pw_hash: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AuthUser for User {
|
impl AuthUser for User {
|
||||||
@@ -26,19 +28,15 @@ impl AuthUser for User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct Backend {
|
pub struct Backend {
|
||||||
// Placeholder for a list of sessions that could/would come from database
|
// Placeholder for a list of sessions that could/would come from database
|
||||||
users: HashMap<i32, User>,
|
pub users: HashMap<i32, User>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is what is used to attempt to login. In this case, if someone attempts
|
#[derive(Clone, Deserialize)]
|
||||||
// to login with just a user_id, they will succeed if it is a valid ID. To make
|
pub struct Credentials {
|
||||||
// this a user/password login, the properties could be `username: String` and
|
pub username: String,
|
||||||
// `password: String`, then update the authentication function to get a user_id
|
pub password: String,
|
||||||
// from that to return from the `users.get(&User::Id)`
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct Credentials {
|
|
||||||
user_id: i32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AuthnBackend for Backend {
|
impl AuthnBackend for Backend {
|
||||||
@@ -48,17 +46,16 @@ impl AuthnBackend for Backend {
|
|||||||
|
|
||||||
async fn authenticate(
|
async fn authenticate(
|
||||||
&self,
|
&self,
|
||||||
Credentials { user_id }: Self::Credentials,
|
Credentials { username, password }: Self::Credentials,
|
||||||
) -> Result<Option<Self::User>, Self::Error> {
|
) -> Result<Option<Self::User>, Self::Error> {
|
||||||
// ex:
|
let user = self.users.values().find(|u| u.username == username);
|
||||||
// if let Some(user) = self.find_user_by_username(&creds.username) {
|
if let Some(user) = user {
|
||||||
// if verify_password(&creds.password, &user.password_hash) {
|
if user.pw_hash == password.as_bytes() {
|
||||||
// return Ok(Some(user));
|
return Ok(Some(user.clone()));
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// Ok(None)
|
|
||||||
|
|
||||||
Ok(self.users.get(&user_id).cloned())
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_user(&self, user_id: &UserId<Self>) -> Result<Option<Self::User>, Self::Error> {
|
async fn get_user(&self, user_id: &UserId<Self>) -> Result<Option<Self::User>, Self::Error> {
|
||||||
|
|||||||
+2
-1
@@ -1,9 +1,10 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::models::config::Config;
|
use crate::models::{auth::Backend, config::Config};
|
||||||
|
|
||||||
pub struct SharedState {
|
pub struct SharedState {
|
||||||
pub config: Config,
|
pub config: Config,
|
||||||
|
pub backend: Backend,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type AppState = Arc<SharedState>;
|
pub type AppState = Arc<SharedState>;
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
use askama::Template;
|
||||||
|
use axum::{
|
||||||
|
Form, Router,
|
||||||
|
extract::State,
|
||||||
|
response::{Html, IntoResponse, Redirect, Response},
|
||||||
|
routing::{get, post},
|
||||||
|
};
|
||||||
|
use axum_login::AuthnBackend;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error::AppError,
|
||||||
|
models::{auth::Credentials, config::SiteMetadata, state::AppState},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(path = "login.html")]
|
||||||
|
struct LoginPage {
|
||||||
|
metadata: SiteMetadata,
|
||||||
|
title: String,
|
||||||
|
error: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_login(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
Form(creds): Form<Credentials>,
|
||||||
|
) -> Result<impl IntoResponse, AppError> {
|
||||||
|
let err_msg: Option<String>;
|
||||||
|
|
||||||
|
match state.backend.authenticate(creds).await {
|
||||||
|
Ok(Some(_)) => return Ok(Redirect::to("/").into_response()),
|
||||||
|
Ok(None) => err_msg = Some(String::from("Invalid login")),
|
||||||
|
Err(_) => err_msg = Some(String::from("Authentication error")),
|
||||||
|
}
|
||||||
|
|
||||||
|
let template = LoginPage {
|
||||||
|
metadata: state.config.metadata.clone(),
|
||||||
|
title: String::from("Login"),
|
||||||
|
error: err_msg,
|
||||||
|
};
|
||||||
|
|
||||||
|
let html = template
|
||||||
|
.render()
|
||||||
|
.map(Html)
|
||||||
|
.map_err(|_| AppError::internal());
|
||||||
|
|
||||||
|
Ok(html.into_response())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn login_page(State(state): State<AppState>) -> Result<Html<String>, AppError> {
|
||||||
|
let template = LoginPage {
|
||||||
|
metadata: state.config.metadata.clone(),
|
||||||
|
title: String::from("Login"),
|
||||||
|
error: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
template
|
||||||
|
.render()
|
||||||
|
.map(Html)
|
||||||
|
.map_err(|_| AppError::internal())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn router() -> Router<AppState> {
|
||||||
|
Router::new()
|
||||||
|
.route("/login", post(handle_login))
|
||||||
|
.route("/login", get(login_page))
|
||||||
|
}
|
||||||
+2
-1
@@ -1,8 +1,9 @@
|
|||||||
use crate::models::state::AppState;
|
use crate::models::state::AppState;
|
||||||
use axum::Router;
|
use axum::Router;
|
||||||
|
|
||||||
|
mod auth;
|
||||||
mod root;
|
mod root;
|
||||||
|
|
||||||
pub fn router() -> Router<AppState> {
|
pub fn router() -> Router<AppState> {
|
||||||
Router::new().merge(root::router())
|
Router::new().merge(root::router()).merge(auth::router())
|
||||||
}
|
}
|
||||||
+5
-2
@@ -1,9 +1,10 @@
|
|||||||
use askama::Template;
|
use askama::Template;
|
||||||
use axum::{Router, extract::State, response::Html, routing::get};
|
use axum::{Router, extract::State, response::Html, routing::get};
|
||||||
|
use axum_login::login_required;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::AppError,
|
error::AppError,
|
||||||
models::{config::SiteMetadata, state::AppState},
|
models::{auth::Backend, config::SiteMetadata, state::AppState},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
@@ -26,5 +27,7 @@ async fn home(State(state): State<AppState>) -> Result<Html<String>, AppError> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn router() -> Router<AppState> {
|
pub fn router() -> Router<AppState> {
|
||||||
Router::new().route("/", get(home))
|
Router::new()
|
||||||
|
.route("/", get(home))
|
||||||
|
.route_layer(login_required!(Backend, login_url = "/login"))
|
||||||
}
|
}
|
||||||
@@ -7,4 +7,16 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>Home Page</h1>
|
<h1>Home Page</h1>
|
||||||
<p>This is the home page</p>
|
<p>This is the home page</p>
|
||||||
|
<br />
|
||||||
|
<form action="/login" method="post">
|
||||||
|
<div>
|
||||||
|
<label for="username">Username</label>
|
||||||
|
<input type="text" id="username" name="username" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="password">Password</label>
|
||||||
|
<input type="password" id="password" name="password" />
|
||||||
|
</div>
|
||||||
|
<button type="submit">Login</button>
|
||||||
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{% extends "layout.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% if let Some(err) = self.error %}
|
||||||
|
{{ err }}
|
||||||
|
{% endif %}
|
||||||
|
<h1>Login Page</h1>
|
||||||
|
<form method="post">
|
||||||
|
<div>
|
||||||
|
<label for="username">Username</label>
|
||||||
|
<input type="text" id="username" name="username" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="password">Password</label>
|
||||||
|
<input type="password" id="password" name="password" />
|
||||||
|
</div>
|
||||||
|
<button type="submit">Login</button>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
Reference in new issue
Block a user