Skip to content

feat(admin-access): single hardcoded admin with full RLS-backed access + audit log - #67

Merged
reik merged 6 commits into
masterfrom
feat/admin-access
Aug 25, 2026
Merged

feat(admin-access): single hardcoded admin with full RLS-backed access + audit log#67
reik merged 6 commits into
masterfrom
feat/admin-access

Conversation

@reik

@reik reik commented Aug 24, 2026

Copy link
Copy Markdown
Owner

Summary

  • is_admin() SECURITY DEFINER fn (matches kurarei+5@gmail.com) + FOR ALL RLS policies across families/announcements/comments/reactions/availability_slots/messages/playdate_requests (coach_events read-only, per the feature spec's own proposed default), plus an append-only admin_audit_log table.
  • New supabase/functions/admin/index.ts: users/content/messages routes behind a single is_admin() gate; service-role client constructed only where Supabase's Admin API is required (email edits, ban/unban, force password reset), never returned to the caller; every mutation writes one audit log row in the same request.
  • New /admin frontend route (Users / Content / Messages tabs), wired into RequireAuth, including the spec-required "you are viewing a private conversation" banner on the Messages view. Nav link + page redirect are UX-only — the real gate is server-side.
  • Full detail (including two things found beyond the literal acceptance criteria — a column-privilege gap that would've silently blocked admin's own writes to messages/playdate_requests, and an unrelated pre-existing bug in playdate_requests' existing update path) is written up in fofafu_vault/features/admin-access.md's Backend/Frontend/Test plan sections.

Screenshots

Users — list, ban/unban, force-password-reset:
Users view

Users — edit (name/bio/kids/avatar/email; added after code review, see below):
Users edit form

Content — announcements/comments (editable) and reactions (delete-only):
Content view

Messages — conversation lookup, the required private-conversation banner, edit/delete:
Messages view

Code review

An independent code-reviewer pass (a separate session running this repo's own /dispatch protocol) found 7 must-fix issues against the first commit here, including two real bugs: an audit-log completeness gap in updateUser (a family-table mutation could persist with zero audit trail if a later email-change step failed) and a security-relevant gap where both non-admin-readonly-column triggers omitted the id column, letting a non-admin rewrite a row's primary key. All 7 are now fixed — full writeup in the feature spec's Code review section (fofafu_vault/features/admin-access.md).

Test plan

  • Backend: 147/147 (full suite, pre-existing tests unaffected)
  • Frontend: 140/140, 32/32 files (full suite, pre-existing tests unaffected)
  • Deno unit tests for the Edge Function: 20/20 (up from 12 — the review found 6/11 routes were untested, incl. DM read/edit and the email-change path; all 11 routes now have happy-path coverage, plus a regression test for the audit-log fix)
  • tsc --noEmit clean on both workspaces

Not covered, flagged rather than silently skipped:

  • RLS policies and the column-grant triggers are manually reviewed only — this repo has no pgTAP/local-Postgres test harness yet, so verify against a real/staging Supabase project before merging
  • No Playwright E2E coverage this pass

Still a draft given the above — the admin identity here has full read/write over every user's data including private DMs on a platform for foster families and children, so I'd rather this get a real look before it's mergeable than default to ready-for-review.

🤖 Generated with Claude Code

reik and others added 4 commits August 20, 2026 13:06
Scaffold fofafu_vault/features/admin-access.md and draft Problem/Acceptance
criteria/Out of scope/Open questions with the user: a single hardcoded admin
account with full edit access (including DMs) across all user data.

Design targets Supabase (is_admin() RLS bypass policies + a new admin Edge
Function + admin_audit_log table) rather than the legacy Express/sqlite
stack, since migrate-render-to-vercel-supabase is actively decommissioning
Express.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
is_admin() will match kurarei+8@gmail.com, per user decision. Not yet
verified as a registered Supabase Auth user in the live project.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Was kurarei+8@gmail.com; user had mixed up which test account was which.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…s + audit log

is_admin() SECURITY DEFINER fn (kurarei+5@gmail.com) + FOR ALL RLS policies
across families/announcements/comments/reactions/availability_slots/
messages/playdate_requests (coach_events read-only per spec's own default),
admin_audit_log (append-only). messages/playdate_requests needed a BEFORE
UPDATE trigger beyond the literal spec since their existing column-grants
would otherwise silently block admin's own-session-token writes.

New admin Edge Function: users/content/messages routes, single is_admin()
gate, service-role client only where Supabase's Admin API is required
(email/ban/reset-password), every mutation audit-logged.

Frontend /admin (Users/Content/Messages) wired into App.tsx + RequireAuth,
including the required "private conversation" banner on the Messages view.

Backend 147/147, frontend 139/139, Deno 12/12, tsc clean both workspaces.
No pgTAP harness exists in this repo for the RLS/trigger SQL (manual-review
only) and no E2E coverage this pass -- both flagged in the feature spec
rather than silently skipped.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 24, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
fofafu-frontend Ready Ready Preview Aug 24, 2026 9:56pm

An independent code-reviewer pass (separate session running this repo's
real dispatcher) found 7 must-fix issues against commit 3e2321c. All fixed:

- updateUser's single deferred audit-log call could leave a family-table
  mutation with zero audit trail if a later email-change step failed.
  Now writes two independent audit entries, one per persisted mutation.
- Both non-admin-readonly-column triggers (messages, playdate_requests)
  didn't guard the id column, letting a non-admin rewrite a row's primary
  key -- something the pre-existing column grants used to fully block.
- UsersView had no UI path to actually edit a user's email/name/bio/
  kidCount/avatarUrl despite the backend route existing. Added an inline
  RHF+Zod edit form.
- Deno test coverage claimed "every route's happy path" but 6 of 11 routes
  were untested. Expanded 12 -> 20 tests, including a regression test for
  the audit-log fix above.
- MessagesView's lookup form and both inline content editors used
  hand-rolled useState instead of this codebase's established useForm +
  zodResolver convention. Refactored to match.
- Request bodies had no runtime narrowing. Added typeof-guards matching
  message/index.ts's own precedent.

Also applied both nice-to-haves (dropped unneeded SECURITY DEFINER on the
trigger functions; admin_audit_log's INSERT policy now also checks
admin_user_id = auth.uid()) and formally resolved the coach_events Open
Question.

Backend 147/147, frontend 140/140, Deno 20/20, tsc clean both workspaces.

Adds real screenshots of all three admin views (Users incl. the new edit
form, Content, Messages incl. the private-conversation banner), captured
via headless Playwright against the actual dev server with mocked network
responses and an injected fake admin session -- no real Supabase project
touched. Documents a new deterministic PR-screenshot requirement in
fofafu_vault/standards/engineering-standards.md per explicit request.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
# Conflicts:
#	fofafu_vault/kanban/company.md
#	fofafu_vault/kanban/engineering.md
#	fofafu_vault/log/2026-08-20.md
#	fofafu_vault/standards/engineering-standards.md
#	frontend/src/components/Navbar.tsx
@reik
reik marked this pull request as ready for review August 25, 2026 01:21
@reik
reik merged commit b5740c6 into master Aug 25, 2026
4 checks passed
reik added a commit that referenced this pull request Aug 25, 2026
- Resolved PR #65's merge conflicts against master (kanban/company.md,
  kanban/engineering.md, log/2026-08-22.md), caused by #66/#67 landing
  on master after this PR's merge-base.
- Corrects the record on the recurring "[Fact-Forcing Gate]" tool
  error that multiple prior sessions flagged as unexplained/injected:
  it's a real PreToolUse hook from the everything-claude-code plugin's
  gateguard skill, not injection.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
reik added a commit that referenced this pull request Aug 25, 2026
Status review -> shipped, kanban moved to Done on both boards. Also
repointed PR #67's screenshot links from the feat/admin-access branch to
master before the source branch gets deleted, and corrected the log record
on the "Fact-Forcing Gate" -- confirmed real (everything-claude-code
plugin's gateguard hook), not injection.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant