import { z } from 'zod'; /** * Password requirements * These are the requirements for the password when signing up or changing the password */ const requirements = { minLength: 6, maxLength: 99, specialChars: process.env.NEXT_PUBLIC_PASSWORD_REQUIRE_SPECIAL_CHARS === 'true', numbers: process.env.NEXT_PUBLIC_PASSWORD_REQUIRE_NUMBERS === 'true', uppercase: process.env.NEXT_PUBLIC_PASSWORD_REQUIRE_UPPERCASE === 'true', }; /** * Password schema * This is used to validate the password on sign in (for existing users when requirements are not enforced) */ export const PasswordSchema = z .string() .min(requirements.minLength) .max(requirements.maxLength); /** * Refined password schema with additional requirements * This is required to validate the password requirements on sign up and password change */ export const RefinedPasswordSchema = PasswordSchema.superRefine((val, ctx) => validatePassword(val, ctx), ); export function refineRepeatPassword( data: { password: string; repeatPassword: string }, ctx: z.RefinementCtx, ) { if (data.password !== data.repeatPassword) { ctx.addIssue({ message: 'auth:errors.passwordsDoNotMatch', path: ['repeatPassword'], code: 'custom', }); } return true; } function validatePassword(password: string, ctx: z.RefinementCtx) { if (requirements.specialChars) { const specialCharsCount = password.match(/[!@#$%^&*(),.?":{}|<>]/g)?.length ?? 0; if (specialCharsCount < 1) { ctx.addIssue({ message: 'auth:errors.minPasswordSpecialChars', code: 'custom', }); } } if (requirements.numbers) { const numbersCount = password.match(/\d/g)?.length ?? 0; if (numbersCount < 1) { ctx.addIssue({ message: 'auth:errors.minPasswordNumbers', code: 'custom', }); } } if (requirements.uppercase) { if (!/[A-Z]/.test(password)) { ctx.addIssue({ message: 'auth:errors.uppercasePassword', code: 'custom', }); } } return true; }