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 error;
|
||||
@@ -12,8 +18,27 @@ mod util;
|
||||
async fn main() {
|
||||
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 {
|
||||
config: config.clone(),
|
||||
backend: Backend { users: temp_users },
|
||||
});
|
||||
|
||||
// Build app with shared state
|
||||
|
||||
+18
-21
@@ -1,13 +1,15 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use axum_login::{AuthUser, AuthnBackend, UserId};
|
||||
use serde::Deserialize;
|
||||
|
||||
// User model - whatever type for `id` is used here needs to be reflected
|
||||
// in `AuthUser::Id` type value as well
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct User {
|
||||
id: i32,
|
||||
pw_hash: Vec<u8>,
|
||||
pub id: i32,
|
||||
pub username: String,
|
||||
pub pw_hash: Vec<u8>,
|
||||
}
|
||||
|
||||
impl AuthUser for User {
|
||||
@@ -26,19 +28,15 @@ impl AuthUser for User {
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Backend {
|
||||
pub struct Backend {
|
||||
// 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
|
||||
// to login with just a user_id, they will succeed if it is a valid ID. To make
|
||||
// this a user/password login, the properties could be `username: String` and
|
||||
// `password: String`, then update the authentication function to get a user_id
|
||||
// from that to return from the `users.get(&User::Id)`
|
||||
#[derive(Clone)]
|
||||
struct Credentials {
|
||||
user_id: i32,
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub struct Credentials {
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
impl AuthnBackend for Backend {
|
||||
@@ -48,17 +46,16 @@ impl AuthnBackend for Backend {
|
||||
|
||||
async fn authenticate(
|
||||
&self,
|
||||
Credentials { user_id }: Self::Credentials,
|
||||
Credentials { username, password }: Self::Credentials,
|
||||
) -> Result<Option<Self::User>, Self::Error> {
|
||||
// ex:
|
||||
// if let Some(user) = self.find_user_by_username(&creds.username) {
|
||||
// if verify_password(&creds.password, &user.password_hash) {
|
||||
// return Ok(Some(user));
|
||||
// }
|
||||
// }
|
||||
// Ok(None)
|
||||
let user = self.users.values().find(|u| u.username == username);
|
||||
if let Some(user) = user {
|
||||
if user.pw_hash == password.as_bytes() {
|
||||
return Ok(Some(user.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
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> {
|
||||
|
||||
+2
-1
@@ -1,9 +1,10 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::models::config::Config;
|
||||
use crate::models::{auth::Backend, config::Config};
|
||||
|
||||
pub struct SharedState {
|
||||
pub config: Config,
|
||||
pub backend: Backend,
|
||||
}
|
||||
|
||||
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 axum::Router;
|
||||
|
||||
mod auth;
|
||||
mod root;
|
||||
|
||||
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 axum::{Router, extract::State, response::Html, routing::get};
|
||||
use axum_login::login_required;
|
||||
|
||||
use crate::{
|
||||
error::AppError,
|
||||
models::{config::SiteMetadata, state::AppState},
|
||||
models::{auth::Backend, config::SiteMetadata, state::AppState},
|
||||
};
|
||||
|
||||
#[derive(Template)]
|
||||
@@ -26,5 +27,7 @@ async fn home(State(state): State<AppState>) -> Result<Html<String>, AppError> {
|
||||
}
|
||||
|
||||
pub fn router() -> Router<AppState> {
|
||||
Router::new().route("/", get(home))
|
||||
Router::new()
|
||||
.route("/", get(home))
|
||||
.route_layer(login_required!(Backend, login_url = "/login"))
|
||||
}
|
||||
Reference in new issue
Block a user