Implement JWT claim injection for RLS tenant scoping
epic-organizational-hierarchy-management-core-services-task-012 — Implement the server-side mechanism (Supabase Edge Function or database hook) that injects the authenticated user's organization unit IDs and role into the JWT as custom claims on each session. RLS USING clauses will reference auth.jwt() -> 'unit_ids' to enforce tenant isolation without application-layer filtering.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 2 - 518 tasks
Can start after Tier 1 completes
Implementation Notes
Supabase supports custom access token hooks as PostgreSQL functions registered in Auth settings (Dashboard → Authentication → Hooks). The function signature must match Supabase's expected hook interface: it receives a JSON event object and returns a JSON response with the modified claims. Write the hook as a SQL function: CREATE OR REPLACE FUNCTION auth.custom_access_token_hook(event jsonb) RETURNS jsonb. Inside the function, extract auth.uid() from event, query unit_assignments and user_roles, then return event with app_metadata claims merged.
Use jsonb_build_object and jsonb_set for claim manipulation. For NHF's multi-chapter support, use array_agg(ua.unit_id) in the query and cast to jsonb. Note: Supabase's custom access token hook was introduced in Supabase Auth v2.99+ — verify the project's Supabase version supports it before implementation. If not available, fall back to a Postgres trigger on auth.sessions or a Realtime-triggered Edge Function, but document the tradeoffs.
JWT claim changes only take effect on the NEXT token refresh — document that unit membership changes require the affected user to refresh their session (or implement server-side session invalidation via Supabase Admin API).
Testing Requirements
Integration tests using a local Supabase instance: (1) Sign in as a peer_mentor user and verify decoded JWT contains app_metadata.unit_ids array with exactly the assigned unit IDs and app_metadata.role = 'peer_mentor'. (2) Sign in as a coordinator and verify unit_ids includes all units in their chapter subtree. (3) Sign in as a global_admin and verify unit_ids = [] and role = 'global_admin'. (4) Test NHF multi-chapter scenario: user assigned to 3 chapters has all 3 unit IDs in the array.
(5) Test hook failure scenario: temporarily break the hook query and verify the resulting JWT triggers 403 (not silent full-access). (6) Performance test: measure hook execution time under load (10 concurrent refreshes) and assert P95 < 500ms. (7) Verify that changing unit membership and forcing a token refresh produces an updated JWT with new unit_ids. All tests must be automated and runnable via supabase db test or a dedicated integration test suite.
Injecting all unit assignment IDs into JWT claims for users assigned to many units (up to 5 for NHF peer mentors, many more for national coordinators) may exceed JWT size limits, causing authentication failures.
Mitigation & Contingency
Mitigation: Store unit IDs in a Supabase session variable or a dedicated Postgres function rather than embedding them directly in the JWT payload. Use set_config('app.unit_ids', ...) within RLS helper functions querying the assignments table at policy evaluation time.
Contingency: Fall back to querying the unit_assignments table directly within RLS policies using the authenticated user ID, accepting a small per-query overhead in exchange for removing the JWT size constraint.
Rendering 1,400+ nodes in a recursive Flutter tree widget may cause jank or memory pressure on lower-end devices used by field peer mentors, degrading the admin experience.
Mitigation & Contingency
Mitigation: Implement lazy tree expansion — only the root level is rendered on initial load. Child nodes are rendered on demand when the parent is expanded. Use const constructors and ListView.builder for all node lists to minimize rebuild scope.
Contingency: Add a search/filter bar that scopes the visible tree to matching nodes, reducing the visible node count. Provide a 'flat list' fallback view for administrators who prefer searching over browsing the tree.
Requirements for what constitutes a valid hierarchy structure may expand during NHF sign-off (e.g., mandatory coordinator assignments per chapter, minimum member counts per region), requiring repeated validator redesign.
Mitigation & Contingency
Mitigation: Design the validator as a pluggable rule engine where each check is a discrete, independently testable function. New rules can be added without changing the core validation orchestration. Surface all rules in a configuration table per organization.
Contingency: Defer non-blocking validation rules to warning-level feedback rather than hard blocks, allowing structural changes to proceed while flagging potential issues for admin review.
Deploying RLS policy migrations to a shared Supabase project used by multiple organizations simultaneously could lock tables or interrupt active sessions, causing downtime during production migration.
Mitigation & Contingency
Mitigation: Write all RLS policies as CREATE POLICY IF NOT EXISTS statements. Schedule migrations during off-peak hours. Use Supabase's migration preview environment to validate policies against production data shapes before applying.
Contingency: Prepare rollback migration scripts for every RLS policy. If a migration causes issues, execute the rollback immediately and re-test the policy logic in staging before reattempting.