import { z } from "zod"; import { validationMessage } from "@/shared/i18n"; const EMAIL_PART_COUNT = 2; const ASCII_SPACE = 32; const ASCII_DELETE = 127; const MAX_EMAIL_LENGTH = 254; const MIN_PASSWORD_LENGTH = 8; const MAX_PASSWORD_BYTES = 72; // Printable ASCII keeps characters == bytes, within the bcrypt 72-byte limit. const ASCII_TILDE = 126; function isEmailCharacterInvalid(character: string): boolean { const codePoint = character.codePointAt(0); return ( /\s/.test(character) || (codePoint !== undefined && codePoint < ASCII_SPACE) || codePoint === ASCII_DELETE ); } function isPrintableAscii(value: string): boolean { return value.split("").every((character) => { const code = character.codePointAt(0) ?? 0; return code >= ASCII_SPACE && code <= ASCII_TILDE; }); } const email = z .string() .trim() .toLowerCase() .max(MAX_EMAIL_LENGTH) .refine( value => value.split("@").length === EMAIL_PART_COUNT && value.split("@").every(Boolean) && value.split("").every(element => !isEmailCharacterInvalid(element)), { error: validationMessage("auth.validation.invalidEmail") }, ); // Login stays lenient: accounts created under an older policy must still sign in. const loginPassword = z .string() .min(1) .refine( value => new TextEncoder().encode(value).length <= MAX_PASSWORD_BYTES, { error: validationMessage("auth.validation.passwordTooLong") }, ); const registerPassword = z .string() .min(MIN_PASSWORD_LENGTH, { error: validationMessage("auth.validation.invalidPasswordLength"), }) .max(MAX_PASSWORD_BYTES, { error: validationMessage("auth.validation.invalidPasswordLength"), }) .refine(isPrintableAscii, { error: validationMessage("auth.validation.invalidPasswordLength"), }); const loginSchema = z.object({ email, password: loginPassword, passwordConfirmation: z.string().optional(), }); const registerSchema = loginSchema .extend({ password: registerPassword }) .refine( value => value.password === value.passwordConfirmation, { error: validationMessage("auth.validation.passwordsDoNotMatch"), path: ["passwordConfirmation"], }, ); type AuthFormValues = z.input; export { type AuthFormValues, loginSchema, registerSchema };