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()
    }
}