Merged in fix/SW-3111-mismatch-password-rules (pull request #2482)
fix(SW-3111): Added test for passwordValidator and updated pw validation * fix(SW-3111): Added test for passwordValidator and updated pw validation * fix: make sure regex is matching Curity * Added error message to input field and changed message * Fixed text Approved-by: Christian Andolf
This commit is contained in:
committed by
Linus Flood
parent
f207cf6601
commit
f0701d6e20
@@ -194,6 +194,10 @@ function NewPasswordValidation({
|
|||||||
},
|
},
|
||||||
{ count: 1 }
|
{ count: 1 }
|
||||||
)
|
)
|
||||||
|
case "allowedCharacters":
|
||||||
|
return intl.formatMessage({
|
||||||
|
defaultMessage: "Only allowed characters",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
98
packages/common/utils/zod/passwordValidator.test.ts
Normal file
98
packages/common/utils/zod/passwordValidator.test.ts
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
import { describe, expect, test } from "vitest"
|
||||||
|
|
||||||
|
import { passwordValidator } from "./passwordValidator"
|
||||||
|
|
||||||
|
// Test cases to find differences between Curity and Zod validation
|
||||||
|
const testCases = [
|
||||||
|
// Valid passwords
|
||||||
|
{ password: "Password123!", description: "standard valid password" },
|
||||||
|
{ password: "ValidPass1@", description: "valid with @" },
|
||||||
|
{ password: "TestPass9#", description: "valid with #" },
|
||||||
|
|
||||||
|
// Edge cases for length
|
||||||
|
{ password: "Pass123!", description: "8 characters (too short for both)" },
|
||||||
|
{ password: "ValidPass1!", description: "10 characters (minimum)" },
|
||||||
|
{ password: "A".repeat(40) + "1!", description: "40 characters (maximum)" },
|
||||||
|
{ password: "A".repeat(41) + "1!", description: "41 characters (too long)" },
|
||||||
|
|
||||||
|
// Special character differences
|
||||||
|
{ password: "Password123*", description: "with asterisk" },
|
||||||
|
{ password: "Password123-", description: "with hyphen" },
|
||||||
|
{ password: "Password123=", description: "with equals" },
|
||||||
|
{ password: "Password123+", description: "with plus" },
|
||||||
|
{ password: "Password123_", description: "with underscore" },
|
||||||
|
{ password: "Password123&", description: "with ampersand" },
|
||||||
|
{ password: "Password123?", description: "with question mark" },
|
||||||
|
{ password: "Password123(", description: "with opening parenthesis" },
|
||||||
|
{ password: "Password123)", description: "with closing parenthesis" },
|
||||||
|
|
||||||
|
// International characters (Curity supports these, Zod might not)
|
||||||
|
{ password: "Påssword123!", description: "with å" },
|
||||||
|
{ password: "Pässword123!", description: "with ä" },
|
||||||
|
{ password: "Pöössword123!", description: "with ö" },
|
||||||
|
{ password: "Pæssword123!", description: "with æ" },
|
||||||
|
{ password: "Pøssword123!", description: "with ø" },
|
||||||
|
{ password: "Püssword123!", description: "with ü" },
|
||||||
|
{ password: "Paßsword123!", description: "with ß" },
|
||||||
|
|
||||||
|
// Mixed case international
|
||||||
|
{ password: "PÅSSWORD123!", description: "with uppercase Å" },
|
||||||
|
{ password: "PÄSSWORD123!", description: "with uppercase Ä" },
|
||||||
|
{ password: "PÖÖSSWORD123!", description: "with uppercase Ö" },
|
||||||
|
|
||||||
|
// Special characters not in Curity regex
|
||||||
|
{ password: "Password123~", description: "with tilde (not in Curity)" },
|
||||||
|
{ password: "Password123`", description: "with backtick (not in Curity)" },
|
||||||
|
{ password: "Password123|", description: "with pipe (not in Curity)" },
|
||||||
|
{ password: "Password123\\", description: "with backslash (not in Curity)" },
|
||||||
|
{
|
||||||
|
password: "Password123/",
|
||||||
|
description: "with forward slash (not in Curity)",
|
||||||
|
},
|
||||||
|
{ password: "Password123<", description: "with less than (not in Curity)" },
|
||||||
|
{
|
||||||
|
password: "Password123>",
|
||||||
|
description: "with greater than (not in Curity)",
|
||||||
|
},
|
||||||
|
{ password: "Password123.", description: "with period (not in Curity)" },
|
||||||
|
{ password: "Password123,", description: "with comma (not in Curity)" },
|
||||||
|
{ password: "Password123;", description: "with semicolon (not in Curity)" },
|
||||||
|
{ password: "Password123:", description: "with colon (not in Curity)" },
|
||||||
|
{ password: "Password123'", description: "with apostrophe (not in Curity)" },
|
||||||
|
{ password: 'Password123"', description: "with quote (not in Curity)" },
|
||||||
|
{
|
||||||
|
password: "Password123[",
|
||||||
|
description: "with opening bracket (not in Curity)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
password: "Password123]",
|
||||||
|
description: "with closing bracket (not in Curity)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
password: "Password123{",
|
||||||
|
description: "with opening brace (not in Curity)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
password: "Password123}",
|
||||||
|
description: "with closing brace (not in Curity)",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const curityPasswordRegex = new RegExp(
|
||||||
|
"^(?!^.{41})(?=.{10,})(?=.*[0-9])(?=.*[a-zåäöæøüß])(?=.*[A-ZÅÄÖÆØÜ])(?=.*[&!?()@#$%^+=_\*\-])[A-Za-zåäöæøüßÅÄÖÆØÜ0-9&!?()@#$%^+=_\*\-]+$",
|
||||||
|
"g"
|
||||||
|
)
|
||||||
|
|
||||||
|
describe("Should validate password the same way as Curity", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// reset regex state before test
|
||||||
|
curityPasswordRegex.lastIndex = 0
|
||||||
|
})
|
||||||
|
test.each(testCases)("$description", ({ password }) => {
|
||||||
|
console.log(password)
|
||||||
|
const curityResult = curityPasswordRegex.test(password)
|
||||||
|
const zodResult = passwordValidator().safeParse(password)
|
||||||
|
|
||||||
|
expect(zodResult.success).toBe(curityResult)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -7,11 +7,11 @@ export const passwordValidators = {
|
|||||||
message: "10 to 40 characters",
|
message: "10 to 40 characters",
|
||||||
},
|
},
|
||||||
hasUppercase: {
|
hasUppercase: {
|
||||||
matcher: (password: string) => /[A-Z]/.test(password),
|
matcher: (password: string) => /[A-ZÅÄÖÆØÜ]/.test(password),
|
||||||
message: "1 uppercase letter",
|
message: "1 uppercase letter",
|
||||||
},
|
},
|
||||||
hasLowercase: {
|
hasLowercase: {
|
||||||
matcher: (password: string) => /[a-z]/.test(password),
|
matcher: (password: string) => /[a-zåäöæøüß]/.test(password),
|
||||||
message: "1 lowercase letter",
|
message: "1 lowercase letter",
|
||||||
},
|
},
|
||||||
hasNumber: {
|
hasNumber: {
|
||||||
@@ -19,16 +19,23 @@ export const passwordValidators = {
|
|||||||
message: "1 number",
|
message: "1 number",
|
||||||
},
|
},
|
||||||
hasSpecialChar: {
|
hasSpecialChar: {
|
||||||
matcher: (password: string) =>
|
matcher: (password: string) => /[&!?()@#$%^+=_\*\-]+/.test(password),
|
||||||
/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]+/.test(password),
|
|
||||||
message: "1 special character",
|
message: "1 special character",
|
||||||
},
|
},
|
||||||
|
allowedCharacters: {
|
||||||
|
matcher: (password: string) =>
|
||||||
|
/^[A-Za-zåäöæøüßÅÄÖÆØÜ0-9&!?()@#$%^+=_\*\-]+$/.test(password),
|
||||||
|
message: "Only allowed characters",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const passwordValidator = (msg = "Required field") =>
|
export const passwordValidator = (msg = "Required field") =>
|
||||||
z
|
z
|
||||||
.string()
|
.string()
|
||||||
.min(1, msg)
|
.min(1, msg)
|
||||||
|
.refine(passwordValidators.allowedCharacters.matcher, {
|
||||||
|
message: passwordValidators.allowedCharacters.message,
|
||||||
|
})
|
||||||
.refine(passwordValidators.length.matcher, {
|
.refine(passwordValidators.length.matcher, {
|
||||||
message: passwordValidators.length.message,
|
message: passwordValidators.length.message,
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user