m20260516_000003_remote_pat.rs 171 lignes · 6274 octets
//! Migration `m20260516_000003_remote_pat` (RFC 0030).
//!
//! Creates the `fediverse_personal_access_tokens` table — Personal
//! Access Tokens issued by the local instance to remote ActivityPub
//! identities.
//!
//! Symmetry with the core `personal_access_tokens` table (different
//! owner column: `remote_identity_id` instead of `user_id`):
//!
//! | column                | type             | notes |
//! |-----------------------|------------------|-------|
//! | `id`                  | uuid PK          | stable identifier |
//! | `remote_identity_id`  | uuid NOT NULL FK `remote_identities(id)` ON DELETE CASCADE | grantee |
//! | `name`                | varchar(64) NOT NULL | user-chosen label |
//! | `token_hash`          | varchar(64) NOT NULL UNIQUE | SHA-256 hex |
//! | `scopes`              | text[]            | reuses the framework `TokenScope` vocabulary |
//! | `expires_at`          | timestamptz NULL  | `NULL` = permanent |
//! | `last_used_at`        | timestamptz NULL  | UX bookkeeping |
//! | `created_at`          | timestamptz NOT NULL | bookkeeping |
//! | `revoked_at`          | timestamptz NULL  | soft-revoke for audit |
//!
//! Indexes:
//! - `idx_fediverse_pat_remote_identity` on `remote_identity_id`
//! - `idx_fediverse_pat_active` partial index on `token_hash` where
//!   `revoked_at IS NULL` — used by `RemotePersonalAccessTokenService::verify`
//!   for sub-millisecond lookups.

use sea_orm_migration::prelude::*;
use sea_orm_migration::sea_orm::ConnectionTrait;

#[derive(DeriveMigrationName)]
pub struct Migration;

#[async_trait::async_trait]
impl MigrationTrait for Migration {
    async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
        manager
            .create_table(
                Table::create()
                    .table(FediversePersonalAccessTokens::Table)
                    .col(
                        ColumnDef::new(FediversePersonalAccessTokens::Id)
                            .uuid()
                            .not_null()
                            .primary_key(),
                    )
                    .col(
                        ColumnDef::new(FediversePersonalAccessTokens::RemoteIdentityId)
                            .uuid()
                            .not_null(),
                    )
                    .col(
                        ColumnDef::new(FediversePersonalAccessTokens::Name)
                            .string_len(64)
                            .not_null(),
                    )
                    .col(
                        ColumnDef::new(FediversePersonalAccessTokens::TokenHash)
                            .string_len(64)
                            .not_null()
                            .unique_key(),
                    )
                    .col(
                        ColumnDef::new(FediversePersonalAccessTokens::Scopes)
                            .array(ColumnType::Text)
                            .not_null(),
                    )
                    .col(
                        ColumnDef::new(FediversePersonalAccessTokens::ExpiresAt)
                            .timestamp_with_time_zone()
                            .null(),
                    )
                    .col(
                        ColumnDef::new(FediversePersonalAccessTokens::LastUsedAt)
                            .timestamp_with_time_zone()
                            .null(),
                    )
                    .col(
                        ColumnDef::new(FediversePersonalAccessTokens::CreatedAt)
                            .timestamp_with_time_zone()
                            .not_null(),
                    )
                    .col(
                        ColumnDef::new(FediversePersonalAccessTokens::RevokedAt)
                            .timestamp_with_time_zone()
                            .null(),
                    )
                    .foreign_key(
                        ForeignKey::create()
                            .name("fk_fediverse_pat_remote_identity")
                            .from(
                                FediversePersonalAccessTokens::Table,
                                FediversePersonalAccessTokens::RemoteIdentityId,
                            )
                            .to(RemoteIdentities::Table, RemoteIdentities::Id)
                            .on_delete(ForeignKeyAction::Cascade),
                    )
                    .to_owned(),
            )
            .await?;

        manager
            .create_index(
                Index::create()
                    .name("idx_fediverse_pat_remote_identity")
                    .table(FediversePersonalAccessTokens::Table)
                    .col(FediversePersonalAccessTokens::RemoteIdentityId)
                    .to_owned(),
            )
            .await?;

        // Partial index on token_hash for non-revoked rows.
        // SeaORM's index builder does not expose `WHERE` clauses
        // portably, so we emit raw SQL.
        manager
            .get_connection()
            .execute_unprepared(
                "CREATE INDEX idx_fediverse_pat_active \
                 ON fediverse_personal_access_tokens (token_hash) \
                 WHERE revoked_at IS NULL",
            )
            .await?;

        Ok(())
    }

    async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
        let conn = manager.get_connection();
        conn.execute_unprepared("DROP INDEX IF EXISTS idx_fediverse_pat_active")
            .await?;

        manager
            .drop_index(
                Index::drop()
                    .name("idx_fediverse_pat_remote_identity")
                    .table(FediversePersonalAccessTokens::Table)
                    .to_owned(),
            )
            .await?;

        manager
            .drop_table(
                Table::drop()
                    .table(FediversePersonalAccessTokens::Table)
                    .to_owned(),
            )
            .await?;

        Ok(())
    }
}

#[derive(Iden)]
enum FediversePersonalAccessTokens {
    Table,
    Id,
    RemoteIdentityId,
    Name,
    TokenHash,
    Scopes,
    ExpiresAt,
    LastUsedAt,
    CreatedAt,
    RevokedAt,
}

#[derive(Iden)]
enum RemoteIdentities {
    Table,
    Id,
}