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

Postgres-backed `AttestoPhoenix.PARStore` for clustered deployments
(RFC 9126).

A Pushed Authorization Request endpoint stores the validated authorization
request parameters behind a one-time `request_uri` reference and returns that
reference to the client; the client then presents only the `request_uri` at
`/authorize`. The default `AttestoPhoenix.Store.PAR.ETS` keeps that mapping in
per-node memory, so a `request_uri` pushed to one node is unknown to another -
fatal behind a load balancer, and FAPI 2.0 *requires* PAR. This store persists
each pushed request so any node resolves a `request_uri` issued by any other,
matching the Ecto-backed code/refresh/nonce/replay stores.

## Behaviour callbacks

  * `put/3` inserts the reference, the stored params, and the derived expiry.
  * `fetch/1` resolves a live (unexpired) reference WITHOUT consuming it - the
    authorization endpoint may re-enter with the same `request_uri` after a
    login/consent detour (RFC 9126), so resolution must not spend it. Expiry
    is enforced on read, so an unswept expired reference is never honored.
  * `take/1` resolves and atomically deletes a live reference in one
    `DELETE … RETURNING` statement, for hosts that want single-use semantics.
    Exactly one of any number of racing callers gets the row.

The repository module is supplied by the host application (`:repo` under the
`:attesto_phoenix` app) and read at call time; a store with no backing
repository fails closed rather than silently no-opping.

# `fetch`

```elixir
@spec fetch(String.t()) :: {:ok, map()} | :error
```

Resolves a live `request_uri` without consuming it.

Returns `{:ok, params}` when a row exists and has not expired, or `:error`
when it is absent or expired. Non-consuming by contract: the authorization
endpoint may resolve the same `request_uri` more than once across a
login/consent detour (RFC 9126); TTL, not resolution, ends its life.

# `put`

```elixir
@spec put(String.t(), map(), pos_integer()) :: :ok | {:error, term()}
```

Persists a pushed authorization request under `request_uri` for
`ttl_seconds`.

`params` is the stored, string-keyed parameter map; it is round-tripped
through `jsonb`, so the authorization endpoint reads back the same string-keyed
map it stored. A duplicate `request_uri` (an astronomically unlikely random
collision) surfaces as `{:error, changeset}` rather than overwriting an
existing reference.

# `take`

```elixir
@spec take(String.t()) :: {:ok, map()} | :error
```

Atomically resolves and deletes a live `request_uri`.

The resolve and the delete are one indivisible `DELETE … WHERE … RETURNING`
statement, so for hosts that opt into single-use PAR references exactly one of
any number of concurrent callers (on any node) gets `{:ok, params}`; the rest
get `:error`. An expired row is treated as absent.

---

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