use chrono::{Duration, Utc}; use jsonwebtoken::{DecodingKey, EncodingKey, Header, Validation}; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use uuid::Uuid; use crate::models::user::UserRole; #[derive(Debug, Serialize, Deserialize)] pub struct Claims { pub sub: Uuid, pub event_id: Uuid, pub role: UserRole, pub exp: i64, pub iat: i64, /// Random per-token identifier. Without it, two `create_token` calls in the /// same wall-clock second for the same (sub, role, event) produce identical /// JWT bytes — and identical sha256(token) hashes — which then collide on /// the `session.token_hash` UNIQUE constraint. The jti is ignored by the /// verifier but breaks the collision. #[serde(default)] pub jti: Uuid, } pub fn create_token( user_id: Uuid, event_id: Uuid, role: UserRole, secret: &str, expiry_days: i64, ) -> Result { let now = Utc::now(); let claims = Claims { sub: user_id, event_id, role, iat: now.timestamp(), exp: (now + Duration::days(expiry_days)).timestamp(), jti: Uuid::new_v4(), }; jsonwebtoken::encode( &Header::default(), &claims, &EncodingKey::from_secret(secret.as_bytes()), ) } pub fn verify_token(token: &str, secret: &str) -> Result { let data = jsonwebtoken::decode::( token, &DecodingKey::from_secret(secret.as_bytes()), &Validation::default(), )?; Ok(data.claims) } pub fn hash_token(token: &str) -> String { let mut hasher = Sha256::new(); hasher.update(token.as_bytes()); format!("{:x}", hasher.finalize()) }