use rocket::request::{FromRequest, Outcome, Request}; use rocket::http::Status; use serde::{Deserialize, Serialize}; use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation, Algorithm}; use chrono::{Utc, Duration}; use std::env; use argon2::{Argon2, PasswordHash, PasswordVerifier, password_hash::SaltString, PasswordHasher}; #[derive(Debug, Serialize, Deserialize)] pub struct Claims { pub sub: i32, // user id pub role: String, pub exp: usize, } pub fn hash_password(password: &str) -> Result { let salt = SaltString::generate(&mut rand::thread_rng()); Argon2::default().hash_password(password.as_bytes(), &salt).map(|h| h.to_string()) } pub fn verify_password(password: &str, hash: &str) -> bool { if let Ok(parsed_hash) = PasswordHash::new(hash) { Argon2::default().verify_password(password.as_bytes(), &parsed_hash).is_ok() } else { false } } pub fn generate_token(user_id: i32, role: &str) -> String { let expiration = Utc::now() + Duration::days(7); let claims = Claims { sub: user_id, role: role.to_string(), exp: expiration.timestamp() as usize, }; let secret = env::var("JWT_SECRET").expect("JWT_SECRET must be set"); encode(&Header::default(), &claims, &EncodingKey::from_secret(secret.as_bytes())) .expect("JWT encoding failed") } pub fn decode_token(token: &str) -> Option { let secret = env::var("JWT_SECRET").ok()?; decode::( token, &DecodingKey::from_secret(secret.as_bytes()), &Validation::new(Algorithm::HS256), ).ok().map(|data| data.claims) } #[derive(Serialize)] pub struct AuthenticatedUser { pub user_id: Option, pub role: String, } #[rocket::async_trait] impl<'r> FromRequest<'r> for AuthenticatedUser { type Error = (); async fn from_request(req: &'r Request<'_>) -> Outcome { if let Some(auth_header) = req.headers().get_one("Authorization") { if let Some(token) = auth_header.strip_prefix("Bearer ") { if let Some(claims) = decode_token(token) { return Outcome::Success(Self { user_id: Some(claims.sub), role: claims.role, }); } } } Outcome::Error((Status::Unauthorized, ())) } } pub struct AdminOnly(pub i32); #[rocket::async_trait] impl<'r> FromRequest<'r> for AdminOnly { type Error = (); async fn from_request(req: &'r Request<'_>) -> Outcome { match AuthenticatedUser::from_request(req).await { Outcome::Success(user) if user.role == "admin" && user.user_id.is_some() => Outcome::Success(AdminOnly(user.user_id.unwrap())), _ => Outcome::Error((Status::Unauthorized, ())) } } }