Skip to content

Add mailbox ACL member-management UI (deferred #240 follow-up) #291

@schmug

Description

@schmug

Task

A mailbox owner can today add/remove ACL members only by hand-calling POST/DELETE /api/v1/mailboxes/:id/acl/members. Add a Settings-page UI so the owner can see the current owner + members of a mailbox they own and add/remove members without touching the API. This is the follow-up #240 explicitly deferred ("UI for member management — API only; the Settings page can surface this in a follow-up"). There is no read endpoint for ACL membership today (workers/routes/acl-members.ts has only POST /, POST /members, DELETE /members/:email), so adding GET /api/v1/mailboxes/:id/acl returning { owner, members } (owner-only, 403 otherwise, documented response when unscoped) is part of this task — the UI needs to render current state.

Context

#27 introduced per-mailbox ACLs; #240 shipped the member add/remove API but deferred any UI; #241 added the per-row "Lock down". Granting a second user access still requires a raw API call. This is the documented-but-never-filed #240 follow-up.

Motivation

As a mailbox owner, I want to manage who can access my mailbox from Settings, so that I don't have to hand-craft API requests to add a teammate.

Pointers

  • workers/routes/acl-members.ts:25-90 — existing POST/DELETE handlers; add the GET here
  • workers/lib/mailbox-acl.ts:16-70MailboxAcl, readMailboxAcl, callerInAcl
  • workers/index.ts:145app.route("/api/v1/mailboxes/:mailboxId/acl", aclMemberRoutes); :141 requireMailbox mount
  • app/services/api.ts:143lockDownMailbox; add getMailboxAcl/addMember/removeMember alongside
  • app/queries/mailboxes.ts:63useLockDownMailbox mutation pattern to mirror
  • app/routes/settings.tsx:90,759SettingsRoute; where SecuritySettingsPanel mounts; add an Access/Members panel here
  • app/components/SecuritySettingsPanel.tsx — panel component pattern to follow
  • tests/lib/mailbox-acl.test.ts, tests/routes/mailboxes-acl-status.test.ts — in-memory R2 stub test patterns

Constraints

  • Only the owner (acl.owner) may read or write membership; any other CF-Access-admitted caller → 403 (same guard as existing /members handlers)
  • Emails normalized to lower-case before write (consistent with existing writeMailboxAcl callers)
  • Owner cannot remove themselves; owner always in members
  • ACL blob is not a settings tier — do NOT route through stripDefaultEqual (CLAUDE.md)
  • Test mock URL host checks must use new URL(url).hostname, not startsWith/includes (CLAUDE.md)
  • On an unscoped mailbox the panel should prompt "Lock down" first (reuse existing flow), not show an empty member editor

Acceptance criteria

  • GET /api/v1/mailboxes/:id/acl as owner → { owner, members } 200; as non-owner → 403; on an unscoped mailbox → a documented response (e.g. 404 or { acl_status: "unscoped" }); all three covered by tests
  • Settings page shows current owner + members for a mailbox the caller owns, with an add (email input) and per-member remove control wired to the existing endpoints
  • Adding/removing a member updates the list without a full page reload; a non-owner viewing the page does not see the editor
  • All new endpoint behavior covered by tests using the in-memory R2 stub pattern in tests/lib/mailbox-acl.test.ts

Out of scope

Cross-references

Dependencies

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions