auth: sign-in ways, sign-in mode, and the admin who keeps a password (S1)
Spec 12 S1: migration 0012 (password_hash nullable, user_identities, sessions.method and the
OpenID token columns), auth.mode (only "password" until S2), GET /api/auth/methods, login body
{login, password} (email still accepted), signIn on users and /api/auth/me, a Signs in with
column on Users. The last active admin with a password can't be deleted, disabled or demoted.
This commit is contained in:
1 parent
ba293f8703
commit
3a9423af1f
19 files changed
+4847
-43
No files matched your search
@@ -3,13 +3,21 @@ import { z } from 'zod';
|
||||
|
||||
export const PASSWORD_MIN = 10;
|
||||
|
||||
export const LoginSchema = z.object({
|
||||
email: z.string().trim().min(3, 'Enter your email').max(254),
|
||||
/** The login form's fields: `login` is an email, or a directory username (12 §4.2). */
|
||||
export const LoginFields = z.object({
|
||||
login: z.string().trim().min(1, 'Enter your email').max(254),
|
||||
password: z.string().min(1, 'Enter your password').max(200),
|
||||
/** The authenticator code, or a recovery code ("k7m3q-x9tpw"), when two-step sign-in is on. */
|
||||
totp: z.string().trim().max(20).optional(),
|
||||
});
|
||||
|
||||
/** POST /api/auth/login. `email`, the field's name before 12, is still accepted. */
|
||||
export const LoginSchema = z.preprocess(
|
||||
(v) =>
|
||||
v && typeof v === 'object' && !('login' in v) && 'email' in v ? { ...v, login: v.email } : v,
|
||||
LoginFields,
|
||||
);
|
||||
|
||||
export const ChangePasswordSchema = z.object({
|
||||
currentPassword: z.string().min(1, 'Enter your current password').max(200),
|
||||
newPassword: z
|
||||
@@ -28,8 +36,22 @@ export type AuthUser = {
|
||||
role: Role;
|
||||
mustChangePassword: boolean;
|
||||
extension: { id: string; number: string; name: string } | null;
|
||||
signIn: SignInWays;
|
||||
};
|
||||
|
||||
/** The ways a user can sign in (12 §2): their password, a connected OpenID account, the directory. */
|
||||
export type SignInWays = {
|
||||
password: boolean;
|
||||
oidc: { email: string | null } | null;
|
||||
ldap: boolean;
|
||||
};
|
||||
|
||||
/** 12 §1.1: which ways the login page offers. */
|
||||
export type AuthMode = 'password' | 'mixed' | 'oidc';
|
||||
|
||||
/** GET /api/auth/methods (public): what the login page shows. */
|
||||
export type AuthMethods = { mode: AuthMode; ldap: boolean; oidc: { label: string } | null };
|
||||
|
||||
/** Two-step sign-in (TOTP, 08 §2.4) for the signed-in user. */
|
||||
export type TotpStatus = { enabled: boolean; recoveryCodesLeft: number };
|
||||
/** Start: the secret to scan (not stored until a code confirms it). */
|
||||
|
||||
@@ -116,6 +116,8 @@ export const SettingsSchemas = {
|
||||
})
|
||||
.nullable()
|
||||
.default(null),
|
||||
/** Sign-in (12 §1.1): passwords only, OpenID only (admins keep passwords), or both. */
|
||||
'auth.mode': z.enum(['password', 'mixed', 'oidc']).default('password'),
|
||||
// Not in 02 §3's table but used by the generator (03 §3).
|
||||
'system.transportHash': z.string().nullable().default(null),
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
import { z } from 'zod';
|
||||
import type { SignInWays } from './auth.ts';
|
||||
|
||||
export const RoleSchema = z.enum(['admin', 'user']);
|
||||
|
||||
@@ -34,6 +35,7 @@ export type UserDto = {
|
||||
lastLoginAt: string | null;
|
||||
createdAt: string;
|
||||
extension: { id: string; number: string } | null;
|
||||
signIn: SignInWays;
|
||||
};
|
||||
|
||||
export type ListResponse<T> = { items: T[]; nextCursor: string | null };
|
||||
Reference in new issue
Block a user