centralize Argon2 setup

This commit is contained in:
2026-02-28 00:23:15 +01:00
parent b6a211bbcf
commit f754e47221
3 changed files with 24 additions and 20 deletions

View File

@@ -24,6 +24,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
} }
database::migrations()?; database::migrations()?;
users::auth::init_password_dummies();
users::setup::initialise_reserved_users_if_needed()?; users::setup::initialise_reserved_users_if_needed()?;
let port = match std::env::var("PORT") { let port = match std::env::var("PORT") {

View File

@@ -1,4 +1,4 @@
use argon2::{Argon2, PasswordHash, PasswordHasher, PasswordVerifier, password_hash::SaltString}; use argon2::{PasswordHash, PasswordHasher, PasswordVerifier, password_hash::SaltString};
use axum::{ use axum::{
http::{ http::{
HeaderMap, StatusCode, HeaderMap, StatusCode,
@@ -15,8 +15,9 @@ use crate::{
users::{ users::{
User, User,
auth::{ auth::{
AuthError, COOKIE_NAME, SessionAuthRequired, SessionAuthenticate, TokenSize, AuthError, COOKIE_NAME, DUMMY_PASSWORD, DUMMY_PASSWORD_PHC, SHARED_ARGON,
UserAuthDummyData, UserAuthRequired, UserAuthenticate, UserPasswordHashing, SessionAuthRequired, SessionAuthenticate, TokenSize, UserAuthRequired,
UserAuthenticate, UserPasswordHashing,
}, },
sessions::Session, sessions::Session,
}, },
@@ -75,29 +76,18 @@ impl SessionAuthRequired for Option<Session> {
impl UserPasswordHashing for User { impl UserPasswordHashing for User {
fn hash_password(passw: &str) -> Result<String, argon2::password_hash::Error> { fn hash_password(passw: &str) -> Result<String, argon2::password_hash::Error> {
use rand08::rngs::OsRng as ArgonOsRng; use rand08::rngs::OsRng as ArgonOsRng;
let argon = Argon2::default();
let passw = passw.as_bytes(); let passw = passw.as_bytes();
let salt = SaltString::generate(&mut ArgonOsRng); let salt = SaltString::generate(&mut ArgonOsRng);
Ok(argon.hash_password(passw, &salt)?.to_string()) Ok(SHARED_ARGON.hash_password(passw, &salt)?.to_string())
} }
fn match_hash_password(passw: &str, hash: &str) -> Result<bool, argon2::password_hash::Error> { fn match_hash_password(passw: &str, hash: &str) -> Result<bool, argon2::password_hash::Error> {
let argon = Argon2::default();
let passw = passw.as_bytes(); let passw = passw.as_bytes();
let hash = PasswordHash::try_from(hash)?; let hash = PasswordHash::try_from(hash)?;
Ok(argon.verify_password(passw, &hash).is_ok()) Ok(SHARED_ARGON.verify_password(passw, &hash).is_ok())
} }
} }
// TODO: generate these at startup using predefined Argon2 params if
// these ever change from ::Default - the PHC must have the same factors as real hashes.
impl UserAuthDummyData for User {
/// This PHC generated for b"password"
const DUMMY_PASSWORD_PHC: &str = "$argon2id$v=19$m=19456,t=2,p=1$PXcTKpFhLRB70fVF35XYDQ$QOW2IxdPUvqD38+ScqX5SgO+jwweaMO9DUGqmkTeofQ";
/// Different than the input password of the PHC
const DUMMY_PASSWORD: &str = "different_password";
}
impl From<argon2::password_hash::Error> for AuthError { impl From<argon2::password_hash::Error> for AuthError {
fn from(err: argon2::password_hash::Error) -> Self { fn from(err: argon2::password_hash::Error) -> Self {
AuthError::PassHashError(err) AuthError::PassHashError(err)
@@ -213,7 +203,7 @@ pub fn authenticate_via_credentials(
false => Err(AuthError::InvalidCredentials), false => Err(AuthError::InvalidCredentials),
}, },
_ => { _ => {
let _ = User::match_hash_password(User::DUMMY_PASSWORD, User::DUMMY_PASSWORD_PHC)?; let _ = User::match_hash_password(DUMMY_PASSWORD, &*DUMMY_PASSWORD_PHC)?;
Err(AuthError::InvalidCredentials) Err(AuthError::InvalidCredentials)
} }
} }

View File

@@ -1,3 +1,6 @@
use std::sync::LazyLock;
use argon2::{Argon2, PasswordHasher, password_hash::SaltString};
use axum::http::HeaderMap; use axum::http::HeaderMap;
use rand08::{RngCore, rngs::OsRng}; use rand08::{RngCore, rngs::OsRng};
@@ -29,9 +32,19 @@ pub trait UserPasswordHashing {
/// Returns whether the password matches the hash /// Returns whether the password matches the hash
fn match_hash_password(passw: &str, hash: &str) -> Result<bool, argon2::password_hash::Error>; fn match_hash_password(passw: &str, hash: &str) -> Result<bool, argon2::password_hash::Error>;
} }
pub trait UserAuthDummyData {
const DUMMY_PASSWORD_PHC: &str; pub static SHARED_ARGON: LazyLock<Argon2> = LazyLock::new(|| Argon2::default());
const DUMMY_PASSWORD: &str; 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)] #[derive(thiserror::Error, Debug)]