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 { 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::(SESSION_USER_ID).ok()??; let username = session.get::(SESSION_USERNAME).ok()??; let role = session.get::(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) }