POPIA toolkit part 1: privacy page, unsubscribe, WhatsApp STOP, data requests

- Privacy page: news opt-in per channel with history, download my data
  (JSON + PDF), correction and deletion requests
- Bulk messages are an Event update or News & announcements; news only
  reaches people who opted in, with unsubscribe links, one-click
  List-Unsubscribe headers and "Reply STOP" on WhatsApp
- WhatsApp STOP via WAWP's message webhook, checked against a secret in
  the webhook URL
- Sign-up opt-in box; existing accounts kept opted in and asked once
- Admin -> Data requests with 30-day due dates and a daily reminder job
- Deleting an account is a request; anonymising now covers attendees,
  form answers, consent details, payer details in provider payloads, etc.
- Unit tests (privacy.test.js) and e2e (privacy.spec.ts); docs

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012mt6TCv85T9VKRq3cV7rXq
This commit is contained in:
2026-10-23 18:13:47 +02:00
co-authored by Claude Opus 5.5
parent be5ad620da
commit 594cb35a85
47 changed files with 2871 additions and 143 deletions
+36 -2
View File
@@ -1,6 +1,6 @@
# Handover — where things stand and how to carry on
Last updated: 2026-10-23, after **1.37.4**. The plan itself is in
Last updated: 2026-10-23, after **1.38.0**. The plan itself is in
[ROADMAP-v2.md](./ROADMAP-v2.md) (decisions table at the end); the previous round of
features is in [FEATURES-ROUND-1.md](./FEATURES-ROUND-1.md).
@@ -8,6 +8,7 @@ features is in [FEATURES-ROUND-1.md](./FEATURES-ROUND-1.md).
| Version | What |
|---------|------|
| 1.38.0 | POPIA toolkit part 1 — Privacy page (news opt-in + history, download my data, correction and deletion requests), Event update vs News & announcements on every bulk send, unsubscribe links + one-click headers, WhatsApp STOP, one-time news question, Admin → Data requests, thorough anonymising |
| 1.32.0 | Early-bird ribbon on event cards |
| 1.33.0 | Multi-day events phase A — sessions, late joining + late pricing, per-session check-in, attendance register, session reminders, certificates (4 templates, verify page) |
| 1.34.0 | Background jobs on pg-boss (admin Background jobs page), Playwright e2e tests, Gitea CI workflow, `scripts/deploy.sh` |
@@ -22,7 +23,11 @@ features is in [FEATURES-ROUND-1.md](./FEATURES-ROUND-1.md).
## Next up
1. **1.38.0–1.39.0 — POPIA toolkit** (spec §3 in the roadmap).
1. **1.39.0 — POPIA toolkit part 2** (spec §3 in the roadmap): Information Officer on the
privacy notice/contact page, privacy notice editor with versions and re-acceptance, consent
log page (the data is already in `CommunicationConsent`), retention policies + nightly job,
operator register, access log (exports, attendee lists, medical list, impersonation),
breach register, POPIA readiness page.
2. **1.40.0 + Hub v1** — install/update tooling and the Hope Events Hub (separate repo
`joshua/hope-events-hub`).
@@ -112,8 +117,37 @@ features is in [FEATURES-ROUND-1.md](./FEATURES-ROUND-1.md).
- `Payment.method` stays the wallet type (card, apple_pay…) because cashups bucket on it;
the provider is `Payment.provider`.
## Where the privacy (POPIA) code lives
- `backend/src/utils/privacy.js` — news opt-in (`setNewsPreferences`, logged in
`CommunicationConsent`), unsubscribe tokens (HMAC with `JWT_SECRET`, never expire),
`filterRecipients` (every bulk send goes through it: `kind` 'update' passes, 'news' only to
opted-in accounts and not to `MessagingOptOut` addresses), email/WhatsApp footers, STOP
handling, data requests, `deletionBlockers`, `anonymiseUser`, `exportUserData` + PDF.
Tests: `backend/tests/privacy.test.js`; e2e: `e2e/privacy.spec.ts`.
- `controllers/privacyController.js` + `routes/privacyRoutes.js` (`/api/privacy`).
- Bulk sends: `broadcastController`, `whatsappBroadcastController`, and `emailEventAttendees` /
`whatsappEventAttendees` in `eventController` take `kind`; scheduled payloads carry it.
- WhatsApp STOP: `handleIncomingMessage` in `whatsappController.js` (WAWP `message` event on
`/api/whatsapp/webhook?secret=…`; secret in AppSetting `WAWP_WEBHOOK_SECRET`).
- Frontend: `app/dashboard/user/privacy`, `app/unsubscribe`, `app/dashboard/admin/data-requests`,
`components/shared/NewsPrompt.tsx` (dashboard layout), `MessageKindPicker.tsx`.
- **Anything new that stores personal data** must be added to `anonymiseUser` and
`exportUserData`.
## Known gaps / follow-ups
- WhatsApp STOP only works once someone pastes the webhook URL from Site settings → WhatsApp
into WAWP (events `message` + `session.status`), and only for senders WhatsApp identifies by
phone number — a sender known only by a "LID" is logged and ignored (they can still use the
link in the message).
- `/api/webhooks/whatsapp` (`webhookController.handleWhatsappWebhook`) is an old empty stub; the
real WAWP webhook is `/api/whatsapp/webhook`. Remove the stub in a later release.
- There's no log of individual messages sent, so "Download my data" can't include "messages
sent to you" yet.
- The audit-log page's action filter list was already missing newer actions before 1.38.0
(only the three privacy ones were added).
- At the door, per-session tickets are booked on new registrations only; editing an
existing registration leaves its booked sessions as they are (the backend refuses
changes to picked lines). "Add & charge" for a session at the scanner isn't built.