ATProto identity <-> mailbox mapping (app DB now, spaces record later)

closed
#3726817 opened by agent Sep 11

Problem

Associating mailboxes with ATProto identities is the core purpose of this program, but no such step exists: mailboxes.did is set to the creator DID at creation and never displayed, edited, or enforced. Concretely:

  • Account/app-password UI handlers perform NO ownership check — once real multi-user OAuth is live, any logged-in DID can manage any mailbox (single-DID dev masks this today).
  • The XRPC mail.createAccount did override lets any caller claim any DID, unscoped.
  • There is no handle -> DID resolution step and no PDS-hosting path for users without an identity.

Decisions (locked in discussion 2026-09-11)

  • Only the tenant owner may reassign a mailbox’s DID.
  • Two association cases must be designed:
    1. mailbox -> EXISTING user handle (resolve handle -> DID, bind it);
    2. mailbox -> NEW DID with sovrn-hosted PDS (create DID + host PDS).

Design questions (case 2 needs careful thought before code)

  • Handle strategy: user.sovrn.at handles vs user.<hosted>.<domain> handles. The latter requires per-name resolution (DNS TXT records the tenant must publish, or sovrn serving /.well-known/atproto-did per hosted name) — weigh tenant manageability against vanity.
  • Where PDS hosting lives (scope vs this issue / spaces delivery).
  • Display: handles, DIDs, or both on the account page.

Proposal

  • Stage A (app DB, pre-spaces): show owner DID/handle on the account page; tenant-owner-only reassign via handle input (resolve via PDS com.atproto.identity.resolveHandle; check what authbroker already offers); enforce caller DID == account DID or tenant owner on account detail + app-password issue/revoke; scope the XRPC did override to tenant owners. App DB stays source-of-truth.
  • Stage B (spaces): mirror the mapping as a user record in the space-permissioned repo; defines the “second ATProto account logs in → sees/creates own mailbox + app password, accesses via IMAP/JMAP” flow.

Files (stage A)

  • internal/appview/provision.go, mail_create_account.go, mail_create_app_password.go, mail_list_app_passwords.go, mail_remove_app_password.go, internal/appview/ui/handler.go, account.templ, internal/store (mailboxes.did already exists)

Acceptance

  • Second ATProto login sees only its own mailboxes; can create mailbox + app password and read mail via IMAP/JMAP.
  • Non-owner cannot manage another DID’s mailbox (tested).

1 Comment

agent 3f712a6 Sep 11

Design plan approved — split into 5 issues

Locked decisions (discussion 2026-09-11 + follow-up): - Admin = Tenant.OwnerDID sole admin; multi-admin deferred. - No mailbox without DID: admin supplies existing handle OR checks “provision new ATProto account” with live <user>.at.<domain> preview. - Vanity default: <user>.at.<domain> via *.at.<domain> CNAME -> pds.sovrn.at + Host-based /.well-known/atproto-did. <user>.sovrn.at fallback only. - Stage A UX-first with pending stub; real PDS wiring after UX locked. - PDS eval covers atproto-crates + tranquil-pds + ZDS; Sept launch, spaces GA Nov upgrade path; enterprise 2FA/passkeys + S3/CDN blobs required. - Non-admin landing = “My mailboxes” + Host-new-domain CTA.

Sub-issues

  1. bug 86021db — Permissions: tenant-owner admin + My-mailboxes UI (Stage A)
    • RequireTenantOwner + CanAccessMailbox, scope XRPC did override, ListAccountsByDID + index, role-gated templates.
  2. bug 74339dc — Mailbox <-> existing handle association (resolve + bind)
    • New internal/identity wrapper (indigo Directory, cache ≤1h), handle? on createAccount, admin handle input + typeahead, account page handle+DID.
  3. bug 3a8659e — New ATProto account stub + at.domain plumbing (pending UX)
    • internal/pdsprovisioner interface + fake, new-account checkbox → pending mailbox, Host-based /.well-known/atproto-did, 1-CNAME DNS guide.
  4. bug 9303770 — TRACKING: PDS selection (atproto-crates vs tranquil vs ZDS; S3, 2FA, GA upgrade)
    • E1–E9 matrix (spaces surface, upgrade cadence, OAuth, ops, 2FA/passkeys, S3/CDN, portability, language, license/bus-factor), spike harness, ADR + backfill plan.
  5. bug 6a79018 — TRACKING: Spaces Stage B mirror
    • Postmaster at.sovrn.mail.account mirror, second-login e2e, reconciliation sweep. Post-launch, not a launch blocker.

Sequencing

Issues 1 → 2 → 3 (each shippable, TDD, jj commit per task). Issues 4–5 run in parallel as tracking; Issue 4 decision unblocks PDSMode=fake→live backfill of pending mailboxes. App DB stays source-of-truth throughout Stage A.