# `Sake.Auth.Store`
[🔗](https://github.com/hattoriai/sake/blob/v0.4.3/lib/sake/auth/store.ex#L1)

Where clients, codes, pushed authorization requests, device grants, tokens and consents live. Implemented by `Sake.Auth.Store.ETS` and `Sake.Auth.Store.Ecto`.

# `state`

```elixir
@type state() :: term()
```

# `consume_code`

```elixir
@callback consume_code(state(), String.t()) :: {:ok, Sake.Auth.Code.t()} | :error
```

Consumes a code, if and only if it was not consumed yet, atomically. The
store keeps knowing the consumed code until it expires, as the fence
`put_code_tokens/3` checks.

# `consume_pushed_request`

```elixir
@callback consume_pushed_request(state(), hash :: String.t()) ::
  {:ok, Sake.Auth.PushedRequest.t()} | :error
```

Takes the pushed authorization request whose `request_uri` has this
hash, if and only if nobody took it before, atomically: of two
concurrent calls, one gets it and the other `:error`. The store may
forget it then; the caller checks its expiry and its client.

# `consume_refresh`

```elixir
@callback consume_refresh(state(), String.t()) ::
  {:ok, Sake.Auth.TokenRecord.t()}
  | {:error, :used, Sake.Auth.TokenRecord.t()}
  | :error
```

Marks a refresh token used now, if and only if it was unused, atomically:
two concurrent calls for the same hash must not both succeed. Returns the
record as it was *before* this call.

# `decide_device_grant`

```elixir
@callback decide_device_grant(
  state(),
  hash :: String.t(),
  {:approved, subject_id :: String.t(), scopes :: [String.t()],
   authentication :: %{acr: String.t() | nil, auth_time: DateTime.t() | nil}}
  | :denied
) :: :ok | :error
```

Records the person's decision on a pending device grant (by its
device code's hash), if and only if it is still pending, atomically:
of two concurrent decisions, one gets `:ok` and the other `:error`.
An approval sets `subject_id`, narrows `scopes` to those granted, and
sets `acr` and `auth_time` from `authentication`, how the person
signed in (either may be nil).

# `delete_client`

```elixir
@callback delete_client(state(), String.t()) :: :ok
```

# `delete_consent`

```elixir
@callback delete_consent(
  state(),
  subject_id :: String.t(),
  client_id :: String.t(),
  resource :: String.t() | :all
) :: :ok
```

Forgets the approval for one resource, or for every resource with `:all`.

# `get_client`

```elixir
@callback get_client(state(), String.t()) :: {:ok, Sake.Auth.Client.t()} | :error
```

# `get_consent`

```elixir
@callback get_consent(
  state(),
  subject_id :: String.t(),
  client_id :: String.t(),
  resource :: String.t()
) ::
  {:ok, Sake.Auth.Consent.t()} | :error
```

# `get_device_grant_by_user_code`

```elixir
@callback get_device_grant_by_user_code(state(), user_code_hash :: String.t()) ::
  {:ok, Sake.Auth.DeviceGrant.t()} | :error
```

# `get_token`

```elixir
@callback get_token(state(), :access | :refresh, String.t()) ::
  {:ok, Sake.Auth.TokenRecord.t()} | :error
```

# `init`

```elixir
@callback init(keyword()) :: state()
```

# `list_clients`

```elixir
@callback list_clients(state()) :: [Sake.Auth.Client.t()]
```

# `once`

```elixir
@callback once(state(), key :: String.t(), expires_at :: integer()) :: :ok | :replayed
```

Records `key` (a digest naming a single-use JWT: a client assertion, a
JWT grant, a DPoP proof) until `expires_at`, in Unix seconds: `:ok` the
first time, `:replayed` while it is recorded. Atomic across every node
that shares the store: of two calls with the same key, exactly one gets
`:ok`.

# `poll_device_grant`

```elixir
@callback poll_device_grant(
  state(),
  hash :: String.t(),
  client_id :: String.t(),
  now :: DateTime.t()
) ::
  {:approved, Sake.Auth.DeviceGrant.t()}
  | :pending
  | :slow_down
  | :denied
  | :expired
  | :used
  | :error
```

A poll of the token endpoint for the device grant `hash`, by the
client `client_id`, at `now`, atomically. `:error` for a grant that
does not exist or is another client's; then, in this order, `:used`
once its tokens were issued, `:denied`, `:expired` (from
`expires_at`), `:slow_down` when the last poll was less than
`interval` seconds ago, `:pending`, and `{:approved, grant}` for an
approved grant, which becomes `:issued`: of two concurrent polls, only
one gets it. Every poll but the first three records `now` as
`last_polled_at`.

# `prune`

```elixir
@callback prune(
  state(),
  now :: DateTime.t(),
  before :: %{consents: DateTime.t(), unused_clients: DateTime.t()}
) :: :ok
```

Deletes the codes, pushed authorization requests, device grants and
tokens that expired by `now`; the consents inserted before
`before.consents` whose subject, client and resource have no live
(unexpired, unrevoked) refresh token left; and the dynamically
registered clients registered before `before.unused_clients` that hold
no live token, no consent, no code, no pushed request and no device
grant.

# `prune_once`

```elixir
@callback prune_once(state(), now :: integer()) :: :ok
```

Forgets the keys `once/3` recorded that expired by `now`, in Unix seconds.

# `put_client`

```elixir
@callback put_client(state(), Sake.Auth.Client.t()) :: :ok
```

# `put_code`

```elixir
@callback put_code(state(), Sake.Auth.Code.t()) :: :ok
```

# `put_code_tokens`

```elixir
@callback put_code_tokens(state(), code_hash :: String.t(), [Sake.Auth.TokenRecord.t()]) ::
  :ok | :revoked
```

Stores the tokens issued for a consumed code (their `family_id` is the
code's hash), all of them or none: none, returning `:revoked`, when the
family was revoked since the code was consumed (a replay of the code).
A store may refuse a code it no longer knows the same way. Atomic with `revoke_family/2`: whichever of the
two runs second sees the other's effect, so a replay never leaves a
live pair behind.

# `put_consent`

```elixir
@callback put_consent(state(), Sake.Auth.Consent.t()) :: :ok
```

Remembers an approval, keyed by subject, client and resource (replacing an earlier one).

# `put_device_grant`

```elixir
@callback put_device_grant(state(), Sake.Auth.DeviceGrant.t()) :: :ok
```

# `put_device_tokens`

```elixir
@callback put_device_tokens(state(), hash :: String.t(), [Sake.Auth.TokenRecord.t()]) ::
  :ok | :revoked
```

Stores the tokens issued for a device grant (their `family_id` is its
hash), all of them or none: none, returning `:revoked`, when the
family was revoked since the grant was issued (a replay of the device
code). Atomic with `revoke_family/2`, as `put_code_tokens/3` is.

# `put_pushed_request`

```elixir
@callback put_pushed_request(state(), Sake.Auth.PushedRequest.t()) :: :ok
```

# `put_rotated_tokens`

```elixir
@callback put_rotated_tokens(state(), refresh_hash :: String.t(), [
  Sake.Auth.TokenRecord.t()
]) ::
  :ok | :revoked
```

Stores the tokens a refresh rotated into, all of them or none: none,
returning `:revoked`, when the refresh token they replace (by hash) is
revoked or gone, as a concurrent reuse of it revokes its family.
Atomic with `revoke_family/2`: whichever of the two runs second sees
the other's effect.

# `put_token`

```elixir
@callback put_token(state(), Sake.Auth.TokenRecord.t()) :: :ok
```

# `revoke_family`

```elixir
@callback revoke_family(state(), String.t()) :: :ok
```

Revokes every token of a family, and the code or device grant whose
hash is the family's id, so that `put_code_tokens/3` and
`put_device_tokens/3` refuse the tokens not stored yet.

# `revoke_token`

```elixir
@callback revoke_token(state(), String.t()) :: :ok
```

# `tokens_for_subject`

```elixir
@callback tokens_for_subject(state(), String.t()) :: [Sake.Auth.TokenRecord.t()]
```

---

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