feat: group-based mailbox ACLs via CF Access JWT groups (#295)#306
Open
schmug wants to merge 1 commit into
Open
feat: group-based mailbox ACLs via CF Access JWT groups (#295)#306schmug wants to merge 1 commit into
schmug wants to merge 1 commit into
Conversation
Extends the per-mailbox ACL to support Cloudflare Access group grants alongside the existing email-member list, so orgs can manage mailbox access by team/role without maintaining per-mailbox email lists. Key changes: - `MailboxAcl.groups?: string[]` — optional field; absent = no group grants; existing email-only ACL blobs remain valid without migration - `callerGroupsFromJwt()` — decodes the `groups` claim from the already-verified CF Access JWT (no re-verification, sourced only from the signed token, never a spoofable header) - `callerInAcl()` gains a third `callerGroups` param; access is granted when the caller is in `members` OR belongs to a granted group name - `requireMailbox` and `GET /api/v1/mailboxes` both extract groups from the JWT and pass them to `callerInAcl` - `POST /api/v1/mailboxes/:id/acl/groups` and `DELETE /api/v1/mailboxes/:id/acl/groups/:name` — owner-only group add/remove; `GET /acl` now returns `groups[]` alongside owner/members Tests: callerInAcl group-grant, deny non-member, email-only unchanged, no-ACL backwards-compat, callerGroupsFromJwt, requireMailbox integration (1093 passing, 0 failing) Deferred (follow-up): group management in AclMembersPanel UI https://claude.ai/code/session_019pmwmDXFom1HzioGUAgAbF
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
ais-hub | 86d47e7 | Commit Preview URL Branch Preview URL |
May 21 2026, 12:21 PM |
7 tasks
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.
Summary
MailboxAclwith an optionalgroups?: string[]field — CF Access group names whose members gain access to the mailbox. Existing email-only ACL blobs are valid without any migration (absent field = no group grants).callerGroupsFromJwt()decodes thegroupsclaim from the already-verified CF Access JWT usingjose'sdecodeJwt(no re-verification; the global Access middleware has already verified the token — group claims are never sourced from a spoofable header).callerInAcl()gains a thirdcallerGroups: string[] = []param; grants access when the caller is inmembersor belongs to a granted CF Access group name. All existing callers are backwards-compat (default value).requireMailboxmiddleware andGET /api/v1/mailboxesboth extract groups from the JWT and pass them tocallerInAcl.POST /api/v1/mailboxes/:id/acl/groups(add group, idempotent) andDELETE /api/v1/mailboxes/:id/acl/groups/:name(remove group).GET /api/v1/mailboxes/:id/aclnow returnsgroups[]alongsideownerandmembers.Closes #295
Authz model / security note
Group membership is sourced exclusively from the
groupsclaim in thecf-access-jwt-assertionheader, which is signed by Cloudflare Access and already verified by the global middleware before any route handler runs.decodeJwt(no re-verification) is safe at this layer — the signature was already checked. No spoofable header is ever trusted for group claims.Group names stored in the ACL must match the names shown in the Cloudflare Access dashboard (e.g.
soc-analysts). The CF Access JWT exposes group names (strings) in thegroupsarray claim.Test plan
npm test— 1093 passing, 0 failing (all pre-existing tests continue to pass)npm run typecheck— exit 0callerInAclgroup-grant: allows caller in granted group, denies non-member, email-only ACL unchanged, no-ACL backwards-compat intactcallerGroupsFromJwt: null/undefined/empty → [], malformed token → [], valid fake JWT → correct groups, non-string entries filteredrequireMailboxintegration: group member allowed (via fake JWT), non-group member denied, no-JWT non-member deniedFiles changed (5)
workers/lib/mailbox-acl.tsgroups?toMailboxAcl; exportcallerGroupsFromJwt(); extendcallerInAclwith group checkworkers/lib/mailbox.tsrequireMailbox; pass tocallerInAclworkers/routes/acl-members.tsGET /aclreturnsgroups[]; addPOST /groupsandDELETE /groups/:nameworkers/index.tsGET /api/v1/mailboxes; pass tocallerInAcltests/lib/mailbox-acl.test.tsDeferred (follow-up: #306)
AclMembersPanel— theGET /aclresponse now includesgroups[], and the add/remove endpoints are live; the panel just needs new UI controls to surface them.tests/routes/acl-members.test.ts.Spec follow-up needed
No — this feature adds a new ACL dimension and does not change or contradict any rule in
SECURITY_SPEC.md.https://claude.ai/code/session_019pmwmDXFom1HzioGUAgAbF
Generated by Claude Code