# `Macaw.Auth.Ash.Sessions`
[🔗](https://github.com/dcoai/macaw_auth_ash/blob/v0.3.2/lib/macaw/auth/ash/sessions.ex#L1)

`Macaw.Auth.Ash`'s sessions, kept in the host's AshAuthentication token
resource.

**A session is a stored user token.** `create/2` signs an ordinary
AshAuthentication user JWT for the user (`AshAuthentication.Jwt`, the
resource's own secret, lifetime and claims) and stores its row: the
`jti`, purpose `"user"`, subject and expiry, and Macaw's metadata in
`extra_data["macaw_auth"]` (when it was made and last used, the client's
address and agent, the second factor, the steps done). The JWT goes to
the client; the session's id is its `jti`.

Why the token resource rather than a session table of our own: it is
where an Ash host already keeps tokens, so one revocation means the same
to AshAuthentication's plugs and to Macaw (a revoked session is a token
AshAuthentication refuses too, and a token AshAuthentication revokes is a
session Macaw refuses), its expunger clears the rows, and the host has no
second table to migrate. What it lacked, an update of `extra_data` and a
revocation by jti that keeps the row's expiry, `Macaw.Auth.Ash.TokenResource`
adds.

The contract's guarantees, and how:

- **No token stored.** The row holds the `jti`, a random value inside
  the JWT's signed payload, never the JWT; a `jti` alone signs nobody in,
  since a token must carry the resource's signature. The kit checks
  every stored value against the tokens it was given.
- **Revocation.** Revoking sets the row's purpose to `"revocation"`
  (`:macaw_auth_revoke`), the state AshAuthentication's own
  `revoked?` check looks for; the row keeps its expiry, so the expunger
  removes it when the JWT could no longer verify anyway. A session is
  valid only while its row has purpose `"user"` and Macaw's metadata, so
  a token AshAuthentication minted for itself is not a Macaw session.
- **Idle expiry slides.** A session unused for `idle_days` expires and is
  revoked at its next look-up; a request (`touch: true`) records use at
  most once an hour, and a re-check (`touch: false`) never does. The
  JWT's own expiry (the user resource's `token_lifetime`) is the
  session's absolute limit, at least `idle_days` (`Macaw.Auth.Ash.Config`
  refuses a shorter one).
- **A deleted user has no new session.** `create/2` for a user deleted
  after signing in is `{:error, :invalid}`.
- **A disabled user has no session.** Looking one up revokes it.

Every function honors `:now`. Every read and write is made with
`authorize?: false`: this is the authentication layer itself, before
there is an actor to authorize.

# `complete_step`

```elixir
@spec complete_step(term(), Macaw.Auth.step(), keyword()) :: :ok | {:error, :invalid}
```

Records a step on a session: the second factor, or another step done.
`:ok`, or `{:error, :invalid}` when there is no live session by that id
(revoked, by Macaw or AshAuthentication, even between this read and its
write; or never one), which is left as it is.

# `create`

```elixir
@spec create(Macaw.Auth.User.t(), keyword()) ::
  {:ok, binary(), Macaw.Auth.Session.t()} | {:error, :invalid}
```

Starts a session for `user`: `{:ok, token, session}`, the JWT for the
client only, or `{:error, :invalid}` when the user is gone (deleted
after signing in): there is no record to sign a token for, and nothing
is stored. Options: `:remote_ip`, `:user_agent`,
`:second_factor_passed` (default true), `:now`.

# `fetch`

```elixir
@spec fetch(binary(), keyword()) ::
  {:ok, Macaw.Auth.User.t(), Macaw.Auth.Session.t()}
  | {:error, :invalid | :expired | :disabled}
```

Resolves a token: its signature and claims verified against the user
resource, then its session. `{:ok, user, session}` or `{:error, :invalid
| :expired | :disabled}`. Options: `:touch` (default false), `:now`.

# `fetch_by_id`

```elixir
@spec fetch_by_id(term(), keyword()) ::
  {:ok, Macaw.Auth.User.t(), Macaw.Auth.Session.t()}
  | {:error, :invalid | :expired | :disabled}
```

As `fetch/2`, by session id (the token's `jti`): what an open page re-checks with.

# `list`

```elixir
@spec list(term(), keyword()) :: [Macaw.Auth.Session.t()]
```

A user's sessions, newest first (created at, then id: timestamps can tie).

# `revoke`

```elixir
@spec revoke(term(), keyword()) :: {term(), [term()]}
```

Revokes one session (an id) or a user's (`{:user, id}`, keeping
`:except`): `{user_id, ids}`, the ids empty when nothing was there.

# `subject_user_id`

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

The user id a token's subject names (the primary key's value, as the
subject carries it), or `:error` for another resource's subject.

---

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