import { z } from 'zod'; /** * Auth-related schemas shared between api (runtime validation) and web * (form validation). Field error messages are i18n keys resolved by the * client; the api returns them inside ApiErrorBody.details. */ export const usernameSchema = z .string() .min(3, 'validation.username.tooShort') .max(32, 'validation.username.tooLong') .regex(/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i, 'validation.username.charset'); /** * Password policy per issue #13: length over composition rules, plus a * small blocklist of the most common passwords (full breach-list checks * are deliberately out of scope for v1). */ const COMMON_PASSWORDS = new Set([ '1234567890', 'qwertyuiop', 'password12', 'password123', 'passwort123', '1q2w3e4r5t', 'iloveyou12', 'sonnenschein', 'schalke04!', 'aaaaaaaaaa', '1234567890a', 'qwertz1234', ]); export const passwordSchema = z .string() .min(10, 'validation.password.tooShort') .max(128, 'validation.password.tooLong') .refine((value) => !COMMON_PASSWORDS.has(value.toLowerCase()), 'validation.password.tooCommon'); export const signupInputSchema = z.object({ username: usernameSchema, email: z.string().email('validation.email.invalid').max(254), displayName: z.string().trim().min(1, 'validation.displayName.required').max(80), password: passwordSchema, locale: z.enum(['de', 'en']).default('en'), /** Invitation token (issue #332): lets this one signup through even * while registration is closed. */ invitationToken: z.string().min(16).max(256).optional(), }); export type SignupInput = z.infer; /** Form-side type: locale is optional before Zod applies its default. */ export type SignupFormInput = z.input; export const loginInputSchema = z.object({ usernameOrEmail: z.string().min(1, 'validation.required'), password: z.string().min(1, 'validation.required'), }); export type LoginInput = z.infer; export const verifyEmailInputSchema = z.object({ token: z.string().min(16).max(256) }); export const resendVerificationInputSchema = z.object({ email: z.string().email('validation.email.invalid'), }); export const forgotPasswordInputSchema = z.object({ email: z.string().email('validation.email.invalid'), }); export const resetPasswordInputSchema = z.object({ token: z.string().min(16).max(256), password: passwordSchema, }); /** Form-side schema for the reset page (token travels via URL, not form). */ export const resetPasswordFormSchema = z.object({ password: passwordSchema }); export const updateProfileInputSchema = z.object({ displayName: z.string().trim().min(1, 'validation.displayName.required').max(80).optional(), locale: z.enum(['de', 'en']).optional(), /** Auto-watch preferences (issue #93). */ autoWatchOwnPages: z.boolean().optional(), autoWatchOnComment: z.boolean().optional(), /** E-mail digest cadence (issue #95). */ digestFrequency: z.enum(['hourly', 'daily', 'off']).optional(), }); export const changePasswordInputSchema = z.object({ currentPassword: z.string().min(1, 'validation.required'), newPassword: passwordSchema, }); /** * What the login screen may offer (issue #214, ADR 0021): the local * password form and/or the deploy-configured OIDC provider. `local` becomes * switchable with #216 (`AUTH_LOCAL_ENABLED`). */ export interface AuthMethodsView { local: boolean; oidc: { label: string } | null; } /** Public shape of the signed-in user, returned by /auth/me. */ export interface CurrentUser { id: string; username: string; email: string; displayName: string; locale: 'de' | 'en'; isSiteAdmin: boolean; autoWatchOwnPages: boolean; autoWatchOnComment: boolean; digestFrequency: 'hourly' | 'daily' | 'off'; }