fix: align password validation with backend ASCII policy (WealthLedger/backend#60) (#89)
Frontend verify / commits (push) Skipped
Frontend verify / commits (pull_request) Failing after 40s
Frontend verify / security (pull_request) Skipped
Frontend verify / verify (pull_request) Skipped
Frontend verify / security (push) Successful in 11s
Frontend verify / verify (push) Failing after 7m35s

This commit was merged in pull request #89.
This commit is contained in:
2026-10-16 10:00:54 +03:00
parent 6f1226b668
commit ebe92684a7
6 changed files with 169 additions and 24 deletions
+38 -14
View File
@@ -7,8 +7,10 @@ const EMAIL_PART_COUNT = 2;
const ASCII_SPACE = 32;
const ASCII_DELETE = 127;
const MAX_EMAIL_LENGTH = 254;
const MIN_PASSWORD_BYTES = 12;
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);
@@ -20,6 +22,14 @@ function isEmailCharacterInvalid(character: string): boolean {
);
}
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()
@@ -33,28 +43,42 @@ const email = z
{ error: validationMessage("auth.validation.invalidEmail") },
);
const password = z
// 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 >= MIN_PASSWORD_BYTES
&& new TextEncoder().encode(value).length <= MAX_PASSWORD_BYTES,
{ error: validationMessage("auth.validation.invalidPasswordLength") },
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,
password: loginPassword,
passwordConfirmation: z.string().optional(),
});
const registerSchema = loginSchema.refine(
value => value.password === value.passwordConfirmation,
{
error: validationMessage("auth.validation.passwordsDoNotMatch"),
path: ["passwordConfirmation"],
},
);
const registerSchema = loginSchema
.extend({ password: registerPassword })
.refine(
value => value.password === value.passwordConfirmation,
{
error: validationMessage("auth.validation.passwordsDoNotMatch"),
path: ["passwordConfirmation"],
},
);
type AuthFormValues = z.input<typeof loginSchema>;