Files
mnemosyne/src/users/auth/mod.rs
2026-02-28 00:23:15 +01:00

91 lines
2.6 KiB
Rust

use std::sync::LazyLock;
use argon2::{Argon2, PasswordHasher, password_hash::SaltString};
use axum::http::HeaderMap;
use rand08::{RngCore, rngs::OsRng};
use crate::users::{
User, UserError,
sessions::{Session, SessionError},
};
pub mod implementation;
pub const COOKIE_NAME: &str = "mnemohash";
pub trait UserAuthenticate {
fn authenticate(headers: &HeaderMap) -> Result<Option<User>, AuthError>;
}
pub trait UserAuthRequired {
fn required(self) -> Result<User, AuthError>;
}
pub trait SessionAuthenticate {
fn authenticate(headers: &HeaderMap) -> Result<Option<Session>, AuthError>;
}
pub trait SessionAuthRequired {
fn required(self) -> Result<Session, AuthError>;
}
pub trait UserPasswordHashing {
/// Returns the hashed password as a String
fn hash_password(passw: &str) -> Result<String, argon2::password_hash::Error>;
/// Returns whether the password matches the hash
fn match_hash_password(passw: &str, hash: &str) -> Result<bool, argon2::password_hash::Error>;
}
pub static SHARED_ARGON: LazyLock<Argon2> = LazyLock::new(|| Argon2::default());
pub const DUMMY_PASSWORD: &str = "PASSWORD";
pub static DUMMY_PASSWORD_PHC: LazyLock<String> = LazyLock::new(|| {
let salt = SaltString::generate(&mut OsRng);
SHARED_ARGON
.hash_password(DUMMY_PASSWORD.as_bytes(), &salt)
.unwrap()
.to_string()
});
pub fn init_password_dummies() {
let _ = &*DUMMY_PASSWORD_PHC;
println!("Password hashing setup finished");
}
#[derive(thiserror::Error, Debug)]
pub enum AuthError {
#[error("Invalid credentials")]
InvalidCredentials,
#[error("Authentication required")]
AuthRequired,
#[error("Session error: {0}")]
SessionError(#[from] SessionError),
#[error("User error: {0}")]
UserError(#[from] UserError),
#[error("Invalid authorization header format")]
InvalidFormat,
#[error("Invalid base64 encoding")]
InvalidBase64(#[from] base64::DecodeError),
#[error("Invalid UTF-8 in credentials")]
InvalidUtf8(#[from] std::string::FromUtf8Error),
#[error("Database error: {0}")]
DatabaseError(#[from] rusqlite::Error),
#[error("Argon2 passhash error: {0}")]
PassHashError(argon2::password_hash::Error),
}
#[derive(Debug, Clone, Copy)]
#[allow(unused)]
pub enum TokenSize {
/// 5 bytes = 8 chars
Char8,
/// 10 bytes = 16 chars
Char16,
/// 20 bytes = 32 chars
Char32,
/// 40 bytes = 64 chars
Char64,
}
pub fn generate_token(len: TokenSize) -> String {
let mut bytes = vec![0u8; len.bytes()];
let mut rng = OsRng;
rng.try_fill_bytes(&mut bytes).unwrap();
base32::encode(base32::Alphabet::Crockford, &bytes)
}