Per organized-ideas.md §6: role assignment, account ban/unban/delete, and direct Admin/Super-Admin CRUD of public catalog entries outside the submission workflow. Backend: - list_users_for_admin(): Super-Admin-gated SECURITY DEFINER function joining profiles + auth.users (username, email, role, banned_until) — auth.users isn't exposed through PostgREST, so this is the only way to list accounts at all. - New Edge Function admin-user-action (ban/unban/delete), using @supabase/server's `auth: 'user'` mode to verify the caller's JWT, then Supabase Auth's Admin API for the actual mutation. This is deliberately an Edge Function rather than a Postgres function like everything else in this codebase: touching auth.users needs the Admin API, the stable documented interface, not a direct write to a schema Supabase manages internally. Self-action guard; verify_jwt = true at the gateway on top of the function's own JWT verification. - 5 new pgTAP tests (43/43 total) for list_users_for_admin (Super-Admin-only, even regular Admins get 42501). - CatalogRepository gains admin* methods (direct edit of a public port/cable/ device entry, plus adminUnpublish which flips is_public rather than deleting) — the update methods were already ownership-agnostic (RLS's is_admin() clause is what actually permits it), so these are thin aliases, not duplicated logic. Frontend: - authStore/AdminUserRepository: minimal role plumbing, shared UserRole type. - adminUserStore + AdminUsersModal: list/role-dropdown/ban/unban/delete, gated to Super Admin only via a new "Manage Users" TopBar button. - PortTypeManager/CableTypeManager/DevicePalette: built-in entries now show direct "Edit"/"Unpublish" for Admins (regular Admin included, per §6's capability table — not Super-Admin-exclusive) instead of "Suggest edit"; unpublish reuses the review-queue's impact-check RPC before confirming. - DeviceTemplateEditor gains an `adminMode` save path alongside its existing submissionMode/resubmitId ones. Verified: tsc -b and oxlint clean; supabase db reset + 43/43 pgTAP tests pass; confirmed both new privileged endpoints (the SQL function and the Edge Function) actually work through the real REST API via live curl calls — signup, email confirm, role promotion, ban/unban/delete round trips, self-action guard, non-super-admin rejection, and verify_jwt=true compatibility all exercised directly, not just asserted.
33 lines
1.4 KiB
TypeScript
33 lines
1.4 KiB
TypeScript
/** A user's role — mirrors `profiles.role`'s check constraint. Defined here
|
|
* (data layer) rather than in state/authStore.ts so both that store and
|
|
* this repository share one definition without state importing from data
|
|
* in the wrong direction. */
|
|
export type UserRole = 'regular' | 'admin' | 'super_admin'
|
|
|
|
export interface AdminUserSummary {
|
|
id: string
|
|
username: string
|
|
email: string
|
|
role: UserRole
|
|
/** Set (a future timestamp) while banned; undefined otherwise. */
|
|
bannedUntil?: string
|
|
createdAt: string
|
|
}
|
|
|
|
/** Storage abstraction for Super-Admin user management (organized-ideas.md
|
|
* §6's "CRUD user accounts"). Listing and role changes are plain
|
|
* RLS/privileged-function reads and writes; ban/unban/delete go through
|
|
* the admin-user-action Edge Function since those specifically need
|
|
* Supabase Auth's Admin API — see that function's own header comment for
|
|
* why this can't just be another SQL function like the rest. */
|
|
export interface AdminUserRepository {
|
|
listUsers(): Promise<AdminUserSummary[]>
|
|
updateRole(userId: string, role: UserRole): Promise<void>
|
|
/** Reversible — blocks login without touching the account's data. */
|
|
banUser(userId: string): Promise<void>
|
|
unbanUser(userId: string): Promise<void>
|
|
/** Irreversible — cascades to the user's profile, diagrams, and owned
|
|
* private catalog entries via their existing foreign keys. */
|
|
deleteUser(userId: string): Promise<void>
|
|
}
|