high priority medium complexity backend pending backend specialist Tier 3

Acceptance Criteria

Calling the invite endpoint with a valid admin JWT, a valid email, and a permitted role creates a row in the `user_invitations` table with status='pending', expiry=72h from now, and a cryptographically random token (min 32 bytes, URL-safe base64)
The edge function returns HTTP 201 with the invitation ID on success
Attempting to invite an email already registered in the org returns HTTP 409 with a descriptive error body
Attempting to assign a role higher than the calling admin's own role returns HTTP 403
An invitation email is dispatched to the invitee within 30 seconds of the API call completing
Re-inviting the same email (while a pending invitation exists) invalidates the old token and creates a new one rather than duplicating
Invitation tokens are single-use: accepting an invitation marks the token as consumed and prevents replay
All invitation records include org_id, invited_by (admin user ID), role_id, created_at, expires_at, and consumed_at (nullable)
Expired invitations (older than 72h) are not honoured on acceptance
Supabase RLS policies prevent non-admin roles from inserting or reading other users' invitation records

Technical Requirements

frameworks
Supabase Edge Functions (Deno/TypeScript)
Supabase Auth
apis
POST /functions/v1/invite-user — payload: { email, role_id, org_id }
Supabase Auth admin.inviteUserByEmail() or custom SMTP via Resend/SendGrid
Supabase DB: user_invitations table insert/upsert
data models
UserInvitation
UserRole
Organisation
AdminUser
performance requirements
Edge function cold start + DB write + email dispatch must complete within 5 seconds (p95)
DB index on (email, org_id, status) for fast duplicate detection
security requirements
Token generated with crypto.getRandomValues() — never Math.random()
Token stored as bcrypt hash in DB; raw token sent only in email link
JWT claim checked server-side: inviting admin's org_id must match request org_id
Role permission check: invited role's permission_level must be <= admin's permission_level
Rate limiting: max 20 invitations per admin per hour (Supabase rate-limit header or edge middleware)
Invitation email link must use HTTPS and include a CSRF-safe one-time token

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Use Supabase's built-in `auth.admin.inviteUserByEmail()` if it supports role pre-assignment metadata; otherwise, implement a custom edge function that calls Supabase Auth's invite endpoint and separately writes the invitation metadata (role, org) to `user_invitations`. The acceptance flow (when the invitee clicks the email link) must read the pre-assigned role from `user_invitations` and apply it at the moment of account creation — store role_id in the invitation row, not in the email link itself. For token storage, store only the hash (SHA-256 or bcrypt) in the DB and send the raw token in the email; this prevents token leakage via DB access. Add a Supabase scheduled function or a DB trigger with pg_cron to mark expired invitations as 'expired' (cron every hour) to keep the table clean.

Document the edge function's environment variables (SMTP credentials, FROM address) in the project's .env.example.

Testing Requirements

Unit tests (Deno): (1) token generation produces unique 32-byte URL-safe strings; (2) role permission check rejects higher-than-admin roles; (3) duplicate detection returns correct HTTP status. Integration tests against a local Supabase instance: (1) full happy path — invite → DB row created → email dispatched; (2) duplicate invite replaces old token; (3) expired token rejected on acceptance; (4) RLS prevents non-admin reads. Use Supabase's local dev environment (`supabase start`) for integration tests. Verify email template renders correctly with the invitation link.

No flutter_test needed for this task — pure backend.

Component
User Account Management Screen
ui high
Epic Risks (3)
medium impact medium prob technical

Displaying NHF users with membership in up to 5 local chapters in a flat list view without duplicating entries requires a non-trivial aggregation query. Incorrect query design could result in duplicated user rows or missing chapter affiliations, confusing admins and causing incorrect role assignments.

Mitigation & Contingency

Mitigation: Design the user list query to GROUP BY user_id and aggregate chapter affiliations as an array field. Use AdminRepository's typed models to surface this aggregated structure to the UI. Validate with a test dataset containing users in 5 chapters.

Contingency: If aggregation query complexity proves too high for real-time filtering, implement a separate multi-chapter affiliation fetch triggered only when a specific user row is expanded, reducing query complexity for the base list.

medium impact medium prob technical

Composable multi-dimensional filters (role + chapter + status + certification state) applied server-side against an org with 2,000+ users may produce slow queries, particularly when filtering by certification state requires joining an additional table.

Mitigation & Contingency

Mitigation: Ensure the relevant filter columns (role, status, chapter_id, certification_expiry) are indexed in Supabase. Use cursor-based pagination rather than OFFSET to maintain consistent performance at high page numbers. Profile filter query combinations against a large dataset during development.

Contingency: If multi-filter performance degrades in production, introduce a denormalised search index table updated on user status changes, allowing the list query to filter from a single table.

medium impact medium prob integration

Deactivating a user account that has ongoing activity assignments, open expense claims, or active chapter affiliations may leave orphaned records or break downstream workflows if the deactivation does not trigger correct cascade handling.

Mitigation & Contingency

Mitigation: Define and document the expected state of each dependent record type on user deactivation before implementing the toggle. Implement deactivation as a UserManagementService operation that checks for and warns about open dependencies before persisting. Write integration tests covering each dependency type.

Contingency: If orphaned record issues are discovered post-launch, provide an admin-accessible reconciliation view that surfaces users with inconsistent dependency states and allows manual resolution without requiring a code deploy.