# `AttestoPhoenix.Store.EctoNonceStore`
[🔗](https://github.com/XukuLLC/attesto_phoenix/blob/v0.19.0/lib/attesto_phoenix/store/ecto_nonce_store.ex#L1)

Postgres-backed `Attesto.DPoP.NonceStore` for clustered deployments
(RFC 9449 §8).

A server may require a server-issued `nonce` in every DPoP proof, binding
each proof to a short-lived, server-chosen value. This defeats proof
pre-generation and narrows the replay window beyond what the `jti` cache
(RFC 9449 §11.1) alone provides. An in-memory store cannot share a nonce
across nodes: a nonce issued on one node would be unknown to another. This
implementation persists each nonce so any node honours a nonce issued by any
other.

## Behaviour callbacks

  * `issue/1` mints, persists, and returns an opaque nonce for the
    `DPoP-Nonce` response header (RFC 9449 §8.1). The TTL is recorded on the
    row as a concrete `expires_at` so a later check needs no TTL argument.
  * `valid?/1` reports whether a nonce was issued by this store, has not
    expired, and has not yet been consumed. It is read-only, so it is the
    shape `Attesto.DPoP.verify_proof/2` expects for `:nonce_check` when the
    caller does not need single-use consumption.

## Single-use consume

A read-only check cannot make a nonce single-use under concurrency: two
requests could both observe the same live nonce. `accept/2` is the atomic
consume primitive that delivers the single-use guarantee (RFC 9449 §8). It
marks the nonce used and returns `:ok` to exactly one caller, or
`{:error, :used | :expired | :unknown}` to every other, via one conditional
statement:

    UPDATE dpop_nonces
       SET used_at = $now
     WHERE nonce = $nonce AND used_at IS NULL AND issued_at >= $cutoff

Postgres serialises concurrent updates to the same row, so exactly one
caller observes an affected-row count of `1` (the winner) and the rest
observe `0`. No read-modify-write race exists.

## TTL

`issue/1` records issuance and the derived expiry; `accept/2` also takes the
TTL so the caller's policy, not this store, fixes the freshness window. A
nonce whose `issued_at` is older than `now - ttl` is rejected with
`{:error, :expired}` (RFC 9449 §8).

The repository is read from the supplied `AttestoPhoenix.Config`.

# `accept`

```elixir
@spec accept(String.t(), pos_integer()) :: :ok | {:error, :used | :expired | :unknown}
```

Atomically consumes `nonce` under a freshness window of `ttl` seconds.

Returns `:ok` to the single caller that wins the consume, and a precise
reason to every other so the server can answer with the correct DPoP error
(RFC 9449 §8) rather than silently rejecting (fail-closed):

  * `{:error, :used}` - the nonce was already consumed.
  * `{:error, :expired}` - the nonce is past the freshness window.
  * `{:error, :unknown}` - the nonce was never issued by this store.

Behaviour-style entrypoint; resolves the repo from the application-wide
configured `AttestoPhoenix.Config`.

# `accept`

```elixir
@spec accept(AttestoPhoenix.Config.t(), String.t(), pos_integer()) ::
  :ok | {:error, :used | :expired | :unknown}
```

Like `accept/2`, using an explicit `AttestoPhoenix.Config`.

# `issue`

```elixir
@spec issue() :: String.t()
```

Mints and persists a fresh nonce valid for `ttl_seconds`, returning the
opaque value to put in a `DPoP-Nonce` header. Behaviour entrypoint; resolves
the repo from the application-wide configured `AttestoPhoenix.Config`.

# `issue`

```elixir
@spec issue(pos_integer()) :: String.t()
```

Mints and persists a fresh nonce valid for `ttl_seconds`, returning the
opaque value to put in a `DPoP-Nonce` header. Behaviour entrypoint; resolves
the repo from the application-wide configured `AttestoPhoenix.Config`.

# `issue`

```elixir
@spec issue(AttestoPhoenix.Config.t(), pos_integer()) :: String.t()
```

Like `issue/1`, using an explicit `AttestoPhoenix.Config`.

# `valid?`

```elixir
@spec valid?(String.t()) :: boolean()
```

Returns true iff `nonce` was issued by this store, has not expired, and has
not been consumed. Behaviour entrypoint; resolves the repo from the
application-wide configured `AttestoPhoenix.Config`.

# `valid?`

```elixir
@spec valid?(AttestoPhoenix.Config.t(), String.t()) :: boolean()
```

Like `valid?/1`, using an explicit `AttestoPhoenix.Config`.

---

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