Files
web/src/auth.rs
Connor Johnstone f6b363c40f Format
2026-03-19 14:06:11 -04:00

62 lines
2.2 KiB
Rust

use actix_session::Session;
use argon2::{
Argon2,
password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString, rand_core::OsRng},
};
use crate::error::ApiError;
/// Hash a password using Argon2id.
pub fn hash_password(password: &str) -> Result<String, ApiError> {
let salt = SaltString::generate(&mut OsRng);
let argon2 = Argon2::default();
argon2
.hash_password(password.as_bytes(), &salt)
.map(|h| h.to_string())
.map_err(|e| ApiError::Internal(format!("failed to hash password: {e}")))
}
/// Verify a password against a stored hash.
pub fn verify_password(password: &str, hash: &str) -> bool {
let Ok(parsed) = PasswordHash::new(hash) else {
return false;
};
Argon2::default()
.verify_password(password.as_bytes(), &parsed)
.is_ok()
}
/// Session keys.
const SESSION_USER_ID: &str = "user_id";
const SESSION_USERNAME: &str = "username";
const SESSION_ROLE: &str = "role";
/// Store user info in the session.
pub fn set_session(session: &Session, user_id: i32, username: &str, role: &str) {
let _ = session.insert(SESSION_USER_ID, user_id);
let _ = session.insert(SESSION_USERNAME, username.to_string());
let _ = session.insert(SESSION_ROLE, role.to_string());
}
/// Extract user info from session. Returns (user_id, username, role).
pub fn get_session_user(session: &Session) -> Option<(i32, String, String)> {
let user_id = session.get::<i32>(SESSION_USER_ID).ok()??;
let username = session.get::<String>(SESSION_USERNAME).ok()??;
let role = session.get::<String>(SESSION_ROLE).ok()??;
Some((user_id, username, role))
}
/// Require authentication. Returns (user_id, username, role) or 401.
pub fn require_auth(session: &Session) -> Result<(i32, String, String), ApiError> {
get_session_user(session).ok_or_else(|| ApiError::Unauthorized("not logged in".into()))
}
/// Require admin role. Returns (user_id, username, role) or 403.
pub fn require_admin(session: &Session) -> Result<(i32, String, String), ApiError> {
let user = require_auth(session)?;
if user.2 != "admin" {
return Err(ApiError::Forbidden("admin access required".into()));
}
Ok(user)
}