use actix_web::{dev::ServiceRequest, web, Error, HttpMessage}; use actix_web_httpauth::extractors::bearer::BearerAuth; use actix_web_httpauth::extractors::AuthenticationError; use actix_web_httpauth::headers::www_authenticate::bearer::Bearer; use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation, Algorithm}; use serde::{Deserialize, Serialize}; use bcrypt::{hash, verify, DEFAULT_COST}; use std::time::{SystemTime, UNIX_EPOCH}; use chrono::{DateTime, Utc}; #[derive(Debug, Serialize, Deserialize)] pub struct Claims { pub user_id: i32, pub username: String, pub exp: usize, } #[derive(Debug, Serialize, Deserialize)] pub struct RegisterRequest { pub username: String, pub password: String, } #[derive(Debug, Serialize, Deserialize)] pub struct LoginRequest { pub username: String, pub password: String, } #[derive(Debug, Serialize, Deserialize)] pub struct LoginResponse { pub token: String, pub user: UserInfo, } #[derive(Debug, Serialize, Deserialize)] pub struct UserInfo { pub id: i32, pub username: String, pub registrations_remaining: i32, pub domain_invite_codes: i32, pub created_at: DateTime, } pub fn generate_jwt(user_id: i32, username: &str, secret: &str) -> Result { let expiration = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() .as_secs() + 86400 * 7; // 7 days let claims = Claims { user_id, username: username.to_string(), exp: expiration as usize, }; encode(&Header::default(), &claims, &EncodingKey::from_secret(secret.as_ref())) } pub fn validate_jwt(token: &str, secret: &str) -> Result { let mut validation = Validation::new(Algorithm::HS256); validation.validate_exp = true; decode::(token, &DecodingKey::from_secret(secret.as_ref()), &validation) .map(|token_data| token_data.claims) } pub fn hash_password(password: &str) -> Result { hash(password, DEFAULT_COST) } pub fn verify_password(password: &str, hash: &str) -> Result { verify(password, hash) } pub async fn jwt_middleware( req: ServiceRequest, credentials: BearerAuth, ) -> Result { let jwt_secret = req .app_data::>() .unwrap() .as_ref(); match validate_jwt(credentials.token(), jwt_secret) { Ok(claims) => { req.extensions_mut().insert(claims); Ok(req) } Err(_) => { let config = AuthenticationError::new(Bearer::default()); Err((Error::from(config), req)) } } }