A member's email is the campaign GMs' to read, and each person's own (#677) #1061

Merged
claude-bot merged 1 commit from fix/677-member-email-gms-only into main 2026-10-04 02:28:39 +00:00
Collaborator

Closes #677. The owner's answer to Q147 (D275).

Summary

The members list sent every member's email address to every member of the campaign. Now another member's email is present only when the reader holds the GM role in that campaign; a reader always gets their own. Everyone else gets null.

  • Where the rule lives. campaign_service.list_members takes a required viewer_id, so no caller can forget who is reading. All five routes that return members go through it: GET /members, POST /members, PATCH /members/{id}, PUT /members/{id}/role, and the unlinked-member assign.
  • Audit. MemberResponse.email was the only campaign-scoped response carrying another person's address.
    • Join requests, invites, ownership offers, rosters, attendance, discussions, wiki owners, search's People group, note authors and the bot API have no email field.
    • UserResponse.email is only on /api/me and the admin pages, unchanged.
  • The owner who only plays (#644) does not see other members' addresses. The decision says GMs; the Members page needs no address for managing who joins, and name search never returned one. It is a one-line change if the owner should count.
  • Frontend. No page ever read a member's email, so nothing renders differently. The Privacy page now says who sees your address.
  • No schema change (email was already nullable), no migration, no bot contract change.

Changelog

  • changelog.d/677-member-email-gms-only.security.md

Test plan

  • Six new tests in test_campaigns.py:
    • a player's list has only their own email;
    • a GM's has all;
    • a paged GM list whose page omits the GM still has them;
    • a player-owner sees no other member's;
    • a player's PATCH of their own record returns their own;
    • a GM's POST returns the added member's.
  • Backend, targeted (campaigns, ownership, characters, invites, member search, server owner, soft delete, parties): 413 passed.
  • ruff format and check: clean. Version sync and changelog checks OK.
  • Frontend eslint: 0 errors. Privacy and Members tests: 103 passed.
  • Not done: the tests were written with the fix, not run against the unfixed code first.
  • The full frontend suite did not come back clean locally: 29 files failed on a dev host at a load average of 35 to 55, with worker start timeouts. The only frontend change is the Privacy page's wording, so CI on this PR is the real run.
  • Full backend suite: CI.

🤖 Generated with Claude Code

Closes #677. The owner's answer to Q147 (D275). ## Summary The members list sent every member's email address to every member of the campaign. Now another member's `email` is present only when the reader holds the GM role in that campaign; a reader always gets their own. Everyone else gets `null`. - **Where the rule lives.** `campaign_service.list_members` takes a required `viewer_id`, so no caller can forget who is reading. All five routes that return members go through it: `GET /members`, `POST /members`, `PATCH /members/{id}`, `PUT /members/{id}/role`, and the unlinked-member assign. - **Audit.** `MemberResponse.email` was the only campaign-scoped response carrying another person's address. - Join requests, invites, ownership offers, rosters, attendance, discussions, wiki owners, search's People group, note authors and the bot API have no email field. - `UserResponse.email` is only on `/api/me` and the admin pages, unchanged. - **The owner who only plays (#644)** does not see other members' addresses. The decision says GMs; the Members page needs no address for managing who joins, and name search never returned one. It is a one-line change if the owner should count. - **Frontend.** No page ever read a member's email, so nothing renders differently. The Privacy page now says who sees your address. - **No schema change** (`email` was already nullable), no migration, no bot contract change. ## Changelog - [x] `changelog.d/677-member-email-gms-only.security.md` ## Test plan - [x] Six new tests in `test_campaigns.py`: - a player's list has only their own email; - a GM's has all; - a paged GM list whose page omits the GM still has them; - a player-owner sees no other member's; - a player's PATCH of their own record returns their own; - a GM's POST returns the added member's. - [x] Backend, targeted (campaigns, ownership, characters, invites, member search, server owner, soft delete, parties): 413 passed. - [x] ruff format and check: clean. Version sync and changelog checks OK. - [x] Frontend eslint: 0 errors. `Privacy` and `Members` tests: 103 passed. - [ ] **Not done:** the tests were written with the fix, not run against the unfixed code first. - [ ] **The full frontend suite did not come back clean locally:** 29 files failed on a dev host at a load average of 35 to 55, with worker start timeouts. The only frontend change is the Privacy page's wording, so CI on this PR is the real run. - [ ] Full backend suite: CI. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
A member's email is the campaign GMs' to read, and each person's own (#677)
All checks were successful
CI / Backend lint (ruff) (pull_request) Successful in 34s
CI / Does this change touch the recording path? (pull_request) Successful in 49s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been skipped
CI / Feedback site tests (pull_request) Successful in 1m0s
CI / Bot/backend version sync (pull_request) Successful in 1m3s
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 1m8s
CI / Bot tests and audit (pull_request) Successful in 2m10s
CI / Docker image build (pull_request) Successful in 4m33s
CI / Frontend (shard 2/2) (pull_request) Successful in 9m59s
CI / Frontend (shard 1/2) (pull_request) Successful in 11m52s
CI / End-to-end journeys (phone) (pull_request) Successful in 14m41s
CI / End-to-end journeys (keyboard) (pull_request) Successful in 15m38s
CI / End-to-end journeys (desktop) (pull_request) Successful in 15m41s
CI / Backend migration, tests, and audit (pull_request) Successful in 26m48s
4ef133bd71
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
claude-bot deleted branch fix/677-member-email-gms-only 2026-10-04 02:28:41 +00:00
rbrooks referenced this pull request from a commit 2026-10-04 06:57:14 +00:00
Sign in to join this conversation.
No description provided.