password-confirmation-schema
EditValidates a password twice with the same schema and reports parsed-value mismatches on confirmation.
import * as z from "zod";
const PASSWORD_MIN_LENGTH = 8;const PASSWORD_MAX_LENGTH = 128;const codePointLength = (value: string): number => Array.from(value).length;const isWellFormedUnicode = (value: string): boolean => { for (let index = 0; index < value.length; index += 1) { const codeUnit = value.charCodeAt(index); if (codeUnit >= 0xd800 && codeUnit <= 0xdbff) { const nextCodeUnit = value.charCodeAt(index + 1); if (!(nextCodeUnit >= 0xdc00 && nextCodeUnit <= 0xdfff)) return false; index += 1; } else if (codeUnit >= 0xdc00 && codeUnit <= 0xdfff) { return false; } } return true;};
/** * Applies one string-output schema to a password and its confirmation, then * reports mismatches on the confirmation field. */export const createPasswordConfirmationSchema = < const Schema extends z.ZodType<string>,>( schema: Schema,) => z .object({ password: schema, confirmPassword: schema, }) .check( z.refine( (value) => { const fields = value as { password: z.output<Schema>; confirmPassword: z.output<Schema>; }; return fields.password === fields.confirmPassword; }, { error: "Passwords do not match", path: ["confirmPassword"], }, ), ) .brand<"PasswordConfirmation">() .meta({ title: "Password confirmation", description: "A password and confirmation validated by the same schema and required to match.", });
const confirmationPasswordValueSchema = z .string({ error: "Password must be a string" }) .check( z.refine(isWellFormedUnicode, { error: "Password must contain only well-formed Unicode", }), z.normalize("NFC"), z.refine( (value) => codePointLength(value) >= PASSWORD_MIN_LENGTH, { error: `Password must contain at least ${PASSWORD_MIN_LENGTH} characters`, }, ), z.refine( (value) => codePointLength(value) <= PASSWORD_MAX_LENGTH, { error: `Password must contain at most ${PASSWORD_MAX_LENGTH} characters`, }, ), ) .brand<"ValidatedPassword">() .brand<"Password">();
export const passwordConfirmationSchema = createPasswordConfirmationSchema(confirmationPasswordValueSchema);
export type PasswordConfirmation = z.infer< typeof passwordConfirmationSchema>;Download
Section titled “Download”wget -O src/schemas/password-confirmation-schema.ts https://raw.githubusercontent.com/jrTilak/lazykit/HEAD/registry/zod-schemas/password-confirmation-schema.tstemp_dir="$(mktemp -d)" && bunx degit jrTilak/lazykit/registry/zod-schemas "$temp_dir" && mv "$temp_dir/password-confirmation-schema.ts" src/schemas/password-confirmation-schema.ts && rm -r "$temp_dir"Examples
Section titled “Examples”import { passwordConfirmationSchema, type PasswordConfirmation,} from "./password-confirmation-schema";
const values = passwordConfirmationSchema.parse({ password: "correct horse", confirmPassword: "correct horse",});
const submitPassword = (input: PasswordConfirmation) => { console.log(`Confirmed ${Array.from(input.password).length} characters`);};
submitPassword(values);createPasswordConfirmationSchema(schema)— Creates a confirmation object from anySchema extends z.ZodType<string>.schema— The same schema is applied independently topasswordandconfirmPassword.password— The primary input value.confirmPassword— The repeated input value.
Returns
Section titled “Returns”The factory preserves the supplied schema’s input and output types and returns
a parsed object branded with "PasswordConfirmation". It compares the two
parsed outputs, so normalization or other transforms supplied by schema
happen before equality is checked.
The exported passwordConfirmationSchema preset applies the same NFC
normalization and 8–128 Unicode-code-point policy as passwordSchema. A
mismatch produces "Passwords do not match" at the confirmPassword path.
Both parsed fields carry the default Password type, including its
"ValidatedPassword" and "Password" brands. The preset accepts valid
surrogate pairs as single code points and rejects an unpaired UTF-16 surrogate
in either field. A custom schema supplied to the factory controls its own
Unicode policy.