harden(forms): self-audit fixes for Phase 1 RLS and indexes

- drop redundant idx_forms_slug (UNIQUE constraint already indexes slug)
- tighten forms_select: forms.manage sees all; ordinary members see
  only published forms (drafts/closed no longer exposed via PostgREST)
- make submitter_email and member_id submission indexes partial
  (both columns are frequently NULL)
This commit is contained in:
Moheeb Zara
2026-05-16 14:42:51 -07:00
parent 3a2d3ca13f
commit 66dd9b7ec0
4 changed files with 33 additions and 15 deletions
+1 -1
View File
@@ -21,7 +21,7 @@ Branch: `main`. Migration: `026` (not yet deployed). `pnpm build` + `pnpm exec v
- New `scripts/026_forms.sql` (idempotent) + mirrored as `schema.sql` Section 17 + Section 15 seed-function updated.
- Tables: `forms` (space_id, globally-UNIQUE `slug`, `kind` form/waiver, `visibility` public_anon/public_auth/members, `status` draft/published/closed, `schema` jsonb, `legal_text`, `version`, `created_by`) and `form_submissions` (form_id, denormalized space_id, member_id, submitter_email, answers, `form_snapshot`/`legal_text_snapshot`/`form_version` immutable snapshots, ip, user_agent). Indexes on space/slug/form/email/member.
- RLS (additive, default-deny): `forms` SELECT = space members; INSERT/UPDATE/DELETE = `user_has_permission(..., 'forms.manage')`. `form_submissions` SELECT = `forms.manage` only; **no write policy at all** → RLS hard-denies every non-service client and makes submissions immutable. Every submission funnels through one validated service-client server action (Phase 2). Public `/f/[slug]` will be served by a service-client server action, so `anon` gets no grant on `forms` (user-chosen option).
- RLS (additive, default-deny): `forms` SELECT = `forms.manage` holders (all forms) or ordinary members (published only — drafts/closed hidden from non-managers); INSERT/UPDATE/DELETE = `user_has_permission(..., 'forms.manage')`. `form_submissions` SELECT = `forms.manage` only; **no write policy at all** → RLS hard-denies every non-service client and makes submissions immutable. Every submission funnels through one validated service-client server action (Phase 2). Public `/f/[slug]` will be served by a service-client server action, so `anon` gets no grant on `forms` (user-chosen option).
- New `forms.manage` permission added to `lib/permissions-catalog.ts` (group Community) + board default; `seed_default_role_permissions()` extended; backfill `board -> forms.manage` for existing spaces (ON CONFLICT DO NOTHING).
- `types/database.ts`: `forms` + `form_submissions` added. Docs updated: DATABASE_SCHEMA (026 row), DB_SCHEMA_MAP (quick-map + migrations), ARCHITECTURE (forms note).