email_validation_token.rs
102 lignes · 3139 octets
use chrono::{DateTime, Utc}; use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; use uuid::Uuid; /// EmailValidationToken entity model /// /// Represents an email validation token for a user. /// Tokens are hashed before storage (SHA-256) and expire after 24 hours. /// /// # Security Features /// /// - Token hash: Only SHA256 hash is stored (not the plain token) /// - Expiration: Tokens expire after 24 hours /// - Idempotent: Tokens can be used multiple times (unlike password reset tokens) /// - Cascade delete: Tokens are deleted when user is deleted /// /// # Token Lifecycle /// /// 1. **Creation**: Token generated (UUID v4), hashed, stored with expiration (24h) /// 2. **Email**: Plain token sent to user via email /// 3. **Validation**: User clicks link, system hashes token and looks up in database /// 4. **Usage**: Token can be used multiple times (idempotent) /// 5. **Cleanup**: Expired tokens can be deleted periodically #[derive(Clone, Debug, Serialize, Deserialize, DeriveEntityModel)] #[sea_orm(table_name = "email_validation_tokens")] pub struct Model { /// Unique identifier for the token #[sea_orm(primary_key)] pub id: Uuid, /// User ID this token belongs to #[sea_orm(indexed)] pub user_id: Uuid, /// SHA256 hash of the validation token (unique, indexed for fast lookup) /// The plain token is never stored - only sent via email #[sea_orm(unique, indexed)] pub token_hash: String, /// Expiration timestamp (24 hours from creation) #[sea_orm(indexed)] pub expires_at: DateTime<Utc>, /// Timestamp when token was first used (null if not used yet) /// Note: Unlike password reset tokens, validation tokens are idempotent pub used_at: Option<DateTime<Utc>>, /// Creation timestamp pub created_at: DateTime<Utc>, } /// SeaORM relations for the EmailValidationToken entity. #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { /// Relationship to User entity #[sea_orm( belongs_to = "super::user::Entity", from = "Column::UserId", to = "super::user::Column::Id", on_delete = "Cascade" )] User, } impl Related<super::user::Entity> for Entity { fn to() -> RelationDef { Relation::User.def() } } impl ActiveModelBehavior for ActiveModel {} impl Model { /// Check if the token is expired /// /// # Returns /// /// `true` if the token's expiration time has passed, `false` otherwise pub fn is_expired(&self) -> bool { Utc::now() > self.expires_at } /// Check if the token has been used /// /// # Returns /// /// `true` if the token was used (used_at is not null), `false` otherwise pub fn is_used(&self) -> bool { self.used_at.is_some() } /// Check if the token is valid (not expired) /// /// # Returns /// /// `true` if the token can be used for email validation, `false` otherwise /// Note: Unlike password reset tokens, validation tokens can be used multiple times pub fn is_valid(&self) -> bool { !self.is_expired() } }
GitRust