mix attesto_phoenix.gen.migration (AttestoPhoenix v4.2.1)

Copy Markdown View Source

Generates an Ecto migration that creates the persistence backing the Ecto-based stores shipped with attesto_phoenix.

The migration creates eleven tables, named to match the runtime schemas exactly so a by-the-docs deploy installs tables the Ecto-backed stores can use without modification:

  • attesto_authorization_codes - the authorization code grant store (AttestoPhoenix.Schema.Authorization). Holds one row per issued authorization code (RFC 6749, section 4.1) plus the PKCE binding (RFC 7636), the optional cnf key binding (RFC 7800), the OIDC nonce, mapped claims, the descendant family_id, consumed markers, and the access-token jti issued from a successful redemption so code reuse can revoke it. Keyed on code_hash as its PRIMARY KEY (no surrogate id); consulted exactly once at the token endpoint.

  • attesto_refresh_tokens - the refresh token store (AttestoPhoenix.Schema.RefreshToken, RFC 6749, section 6). Each row carries the rotation family_id and generation it belongs to, the consumed/consumed_at idempotency markers, successor retry payload, family_revoked sticky revocation flag, the cnf key binding, mapped claims, and the diagnostic parent_hash, so that reuse of a rotated token can be detected and the whole family revoked (RFC 6819, section 5.2.2.3 - refresh token rotation / replay detection).

  • attesto_refresh_family_revocations - durable refresh-family revocation tombstones. Refresh-token rows are eligible for expiry cleanup, so this separate table preserves a revocation after every row in the family has been swept. Existing installations upgrading to a release that uses this table must apply a forward migration before starting the new code; see the upgrade notes in the README and CHANGELOG.

  • attesto_device_codes - the device authorization grant store (AttestoPhoenix.Schema.DeviceCode, RFC 8628). One row per device code, keyed on device_code_hash (the poll key) and user_code (the verification key), carrying the bound scope/resource/dpop_jkt, the status state machine (pending → approved|denied → consumed), the approved subject/granted_scope/granted_claims, and last_polled_at for the section 3.5 poll-interval guard.

  • attesto_ciba_requests - the OpenID Connect CIBA authentication-request store (AttestoPhoenix.Schema.CIBARequest / EctoCIBAStore, CIBA Core 1.0). One row per auth_req_id, keyed on auth_req_id_hash, carrying the bound scope/resource/dpop_jkt, the delivery_mode, the ping client_notification_token, the hint-resolved hint_subject, the status state machine (pending → approved|denied → consumed), the approved subject/acr/auth_time/granted_scope/granted_claims, the frozen poll interval, and last_polled_at for the §7.3 poll-interval guard.

  • attesto_logout_sessions - the logout session store (AttestoPhoenix.Schema.LogoutSession, OpenID Connect Back-Channel Logout 1.0 + Front-Channel Logout 1.0). One row per (session, Relying Party) pair, recorded at ID-Token mint and read at the end-session endpoint to deliver a logout_token and/or render the RP's frontchannel_logout_uri. Upserted on (sid, client_id); carries the subject, the RP's backchannel_logout_uri/session_required and frontchannel_logout_uri/frontchannel_session_required, and the expires_at that bounds an abandoned session. delivery_id identifies a durable handoff, while lease_id/lease_expires_at make concurrent delivery claim, acknowledgement, and retry atomic.

  • dpop_nonces - server-issued DPoP nonces (AttestoPhoenix.Schema.DPoPNonce, RFC 9449, section 8). Each row is a single-use nonce carrying issued_at, expires_at, and the used_at consumption marker.

  • dpop_replays - the DPoP proof replay cache keyed by the proof's jti as its PRIMARY KEY (AttestoPhoenix.Schema.DPoPReplay, RFC 9449, section 11.1). A row is the record that a given proof JWT has already been seen within its acceptance window.

  • attesto_pushed_authorization_requests - the Pushed Authorization Request store (AttestoPhoenix.Schema.PushedAuthorizationRequest, RFC 9126). Each row maps a one-time request_uri reference (the PRIMARY KEY) to the stored, validated authorization request params and the reference expires_at, so a request_uri pushed to one node is resolvable on every node (FAPI 2.0 requires PAR).

  • attesto_client_id_metadata - the Client ID Metadata Document cache (AttestoPhoenix.Schema.ClientIdMetadata, draft-ietf-oauth-client-id-metadata-document-01). Each row caches one validated CIMD document under its client_id URL (the PRIMARY KEY), as a jsonb metadata map plus the expires_at derived from the response's HTTP freshness directives (RFC 9111). Keeps every authorization request from re-fetching the URL and, being shared, makes the cache coherent across a cluster and bounds the outbound fetch fan-out.

  • attesto_consent_grants - the single-use, request-bound consent grant store (AttestoPhoenix.Schema.ConsentGrant / EctoConsentGrantStore, RFC 6749 §4.1.1). Each row records one consent decision keyed on an unguessable token (the PRIMARY KEY), with a binding_hash over the exact request the user saw and a short expires_at; consumed_at marks single use. The host consent screen mints a row; the host's :consent callback consumes it before a code is issued, so one consent click cannot approve a different client/redirect/scope/challenge.

Usage

# Fresh installation:
mix attesto_phoenix.gen.migration --repo MyApp.Repo

# Upgrading an existing database to 3.0:
mix attesto_phoenix.gen.migration --upgrade 3.0 --repo MyApp.Repo

# Promoting the historical authorization-code unique index to its
# primary key (run after the 3.0 migration when upgrading 2.x):
mix attesto_phoenix.gen.migration --upgrade 3.1 --repo MyApp.Repo

# Add refresh-family deadlines and independent client-instance bindings:
mix attesto_phoenix.gen.migration --upgrade 3.4 --repo MyApp.Repo

# Widen cached CIMD client_id URLs to text:
mix attesto_phoenix.gen.migration --upgrade 3.6 --repo MyApp.Repo

# Retain authorization-code replay markers through refresh-family life:
mix attesto_phoenix.gen.migration --upgrade 3.7 --repo MyApp.Repo

# Add durable logout-delivery leases:
mix attesto_phoenix.gen.migration --upgrade 4.2 --repo MyApp.Repo

Options

  • --help - print concise usage, accepted options, and supported upgrade milestones, then exit without generating a migration.

  • --upgrade - generate a migration to upgrade an existing database rather than creating fresh tables. Supported values: 3.0/3.0.0 and 3.1/3.1.0, 3.4/3.4.0, 3.6/3.6.0, 3.7/3.7.0, and 4.2/4.2.0. The 3.4 migration adds the nullable immutable family_expires_at and attestation_jkt columns without changing existing families. Pre-upgrade attested families require revocation and re-authorization because their original instance key was not persisted. The migration also widens an existing CIBA client_notification_token column to text for the full 1,024-character protocol limit. Installations without a CIBA table skip that change. The 3.6 migration widens the Client ID Metadata cache's URL primary key from varchar(255) to text; request admission still caps the URL at 2,048 bytes before cache or network access. The 3.7 migration adds the nullable authorization-code replay_retain_until deadline. Existing successful rows linked to a refresh family remain NULL; the runtime sweeper retains them while an unexpired refresh-token descendant exists and retires them after the family ends. The 4.2 migration adds durable logout-delivery identifiers and leases. Existing rows derive their stable delivery_id from their UUID primary key before the column becomes required. The 3.0 migration adopts or creates the exact unique index on attesto_refresh_tokens(family_id, generation), creates or adopts the attesto_refresh_family_revocations table, and safely backfills every currently-revoked refresh-token family. The 3.1 migration promotes the historical unique index on attesto_authorization_codes(code_hash) to the primary key required by current schemas. Run both, in order, for a 2.x database. Each upgrade validates any pre-existing object before backfilling or changing it; malformed collisions fail the migration transaction rather than being silently accepted.

  • --repo, -r - the Ecto repo module the migration is generated for. May be given more than once to target several repos. When omitted the repos configured for the host application are used (the same resolution mix ecto.gen.migration performs).

  • --schema-prefix - an optional PostgreSQL schema selected by Ecto's prefix: option for every generated table and index (for example --schema-prefix oauth creates attesto_authorization_codes in schema oauth). Runtime Ecto queries use the same prefix: option; the table names themselves remain canonical. When omitted, the prefix configured for the host (:schema_prefix on the AttestoPhoenix.Config the host puts in its application environment) is used so the generated schema matches the prefix the Ecto stores read at runtime. If the host has no configured prefix, the generated migration defers to the migrator or repo default at execution time. The task never invents a prefix. The 2.x --table-prefix option is rejected because it controlled literal names in generated migrations, not one coherent runtime layout: most 2.x stores queried canonical tables in public, while only the CIBA store and sweeper treated the value as an Ecto schema prefix. Inventory an existing database before choosing a 3.0 schema; without --upgrade, this task is for fresh migrations only.

  • --migrations-path - directory the migration file is written to. Defaults to the repo's priv/<repo>/migrations directory, the same location mix ecto.gen.migration uses.

  • --otp-app - the host application whose environment holds the AttestoPhoenix.Config keyword or struct to read :schema_prefix from when --schema-prefix is omitted. Optional; when omitted, the task first uses config :attesto_phoenix, otp_app: ... and then the current Mix project's :app. If neither application has a configured prefix, the task embeds no explicit prefix and the migration uses the migrator or repo default at execution time (normally public). Keep a custom migration default aligned with the runtime database connection's search path, or pass --schema-prefix explicitly.

  • --config-key - the application environment key the host stores its AttestoPhoenix.Config keyword under. Defaults to AttestoPhoenix.Config, matching AttestoPhoenix.Config.from_otp_app/2.

Fresh-install migrations are reversible. The 3.0 upgrade migration has a guarded rollback that refuses to discard revocations which 2.x cannot represent. An application downgrade also requires stopped writers and a drain of active 3.x refresh-retry deadlines because 2.x cannot recover 3.x successor envelopes. The 3.4 rollback locks the refresh table and removes its new columns only when every row has NULL values in both columns. Any stored deadline or instance binding, including on expired or revoked rows, blocks rollback. Remove those rows only after a reviewed revocation and retention decision; clearing their security fields is not a safe rollback. The widened CIBA text column is retained to avoid truncating stored tokens.