Pending ATProto accounts: admin setup-nudge + user activation/password flow

open
#31b625f opened by agent Sep 12

Problem

The “Create a New Atmosphere Account” checkbox (bug 3a8659e) creates a mailbox with Status: pending and a placeholder DID (did:plc:pending:<handle>), but after the one-time creation notice there is no persistent indication of which accounts are still awaiting setup, no way for the tenant admin to nudge the new user, and no flow for the new user to activate their ATProto account and set a password. Pending accounts are effectively invisible.

Is it possible with the current design? Yes — status is already durable

  • store.Account.Status (pending | active | ..., internal/store/store.go:49) is persisted at creation via ProvisionAccountWithStatus and returned as XRPC state. The preview handle is re-derivable (<local>.at.<domain> from address + domain), so rendering a pending indication needs no migration and no PDS.
  • Gaps the current design does NOT cover (this issue):
    • Nothing renders Status: mailboxRow (fragments.templ:22) shows address only; AccountPage (account.templ) has no pending banner.
    • Store has no account-update method (only Create/Get/List/Delete) — activation (real DID + pending→active) will need e.g. UpdateAccountDID/UpdateAccountStatus + sqlite impl.
    • No invite/setup-token primitive exists anywhere (grep: no invite/activate/claim flow). internal/tokenissuer (ES256 JWT + TTL clamping) is a usable building block, but single-use setup tokens need new store + endpoint design.
    • Auth boundary: all UI sits behind SessionAuth (ATProto OAuth); a pending user has no ATProto account yet and cannot log in, so the setup link must be a token-gated route outside session middleware (like /login + /static/ today).
    • Password setting is PDS-side: pdsprovisioner.Provisioner currently only previews/provisions stubs. Setting a user password requires the live provisioner (Issue 4) to grow an activation/password capability — so the activation half of this flow waits on PDS integration, as expected.

Proposal

Part A — admin-visible pending indication (possible now, no PDS): - mailboxRow: pending badge next to the address when a.Status == "pending" (list + HX OOB paths already re-render the list). - AccountPage: pending banner with the derived handle (<local>.at.<domain>), the Manual.Instructions() DNS/operator hint, and a disabled-by-default “copy setup link” affordance stubbed until Part B defines the token URL. - Tests: list rendering with mixed statuses; account page banner for pending vs absent for active.

Part B — setup link + activation/password flow (needs design; activation waits on live PDS): - Single-use setup token: tokenissuer-minted JWT bound to account ID with short TTL + single-use enforcement (new store table or account-column + consume-on-use); admin “copy setup link” copies /setup/<token>. - Unauthenticated GET /setup/<token> (mounted outside SessionAuth.Middleware, cf. router.go top-level routes): validates token, shows handle + mailbox, prompts for new ATProto password (+ confirmation, strength rules). - POST /setup/<token>: calls live provisioner activate/set-password, then UpdateAccountDID (real DID) + status active, consumes the token, redirects to login. Expired/used/unknown tokens → expired-link page (admin can re-issue = mint a fresh token). - XRPC surface TBD: whether activation gets a lexicon (e.g. at.sovrn.mail.activateAccount) or stays browser-only — decide in design. - Abuse notes: tokens pre-image unguessable, TTL ≤ 7d, single-use, rate-limit POST, never log the token; tenant-owner-only minting.

Files likely touched (plan, not yet implemented)

  • internal/appview/ui/fragments.templ, account.templ, domain.templ (badge/banner/link UI)
  • internal/appview/ui/handler.go, router.go (token-gated setup routes outside session auth)
  • internal/store/store.go, sqlite.go (+ tests) for UpdateAccountDID/UpdateAccountStatus and token single-use
  • internal/pdsprovisioner/* for activate/set-password on the live backend (Issue 4)
  • internal/tokenissuer reuse for setup JWTs; possible new lexicon for activation

Acceptance

  • Tenant admin can see at a glance which mailboxes are pending (list badge + account banner) and copy a setup link per pending account.
  • New user opening a fresh link sets their ATProto password, lands on login, and their mailbox flips to active with the real DID; used/expired links are rejected and re-issuable.
  • No real PLC/PDS calls until Issue 4 lands (Part A first, Part B activation behind the live provisioner).

Depends: bug 3a8659e (pending creation UX). Blocks on Issue 4 (live PDS) for the password/activation half only.