import { z } from "zod"; /** * The single password rule for every surface that sets a password. It mirrors the * server's Identity options: the character classes are ASCII, matching how the * server classifies uppercase letters, digits and non-alphanumeric characters. */ export const PASSWORD_MIN_LENGTH = 6; export type PasswordRuleId = "length" | "uppercase" | "number" | "special"; export type PasswordRule = { id: PasswordRuleId; label: string; test: (password: string) => boolean; }; export type PasswordRuleResult = { id: PasswordRuleId; label: string; met: boolean; }; export const PASSWORD_RULES: readonly PasswordRule[] = [ { id: "length", label: `At least ${PASSWORD_MIN_LENGTH} characters`, test: (password) => password.length >= PASSWORD_MIN_LENGTH, }, { id: "uppercase", label: "One uppercase letter", test: (password) => /[A-Z]/.test(password) }, { id: "number", label: "One number", test: (password) => /[0-9]/.test(password) }, { id: "special", label: "One special character", test: (password) => /[^A-Za-z0-9]/.test(password), }, ]; export const PASSWORD_REQUIREMENTS_MESSAGE = "Password must meet every requirement below"; export const PASSWORDS_DO_NOT_MATCH_MESSAGE = "Passwords don't match"; export function evaluatePasswordRules(password: string): PasswordRuleResult[] { return PASSWORD_RULES.map(({ id, label, test }) => ({ id, label, met: test(password) })); } export function meetsPasswordPolicy(password: string): boolean { return PASSWORD_RULES.every((rule) => rule.test(password)); } export const passwordSchema = z.string().refine(meetsPasswordPolicy, { message: PASSWORD_REQUIREMENTS_MESSAGE, });