fediverse_personal_access_token.rs 80 lignes · 2931 octets
//! SeaORM entity for `fediverse_personal_access_tokens` (RFC 0030).
//!
//! Personal access tokens issued by the local instance to remote
//! ActivityPub identities. Companion of the core
//! [`personal_access_token`](rustwarden_core::models::personal_access_token)
//! but scoped to `remote_identities` instead of `users`.
//!
//! # Storage
//!
//! Only the SHA-256 hex digest of the plaintext token is persisted in
//! `token_hash` — the plaintext is returned once from
//! [`crate::services::remote_personal_access_token_service::RemotePersonalAccessTokenService::create`]
//! and never stored.
//!
//! # Token format
//!
//! Tokens emitted by this service are prefixed `rwfed_pat_` followed by
//! 64 hex characters (32 bytes of entropy). The prefix lets the
//! consumer disambiguate remote PATs from framework PATs (`rw_pat_…`)
//! when both populations can present a token on the same endpoint.

use chrono::{DateTime, Utc};
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
use uuid::Uuid;

/// Persistent record of one remote-identity-owned PAT.
#[derive(Clone, Debug, Serialize, Deserialize, DeriveEntityModel)]
#[sea_orm(table_name = "fediverse_personal_access_tokens")]
pub struct Model {
    /// Stable PK.
    #[sea_orm(primary_key, auto_increment = false)]
    pub id: Uuid,
    /// FK to `remote_identities.id` (owner).
    pub remote_identity_id: Uuid,
    /// User-chosen label (max 64 chars, validated at service layer).
    pub name: String,
    /// SHA-256 hex digest of the plaintext token (64 chars).
    #[sea_orm(unique)]
    pub token_hash: String,
    /// Requested scopes (consumer vocabulary via
    /// [`rustwarden_core::services::personal_token_service::TokenScope`]).
    pub scopes: Vec<String>,
    /// Optional expiration. `None` = permanent token.
    #[sea_orm(nullable)]
    pub expires_at: Option<DateTime<Utc>>,
    /// Last successful `verify()` (UX).
    #[sea_orm(nullable)]
    pub last_used_at: Option<DateTime<Utc>>,
    /// Creation timestamp.
    pub created_at: DateTime<Utc>,
    /// Soft-revocation timestamp. A revoked token is kept for audit but
    /// refuses authentication.
    #[sea_orm(nullable)]
    pub revoked_at: Option<DateTime<Utc>>,
}

/// SeaORM relations (none modelled here — cross-table FK to
/// `remote_identities` is enforced by the migration).
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}

impl ActiveModelBehavior for ActiveModel {}

impl Model {
    /// Returns true if `expires_at` is set and in the past.
    pub fn is_expired(&self) -> bool {
        self.expires_at.is_some_and(|t| Utc::now() > t)
    }

    /// Returns true if `revoked_at` is set.
    pub fn is_revoked(&self) -> bool {
        self.revoked_at.is_some()
    }

    /// Returns true if the token is neither revoked nor expired.
    pub fn is_active(&self) -> bool {
        !self.is_revoked() && !self.is_expired()
    }
}