Skip to content

Commit 796880e

Browse files
committed
GH-1 # add password generation lib
1 parent ce0d94e commit 796880e

File tree

3 files changed

+224
-1
lines changed

3 files changed

+224
-1
lines changed

src/static/password_generation.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<meta charset="UTF-8">
55
<title>HIBP - clone</title>
66
<link href="main.css" rel="stylesheet"/>
7-
<script type="module" src="bundle.js"></script>
7+
<script type="module" src="password_generation.js"></script>
88
</head>
99
<body>
1010
<div class="sidenav">

src/static/password_generation.js

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
const LOWERCASE = 'abcdefghijklmnopqrstuvwxyz';
2+
const UPPERCASE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
3+
const NUMBER = '0123456789';
4+
const SPECIAL_CHAR = '#?!@$%^&*-\'+()_[]';
5+
6+
/**
7+
* Generate a password of the given length satisfying the requirements set in the optional parameters
8+
*/
9+
function generatePassword(
10+
length,
11+
min_lowercase = 0,
12+
has_uppercase = false,
13+
min_uppercase = 0,
14+
has_number = false,
15+
min_number = 0,
16+
has_special_char = false,
17+
min_special_char = 0,
18+
) {
19+
// We don't check parameters consistency
20+
let possibleChars = LOWERCASE;
21+
if (has_uppercase || min_uppercase > 0) {
22+
possibleChars += UPPERCASE;
23+
}
24+
if (has_number || min_number > 0) {
25+
possibleChars += NUMBER;
26+
}
27+
if (has_special_char || min_special_char > 0) {
28+
possibleChars += SPECIAL_CHAR;
29+
}
30+
31+
let password = ''
32+
do {
33+
password = _generatePassword(length, possibleChars)
34+
} while(!checkRequirements(password, min_lowercase, min_uppercase, min_number, min_special_char))
35+
return password;
36+
}
37+
38+
function _generatePassword(length, possibleChars) {
39+
let password = ''
40+
for (var i = 0; i < length; i++) {
41+
password += getRandomChar(possibleChars)
42+
}
43+
return password;
44+
}
45+
46+
/**
47+
* Validate password requirements. We currently use countRequirement requiring to loop over strings multiple times.
48+
*/
49+
function checkRequirements(password, min_lowercase, min_uppercase, min_number, min_special_char) {
50+
if (min_lowercase > 0 && countRequirement(password, LOWERCASE) < min_lowercase) {
51+
return false
52+
}
53+
if (min_uppercase > 0 && countRequirement(password, UPPERCASE) < min_uppercase) {
54+
return false
55+
}
56+
if (min_number > 0 && countRequirement(password, NUMBER) < min_number) {
57+
return false
58+
}
59+
if (min_special_char > 0 && countRequirement(password, SPECIAL_CHAR) < min_special_char) {
60+
return false
61+
}
62+
return true;
63+
}
64+
65+
function getRandomChar(possibleChars) {
66+
if (possibleChars.length >= Math.pow(2, 8)) {
67+
throw new Error(`possibleChar length (${possibleChars.length}) is too long, some char will never be generated`);
68+
}
69+
// Create byte array and fill with 1 random number
70+
let byteArray = new Uint8Array(1);
71+
do {
72+
crypto.getRandomValues(byteArray);
73+
} while (byteArray[0] >= possibleChars.length)
74+
return possibleChars[byteArray[0]];
75+
}
76+
77+
function countRequirement(stringTested, charRequirement) {
78+
let match_count = 0;
79+
stringTested.split('').forEach((letter) => {
80+
if (charRequirement.includes(letter)) {
81+
match_count += 1
82+
}
83+
});
84+
return match_count;
85+
}
86+
87+
export {countRequirement, getRandomChar, generatePassword};
+136
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import { countRequirement, getRandomChar, generatePassword } from '../static/password_generation.js';
2+
3+
const LOWERCASE = 'abcdefghijklmnopqrstuvwxyz';
4+
const UPPERCASE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
5+
const NUMBER = '0123456789';
6+
const SPECIAL_CHAR = '#?!@$%^&*-\'+()_[]';
7+
8+
9+
test('getRandomChar return char if single possibility', () => {
10+
let random_char = getRandomChar('a');
11+
12+
expect(random_char).toBe('a');
13+
});
14+
15+
test('getRandomChar return one of given possibilities', () => {
16+
let possibilities = 'abcdef';
17+
let random_char = getRandomChar(possibilities);
18+
19+
expect(possibilities.includes(random_char)).toBe(true);
20+
});
21+
22+
test('password length is correct', async () => {
23+
let expected_length = 7;
24+
25+
let password = await generatePassword(expected_length);
26+
27+
expect(password).toHaveLength(expected_length);
28+
});
29+
30+
test('password only have lowercase by default', async () => {
31+
let expected_length = 7;
32+
33+
let password = await generatePassword(expected_length);
34+
35+
expect(password).toMatch(/[a-z]+/);
36+
});
37+
38+
test('countRequirement is 0 if chars are not present', async () => {
39+
expect(countRequirement('abc', 'def')).toBe(0);
40+
});
41+
42+
test('countRequirement is 1 if chars match a single time', async () => {
43+
expect(countRequirement('abcd', 'def')).toBe(1);
44+
});
45+
46+
test('countRequirement counts duplicate', async () => {
47+
expect(countRequirement('abba', 'bcd')).toBe(2);
48+
});
49+
50+
test('password with minimum 1 uppercase', async () => {
51+
let expected_length = 7;
52+
let min_uppercase = 1;
53+
54+
let password = await generatePassword(
55+
expected_length,
56+
0,
57+
true,
58+
min_uppercase,
59+
);
60+
61+
let uppercase_count = countRequirement(password, UPPERCASE);
62+
expect(uppercase_count).toBeGreaterThanOrEqual(min_uppercase);
63+
});
64+
65+
test('password with minimum 5 uppercase', async () => {
66+
let expected_length = 7;
67+
let min_uppercase = 5;
68+
69+
let password = await generatePassword(
70+
expected_length,
71+
0,
72+
true,
73+
min_uppercase,
74+
);
75+
76+
let uppercase_count = countRequirement(password, UPPERCASE);
77+
expect(uppercase_count).toBeGreaterThanOrEqual(min_uppercase);
78+
});
79+
80+
test('password with minimum 3 uppercase and 3 numbers', async () => {
81+
let expected_length = 7;
82+
let min_uppercase = 3;
83+
let min_number = 3;
84+
85+
let password = await generatePassword(
86+
expected_length,
87+
0,
88+
true,
89+
min_uppercase,
90+
true,
91+
min_number,
92+
);
93+
94+
let uppercase_count = countRequirement(password, UPPERCASE);
95+
expect(uppercase_count).toBeGreaterThanOrEqual(min_uppercase);
96+
let number_count = countRequirement(password, NUMBER);
97+
expect(number_count).toBeGreaterThanOrEqual(min_number);
98+
});
99+
100+
test('password with minimum 3 lowercase and other chars allowed', async () => {
101+
let expected_length = 7;
102+
let min_lowercase = 3;
103+
104+
let password = await generatePassword(
105+
expected_length,
106+
min_lowercase,
107+
true,
108+
0,
109+
true,
110+
0,
111+
true,
112+
0,
113+
);
114+
115+
let lowercase_count = countRequirement(password, LOWERCASE);
116+
expect(lowercase_count).toBeGreaterThanOrEqual(min_lowercase);
117+
});
118+
119+
test('password with minimum 4 special chars and other chars allowed', async () => {
120+
let expected_length = 7;
121+
let min_special_char = 4;
122+
123+
let password = await generatePassword(
124+
expected_length,
125+
0,
126+
true,
127+
0,
128+
true,
129+
0,
130+
true,
131+
min_special_char,
132+
);
133+
134+
let special_char_count = countRequirement(password, SPECIAL_CHAR);
135+
expect(special_char_count).toBeGreaterThanOrEqual(min_special_char);
136+
});

0 commit comments

Comments
 (0)