feat: bulk-reveal encrypted Allergien on member list (admin-only) #198

Merged
shahondin1624 merged 1 commits from feature/member-list-allergien-reveal into main 2026-04-17 21:58:16 +02:00
Owner

Summary

Adds an "Allergien anzeigen" toggle to the Mitgliederliste. On click (after confirmation), the server decrypts allergien_encrypted for every non-deleted member and returns the plaintext map. The list grows an extra "Allergien" column rendered from the map. Toggling off discards the plaintext — nothing is persisted to localStorage, Pinia, or beyond the component's lifetime.

Security

  • Admin-only: enforced by AuthorizationMiddleware (ADMIN_METHODS_MEMBER = ['revealAllergies', 'archive']). Non-admins get 403.
  • Audited: each bulk reveal logs user + members_total + members_with_allergien — never the plaintext.
  • No-store response: Cache-Control: no-store on the API response + Cache-Control: no-store request header so the browser doesn't cache it.
  • Data lifetime: state lives in local component refs (not the Pinia store). Toggling off overwrites every map entry with null before dropping the ref; onBeforeUnmount does the same. The "Allergien" column is never registered in useColumnVisibility, so the preference for showing it is not persisted.
  • Session scope: the fetched plaintext dies with navigation / reload.

Test plan

  • 3 new MemberService tests (happy path, audit masks value, missing EncryptionService)
  • All 1125 unit tests pass
  • Webpack production build clean
  • Version bumped to 0.2.11

🤖 Generated with Claude Code

## Summary Adds an "Allergien anzeigen" toggle to the Mitgliederliste. On click (after confirmation), the server decrypts `allergien_encrypted` for every non-deleted member and returns the plaintext map. The list grows an extra "Allergien" column rendered from the map. Toggling off **discards the plaintext** — nothing is persisted to localStorage, Pinia, or beyond the component's lifetime. ## Security - **Admin-only**: enforced by `AuthorizationMiddleware` (`ADMIN_METHODS_MEMBER = ['revealAllergies', 'archive']`). Non-admins get 403. - **Audited**: each bulk reveal logs `user + members_total + members_with_allergien` — never the plaintext. - **No-store response**: `Cache-Control: no-store` on the API response + `Cache-Control: no-store` request header so the browser doesn't cache it. - **Data lifetime**: state lives in local component refs (not the Pinia store). Toggling off overwrites every map entry with `null` before dropping the ref; `onBeforeUnmount` does the same. The "Allergien" column is never registered in `useColumnVisibility`, so the preference for showing it is not persisted. - **Session scope**: the fetched plaintext dies with navigation / reload. ## Test plan - [x] 3 new `MemberService` tests (happy path, audit masks value, missing `EncryptionService`) - [x] All 1125 unit tests pass - [x] Webpack production build clean - [x] Version bumped to 0.2.11 🤖 Generated with [Claude Code](https://claude.com/claude-code)
shahondin1624 added 1 commit 2026-04-17 21:58:10 +02:00
feat: bulk-reveal encrypted Allergien on member list (admin-only)
Database Portability Tests / Unit Tests (PlatformHelper) (pull_request) Failing after 37s
Database Portability Tests / Integration (mysql) (pull_request) Has been skipped
Database Portability Tests / Integration (postgres) (pull_request) Has been skipped
Database Portability Tests / Integration (sqlite) (pull_request) Has been skipped
Database Portability Tests / Verify no MySQL-specific SQL (pull_request) Successful in 3s
893f4445f9
Adds an "Allergien anzeigen" toggle button to the Mitgliederliste. When
clicked (after confirmation) the server decrypts allergien_encrypted for
every non-deleted member and returns the plaintext map; the list grows
an "Allergien" column rendered from the map. Toggling off discards the
plaintext from local state — nothing is persisted to localStorage or
the Pinia store, and onBeforeUnmount() wipes it as well.

Backend
- New MemberService::revealAllAllergies(string $userId): array mapping
  memberId → plaintext|null. Requires injected EncryptionService.
- New MemberController::revealAllergies() endpoint.
  Response carries Cache-Control: no-store.
- New route: POST /api/v1/members/allergien/reveal
- AuthorizationMiddleware::ADMIN_METHODS_MEMBER = ['revealAllergies',
  'archive'] — admin-only enforced before the route handler runs.
- Audit log captures user + total members + count-with-allergies, never
  the plaintext value (same pattern as QueryService audit for allergien
  queries).

Frontend
- MemberList toolbar gets a toggle button with Eye / EyeOff icon.
- allergienVisible / allergienMap are plain refs inside the view
  (not pushed into the Pinia store) so the data doesn't leak to other
  views and dies with the component.
- displayColumns computes visibleColumns + optional "Allergien" column.
  The column is NEVER registered in useColumnVisibility so it doesn't
  get persisted to localStorage.
- Toggling off overwrites every map entry with null before dropping the
  ref so Vue's reactive proxy cannot leak the prior values.

Tests: 3 new MemberService tests (happy path, audit-without-value,
missing-encryption-service). All 1125 unit tests pass.

Version bumped to 0.2.11.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
shahondin1624 merged commit 5a3e1c9ef2 into main 2026-04-17 21:58:16 +02:00
shahondin1624 deleted branch feature/member-list-allergien-reveal 2026-04-17 21:58:16 +02:00
Sign in to join this conversation.