users: admin CRUD API and admin shell UI
Login, forced password change, admin layout with every 05 §2 page in the sidebar (unbuilt ones name their milestone), Users table/cards + drawer (General tab), one-time temporary passwords, reset password. The last active admin can't be demoted, deactivated or deleted. Audit diffs only mask secret strings. scripts/e2e has a dependency-free CDP browser check for this task's acceptance flow. Task: M1-T4
This commit is contained in:
1 parent
13733ac262
commit
9a81e37725
25 files changed
+1774
-33
No files matched your search
@@ -4,8 +4,8 @@ import { z } from 'zod';
|
||||
export const PASSWORD_MIN = 10;
|
||||
|
||||
export const LoginSchema = z.object({
|
||||
email: z.string().trim().min(3).max(254),
|
||||
password: z.string().min(1).max(200),
|
||||
email: z.string().trim().min(3, 'Enter your email').max(254),
|
||||
password: z.string().min(1, 'Enter your password').max(200),
|
||||
totp: z
|
||||
.string()
|
||||
.regex(/^\d{6}$/)
|
||||
@@ -13,8 +13,11 @@ export const LoginSchema = z.object({
|
||||
});
|
||||
|
||||
export const ChangePasswordSchema = z.object({
|
||||
currentPassword: z.string().min(1).max(200),
|
||||
newPassword: z.string().min(PASSWORD_MIN).max(200),
|
||||
currentPassword: z.string().min(1, 'Enter your current password').max(200),
|
||||
newPassword: z
|
||||
.string()
|
||||
.min(PASSWORD_MIN, `Use at least ${PASSWORD_MIN} characters`)
|
||||
.max(200, 'Use at most 200 characters'),
|
||||
});
|
||||
|
||||
export type Role = 'admin' | 'user';
|
||||
|
||||
@@ -4,3 +4,4 @@ export * from './phone.ts';
|
||||
export * from './destination.ts';
|
||||
export * from './settings.ts';
|
||||
export * from './auth.ts';
|
||||
export * from './users.ts';
|
||||
@@ -0,0 +1,36 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
import { z } from 'zod';
|
||||
|
||||
export const RoleSchema = z.enum(['admin', 'user']);
|
||||
|
||||
export const UserCreateSchema = z.object({
|
||||
email: z.email().max(254),
|
||||
displayName: z.string().trim().min(1).max(100),
|
||||
role: RoleSchema.default('user'),
|
||||
});
|
||||
|
||||
export const UserUpdateSchema = z
|
||||
.object({
|
||||
email: z.email().max(254),
|
||||
displayName: z.string().trim().min(1).max(100),
|
||||
role: RoleSchema,
|
||||
isActive: z.boolean(),
|
||||
})
|
||||
.partial();
|
||||
|
||||
export type UserCreate = z.input<typeof UserCreateSchema>;
|
||||
export type UserUpdate = z.input<typeof UserUpdateSchema>;
|
||||
|
||||
/** A user as the API returns it (no password hash, no TOTP secret). */
|
||||
export type UserDto = {
|
||||
id: string;
|
||||
email: string;
|
||||
displayName: string;
|
||||
role: 'admin' | 'user';
|
||||
isActive: boolean;
|
||||
mustChangePassword: boolean;
|
||||
lastLoginAt: string | null;
|
||||
createdAt: string;
|
||||
};
|
||||
|
||||
export type ListResponse<T> = { items: T[]; nextCursor: string | null };
|
||||
Reference in new issue
Block a user