fix(rls): block member self-change of payment/dues columns (D9)

prevent_member_self_role_change covered role/status/space but not the
financial columns; RLS members_update lets a member UPDATE their own
row, so payment_status/dues_paid_until/last_paid_at/last_payment_at/
stripe_customer_id/payment_note/joined_at were self-settable via
PostgREST to forge a dues-good signal. Migration 044 adds them to the
blocked set; service-client (auth.uid() NULL) + privileged-on-another-
member writes still bypass (Stripe webhook unaffected). Schema + maps.
This commit is contained in:
Moheeb Zara
2026-05-18 18:41:35 -07:00
parent 26e54f1fcb
commit 785e8e0ef9
4 changed files with 70 additions and 5 deletions
+1
View File
@@ -409,6 +409,7 @@ Default channels created by trigger on space INSERT: `general`, `announcements`,
| 041 | Notifications P2: `notifications` outbox (type, recipient, subject, body_html/text, status, attempts, dedupe_key). UNIQUE(space_id,dedupe_key) = idempotent webhook enqueue; partial idx (created_at) WHERE status='pending'. SELECT admin/board/treasurer, service-client-only writes (webhook enqueue + dispatcher cron); member self-view via validated action |
| 042 | Equipment double-booking P0 fix: `btree_gist` extension + `equipment_reservations_no_overlap` GiST EXCLUDE (equipment_id =, tstzrange(starts_at,ends_at,'[)') &&) WHERE status='reserved'. DB is the concurrency arbiter; reserveEquipment maps 23P01 to a friendly message. No new table/column |
| 043 | `members_update` RLS gains a WITH CHECK mirroring its USING (defense-in-depth: a privileged caller can't mutate a member row into a space they don't administer). No new table/column |
| 044 | `prevent_member_self_role_change` also blocks self-change of payment_status, payment_note, dues_paid_until, last_paid_at, last_payment_at, stripe_customer_id, joined_at (member could otherwise forge a dues-good signal via PostgREST). Service-client/privileged writes still bypass. No new table/column |
---