feat: Setup fake auth backend, need to wire it up

This commit is contained in:
Cyper committed 2026-03-02 00:38:00 -05:00
1 parent 5e79129cde
commit 19703ae07f
8 files changed
+151 -27

No files matched your search

+27 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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>;
+66
View File
@@ -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
View File
@@ -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
View File
@@ -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"))
}
+12
View File
@@ -7,4 +7,16 @@
{% block content %}
<h1>Home Page</h1>
<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 %}
+19
View File
@@ -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 %}