Manage Users

Feature Owner (original implementation): leidc024 (Leian Carl Dela Cruz) — original RBAC + Manage Users UI (2025-07)
Updated by / current owner: scorevi (multi-role system, admin user routes, majority of fixes)
Hand-Off author: Patrick Babala
Module: User Management — [6.1] Manage Users (with [6.3] Suspend User and [6.4] Permanent User Delete)
Priority: P0
Status: Handoff — New (v1.0)
Date: 2026-10-01
Audit: docs/FEATURE_AUDIT_MAY2026.md Module 6 — User Management
QA cases: QA-073.5 (Secure User Delete), QA-081 (Verification Prompt Shows)
Companion doc: Internal Technical Guide — Internal-Technical-Guide-Manage-Users.md (architecture, DB schema, API specs, helpers, ops, troubleshooting). This Hand-Off stays product-facing; where engineering depth is needed it cross-references the ITG instead of duplicating it.

EXECUTIVE SUMMARY

What is this feature?

Manage Users is the Super-Admin console at /admin/manage-users for controlling platform access. It lists every user with their platform roles and agency-assigned role, lets an admin assign one or many roles, invite new users by email, suspend or unsuspend access, and permanently delete an account. Destructive actions (role change, delete, suspend) are gated behind a re-authentication challenge and written to an audit trail.

Why does it matter?

Roles decide what every user can see and do across WyzQuests. Before this console, access decisions were manual and unaudited. Manage Users centralizes role assignment, onboarding, suspension, and removal into a single screen, and makes every sensitive action attributable through audit_logs.

MVP scope (current implementation):

  • List all users with platform roles, primary role, and agency-assigned role.

  • Assign multiple platform roles per user (normalized user_roles).

  • Invite a user by email: create a Supabase Auth account, assign roles, send a branded “set your password” email.

  • Suspend / unsuspend a user (app_users.status).

  • Permanently delete a user (typed confirmation + re-authentication).

  • Sync profile name/email/avatar from Supabase Auth into the app profile.

  • Re-authentication challenge (≤5 min, single-use) for destructive actions.

  • Audit logging for role changes, deletes, invites, lookups, and re-auth events.

Shipped scope (v1.0):

  • Database — user_roles (multi-role junction), admin_reauth_tokens, audit_logs; app_users.role, .status, .profile_image_url.

  • API — list-users, change-role, delete-user, invite-user, suspend-user, unsuspend-user, reauth (GET/POST), sync-users, lookup-user.

  • UI — Manage Users table, MultiSelect role editor, ReAuthModal, shared ConfirmDestructiveModal, InviteUserDialog, SuspendDialog, UnsuspendDialog.

Still deferred: soft-delete / GDPR erasure with a grace window, bulk actions, an in-app audit-log viewer, session revocation on role change, resend-invite, and per-user activity timeline.


1. USER PAIN POINT & SOLUTION

Current State (Without Feature)

There is no single console for access control. Role changes and account removal require manual database work, invitations are done outside the product, suspension is not consistently enforced, and there is no attributable record of who changed what.

Pain Point

  • Emotional: Admins are anxious about irreversible actions and cannot confirm a change actually took effect.

  • Functional: No multi-role support, no clean onboarding path, and no audit trail for sensitive actions.

  • Business Impact: Slower user onboarding, inconsistent access control, and no compliance record for deletions or privilege changes.

Future State (With Feature)

An admin opens one screen, sees every user and their roles, and safely assigns roles, invites, suspends, or deletes accounts. Destructive actions require the admin to re-verify their identity, and every action is logged.

Marketing Hook

“Every role, invite, and access decision in one audited console.”


2. CODEBASE ASSESSMENT

Current Implementation Status

Manage Users is implemented across four database objects, nine API routes, one admin page, and a set of dialogs. All endpoints are ADMIN-only; role change, delete, and suspend additionally require a re-auth token; delete also requires a typed confirmation.

Primary Files

  • app/admin/manage-users/page.tsx — the console: user table, sorting, role MultiSelect, actions menu, dialog orchestration.

  • app/api/admin/list-users/route.ts — lists users; also serves agency owner/member pickers via mode.

  • app/api/admin/change-role/route.ts — multi-role diff and apply (re-auth gated).

  • app/api/admin/invite-user/route.ts — create auth user, assign roles, send email, roll back on failure.

  • app/api/admin/suspend-user/route.ts / unsuspend-user/route.ts — status flip + auth cache invalidation.

  • app/api/admin/delete-user/route.ts — typed-confirm + re-auth delete.

  • app/api/admin/reauth/route.ts — issues/consumes the ≤5-minute single-use token.

  • app/api/admin/sync-users/route.ts — profile sync from Supabase Auth.

  • app/api/admin/lookup-user/route.ts — email lookup + agency eligibility (audited).

  • components/admin/manage-users/{InviteUserDialog,SuspendDialog,UnsuspendDialog}.tsx, components/admin/ReAuthModal.tsx, components/shared/confirm-destructive-modal.tsx, components/ui/MultiSelect.tsx.

  • supabase/migrations/20260612_create_user_roles_multi_role.sql, 20260526_create_admin_reauth_tokens.sql, 20260414_create_audit_logs_table.sql, 20260430_add_profile_image_url_to_app_users.sql, 20260219_add_reviewer_role.sql.

Current Behavior Summary

  • The console is reachable only to ADMIN (app/admin/layout.tsx, constants/getNavGroups.ts).

  • Users can hold multiple platform roles; the highest-priority role is mirrored to app_users.role for backward compatibility.

  • Agency membership roles are shown read-only alongside platform roles.

  • Invites create a Supabase Auth account and email a password-reset link (no credentials are ever emailed).

  • Suspension is enforced on page navigation by middleware and inside the API auth chain, not only in this console.

  • Every sensitive action is written to audit_logs.

Strengths Already Present

  • Multi-role model with a clear priority order and an agency-role bridge.

  • Re-auth tokens are single-use and time-bounded; failed verification is audited.

  • Invite flow is self-healing: any failure after account creation rolls back the auth user, profile, and roles.

  • Delete requires both a re-auth token and a typed DELETE token re-validated server-side.

  • Suspension takes effect immediately in the serving process and is asserted on cache-hit branches.

Gaps / Hardening Opportunities

  • Deletes are hard (no deleted_at / grace window).

  • delete-user has no compensating rollback; a failed final delete can leave an orphaned auth account.

  • The multi-step role change is not transactional.

  • Expired re-auth tokens have no cleanup job (the migration comment assumes a nightly job).

  • app_users is defined outside supabase/migrations/, so a fresh database cannot be rebuilt from the repo.

  • No unit tests for the admin routes; e2e specs are brittle and hardcode credentials.

  • Button copy still says “Sync from Clerk” after the Supabase Auth migration.

  • unsuspend-user is not re-auth gated while suspend-user is.

  • Invite email rate limiting exists on origin/develop but is absent in the current checkout (see Open Questions).


3. 4D FRAMEWORK MAPPING

Diagnose

Admins lacked a safe, auditable way to assign roles, onboard, suspend, and remove users.

Design

Model access as a set of platform roles per user (user_roles) plus a lifecycle status (app_users.status), and gate every destructive mutation behind re-authentication and an audit record.

Develop

Provide ADMIN-only APIs for list/role/invite/suspend/delete/sync/lookup, a re-auth token service, and a console that surfaces roles, status, and actions.

Deliver

Admins manage the entire user lifecycle from one screen with confidence that actions are deliberate and recorded.


4. USER FLOWS

Entry Point

Admin signs in → /admin → sidebar Manage Users → /admin/manage-users. (The page is guarded server-side by an ADMIN role check; non-admins are redirected or shown Unauthorized.)

Success Criteria

  • Every user appears with correct platform roles, agency role (if any), and status.

  • A role change succeeds only after successful re-authentication and is audited.

  • An invited user receives the invitation email and can set a password.

  • A suspended user loses access; an unsuspended user regains it.

  • A deleted user’s account, profile, and memberships are removed; the deletion is audited.

  • A profile sync updates names/emails without touching roles.

Main Flow (Happy Path)

  1. Admin opens Manage Users; the table loads all users (GET /api/admin/list-users).

  2. Admin changes a user’s roles in the Change Role multi-select; ReAuthModal opens.

  3. Admin verifies identity (password, or email confirmation for Google accounts).

  4. POST /api/admin/change-role consumes the token, diffs roles, saves, and returns success.

  5. Admin clicks Invite User, enters an email and roles; POST /api/admin/invite-user creates the account and sends the email.

  6. Admin suspends a user: Actions → Suspend → verify identity → POST /api/admin/suspend-user.

  7. Admin deletes a user: Actions → Delete → type DELETE → verify identity → DELETE /api/admin/delete-user.

  8. Admin optionally clicks Sync from Clerk to refresh names/emails (POST /api/admin/sync-users).

Edge Cases

  • Wrong re-auth password: action is blocked; REAUTH_FAILED is audited; modal stays open for retry.

  • Expired/used token: 403 — “Re-authentication token is invalid or expired…”.

  • Delete an ADMIN: blocked with “Cannot delete admin users”.

  • Delete a missing user: 404 — “User not found”.

  • Invite an existing email: 409 with guidance to use Change Role.

  • Invite email fails: the invite is rolled back so it can be retried cleanly.

  • Not an admin / View-As mode: re-auth returns 403; the modal explains to exit View-As.

  • Admin’s own account suspended: API returns 403 ACCOUNT_SUSPENDED.

  • No roles selected: the Change Role multi-select refuses to submit an empty set.

Decision Points

  • IF the caller is not ADMIN → no access to the console or APIs.

  • IF the user has an agency-assigned role → that role is disabled (read-only) in the multi-select.

  • IF multiple roles are selected → the highest-priority becomes the primary app_users.role.

  • IF the target is the last page of the users table → no pagination issues exist today (full list returned).


5. INFORMATION ARCHITECTURE

Primary Information (Always visible)

  • User (avatar, name, short internal ID)

  • Email

  • Platform role badges

  • Change Role multi-select

  • Status (Active / Suspended)

  • Actions menu

Secondary Information

  • Agency-assigned role badge (role · agency name, read-only)

  • Invite User button

  • Sync from Clerk button (profile refresh)

Tertiary Information (Hidden until needed)

  • Full internal app_users.id

  • clerk_id (Supabase auth UUID)

  • Audit records (audit_logs)

Actions

  • Primary CTA: Invite User

  • Secondary Actions: Change Role, Sync from Clerk, Suspend / Unsuspend, Delete User (destructive, confirmation + re-auth required)


6. WIREFRAMES

The feature is a single Super-Admin page: a card with a header (title, description, Invite User and Sync from Clerk) and a sortable user table.

Key Screens:

  • Manage Users table (sortable columns: User, Email, Role, Change Role, Status, Actions)

  • Invite User dialog (email + role multi-select)

  • Confirm Identity modal (ReAuthModal: password or email confirmation)

  • Delete User modal (ConfirmDestructiveModal: type DELETE)

  • Suspend / Unsuspend confirmation dialogs

Annotations:

  • Agency-assigned roles render as read-only badges and are disabled in the multi-select.

  • An empty role selection is rejected client-side (at least one role required).

  • Status drives which action appears (Suspend for Active, Unsuspend for Suspended).

  • The Sync from Clerk label is legacy copy; the sync now reads Supabase Auth.

The architecture/system diagram lives with the companion ITG (Internal-Technical-Guide-Manage-Users.md), not here.


7. WIREFLOWS

Admin opens Manage Users → table loads → selects roles in Change Role → Confirm Identity (password/email) → roles saved and toast shown → optionally Invite User (email + roles) → invitation email sent → optionally Suspend/Unsuspend (identity check for suspend) → optionally Delete (typed DELETE then identity check) → account and memberships removed and audited → optionally Sync from Clerk to refresh profile fields.


8. PROTOTYPE

Figma Prototype Link: Not currently available for this feature.

How to test:

  1. Sign in as an ADMIN and open /admin/manage-users.

  2. Change a user’s roles in the Change Role column and complete the Confirm Identity prompt.

  3. Click Invite User, enter an email and roles, and confirm the invite email is received.

  4. Suspend a user, then verify they lose access; unsuspend and verify access returns.

  5. Delete a test user: type DELETE, complete the identity prompt, and verify the row disappears and an audit_logs USER_DELETE entry exists.


9. DATA MODEL

Core Tables

  • app_users — the application profile row: id (internal UUID), clerk_id (auth UUID, legacy name), email, name, role (primary role for backward compatibility), status (Active / Suspended), profile_image_url, agency_id. This table is created outside supabase/migrations/ and is not reconstructable from the repo.

  • user_roles — one row per user↔platform-role pair (user_id FK → app_users.id ON DELETE CASCADE, role ∈ ADMIN/AGENCY/CREATOR/REVIEWER/LEARNER, UNIQUE(user_id, role)).

  • admin_reauth_tokens — short-lived single-use tokens (admin_clerk_id, token UNIQUE, expires_at, used, ip_address).

  • audit_logs — sensitive-action trail (user_id, action, entity_type, entity_id, details JSONB, ip_address, user_agent, created_at).

Primary Role Rule

A user may hold multiple roles. The primary role stored in app_users.role is the highest priority: ADMIN > AGENCY > CREATOR > REVIEWER > LEARNER.

Agency Roles

Roles held within an agency (agency_members.role) are merged into the displayed role set but shown read-only; they cannot be changed from Manage Users.

Full column types, indexes, constraints, RLS policies, and the re-auth token lifecycle are in the ITG §3 (Data Model / Schema).


10. API CONTRACTS

All endpoints require the ADMIN platform role. Re-auth token endpoints enforce ADMIN unconditionally (they bypass the dev/staging View-As bypass).

List Users

  • Endpoint: GET /api/admin/list-users (optional ?mode=picker|members&agencyId=)

  • Auth: ADMIN.

  • Behavior: returns all users with role, status, enriched roles[], and agencyRole/agencyName; mode variants serve the agency owner/member pickers.

  • Response: { users: [...] }.

Change Role

  • Endpoint: POST /api/admin/change-role

  • Auth: ADMIN + reauthToken.

  • Body: { userId, roles: string[], reauthToken }.

  • Behavior: consumes the token, diffs current vs. requested roles, applies deletes/inserts, mirrors the primary role; audits ROLE_CHANGE.

  • Response: 200 — “User role updated successfully”.

Delete User

  • Endpoint: DELETE /api/admin/delete-user

  • Auth: ADMIN + reauthToken + typed confirm_text: "DELETE".

  • Behavior: blocks ADMIN targets, deletes the auth account, cleans up agency memberships, deletes the profile row; audits USER_DELETE.

  • Response: 200 — “User <email> has been permanently deleted”.

Invite User

  • Endpoint: POST /api/admin/invite-user

  • Auth: ADMIN.

  • Body: { email, roles: string[] }.

  • Behavior: rejects existing emails (409), creates the auth user, syncs the profile, assigns roles, emails a set-password link; rolls back all artifacts on any failure.

  • Response: 201 { userId, emailSent: true }.

Suspend / Unsuspend User

  • Endpoint: POST /api/admin/suspend-user (re-auth gated) / POST /api/admin/unsuspend-user

  • Body: { userId } (suspend also { reauthToken }).

  • Behavior: sets app_users.status and invalidates the in-process auth caches; audits the re-auth outcome.

  • Response: 200 — “User suspended successfully” / “User unsuspended successfully”.

Re-Authenticate

  • Endpoints: GET /api/admin/reauth (returns { method, hasOAuth }), POST /api/admin/reauth

  • Body: { password } (email/password) or { confirmEmail } (OAuth).

  • Behavior: verifies identity, issues a 5-minute single-use token; audits REAUTH_SUCCESS / REAUTH_FAILED.

  • Response: 200 { token, expiresInSeconds: 300 }.

Sync Users

  • Endpoint: POST /api/admin/sync-users (optional ?clerkId=)

  • Behavior: pulls name/email/avatar from Supabase Auth and updates app_users (batched).

  • Response: 200 { synced, failed }.

Lookup User

  • Endpoint: GET /api/admin/lookup-user?email=

  • Behavior: returns profile + agency eligibility; records a USER_LOOKUP audit entry (enumeration guard).

Auth checks, exact validation schemas, and full request/response shapes are in the ITG §3 (API Specification).


11. DATA REQUIREMENTS

Frontend Needs

  • The full user list (id, clerk_id, name, email, primary role, status, all roles, agency role/name).

  • Whether the current admin can act (ADMIN role, plus successful re-auth for gated actions).

  • Role option set (LEARNER, CREATOR, AGENCY, REVIEWER, ADMIN).

  • The auth method for the current admin (password vs. OAuth) to render the correct re-auth input.

  • Invite and sync results for user feedback.

Backend Needs

  • app_users (profile + status + primary role).

  • user_roles (multi-role assignments).

  • admin_reauth_tokens (token issue/consume).

  • audit_logs (action trail).

  • Supabase Auth admin API (create/delete/get/list users).

  • agency_members + agencies (agency-role enrichment).

  • Email service + branding (invitation email).


12. SECURITY & AUTHORIZATION

Who can access this feature?

  • ADMIN: ✔ full access (list, change role, invite, suspend, unsuspend, delete, sync, lookup).

  • AGENCY / CREATOR / REVIEWER / LEARNER: ✘ cannot open the console or call the APIs.

  • Invited user: receives an email only; no console access.

Authorization Logic

  • Every route calls authenticateRole("ADMIN"); the admin layout re-checks ADMIN server-side before rendering.

  • Role change, delete, and suspend require a re-auth token that is single-use, belongs to the requesting admin, and has not expired (≤5 minutes).

  • Re-auth deliberately enforces ADMIN even in dev/View-As mode.

  • Delete additionally re-validates the typed DELETE token server-side.

  • The identity provider is Supabase Auth; the legacy-named app_users.clerk_id column stores the Supabase auth UUID. Paths and columns named “clerk” are legacy names.

  • Suspension is enforced on page navigation (middleware) and inside the API auth chain, independent of this console.

  • All sensitive actions are written to audit_logs (ROLE_CHANGE, USER_DELETE, INVITE_USER, USER_LOOKUP, REAUTH_SUCCESS, REAUTH_FAILED, REAUTH_TOKEN_INVALID).

Note: re-auth tokens are stored server-side only; the token table is not client-accessible by policy, and the runtime uses the service-role client.


13. ERROR HANDLING

Common Errors

  • Invalid/expired re-auth token (“Re-authentication token is invalid or expired. Please re-authenticate.”).

  • Wrong password (“Invalid password. Re-authentication failed.”).

  • Verification service unavailable (“Could not reach the verification service. Please try again in a moment.”).

  • OAuth email mismatch (“Email does not match your account.”).

  • Non-admin session (“Admin role required for re-authentication” / “Your account does not have admin privileges…”).

  • Target not found (“User not found”).

  • Deleting an admin (“Cannot delete admin users”).

  • Existing email on invite (“A user with this email already exists. Use Change Role to update their roles.”).

  • Invite email failure (“The invitation email could not be sent, so the invite was rolled back. Please try again.”).

  • Suspended admin account (403 ACCOUNT_SUSPENDED).

  • Validation errors (“userId must be a valid UUID”, “reauthToken must be a valid UUID”, “At least one role is required”).

Handling Guidance

  • On a re-auth 403, re-open the Confirm Identity modal and retry; do not retry the destructive call without a new token.

  • On 409 invite, switch the user to Change Role instead of re-inviting.

  • On an invite email failure, retry the invite (it was fully rolled back).

  • Treat 403 ACCOUNT_SUSPENDED on the acting admin as an out-of-band suspension; contact a platform owner.

  • Surface exact messages to the admin; do not silently swallow role or delete failures.


14. TESTING CHECKLIST

Happy Path

  • Admin opens /admin/manage-users; all users load with roles and status.

  • Change role (single and multiple) with a valid re-auth token succeeds and updates the table.

  • Invite a new email; the invitation email arrives; the user appears in the list.

  • Suspend a user; status flips to Suspended; unsuspend restores Active.

  • Delete a user after typing DELETE and verifying identity; the row disappears.

  • Sync from Clerk refreshes names/emails without changing roles.

Edge Cases

  • Wrong re-auth password → action blocked, modal stays open, REAUTH_FAILED audited.

  • Reused/expired token → 403 on the action.

  • Delete an ADMIN → 403 “Cannot delete admin users”.

  • Delete a non-existent user → 404.

  • Invite an existing email → 409.

  • Invite email fails → invite rolled back, retry succeeds.

  • Empty role selection → cannot submit.

  • Agency-assigned role → shown read-only and disabled in the multi-select.

  • Non-admin / View-As session → re-auth 403 with guidance.

  • Suspended acting admin → 403 ACCOUNT_SUSPENDED.

Coverage Notes

  • Automated unit coverage exists only for the shared suspension layer (tests/api/suspension-enforcement.test.ts).

  • E2E coverage: tests/e2e/admin/secure-user-delete.spec.ts (QA-073.5) and tests/e2e/admin/role-change-verification-prompt.spec.ts (QA-081); both hardcode credentials and use brittle selectors. tests/e2e/suspension/suspension.spec.ts covers suspension behavior.

  • No route-level unit tests exist for role change, invite, sync, or lookup.


15. OPEN QUESTIONS

For Product

  • Should user deletion be a soft delete with a grace window and purge, for GDPR / right-to-erasure? — Still open. Today it is a hard delete.

  • Should unsuspend also require re-authentication? — Still open. Suspend does; unsuspend does not.

  • Should changing a user’s roles revoke their active sessions? — Still open. No session revocation today; role changes apply on the next authorization check.

  • Should there be a Resend Invite action for an invite that was received but not acted on? — Still open. A new invite cannot be created for an existing account (409).

  • Should the console include an in-app audit-log viewer? — Still open. Audit data is stored but shown only in the DB.

For Engineering

  • Should app_users be captured in supabase/migrations/ so environments are reproducible? — Still open (currently out-of-band).

  • Is there a nightly cleanup job for expired admin_reauth_tokens as the migration assumes? — Still open / needs ops input.

  • Should lookup-user eligibility use the full user_roles set instead of only the primary role? — Still open.

  • Should the admin routes get route-level unit tests and a stable Playwright spec? — Still open.

  • Is invite email rate limiting (present on origin/develop) merged into this branch? — Still open for this checkout.


16. OUT OF SCOPE (v1.1+)

  • Soft-delete / GDPR purge workflow with a grace window — out of scope.

  • Bulk role assignment, bulk suspend, bulk delete — out of scope.

  • Session revocation on role change — out of scope.

  • CSV import / export of users — out of scope.

  • Per-user activity timeline or content reassignment on delete — out of scope.

  • Managing agency membership from Manage Users — out of scope (owned by Manage Agencies).

  • Self-service profile editing — out of scope (owned by user settings).

  • Renaming the legacy clerk_id / “Clerk” copy — out of scope (deferred cleanup).


17. SUCCESS METRICS

  • Role-change success rate (post-re-auth).

  • Invite delivery success rate.

  • Suspend/unsuspend success rate.

  • Delete completion rate with audit trail present.

  • Re-auth failure rate (wrong password vs. infra) as a security signal.

  • Time to onboard a new user (invite → first sign-in).

  • Count of audit_logs entries per sensitive action (coverage).


18. DEPENDENCIES

This feature depends on

  • Authentication (Supabase Auth) — session resolution, admin create/delete/get/list.

  • app_users profile table (out-of-band schema).

  • user_roles multi-role model.

  • admin_reauth_tokens + the re-auth service.

  • audit_logs store.

  • agency_members + agencies (agency-role enrichment and owner/member pickers).

  • Email service + branding (invitation email).

  • Shared UI: MultiSelect, ReAuthModal, ConfirmDestructiveModal.

  • Admin layout role guard.

These features depend on this

  • Every role-gated area (Creator, Agency, Reviewer, Learner dashboards) — roles set here decide access.

  • Suspension enforcement across the API auth chain and middleware.

  • Manage Agencies add-member/owner pickers (list-users?mode=picker|members, lookup-user).

  • Enrollment / team flows that resolve user identity and roles.

  • Audit and compliance reporting.


19. TIMELINE & OWNERSHIP

  • Original owner / Hand-Off author: leidc024 (Leian Carl Dela Cruz) — original RBAC and Manage Users UI (2025-07).

  • Lead / current owner: scorevi — multi-role system, admin routes, majority of fixes (2026-02 → 2026-06).

  • Contributors: Joylynne Esportuno (suspend/unsuspend + rollback), Onise Cortez (secure delete with admin password + audit logging), Patrick Babala (email invite, ConfirmDestructiveModal, Supabase Auth Phase 2/3), Christian Denzon (suspension relocation + uniform 403, PR #840), clydetims (invite email rate limiting on origin/develop), Mich-Tapawan (theme/restructuring), WyzQuests Deploy (multi-role UI fixes).

  • Reviewers: Patrick Babala, Christian Denzon (“cdnzn”) — PR #840 review rounds.

  • QA: Manual end-to-end verification plus QA-073.5 and QA-081 Playwright specs; shared suspension unit tests.

  • Shipped: incremental; consolidated multi-role work on 2026-06-12, Supabase auth cutover on 2026-09-10, suspension relocation merged via PR #840 (2026-09-21).

  • Estimated Completion: core complete; remaining items are the Open Questions and Out of Scope sections above.


REFERENCES

  • Audit: docs/FEATURE_AUDIT_MAY2026.md — Module 6, User Management ([6.1] Manage Users, [6.3] Suspend User, [6.4] Permanent User Delete).

  • Primary PRs: #840 — suspension relocation / uniform 403 ACCOUNT_SUSPENDED; #777 — shared ConfirmDestructiveModal.

  • Related PRs: #406 — View-As role switching; #520, #558, #563, #607 — release syncs.

  • Migrations: supabase/migrations/20260219_add_reviewer_role.sql, 20260414_create_audit_logs_table.sql, 20260430_add_profile_image_url_to_app_users.sql, 20260526_create_admin_reauth_tokens.sql, 20260612_create_user_roles_multi_role.sql, 20260612_update_rls_policies_multi_role.sql, 20260909_create_auth_users_triggers.sql.

  • QA: tests/e2e/admin/secure-user-delete.spec.ts (QA-073.5), tests/e2e/admin/role-change-verification-prompt.spec.ts (QA-081), tests/api/suspension-enforcement.test.ts, tests/e2e/suspension/suspension.spec.ts.

  • Companion ITG: Internal-Technical-Guide-Manage-Users.md.


VERSION HISTORY

  • 1.0 — 2026-10-01 — Patrick Babala, Initial Hand-Off for Manage Users ([6.1], with [6.3]/[6.4]). Covers the user console, multi-role assignment, email invites, suspend/unsuspend, permanent delete, re-authentication, audit logging, API contracts, testing checklist, and known limitations.


Was this article helpful?