# `Attesto.CodeStore`
[🔗](https://github.com/XukuLLC/attesto/blob/v0.13.0/lib/attesto/code_store.ex#L1)

Storage seam for authorization codes.

`Attesto.AuthorizationCode` is pure: it generates a code, hashes it, and
validates a redemption, but it never decides *where* the code lives.
That is this behaviour. A host implements it over whatever store fits
(Postgres, Redis, ETS); `Attesto.CodeStore.ETS` is a ready single-node
implementation.

## The single-use contract (load-bearing)

`take/1` MUST be atomic: it returns the record for `code_hash` **and
removes it** in one indivisible step, so two concurrent redemptions of
the same code cannot both succeed. Authorization codes are single-use
(RFC 6749 §4.1.2); a store that let `take/1` race would let a captured
code be replayed. A SQL implementation uses
`DELETE ... WHERE code_hash = $1 RETURNING ...`; an ETS implementation
uses `:ets.take/2`.

The code is consumed by `take/1` even if `Attesto.AuthorizationCode`
then rejects the redemption (wrong redirect URI, failed PKCE): a code
that has been presented once is spent, which denies an attacker repeated
validation attempts against a stolen code.

## Optional reuse tracking (additive, fail-safe)

The single-use contract above stops a code being redeemed twice, but on
its own it cannot tell a *replay of an already-redeemed code* apart from
a *never-issued code*: once `take/1` has removed the row, a second
presentation looks identical to garbage. The OAuth 2.0 Security BCP
§4.13 (and RFC 6749 §4.1.2) say more: when a code is presented a second
time the AS SHOULD revoke the tokens already issued from the first
redemption, because a second presentation is an attack signal (the code
leaked). Acting on that signal requires remembering which family the
first redemption spawned.

A store MAY opt into this by implementing the OPTIONAL `mark_consumed/2`
callback and extending `take/1` to return `{:error, :consumed, meta}`
for a code that was already redeemed. This is purely additive:

  * A store that does NOT implement reuse tracking keeps `take/1`'s
    original `{:ok, entry} | :error` contract. `Attesto.AuthorizationCode`
    treats a re-presented (now absent) code as `:invalid_grant`, exactly
    as before. Single use is unaffected.
  * A store that DOES implement reuse tracking calls `mark_consumed/2`
    when a redemption succeeds, recording the `code_hash` together with
    `meta` (the `family_id`/`subject` of that first redemption). A later
    `take/1` of the same hash then returns `{:error, :consumed, meta}`,
    and `Attesto.AuthorizationCode.redeem/4` surfaces
    `{:error, {:reuse, meta}}` so the caller can revoke the family.

Fail-safe means: the absence of reuse tracking never makes the system
*less* safe than single use already guarantees; it only forgoes the
extra descendant-revocation signal. A store therefore implements the
callback only when it can persist the consumed marker durably enough to
be useful (a single-redemption window is already closed by `take/1`
whether or not the marker survives).

## Record shape

A stored record is a map with:

  * `:code_hash` - the `Attesto.Secret.hash/1` of the code (the key).
  * `:data` - the opaque grant context the host round-trips
    (client, redirect URI, scope, PKCE challenge, optional DPoP
    thumbprint, subject, and any host claims).
  * `:expires_at` - absolute expiry, unix seconds. The store MAY evict
    expired records, but `Attesto.AuthorizationCode` re-checks expiry
    after `take/1`, so eviction timing is not security-critical.

# `code_hash`

```elixir
@type code_hash() :: String.t()
```

# `consumed_meta`

```elixir
@type consumed_meta() :: map()
```

Reuse metadata recorded at the first redemption and replayed to a later
`take/1` of the same `code_hash`. Opaque to `Attesto.CodeStore`; carried
through `Attesto.AuthorizationCode.redeem/4` to the caller so it can
revoke the family the leaked code spawned. Conventionally holds the
`:family_id` and `:subject` of the first redemption.

# `entry`

```elixir
@type entry() :: %{code_hash: code_hash(), data: map(), expires_at: integer()}
```

# `get`
*optional* 

```elixir
@callback get(code_hash()) :: {:ok, entry()} | :error
```

OPTIONAL. Read the entry for `code_hash` WITHOUT consuming it (unlike
`take/1`). Returns `{:ok, entry}` when a code with that hash is stored, else
`:error`.

Used for read-only pre-checks at the token endpoint - e.g. surfacing a
holder-of-key (DPoP) requirement for a sender-constrained code (RFC 9449 §10 /
FAPI2) before the client-authentication failure, WITHOUT burning the
single-use code. A store that does not implement it is detected via
`function_exported?/3` and the pre-check is skipped (single-use behaviour and
the normal error are unchanged).

# `mark_consumed`
*optional* 

```elixir
@callback mark_consumed(code_hash(), consumed_meta()) :: :ok
```

OPTIONAL. Record that `code_hash` was successfully redeemed and spawned
the family described by `meta`, so a later `take/1` of the same hash can
report `{:error, :consumed, meta}`.

Implemented only by stores that support code-reuse detection (OAuth 2.0
Security BCP §4.13 / RFC 6749 §4.1.2). `Attesto.AuthorizationCode` calls
it exactly once, after a redemption fully validates, with `meta` carrying
the first redemption's `:family_id` and `:subject`. A store that does not
implement this callback simply omits it from the behaviour; the redeemer
detects its absence (`function_exported?/3`) and skips the call, leaving
single-use behaviour unchanged.

Returns `:ok`. The marker SHOULD persist at least as long as the code's
original lifetime would have remained useful; a store MAY key it by
`code_hash` alongside the consumed-token bookkeeping it already keeps.

# `put`

```elixir
@callback put(entry()) :: :ok
```

Persist a code record.

# `take`

```elixir
@callback take(code_hash()) ::
  {:ok, entry()} | :error | {:error, :consumed, consumed_meta()}
```

Atomically fetch and delete the record for `code_hash`.

MUST be a single indivisible operation to preserve single use. Returns:

  * `{:ok, record}` - the code existed and was unredeemed; it is now
    removed. This is the primary path every store implements.
  * `:error` - no such code (never issued, expired-and-evicted, or - for
    a store WITHOUT reuse tracking - already redeemed). Treated as
    `:invalid_grant` by `Attesto.AuthorizationCode.redeem/4`.
  * `{:error, :consumed, meta}` - OPTIONAL, only for a store that
    implements `mark_consumed/2`: the code was already successfully
    redeemed once. `meta` is the value passed to `mark_consumed/2` at
    that first redemption (carrying the `family_id`/`subject`). This is
    the code-reuse attack signal (OAuth 2.0 Security BCP §4.13); the
    redeemer surfaces it so the caller can revoke descendants.

A store that does not track reuse never returns the third form, so the
contract stays `{:ok, entry} | :error` for it and reuse tracking is
purely additive.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
