totp_challenge.rs
77 lignes · 2110 octets
use chrono::{DateTime, Utc}; use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; use uuid::Uuid; /// TotpChallenge entity model /// /// Ephemeral challenge for the 2FA login flow. /// Created after successful password verification when user has TOTP enabled. /// Expires after 5 minutes. Max 5 attempts per challenge. #[derive(Clone, Debug, Serialize, Deserialize, DeriveEntityModel)] #[sea_orm(table_name = "totp_challenges")] pub struct Model { /// Unique identifier #[sea_orm(primary_key)] pub id: Uuid, /// User ID pub user_id: Uuid, /// Random challenge token (sent to client, used to identify the challenge) #[sea_orm(unique)] pub challenge_token: String, /// Number of verification attempts pub attempts: i32, /// Maximum allowed attempts pub max_attempts: i32, /// Challenge expiration time pub expires_at: DateTime<Utc>, /// Creation timestamp pub created_at: DateTime<Utc>, } /// SeaORM relations for the TotpChallenge entity. #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { /// The user this challenge was issued for. #[sea_orm( belongs_to = "super::user::Entity", from = "Column::UserId", to = "super::user::Column::Id" )] User, } impl Related<super::user::Entity> for Entity { fn to() -> RelationDef { Relation::User.def() } } impl ActiveModelBehavior for ActiveModel {} #[cfg(test)] mod tests { use super::*; #[test] fn test_totp_challenge_model() { let id = Uuid::new_v4(); let user_id = Uuid::new_v4(); let now = Utc::now(); let challenge = Model { id, user_id, challenge_token: "test-token".to_string(), attempts: 0, max_attempts: 5, expires_at: now + chrono::Duration::minutes(5), created_at: now, }; assert_eq!(challenge.id, id); assert_eq!(challenge.attempts, 0); assert_eq!(challenge.max_attempts, 5); assert!(challenge.expires_at > now); } }
GitRust