forked from Deen-Bridge/dnb-backend
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpasswordPolicy.test.js
More file actions
113 lines (100 loc) · 4.03 KB
/
Copy pathpasswordPolicy.test.js
File metadata and controls
113 lines (100 loc) · 4.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
import {
PASSWORD_MIN,
PASSWORD_MAX,
passwordIssues,
isPasswordAcceptable,
firstPasswordIssue,
} from "../src/utils/passwordPolicy.js";
const CONTEXT = { name: "Aisha Bello", email: "aishabello@example.com" };
describe("password policy", () => {
describe("rejects weak passwords", () => {
const weak = [
["too short", "Ab3!x"],
["single character class", "password"],
["digits only", "12345678"],
["letters only", "abcdefgh"],
["common word with decoration", "Password123!"],
["common word, leet-substituted", "P@ssw0rd123!"],
["common keyboard walk", "Qwerty123!"],
["brand name", "Deenbridge!23"],
["padded with symbols", "!!Admin!!"],
["contains the user's name", "aishabello1!A"],
["contains the email local part", "Xy!aishabello9"],
["repeated character run", "Aaa1!bbb"],
["ascending sequence", "abcd1234EF!"],
["descending sequence", "Zyxw!987q"],
["leading space", " Str0ng!Pass"],
["trailing space", "Str0ng!Pass "],
];
test.each(weak)("%s", (_label, password) => {
expect(isPasswordAcceptable(password, CONTEXT)).toBe(false);
expect(passwordIssues(password, CONTEXT).length).toBeGreaterThan(0);
});
});
describe("accepts strong passwords", () => {
const strong = [
["mixed classes", "Str0ng!Pass"],
["passphrase", "correct horse battery staple 7Z"],
["domain words, not blocklisted", "Tajweed#Halaqah42"],
["random-looking", "Mgx7#qLpv2"],
["three classes, no symbol", "Qamar91Zubayr"],
];
test.each(strong)("%s", (_label, password) => {
expect(passwordIssues(password, CONTEXT)).toEqual([]);
expect(isPasswordAcceptable(password, CONTEXT)).toBe(true);
});
});
describe("length bounds", () => {
it(`requires at least ${PASSWORD_MIN} characters`, () => {
// 7 chars, three classes, nothing else wrong — only length should fail.
expect(passwordIssues("Ab3!xQz".slice(0, 7), CONTEXT)).toContain(
`Password must be at least ${PASSWORD_MIN} characters`
);
});
it(`rejects more than ${PASSWORD_MAX} characters`, () => {
const long = `Aa1!${"qWeRtY9#".repeat(10)}`;
expect(long.length).toBeGreaterThan(PASSWORD_MAX);
expect(passwordIssues(long, CONTEXT)).toContain(
`Password must be at most ${PASSWORD_MAX} characters`
);
});
it("rejects multibyte passwords past bcrypt's 72-byte limit", () => {
// 30 four-byte emoji = 120 bytes, but only 60 JS characters, so the
// character-count check passes and the byte check has to catch it.
const emoji = `Aa1!${"🕌".repeat(30)}`;
expect(emoji.length).toBeLessThanOrEqual(PASSWORD_MAX);
expect(Buffer.byteLength(emoji, "utf8")).toBeGreaterThan(72);
expect(passwordIssues(emoji, CONTEXT)).toContain(
"Password is too long — please shorten it"
);
});
});
describe("input handling", () => {
it("rejects non-string input instead of throwing", () => {
for (const value of [undefined, null, 12345678, {}, []]) {
expect(() => passwordIssues(value, CONTEXT)).not.toThrow();
expect(isPasswordAcceptable(value, CONTEXT)).toBe(false);
}
});
it("works without a context", () => {
expect(isPasswordAcceptable("Tajweed#Halaqah42")).toBe(true);
expect(isPasswordAcceptable("password")).toBe(false);
});
it("ignores name fragments shorter than 4 characters", () => {
// "Ali" is too short to be a useful signal and would block far too much.
expect(
isPasswordAcceptable("Ali#Qamar72", { name: "Ali", email: "a@b.com" })
).toBe(true);
});
});
describe("firstPasswordIssue", () => {
it("returns null when the password is acceptable", () => {
expect(firstPasswordIssue("Tajweed#Halaqah42", CONTEXT)).toBeNull();
});
it("returns the length problem first when several apply", () => {
expect(firstPasswordIssue("abc", CONTEXT)).toBe(
`Password must be at least ${PASSWORD_MIN} characters`
);
});
});
});