Some checks failed
CD / Build and push images (push) Successful in 3m12s
CI / Lint, typecheck, test (push) Failing after 2m29s
CI / Auth e2e pack (push) Successful in 3m32s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 8s
CD / Smoke tests against Test (push) Successful in 1m11s
CD / Promote to Int (push) Successful in 11s
Instance operators get basic user administration for support, abuse handling, and GDPR groundwork (security.md §Privacy). - api `admin/`: Site-Admin-gated `/admin/users` — a searchable, paginated list (username, e-mail, status, role, pond count, last login) plus lifecycle actions: disable/enable (a disabled user is logged out everywhere and login is refused with the distinct `account_disabled`), resend verification, delete, and grant/revoke Site Admin. Guards: you cannot act on your own account (`cannot_modify_self`) and the last Site Admin cannot be dropped (`last_site_admin`). Every action is audit-logged with the actor. - `PseudonymizationService`: account deletion scrubs the PII, removes all login identities + sessions, and trashes the personal pond — the kept row is what authorship references, so shared content the user authored shows as "Deleted user" (no orphaned/cascaded content). - web: the Admin area gains a 'Users' surface — search, pagination, and the actions (destructive ones behind an inline two-step confirm; self-actions hidden). New `users` i18n namespace (de+en). - tests: `user-admin.e2e.db.test.ts` (disable → logout + login blocked; delete → pseudonymized authorship + personal pond trashed + credentials gone; last Site Admin and self protected; Site-Admin gating); a non-destructive browser `admin-users` pack proving disable-in-UI blocks login and enable restores it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1
44 lines
1.3 KiB
TypeScript
44 lines
1.3 KiB
TypeScript
import { z } from 'zod';
|
|
|
|
/**
|
|
* Site-Admin user management (issue #59): the list + actions an instance
|
|
* operator uses for support, abuse handling, and GDPR groundwork. Deleting a
|
|
* user pseudonymizes their authorship ("Deleted user") and trashes their
|
|
* personal pond (security.md §Privacy) rather than hard-deleting rows.
|
|
*/
|
|
|
|
export type AdminUserStatus = 'PENDING_VERIFICATION' | 'ACTIVE' | 'DISABLED';
|
|
|
|
export interface AdminUserView {
|
|
id: string;
|
|
username: string;
|
|
email: string;
|
|
displayName: string;
|
|
status: AdminUserStatus;
|
|
isSiteAdmin: boolean;
|
|
createdAt: string;
|
|
lastLoginAt: string | null;
|
|
/** Ponds this user owns (personal + shared). */
|
|
pondCount: number;
|
|
}
|
|
|
|
export interface AdminUserListView {
|
|
users: AdminUserView[];
|
|
total: number;
|
|
page: number;
|
|
pageSize: number;
|
|
}
|
|
|
|
export const adminUserListQuerySchema = z.object({
|
|
q: z.string().trim().optional(),
|
|
page: z.coerce.number().int().min(1).default(1),
|
|
pageSize: z.coerce.number().int().min(1).max(100).default(20),
|
|
});
|
|
export type AdminUserListQuery = z.infer<typeof adminUserListQuerySchema>;
|
|
|
|
export const setUserDisabledSchema = z.object({ disabled: z.boolean() });
|
|
export const setSiteAdminSchema = z.object({ isSiteAdmin: z.boolean() });
|
|
|
|
/** The pseudonym a deleted user's authorship shows as. */
|
|
export const DELETED_USER_DISPLAY_NAME = 'Deleted user';
|