Files
rose-ash/plans/identity-on-sx.md

4.1 KiB

identity-on-sx: OAuth2, sessions & membership on Erlang

DRAFT outline. The identity core acl-on-sx assumes already exists. acl answers "may X do Y"; identity answers "who is X, and how did they prove it." Depends on persist-on-sx (grant/audit ledger). Pairs with acl-on-sx.

rose-ash's account domain is the OAuth2 authorization server every other app is a client of: silent SSO, per-app first-party cookies, grant verification, membership. Sessions and grants are long-lived, concurrent, individually addressable, and expire on their own — that is the actor model. Erlang's processes + mailboxes map cleanly: a session is a process, token issue/refresh/ revoke are messages, expiry is a process timeout, and SSO is one process answering many apps.

End-state: an Erlang-on-SX layer with the OAuth2 authorization-code + silent (prompt=none) flows as message protocols, a session/grant registry, token lifecycle (issue/refresh/revoke/introspect), and membership state — all auditable through the event log, all authorization questions delegated to acl-on-sx.

Status (rolling)

bash lib/identity/conformance.sh0/0 (not yet started)

Ground rules

  • Scope: only lib/identity/** and plans/identity-on-sx.md. May import from lib/erlang/, and (once they exist) lib/persist/ + lib/acl/. Do not edit substrates.
  • Architecture: a session/grant is a process holding its own state; the registry routes messages by subject/client id. Tokens are opaque + introspected, not self-validating (revocation must be real). Authorization decisions are NOT made here — identity proves identity, acl decides permission.
  • Security: revocation is immediate (kill the process / tombstone the grant); no decision relies on a token that outlived its grant. Negative answers are explicit, never "absence of a yes."
  • Commits: one feature per commit. Progress log + tick boxes.

Architecture sketch

Auth request                            Token / session
  (authorize client scope subject)        {:access :refresh :expires :grant}
        │                                       ▲
        ▼                                       │
lib/identity/oauth.sx                   lib/identity/token.sx
  — authz-code + prompt=none flows        — issue / refresh / revoke / introspect
  — as Erlang message protocols           — opaque tokens, grant-backed
        │                                       ▲
        ▼                                       │
lib/identity/session.sx                 lib/identity/registry.sx
  — session = process, expiry=timeout     — route by subject/client; SSO fan-out
        │                                       │
        ▼                                       ▼
lib/identity/api.sx ── (identity/login) (identity/grant?) (identity/revoke) ──┐
        │                                                                      │
        └──────── grant + audit events → persist ;  permission? → acl ──────────┘

Phase 1 — Sessions + tokens

  • session.sx — session process, create/lookup/expire
  • token.sx — issue/introspect/revoke (opaque, grant-backed)
  • registry.sx — route by subject/client
  • api.sx + tests + scoreboard + conformance.sh

Phase 2 — OAuth2 flows

  • authorization-code flow as a message protocol
  • refresh + rotation; revocation cascades to issued tokens
  • tests: full code exchange, refresh, revoke-then-use (must fail)

Phase 3 — Silent SSO + membership

  • prompt=none cross-app login (one session, many clients)
  • membership state + per-app grant projection
  • grant verification delegated cache (mirror Redis-cache pattern)

Phase 4 — Audit + federation

  • every issue/refresh/revoke is a persist event; (identity/audit subject)
  • federated identity (peer-asserted subject) — advisory, trust-gated stub
  • tests: audit completeness, cross-instance subject mapping

Progress log

(loop fills this in)

Blockers

(loop fills this in)