[C4] OAuth broker walking skeleton — indigo ClientApp vs live PDS

closed
#a589d34 opened by agent Aug 24

Goal

Prove the signup/login authn path end-to-end: a Go internal/authbroker using indigo’s atproto/auth/oauth ClientApp that performs a complete confidential-client atproto OAuth flow against the live local PDS container from C1 (and once against a community PDS for federation sanity), binding the resulting session to a sovrn identity row.

Context: docs/02-identity-and-auth.md §1–§3, docs/07-language-and-stack.md §2.

Tasks

  • [ ] Vendor/pin indigo; minimal Go module wiring per C4 of language doc
  • [ ] Publish client-metadata document; generate ES256 attestation key into file-based secret store (dev); register as confidential client (private_key_jwt, dpop_bound_access_tokens)
  • [ ] Postgres-backed ClientAuthStore (auth requests + sessions, multi-session per account)
  • [ ] /oauth/login + /oauth/callback handlers: PAR, PKCE, DPoP nonces, refresh handling
  • [ ] Enforce indigo’s mandatory checks incl. sub == starting DID and iss consistency; reject otherwise
  • [ ] Identity-only scope profile first (atproto); second test with granular repo:com.sovrn.mail.settings… scope to validate partial-grant behaviour
  • [ ] Integration tests in CI against C1 PDS container; one manual test vs external PDS recorded in issue

Acceptance criteria

  • CI test: full browser-simulated flow (HTTP client following redirects) yields session bound to expected DID
  • Tamper tests: mismatched sub, wrong iss, expired code → rejected with clear errors
  • Session survives process restart via DB store; refresh rotates tokens correctly

Risks this should surface

  • indigo API instability / gaps vs current alpha spec (scope string handling is “simple strings” upstream)
  • Confidential-client attestation requirements differing across AS implementations (entryway vs plain PDS)
  • DPoP nonce edge cases on token endpoint

2 Comments

BT ae5a859 Aug 27

SQLite ClientAuthStore + Broker landed — 2026-08-26

Built the C4 authbroker skeleton against indigo, with the SQLite-backed auth store (per the RTW reference evaluation).

What was built

  • internal/authbroker/store.go — SQLite-backed indigo oauth.ClientAuthStore (6 methods), adapted from ../rtw/default/server/atproto/store.go. Schema: oauth_session(did, session_id, data BLOB, mtime) unique on (did,session_id); oauth_request(state, data BLOB, mtime) unique on state; 6-month GC triggers. Driver-agnostic (database/sql); SaveSession upsert + SaveAuthRequestInfo create-only. Unit tests ported from RTW (stdlib, no testify).
  • internal/authbroker/authbroker.go — Broker wrapping indigo oauth.ClientApp as a confidential client (private_key_jwt, ES256 attestation key). StartLogin/CompleteLogin/ResumeSession/Logout, plus ClientMetadata() (embedding the attestation JWKS inline — indigo leaves confidential JWKS to jwks_uri, so we set it) and JWKS().
  • Dependencies: indigo pinned v0.0.0-20260815054354-160dac5de9e8 (same as RTW).

Decision reversal (record for docs/ADR-0002)

SQLite driver is mattn/go-sqlite3 (cgo), NOT modernc.org/sqlite. Owner chose cgo+mattn. Implication: CGO_ENABLED=1 and a C toolchain are now required for the build/CI (gcc is present in the devenv shell). ADR-0002 “pure-Go, no cgo” rationale is superseded; docs/01 §2 and docs/07 §4 (modernc.org/sqlite) need updating when convenient.

RTW evaluation (the ask)

../rtw/default/server/atproto/store.go is a clean, complete oauth.ClientAuthStore — persistence-only, driver-agnostic, correct upsert/create-only semantics. Directly reusable; only the driver (mattn already, actually) and test framework (testify→stdlib) differed. The flow itself lives in indigo ClientApp, which is the right split.

Remaining for C4 (the live-PDS E2E)

Not yet done; this is the substantial part:

  1. Local DID/handle resolution: the PDS issues did:plc:* DIDs that are NOT on the public plc.directory. indigo identity.Directory must be pointed at the local PDS (com.atproto.identity.resolveHandle + com.atproto.repo.describeRepo both work). Needs a small custom identity wrapper (component B8).
  2. Client-metadata reachability: PDS (container) must fetch the client-metadata doc at ClientID; use the host.containers.internal pattern proven in the C5 OIDC test.
  3. Headless consent: the PDS authorize endpoint serves an HTML consent form; the test must parse + submit it (PDS-version-fragile), or the PDS needs a prompt=none/auto-approve mode.

Account creation verified live: com.atproto.server.createAccount → alice.test / did:plc:umr4euvtxxs722d5t4taw6me, PDS auth-server metadata has PAR/DPoP/private_key_jwt/ES256 all supported.

BT aa5b8a9 Aug 27

C4 E2E deferred to C6 — 2026-08-26

Decision: the live-PDS OAuth E2E is deferred to C6 (0d541aa), which already plans the browser-sim atproto login as part of its happy path. C4 is complete at store + broker.

Rationale tied to the did:plc clarification (docs/06 §1): production hosts a public-directory did:plc PDS, so the production identity path uses standard indigo directory resolution — no production local-resolver code is warranted. The local dev PDS (non-public did:plc) is a dev-only artifact, so the local-resolution wrapper would be throwaway dev plumbing better folded into C6’s login step where the whole flow is exercised anyway.

Store + Broker (SQLite/mattn, confidential-client) remain the C4 deliverable and are unit-tested green.