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, }
GitRust