use chrono::{Duration, Utc}; use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation}; use serde::{Deserialize, Serialize}; use crate::error::{AppError, Result}; /// JWT Claims structure #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Claims { /// Subject (user ID) pub sub: i32, /// User's email pub email: String, /// Username pub username: String, /// Is admin flag pub is_admin: bool, /// Expiration time (Unix timestamp) pub exp: i64, /// Issued at time (Unix timestamp) pub iat: i64, } impl Claims { /// Create new claims for a user pub fn new(user_id: i32, email: &str, username: &str, is_admin: bool, expiration_hours: i64) -> Self { let now = Utc::now(); let exp = now + Duration::hours(expiration_hours); Claims { sub: user_id, email: email.to_string(), username: username.to_string(), is_admin, exp: exp.timestamp(), iat: now.timestamp(), } } /// Get the user ID from claims pub fn user_id(&self) -> i32 { self.sub } } /// Create a JWT token for a user pub fn create_token( user_id: i32, email: &str, username: &str, is_admin: bool, secret: &str, expiration_hours: i64, ) -> Result { let claims = Claims::new(user_id, email, username, is_admin, expiration_hours); encode( &Header::default(), &claims, &EncodingKey::from_secret(secret.as_bytes()), ) .map_err(|e| AppError::Internal(format!("Failed to create token: {}", e))) } /// Decode and validate a JWT token pub fn decode_token(token: &str, secret: &str) -> Result { let token_data = decode::( token, &DecodingKey::from_secret(secret.as_bytes()), &Validation::default(), )?; Ok(token_data.claims) } #[cfg(test)] mod tests { use super::*; #[test] fn test_create_and_decode_token() { let secret = "test_secret_key_123"; let token = create_token(1, "test@example.com", "testuser", false, secret, 24).unwrap(); let claims = decode_token(&token, secret).unwrap(); assert_eq!(claims.sub, 1); assert_eq!(claims.email, "test@example.com"); assert_eq!(claims.username, "testuser"); assert!(!claims.is_admin); } }