AttestoPhoenix.Store.EctoRefreshStore (AttestoPhoenix v3.2.1)

Copy Markdown View Source

PostgreSQL implementation of Attesto.RefreshStore.

Rotation is one family-serialized transaction. The transaction locks the family with a transaction-scoped advisory lock, locks the parent row, marks the parent consumed with its authenticated retry state, and inserts the successor before it commits. A blocked caller therefore sees either the complete committed winner or a complete reuse record; it can never observe a consumed parent before its successor exists.

Positive retry state is authenticated-encrypted before the transaction. The encryption key must be stable across every node that can serve the family. Strict rotation stores only %{retry_until: now, recoverable: false} and needs no encryption secret. Expired retry state is redacted by the sweeper, while a consumed parent is retained until that deadline or its own expiry, whichever comes first. Family revocation is also recorded in the separate RefreshFamilyRevocation table, whose tombstones are never swept.

Summary

Functions

Strong primary read of a refresh record.

Persists a new unconsumed refresh token.

Serializes family revocation with inserts and rotations, durably records the tombstone, and removes every row in the family.

Atomically rotates parent_hash into child.

Types

rotation_error()

@type rotation_error() ::
  :family_revoked
  | :retry_state_unavailable
  | :token_conflict
  | :family_integrity_error
  | :invalid_rotation
  | :expired

Functions

get(token_hash)

Strong primary read of a refresh record.

Revoked families are intentionally hidden from the protocol layer. The query is always executed through the configured primary repo and carries the configured Ecto prefix explicitly.

insert(record)

@spec insert(Attesto.RefreshStore.entry()) ::
  :ok | {:error, :family_revoked | :conflict}

Persists a new unconsumed refresh token.

Family revocation and (family_id, generation)/token uniqueness are checked while holding the same family lock used by rotation and revocation.

revoke_family(family_id)

@spec revoke_family(Attesto.RefreshStore.family_id()) :: :ok

Serializes family revocation with inserts and rotations, durably records the tombstone, and removes every row in the family.

rotate(parent_hash, child, successor, opts \\ [])

Atomically rotates parent_hash into child.

The successor is protected before any database work. A positive successor requires the stable refresh-successor secret; failure to protect it returns :retry_state_unavailable without touching the database. Strict mode uses an exact non-secret tombstone and remains usable without that secret.