Macaw.Auth.Native.Sessions (macaw_auth v0.4.1)

Copy Markdown View Source

The native backend's sessions (ported from exio's Accounts.Sessions), in macaw_auth_sessions (Macaw.Auth.Native.SessionRecord).

  • Only a hash is stored. A session's token is 32 random bytes; the client holds it and the table holds its SHA-256. The token is never written, so reading the table (a backup, a leaked dump) signs nobody in, and since the token is uniformly random, a plain unsalted hash is as strong as the token: there is nothing to guess.
  • Idle expiry slides. A session unused for session_idle_days (Macaw.Auth.Native.Config, default 14) expires and is deleted at its next look-up. A request (touch: true) records use, but writes last_used_at at most once an hour: a write per request would make every page view a database write, which SQLite's single writer feels. A re-check (touch: false, an open page's periodic look) never extends a session, or an idle browser tab would keep it alive forever.
  • A disabled user has no session. Looking one up deletes it and refuses it, so disabling a user takes effect at their next request, and their open pages leave at their next re-check.
  • Revocation only deletes. revoke/2 returns {user_id, ids}; the push to open pages is the facade's (Macaw.Auth.revoke/2), the same for every backend.

Every function honors :now (a DateTime). Telemetry [:macaw_auth, :native, :session]: %{count: n} with %{event: :created | :expired | :revoked, reason: atom | nil, user_id: id}; never a token or its hash.

Summary

Functions

Records a completed step: :second_factor and :second_factor_enroll mark the second factor passed; any other step joins completed_steps. A session that doesn't exist, or was deleted meanwhile (a revocation racing the step), is left gone and refused: {:error, :invalid}.

Starts a session for user: {:ok, token, session}, the token for the client only, or {:error, :invalid} when the user no longer exists (deleted since signing in). Options: :remote_ip (a string or an address tuple), :user_agent, :second_factor_passed (default true), :now.

Resolves a token: {:ok, user, session} or {:error, :invalid | :expired | :disabled}. Options: :touch (default false), :now.

As fetch/2, by session id: what an open page re-checks with.

The SHA-256 of a token: what the table stores.

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

Deletes one session (an id) or a user's ({:user, id}, keeping :except): {user_id, ids}, the ids empty when nothing was there. :reason goes to telemetry.

Deletes every session of user user_id's, reason going to telemetry, and tells their open pages (Macaw.Auth.announce_revoked/2), which disconnect, unless option announce: false: a process that is not the app's node (a release's eval, a mix task) has no page to tell, and the pages leave at their next re-check. What an administrator's change of a user (Macaw.Auth.Native.set_password/3, admin_reset/2) revokes with.

A row as the contract's %Macaw.Auth.Session{}.

Functions

complete_step(id, step, opts)

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

Records a completed step: :second_factor and :second_factor_enroll mark the second factor passed; any other step joins completed_steps. A session that doesn't exist, or was deleted meanwhile (a revocation racing the step), is left gone and refused: {:error, :invalid}.

create(user, opts)

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

Starts a session for user: {:ok, token, session}, the token for the client only, or {:error, :invalid} when the user no longer exists (deleted since signing in). Options: :remote_ip (a string or an address tuple), :user_agent, :second_factor_passed (default true), :now.

fetch(token, opts)

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

Resolves a token: {:ok, user, session} or {:error, :invalid | :expired | :disabled}. Options: :touch (default false), :now.

fetch_by_id(id, opts)

@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: what an open page re-checks with.

hash(token)

@spec hash(binary()) :: binary()

The SHA-256 of a token: what the table stores.

list(user_id, opts)

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

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

revoke(id, opts)

@spec revoke(term() | {:user, term()}, keyword()) :: {term(), [term()]}

Deletes one session (an id) or a user's ({:user, id}, keeping :except): {user_id, ids}, the ids empty when nothing was there. :reason goes to telemetry.

revoke_all(user_id, reason, opts)

@spec revoke_all(term(), atom(), keyword()) :: :ok

Deletes every session of user user_id's, reason going to telemetry, and tells their open pages (Macaw.Auth.announce_revoked/2), which disconnect, unless option announce: false: a process that is not the app's node (a release's eval, a mix task) has no page to tell, and the pages leave at their next re-check. What an administrator's change of a user (Macaw.Auth.Native.set_password/3, admin_reset/2) revokes with.

to_session(r)

A row as the contract's %Macaw.Auth.Session{}.