feat: per-agent scoped mailbox auth + login-with-raft (v0) - #6
Open
TennyZhuang wants to merge 9 commits into
Open
feat: per-agent scoped mailbox auth + login-with-raft (v0)#6TennyZhuang wants to merge 9 commits into
TennyZhuang wants to merge 9 commits into
Conversation
Foundation for task #1 (per-agent API keys + multi-mailbox ownership). Pure, unit-tested logic with no KV/binding dependency: - hashApiKey: sha-256 hex (keys stored/compared hashed, never raw) - ownerCanAccess: API access scoped to mailbox owner (admins exempt; receiving is never owner-gated) - reservedHandleForLocalPart/Address + createAllowedByPrefix: <handle>/<handle>-* prefix reservation to prevent identity squatting Middleware/endpoint wiring, KV key registry, owner field on mailboxes, and the migration backfill follow once the KV namespace (agentic-inbox-keys) is minted. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Postel <postel@mail.build>
…rinfo, tiering Extends the per-agent-auth foundation (task #1) with the locked 3-resource model (Owner / Mailbox / Access Key) primitives, pure + unit-tested, for Gogo's app.ts wiring: - mailboxAccessAllowed(key, mailbox): scope-aware access — admin | account (all owner's boxes) | a single mailbox-id (finest isolation, one-key-one-box). - ownerFromRaftUserinfo: derive owner=raft:${server_id}:${type}:${sub} from OAuth userinfo, trusting only type/sub/server_id (never preferred_username). - tiering: DEFAULT_PLAN_LIMITS / maxMailboxesForPlan / canCreateMailbox — free=1 mailbox (tygg's confirmed first rule); numbers overridable by pricing. npm test: 31/31. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Postel <postel@mail.build>
…lation, tiering Implements the task #1 3-resource model backend (Owner / Mailbox / Access Key): - workers/lib/keyRegistry.ts: KV-backed access-key registry (resolve/mint/revoke, hashed tokens, per-owner mailbox index for quota counts). Soft-disable revoke (KV ~60s propagation, documented). - workers/app.ts: auth middleware resolves a scoped Bearer key -> owner/scope context; legacy global API_KEY kept as a temporary admin fallback (feature seam, removed at cutover). CF Access unchanged (legacy human path). - workers/index.ts: POST /mailboxes is now a CLAIM — requires an authenticated owner, enforces the tier quota (free=1, pro=10 by raft-server tier), stamps `owner`, and mints a mailbox-scoped key (returned once). - workers/lib/mailbox.ts: requireMailbox enforces owner-scoped access for scoped-key callers (mailboxAccessAllowed); legacy sessions pass during transition. - workers/lib/auth.ts: + mailboxAccessAllowed, ownerFromRaftUserinfo, tiering (planForOwner / canCreateMailbox), scope helpers. - wrangler.jsonc: AGENTIC_INBOX_KEYS KV binding (Gogo mints id) + PRO_SERVER_IDS / RAFT_OAUTH_* vars. CLIENT_SECRET via wrangler secret. typecheck clean; npm test 33/33. Remaining for v0: OAuth login-with-raft route, list-endpoint scoping, frontend login/claim screens; Gogo: KV mint + Connected App registration + secrets + deploy + live E2E. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Postel <postel@mail.build>
Scoped-key callers see only their own mailboxes (via the per-owner index); admin/legacy sees all. Completes the v0 backend isolation surface. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Postel <postel@mail.build>
Adds the v0 login screen: humans and agents sign in through the same OAuth flow (handed off to the worker's /auth/raft/login route). Wired into the router. The mailbox list + claim UI already exists on the home route (now backed by the scoped-key claim endpoint). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Postel <postel@mail.build>
The claim endpoint returns a mailbox-scoped access key; surface it in a dialog (copy + 'shown once, unrecoverable' warning) after claiming. Completes the v0 frontend claim tail. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Postel <postel@mail.build>
…erse-only) Per tygg (2026-07-15): v0 login restricted to botiverse users. serverAllowed gates by raft server_id (empty allowlist = unrestricted). Enforce in the OAuth callback after userinfo; server_id list via env. Pure + tested. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Postel <postel@mail.build>
- AGENTIC_INBOX_KEYS KV id = bbf03eefcab5455f9efb4496dc532a40 (minted by Gogo). - ALLOWED_SERVER_IDS = botiverse server_id (login gate; enforce in OAuth callback via serverAllowed()). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Postel <postel@mail.build>
…klist Addresses Gogo's PR #6 review finding 2: the claim endpoint didn't enforce the (defined+tested) anti-squat logic, so an authenticated agent could claim another agent's identity address (gogo@, postel@). Now a non-admin caller may only claim within its own handle namespace (<handle>@ / <handle>-*, handle = session preferred_username via new authHandle context var) and never a reserved system name (admin/postmaster/noreply/...). Admin (provisioning) exempt. npm test 40/40, typecheck clean. (authHandle is set by the OAuth session middleware — Gogo's port.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Postel <postel@mail.build>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
V0 of "mail app on raft" (task #1). Backend model + login screen — ready for @gogo's security review. OAuth route + deploy are coordinated remaining work (below).
What's in (typecheck clean,
npm test33/33)3-resource model (Owner / Mailbox / Access Key), locked with tygg/Ray/Gogo:
workers/lib/keyRegistry.ts— KV-backed access-key registry:resolve/mint/revoke, hashed tokens (raw shown once), per-owner mailbox index. Revoke = soft-disable (KV ~60s propagation, documented).workers/app.ts— auth middleware resolves a scoped Bearer key →owner/scopecontext. Legacy globalAPI_KEYkept as a temporary admin fallback (feature seam, removed at cutover). CF Access unchanged.workers/index.ts—POST /api/v1/mailboxesis now a claim: requires an authed owner, enforces the tier quota (free=1, pro=10 by raft-server tier), stampsowner, mints a mailbox-scoped key (returned once). List endpoint returns only the caller's mailboxes.workers/lib/mailbox.ts—requireMailboxenforcesmailboxAccessAllowedfor scoped-key callers (finest isolation: a key touches only its one mailbox).workers/lib/auth.ts— pure, tested primitives:mailboxAccessAllowed,ownerFromRaftUserinfo(owner=raft:${server_id}:${type}:${sub}),planForOwner/canCreateMailbox, scope helpers.app/routes/login.tsx— Login with Raft screen (humans + agents, one flow).wrangler.jsonc—AGENTIC_INBOX_KEYSKV binding +PRO_SERVER_IDS/RAFT_OAUTH_*vars.Remaining for v0 (coordinated — do NOT merge until done)
/auth/raft/login+ callback/token/userinfo) — @gogo to port his proven me.build implementation; integrates withownerFromRaftUserinfo+ the claim +mintKey.wrangler kv namespace create agentic-inbox-keys(fill the id), register the Connected App (client_id/secret),wrangler secret put RAFT_OAUTH_CLIENT_SECRET,npm run deploy, live E2E.Notes
auth.tsbefore merge.gh --approve; discuss on Slock).🤖 Generated with Claude Code
Need help on this PR? Tag
/codesmithwith what you need. Autofix is disabled.