Ecto implementation of the Attesto.CodeStore behaviour.
Authorization codes are single-use (RFC 6749 §4.1.2) and, with PKCE mandatory (RFC 7636), the code is the only browser-deliverable secret in the authorization-code flow. The single-use guarantee therefore cannot be advisory: it must be enforced by the store so that two concurrent redemptions of one code cannot both succeed.
take/1 issues a DELETE ... WHERE code_hash = $1 RETURNING ..., so the
fetch and the delete are one statement. Exactly one of any number of racing
redemptions sees the row; every other caller sees an empty result and gets
:error. This holds across all nodes sharing the database, which the
single-node ETS store cannot offer. The code is consumed even when the
caller later rejects the redemption (mismatched redirect URI, failed PKCE
verifier): a code presented once is spent, which denies an attacker
repeated validation attempts against a captured code.
The plaintext code is never persisted; the primary key is the
Attesto.Secret.hash/1 digest of the code. The column layout and the
record bridge live in AttestoPhoenix.Schema.Authorization; this module
only owns the two atomic database operations.
The repository module is supplied by the host application (:repo under
the :attesto_phoenix app) and is read at call time. A store with no
backing repository can make no guarantees, so a missing :repo fails
closed rather than silently no-opping.
Summary
Functions
Persists an authorization-code record keyed by its :code_hash.
Atomically fetches and deletes the record for code_hash.
Functions
@spec put(Attesto.CodeStore.entry()) :: :ok
Persists an authorization-code record keyed by its :code_hash.
The record is the plain map the protocol layer hands over: a :code_hash,
the opaque grant :data, and an integer :expires_at in unix seconds.
AttestoPhoenix.Schema.Authorization.from_record/1 spreads it across the
row's columns and validates it fail-closed (missing required field or a
non-S256 PKCE method is rejected, not defaulted).
The hash is the primary key, so a duplicate insert is a caller bug:
Attesto.AuthorizationCode derives the hash from freshly generated random
bytes, so a collision means the random source repeated or the same entry
was put twice. insert!/1 raises on the unique-constraint violation rather
than silently overwriting an existing, possibly already-issued, code. Fail
closed; no upsert.
@spec take(Attesto.CodeStore.code_hash()) :: {:ok, Attesto.CodeStore.entry()} | :error
Atomically fetches and deletes the record for code_hash.
Returns {:ok, entry} when the row existed (and is now gone), or :error
when it was absent. The fetch and the delete are one indivisible statement
(DELETE ... RETURNING), so the single-use contract of Attesto.CodeStore
holds against concurrent redemptions.
The loaded row is folded back into the :code_hash / :data /
:expires_at (unix seconds) map via
AttestoPhoenix.Schema.Authorization.to_record/1. Expiry is not checked
here: Attesto.AuthorizationCode re-checks :expires_at after take/1,
and consuming the row regardless of freshness preserves single use, since
an expired-but-present code is still spent on first presentation.