@twin.org/crypto 0.0.3-next.14 → 0.0.3-next.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/es/passwords/passwordGenerator.js +68 -12
- package/dist/es/passwords/passwordGenerator.js.map +1 -1
- package/dist/es/passwords/passwordValidator.js +68 -4
- package/dist/es/passwords/passwordValidator.js.map +1 -1
- package/dist/types/passwords/passwordGenerator.d.ts +25 -2
- package/dist/types/passwords/passwordValidator.d.ts +34 -2
- package/docs/changelog.md +38 -0
- package/docs/reference/classes/PasswordGenerator.md +38 -2
- package/docs/reference/classes/PasswordValidator.md +115 -2
- package/package.json +3 -3
|
@@ -1,28 +1,84 @@
|
|
|
1
1
|
// Copyright 2024 IOTA Stiftung.
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0.
|
|
3
|
-
import { RandomHelper } from "@twin.org/core";
|
|
3
|
+
import { Converter, Guards, RandomHelper } from "@twin.org/core";
|
|
4
|
+
import { Blake2b } from "../hashes/blake2b.js";
|
|
4
5
|
/**
|
|
5
6
|
* Generate random passwords.
|
|
6
7
|
*/
|
|
7
8
|
export class PasswordGenerator {
|
|
9
|
+
/**
|
|
10
|
+
* Runtime name for the class.
|
|
11
|
+
*/
|
|
12
|
+
static CLASS_NAME = "PasswordGenerator";
|
|
13
|
+
/**
|
|
14
|
+
* The minimum password length, 15 to match owasp rules.
|
|
15
|
+
* @see https://www.owasp.org/index.php/Authentication_Cheat_Sheet#Implement_Proper_Password_Strength_Controls .
|
|
16
|
+
* @internal
|
|
17
|
+
*/
|
|
18
|
+
static _DEFAULT_MIN_PASSWORD_LENGTH = 15;
|
|
8
19
|
/**
|
|
9
20
|
* Generate a password of given length.
|
|
10
|
-
* @param length The length of the password to generate.
|
|
21
|
+
* @param length The length of the password to generate, default to 15.
|
|
11
22
|
* @returns The random password.
|
|
12
23
|
*/
|
|
13
|
-
static generate(length) {
|
|
14
|
-
const
|
|
15
|
-
const
|
|
24
|
+
static generate(length = PasswordGenerator._DEFAULT_MIN_PASSWORD_LENGTH) {
|
|
25
|
+
const lower = "abcdefghijklmnopqrstuvwxyz";
|
|
26
|
+
const upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
27
|
+
const digits = "0123456789";
|
|
28
|
+
const specials = "!#$£%^&*+=@~?}";
|
|
29
|
+
const alphabet = `${lower}${upper}`;
|
|
30
|
+
const allChars = `${alphabet}${digits}${specials}`;
|
|
31
|
+
const targetLength = Math.max(length, PasswordGenerator._DEFAULT_MIN_PASSWORD_LENGTH);
|
|
16
32
|
const chars = [];
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
chars.
|
|
33
|
+
// Ensure required character classes are present.
|
|
34
|
+
PasswordGenerator.pushChar(chars, lower);
|
|
35
|
+
PasswordGenerator.pushChar(chars, upper);
|
|
36
|
+
PasswordGenerator.pushChar(chars, digits);
|
|
37
|
+
PasswordGenerator.pushChar(chars, specials);
|
|
38
|
+
while (chars.length < targetLength) {
|
|
39
|
+
const charSet = chars.length === 0 ? alphabet : allChars;
|
|
40
|
+
PasswordGenerator.pushChar(chars, charSet);
|
|
24
41
|
}
|
|
25
42
|
return chars.join("");
|
|
26
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Hash the password for the user.
|
|
46
|
+
* @param passwordBytes The password bytes.
|
|
47
|
+
* @param saltBytes The salt bytes.
|
|
48
|
+
* @returns The hashed password.
|
|
49
|
+
*/
|
|
50
|
+
static async hashPassword(passwordBytes, saltBytes) {
|
|
51
|
+
Guards.uint8Array(PasswordGenerator.CLASS_NAME, "passwordBytes", passwordBytes);
|
|
52
|
+
Guards.uint8Array(PasswordGenerator.CLASS_NAME, "saltBytes", saltBytes);
|
|
53
|
+
const combined = new Uint8Array(saltBytes.length + passwordBytes.length);
|
|
54
|
+
combined.set(saltBytes);
|
|
55
|
+
combined.set(passwordBytes, saltBytes.length);
|
|
56
|
+
const hashedPassword = Blake2b.sum256(combined);
|
|
57
|
+
return Converter.bytesToBase64(hashedPassword);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Get a random character from the given character set.
|
|
61
|
+
* @param charSet The character set to get a random character from.
|
|
62
|
+
* @returns A random character from the given character set.
|
|
63
|
+
*/
|
|
64
|
+
static getRandomChar(charSet) {
|
|
65
|
+
let b = 0;
|
|
66
|
+
do {
|
|
67
|
+
b = RandomHelper.generate(1)[0];
|
|
68
|
+
} while (b >= charSet.length);
|
|
69
|
+
return charSet[b];
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Push a random character from the given character set to the chars array, ensuring no three repeated characters in a row.
|
|
73
|
+
* @param chars The array to push the character to.
|
|
74
|
+
* @param charSet The character set to get a random character from.
|
|
75
|
+
*/
|
|
76
|
+
static pushChar(chars, charSet) {
|
|
77
|
+
let next = PasswordGenerator.getRandomChar(charSet);
|
|
78
|
+
while (chars.length >= 2 && next === chars.at(-1) && next === chars.at(-2)) {
|
|
79
|
+
next = PasswordGenerator.getRandomChar(charSet);
|
|
80
|
+
}
|
|
81
|
+
chars.push(next);
|
|
82
|
+
}
|
|
27
83
|
}
|
|
28
84
|
//# sourceMappingURL=passwordGenerator.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"passwordGenerator.js","sourceRoot":"","sources":["../../../src/passwords/passwordGenerator.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"passwordGenerator.js","sourceRoot":"","sources":["../../../src/passwords/passwordGenerator.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { Converter, Guards, RandomHelper } from \"@twin.org/core\";\nimport { nameof } from \"@twin.org/nameof\";\nimport { Blake2b } from \"../hashes/blake2b.js\";\n\n/**\n * Generate random passwords.\n */\nexport class PasswordGenerator {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<PasswordGenerator>();\n\n\t/**\n\t * The minimum password length, 15 to match owasp rules.\n\t * @see https://www.owasp.org/index.php/Authentication_Cheat_Sheet#Implement_Proper_Password_Strength_Controls .\n\t * @internal\n\t */\n\tprivate static readonly _DEFAULT_MIN_PASSWORD_LENGTH: number = 15;\n\n\t/**\n\t * Generate a password of given length.\n\t * @param length The length of the password to generate, default to 15.\n\t * @returns The random password.\n\t */\n\tpublic static generate(length: number = PasswordGenerator._DEFAULT_MIN_PASSWORD_LENGTH): string {\n\t\tconst lower = \"abcdefghijklmnopqrstuvwxyz\";\n\t\tconst upper = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\";\n\t\tconst digits = \"0123456789\";\n\t\tconst specials = \"!#$£%^&*+=@~?}\";\n\t\tconst alphabet = `${lower}${upper}`;\n\t\tconst allChars = `${alphabet}${digits}${specials}`;\n\n\t\tconst targetLength = Math.max(length, PasswordGenerator._DEFAULT_MIN_PASSWORD_LENGTH);\n\t\tconst chars: string[] = [];\n\n\t\t// Ensure required character classes are present.\n\t\tPasswordGenerator.pushChar(chars, lower);\n\t\tPasswordGenerator.pushChar(chars, upper);\n\t\tPasswordGenerator.pushChar(chars, digits);\n\t\tPasswordGenerator.pushChar(chars, specials);\n\n\t\twhile (chars.length < targetLength) {\n\t\t\tconst charSet = chars.length === 0 ? alphabet : allChars;\n\t\t\tPasswordGenerator.pushChar(chars, charSet);\n\t\t}\n\n\t\treturn chars.join(\"\");\n\t}\n\n\t/**\n\t * Hash the password for the user.\n\t * @param passwordBytes The password bytes.\n\t * @param saltBytes The salt bytes.\n\t * @returns The hashed password.\n\t */\n\tpublic static async hashPassword(\n\t\tpasswordBytes: Uint8Array,\n\t\tsaltBytes: Uint8Array\n\t): Promise<string> {\n\t\tGuards.uint8Array(PasswordGenerator.CLASS_NAME, nameof(passwordBytes), passwordBytes);\n\t\tGuards.uint8Array(PasswordGenerator.CLASS_NAME, nameof(saltBytes), saltBytes);\n\n\t\tconst combined = new Uint8Array(saltBytes.length + passwordBytes.length);\n\t\tcombined.set(saltBytes);\n\t\tcombined.set(passwordBytes, saltBytes.length);\n\n\t\tconst hashedPassword = Blake2b.sum256(combined);\n\n\t\treturn Converter.bytesToBase64(hashedPassword);\n\t}\n\n\t/**\n\t * Get a random character from the given character set.\n\t * @param charSet The character set to get a random character from.\n\t * @returns A random character from the given character set.\n\t */\n\tprivate static getRandomChar(charSet: string): string {\n\t\tlet b = 0;\n\t\tdo {\n\t\t\tb = RandomHelper.generate(1)[0];\n\t\t} while (b >= charSet.length);\n\t\treturn charSet[b];\n\t}\n\n\t/**\n\t * Push a random character from the given character set to the chars array, ensuring no three repeated characters in a row.\n\t * @param chars The array to push the character to.\n\t * @param charSet The character set to get a random character from.\n\t */\n\tprivate static pushChar(chars: string[], charSet: string): void {\n\t\tlet next = PasswordGenerator.getRandomChar(charSet);\n\t\twhile (chars.length >= 2 && next === chars.at(-1) && next === chars.at(-2)) {\n\t\t\tnext = PasswordGenerator.getRandomChar(charSet);\n\t\t}\n\t\tchars.push(next);\n\t}\n}\n"]}
|
|
@@ -1,25 +1,35 @@
|
|
|
1
1
|
// Copyright 2024 IOTA Stiftung.
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0.
|
|
3
|
-
import { Validation } from "@twin.org/core";
|
|
3
|
+
import { Converter, Guards, Validation } from "@twin.org/core";
|
|
4
4
|
/**
|
|
5
5
|
* Test password strength.
|
|
6
|
-
*
|
|
6
|
+
* @see https://www.owasp.org/index.php/Authentication_Cheat_Sheet#Implement_Proper_Password_Strength_Controls .
|
|
7
7
|
*/
|
|
8
8
|
export class PasswordValidator {
|
|
9
|
+
/**
|
|
10
|
+
* Runtime name for the class.
|
|
11
|
+
*/
|
|
12
|
+
static CLASS_NAME = "PasswordValidator";
|
|
13
|
+
/**
|
|
14
|
+
* The minimum password length, 15 to match owasp rules.
|
|
15
|
+
* @see https://www.owasp.org/index.php/Authentication_Cheat_Sheet#Implement_Proper_Password_Strength_Controls .
|
|
16
|
+
* @internal
|
|
17
|
+
*/
|
|
18
|
+
static _DEFAULT_MIN_PASSWORD_LENGTH = 15;
|
|
9
19
|
/**
|
|
10
20
|
* Test the strength of the password.
|
|
11
21
|
* @param property The name of the property.
|
|
12
22
|
* @param password The password to test.
|
|
13
23
|
* @param failures The list of failures to add to.
|
|
14
24
|
* @param options Options to configure the testing.
|
|
15
|
-
* @param options.minLength The minimum length of the password, defaults to 8.
|
|
25
|
+
* @param options.minLength The minimum length of the password, defaults to 15, can be 8 if MFA is enabled.
|
|
16
26
|
* @param options.maxLength The minimum length of the password, defaults to 128.
|
|
17
27
|
* @param options.minPhraseLength The minimum length of the password for it to be considered a pass phrase.
|
|
18
28
|
*/
|
|
19
29
|
static validate(property, password, failures, options) {
|
|
20
30
|
const isString = Validation.stringValue(property, password, failures);
|
|
21
31
|
if (isString) {
|
|
22
|
-
const minLength = options?.minLength ??
|
|
32
|
+
const minLength = options?.minLength ?? PasswordValidator._DEFAULT_MIN_PASSWORD_LENGTH;
|
|
23
33
|
if (password.length < minLength) {
|
|
24
34
|
failures.push({
|
|
25
35
|
property,
|
|
@@ -77,5 +87,59 @@ export class PasswordValidator {
|
|
|
77
87
|
}
|
|
78
88
|
}
|
|
79
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Validate the password against security policy.
|
|
92
|
+
* @param password The password to validate.
|
|
93
|
+
* @param options Options to configure the testing.
|
|
94
|
+
* @param options.minLength The minimum length of the password, defaults to 15.
|
|
95
|
+
* @param options.maxLength The minimum length of the password, defaults to 128.
|
|
96
|
+
* @param options.minPhraseLength The minimum length of the password for it to be considered a pass phrase.
|
|
97
|
+
* @throws Error if the password does not meet the requirements.
|
|
98
|
+
*/
|
|
99
|
+
static validatePassword(password, options) {
|
|
100
|
+
Guards.stringValue(PasswordValidator.CLASS_NAME, "password", password);
|
|
101
|
+
const failures = [];
|
|
102
|
+
PasswordValidator.validate("password", password, failures, options);
|
|
103
|
+
Validation.asValidationError(PasswordValidator.CLASS_NAME, "password", failures);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Compare two password byte arrays in constant time to prevent timing attacks.
|
|
107
|
+
* @param hashedPasswordBytes The computed password bytes to compare.
|
|
108
|
+
* @param storedPasswordBytes The stored password bytes to compare against.
|
|
109
|
+
* @returns True if the bytes match, false otherwise.
|
|
110
|
+
*/
|
|
111
|
+
static comparePasswordBytes(hashedPasswordBytes, storedPasswordBytes) {
|
|
112
|
+
Guards.uint8Array(PasswordValidator.CLASS_NAME, "hashedPasswordBytes", hashedPasswordBytes);
|
|
113
|
+
Guards.uint8Array(PasswordValidator.CLASS_NAME, "storedPasswordBytes", storedPasswordBytes);
|
|
114
|
+
// Return immediately if lengths differ
|
|
115
|
+
if (hashedPasswordBytes.length !== storedPasswordBytes.length) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
// Compare bytes in constant time
|
|
119
|
+
let result = 0;
|
|
120
|
+
for (let i = 0; i < hashedPasswordBytes.length; i++) {
|
|
121
|
+
// eslint-disable-next-line no-bitwise
|
|
122
|
+
result |= hashedPasswordBytes[i] ^ storedPasswordBytes[i];
|
|
123
|
+
}
|
|
124
|
+
return result === 0;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Compare two hashed passwords in constant time to prevent timing attacks.
|
|
128
|
+
* @param hashedPassword The computed hash to compare.
|
|
129
|
+
* @param storedPassword The stored hash to compare against.
|
|
130
|
+
* @returns True if the hashes match, false otherwise.
|
|
131
|
+
*/
|
|
132
|
+
static comparePasswordHashes(hashedPassword, storedPassword) {
|
|
133
|
+
Guards.stringValue(PasswordValidator.CLASS_NAME, "hashedPassword", hashedPassword);
|
|
134
|
+
Guards.stringValue(PasswordValidator.CLASS_NAME, "storedPassword", storedPassword);
|
|
135
|
+
// Return immediately if lengths differ
|
|
136
|
+
if (hashedPassword.length !== storedPassword.length) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
// Decode base64 strings to bytes
|
|
140
|
+
const hashedBytes = Converter.base64ToBytes(hashedPassword);
|
|
141
|
+
const storedBytes = Converter.base64ToBytes(storedPassword);
|
|
142
|
+
return PasswordValidator.comparePasswordBytes(hashedBytes, storedBytes);
|
|
143
|
+
}
|
|
80
144
|
}
|
|
81
145
|
//# sourceMappingURL=passwordValidator.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"passwordValidator.js","sourceRoot":"","sources":["../../../src/passwords/passwordValidator.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,OAAO,EAA2B,UAAU,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"passwordValidator.js","sourceRoot":"","sources":["../../../src/passwords/passwordValidator.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { Converter, Guards, type IValidationFailure, Validation } from \"@twin.org/core\";\nimport { nameof } from \"@twin.org/nameof\";\n\n/**\n * Test password strength.\n * @see https://www.owasp.org/index.php/Authentication_Cheat_Sheet#Implement_Proper_Password_Strength_Controls .\n */\nexport class PasswordValidator {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<PasswordValidator>();\n\n\t/**\n\t * The minimum password length, 15 to match owasp rules.\n\t * @see https://www.owasp.org/index.php/Authentication_Cheat_Sheet#Implement_Proper_Password_Strength_Controls .\n\t * @internal\n\t */\n\tprivate static readonly _DEFAULT_MIN_PASSWORD_LENGTH: number = 15;\n\n\t/**\n\t * Test the strength of the password.\n\t * @param property The name of the property.\n\t * @param password The password to test.\n\t * @param failures The list of failures to add to.\n\t * @param options Options to configure the testing.\n\t * @param options.minLength The minimum length of the password, defaults to 15, can be 8 if MFA is enabled.\n\t * @param options.maxLength The minimum length of the password, defaults to 128.\n\t * @param options.minPhraseLength The minimum length of the password for it to be considered a pass phrase.\n\t */\n\tpublic static validate(\n\t\tproperty: string,\n\t\tpassword: string,\n\t\tfailures: IValidationFailure[],\n\t\toptions?: {\n\t\t\tminLength?: number;\n\t\t\tmaxLength?: number;\n\t\t\tminPhraseLength?: number;\n\t\t}\n\t): void {\n\t\tconst isString = Validation.stringValue(property, password, failures);\n\n\t\tif (isString) {\n\t\t\tconst minLength = options?.minLength ?? PasswordValidator._DEFAULT_MIN_PASSWORD_LENGTH;\n\t\t\tif (password.length < minLength) {\n\t\t\t\tfailures.push({\n\t\t\t\t\tproperty,\n\t\t\t\t\treason: \"validation.minLengthRequired\",\n\t\t\t\t\tproperties: {\n\t\t\t\t\t\tminLength,\n\t\t\t\t\t\tactualLength: password.length\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst maxLength = options?.maxLength ?? 128;\n\t\t\tif (password.length > maxLength) {\n\t\t\t\tfailures.push({\n\t\t\t\t\tproperty,\n\t\t\t\t\treason: \"validation.maxLengthRequired\",\n\t\t\t\t\tproperties: {\n\t\t\t\t\t\tmaxLength,\n\t\t\t\t\t\tactualLength: password.length\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (/(.)\\1{2,}/.test(password)) {\n\t\t\t\tfailures.push({\n\t\t\t\t\tproperty,\n\t\t\t\t\treason: \"validation.repeatedCharacters\"\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// If this looks like a phrase then apply additional rules\n\t\t\tconst minPhraseLength = options?.minPhraseLength ?? 20;\n\n\t\t\tif (password.length < minPhraseLength || !password.includes(\" \")) {\n\t\t\t\tif (!/[a-z]/.test(password)) {\n\t\t\t\t\tfailures.push({\n\t\t\t\t\t\tproperty,\n\t\t\t\t\t\treason: \"validation.atLeastOneLowerCase\"\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tif (!/[A-Z]/.test(password)) {\n\t\t\t\t\tfailures.push({\n\t\t\t\t\t\tproperty,\n\t\t\t\t\t\treason: \"validation.atLeastOneUpperCase\"\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tif (!/\\d/.test(password)) {\n\t\t\t\t\tfailures.push({\n\t\t\t\t\t\tproperty,\n\t\t\t\t\t\treason: \"validation.atLeastOneNumber\"\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tif (!/[^\\dA-Za-z]/.test(password)) {\n\t\t\t\t\tfailures.push({\n\t\t\t\t\t\tproperty,\n\t\t\t\t\t\treason: \"validation.atLeastOneSpecialChar\"\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Validate the password against security policy.\n\t * @param password The password to validate.\n\t * @param options Options to configure the testing.\n\t * @param options.minLength The minimum length of the password, defaults to 15.\n\t * @param options.maxLength The minimum length of the password, defaults to 128.\n\t * @param options.minPhraseLength The minimum length of the password for it to be considered a pass phrase.\n\t * @throws Error if the password does not meet the requirements.\n\t */\n\tpublic static validatePassword(\n\t\tpassword: string,\n\t\toptions?: {\n\t\t\tminLength?: number;\n\t\t\tmaxLength?: number;\n\t\t\tminPhraseLength?: number;\n\t\t}\n\t): void {\n\t\tGuards.stringValue(PasswordValidator.CLASS_NAME, nameof(password), password);\n\n\t\tconst failures: IValidationFailure[] = [];\n\n\t\tPasswordValidator.validate(nameof(password), password, failures, options);\n\n\t\tValidation.asValidationError(PasswordValidator.CLASS_NAME, nameof(password), failures);\n\t}\n\n\t/**\n\t * Compare two password byte arrays in constant time to prevent timing attacks.\n\t * @param hashedPasswordBytes The computed password bytes to compare.\n\t * @param storedPasswordBytes The stored password bytes to compare against.\n\t * @returns True if the bytes match, false otherwise.\n\t */\n\tpublic static comparePasswordBytes(\n\t\thashedPasswordBytes: Uint8Array,\n\t\tstoredPasswordBytes: Uint8Array\n\t): boolean {\n\t\tGuards.uint8Array(\n\t\t\tPasswordValidator.CLASS_NAME,\n\t\t\tnameof(hashedPasswordBytes),\n\t\t\thashedPasswordBytes\n\t\t);\n\t\tGuards.uint8Array(\n\t\t\tPasswordValidator.CLASS_NAME,\n\t\t\tnameof(storedPasswordBytes),\n\t\t\tstoredPasswordBytes\n\t\t);\n\n\t\t// Return immediately if lengths differ\n\t\tif (hashedPasswordBytes.length !== storedPasswordBytes.length) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// Compare bytes in constant time\n\t\tlet result = 0;\n\t\tfor (let i = 0; i < hashedPasswordBytes.length; i++) {\n\t\t\t// eslint-disable-next-line no-bitwise\n\t\t\tresult |= hashedPasswordBytes[i] ^ storedPasswordBytes[i];\n\t\t}\n\n\t\treturn result === 0;\n\t}\n\n\t/**\n\t * Compare two hashed passwords in constant time to prevent timing attacks.\n\t * @param hashedPassword The computed hash to compare.\n\t * @param storedPassword The stored hash to compare against.\n\t * @returns True if the hashes match, false otherwise.\n\t */\n\tpublic static comparePasswordHashes(hashedPassword: string, storedPassword: string): boolean {\n\t\tGuards.stringValue(PasswordValidator.CLASS_NAME, nameof(hashedPassword), hashedPassword);\n\t\tGuards.stringValue(PasswordValidator.CLASS_NAME, nameof(storedPassword), storedPassword);\n\n\t\t// Return immediately if lengths differ\n\t\tif (hashedPassword.length !== storedPassword.length) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// Decode base64 strings to bytes\n\t\tconst hashedBytes = Converter.base64ToBytes(hashedPassword);\n\t\tconst storedBytes = Converter.base64ToBytes(storedPassword);\n\n\t\treturn PasswordValidator.comparePasswordBytes(hashedBytes, storedBytes);\n\t}\n}\n"]}
|
|
@@ -2,10 +2,33 @@
|
|
|
2
2
|
* Generate random passwords.
|
|
3
3
|
*/
|
|
4
4
|
export declare class PasswordGenerator {
|
|
5
|
+
/**
|
|
6
|
+
* Runtime name for the class.
|
|
7
|
+
*/
|
|
8
|
+
static readonly CLASS_NAME: string;
|
|
5
9
|
/**
|
|
6
10
|
* Generate a password of given length.
|
|
7
|
-
* @param length The length of the password to generate.
|
|
11
|
+
* @param length The length of the password to generate, default to 15.
|
|
8
12
|
* @returns The random password.
|
|
9
13
|
*/
|
|
10
|
-
static generate(length
|
|
14
|
+
static generate(length?: number): string;
|
|
15
|
+
/**
|
|
16
|
+
* Hash the password for the user.
|
|
17
|
+
* @param passwordBytes The password bytes.
|
|
18
|
+
* @param saltBytes The salt bytes.
|
|
19
|
+
* @returns The hashed password.
|
|
20
|
+
*/
|
|
21
|
+
static hashPassword(passwordBytes: Uint8Array, saltBytes: Uint8Array): Promise<string>;
|
|
22
|
+
/**
|
|
23
|
+
* Get a random character from the given character set.
|
|
24
|
+
* @param charSet The character set to get a random character from.
|
|
25
|
+
* @returns A random character from the given character set.
|
|
26
|
+
*/
|
|
27
|
+
private static getRandomChar;
|
|
28
|
+
/**
|
|
29
|
+
* Push a random character from the given character set to the chars array, ensuring no three repeated characters in a row.
|
|
30
|
+
* @param chars The array to push the character to.
|
|
31
|
+
* @param charSet The character set to get a random character from.
|
|
32
|
+
*/
|
|
33
|
+
private static pushChar;
|
|
11
34
|
}
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
import { type IValidationFailure } from "@twin.org/core";
|
|
2
2
|
/**
|
|
3
3
|
* Test password strength.
|
|
4
|
-
*
|
|
4
|
+
* @see https://www.owasp.org/index.php/Authentication_Cheat_Sheet#Implement_Proper_Password_Strength_Controls .
|
|
5
5
|
*/
|
|
6
6
|
export declare class PasswordValidator {
|
|
7
|
+
/**
|
|
8
|
+
* Runtime name for the class.
|
|
9
|
+
*/
|
|
10
|
+
static readonly CLASS_NAME: string;
|
|
7
11
|
/**
|
|
8
12
|
* Test the strength of the password.
|
|
9
13
|
* @param property The name of the property.
|
|
10
14
|
* @param password The password to test.
|
|
11
15
|
* @param failures The list of failures to add to.
|
|
12
16
|
* @param options Options to configure the testing.
|
|
13
|
-
* @param options.minLength The minimum length of the password, defaults to 8.
|
|
17
|
+
* @param options.minLength The minimum length of the password, defaults to 15, can be 8 if MFA is enabled.
|
|
14
18
|
* @param options.maxLength The minimum length of the password, defaults to 128.
|
|
15
19
|
* @param options.minPhraseLength The minimum length of the password for it to be considered a pass phrase.
|
|
16
20
|
*/
|
|
@@ -19,4 +23,32 @@ export declare class PasswordValidator {
|
|
|
19
23
|
maxLength?: number;
|
|
20
24
|
minPhraseLength?: number;
|
|
21
25
|
}): void;
|
|
26
|
+
/**
|
|
27
|
+
* Validate the password against security policy.
|
|
28
|
+
* @param password The password to validate.
|
|
29
|
+
* @param options Options to configure the testing.
|
|
30
|
+
* @param options.minLength The minimum length of the password, defaults to 15.
|
|
31
|
+
* @param options.maxLength The minimum length of the password, defaults to 128.
|
|
32
|
+
* @param options.minPhraseLength The minimum length of the password for it to be considered a pass phrase.
|
|
33
|
+
* @throws Error if the password does not meet the requirements.
|
|
34
|
+
*/
|
|
35
|
+
static validatePassword(password: string, options?: {
|
|
36
|
+
minLength?: number;
|
|
37
|
+
maxLength?: number;
|
|
38
|
+
minPhraseLength?: number;
|
|
39
|
+
}): void;
|
|
40
|
+
/**
|
|
41
|
+
* Compare two password byte arrays in constant time to prevent timing attacks.
|
|
42
|
+
* @param hashedPasswordBytes The computed password bytes to compare.
|
|
43
|
+
* @param storedPasswordBytes The stored password bytes to compare against.
|
|
44
|
+
* @returns True if the bytes match, false otherwise.
|
|
45
|
+
*/
|
|
46
|
+
static comparePasswordBytes(hashedPasswordBytes: Uint8Array, storedPasswordBytes: Uint8Array): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Compare two hashed passwords in constant time to prevent timing attacks.
|
|
49
|
+
* @param hashedPassword The computed hash to compare.
|
|
50
|
+
* @param storedPassword The stored hash to compare against.
|
|
51
|
+
* @returns True if the hashes match, false otherwise.
|
|
52
|
+
*/
|
|
53
|
+
static comparePasswordHashes(hashedPassword: string, storedPassword: string): boolean;
|
|
22
54
|
}
|
package/docs/changelog.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
# @twin.org/crypto - Changelog
|
|
2
2
|
|
|
3
|
+
## [0.0.3-next.16](https://github.com/twinfoundation/framework/compare/crypto-v0.0.3-next.15...crypto-v0.0.3-next.16) (2026-02-06)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* improved password generation and validation ([#232](https://github.com/twinfoundation/framework/issues/232)) ([ca4e18f](https://github.com/twinfoundation/framework/commit/ca4e18f388b1882cdfb774fc0d0921b8530fac33))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Dependencies
|
|
12
|
+
|
|
13
|
+
* The following workspace dependencies were updated
|
|
14
|
+
* dependencies
|
|
15
|
+
* @twin.org/core bumped from 0.0.3-next.15 to 0.0.3-next.16
|
|
16
|
+
* @twin.org/nameof bumped from 0.0.3-next.15 to 0.0.3-next.16
|
|
17
|
+
* devDependencies
|
|
18
|
+
* @twin.org/nameof-transformer bumped from 0.0.3-next.15 to 0.0.3-next.16
|
|
19
|
+
* @twin.org/nameof-vitest-plugin bumped from 0.0.3-next.15 to 0.0.3-next.16
|
|
20
|
+
* @twin.org/validate-locales bumped from 0.0.3-next.15 to 0.0.3-next.16
|
|
21
|
+
|
|
22
|
+
## [0.0.3-next.15](https://github.com/twinfoundation/framework/compare/crypto-v0.0.3-next.14...crypto-v0.0.3-next.15) (2026-01-29)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
### Miscellaneous Chores
|
|
26
|
+
|
|
27
|
+
* **crypto:** Synchronize repo versions
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
### Dependencies
|
|
31
|
+
|
|
32
|
+
* The following workspace dependencies were updated
|
|
33
|
+
* dependencies
|
|
34
|
+
* @twin.org/core bumped from 0.0.3-next.14 to 0.0.3-next.15
|
|
35
|
+
* @twin.org/nameof bumped from 0.0.3-next.14 to 0.0.3-next.15
|
|
36
|
+
* devDependencies
|
|
37
|
+
* @twin.org/nameof-transformer bumped from 0.0.3-next.14 to 0.0.3-next.15
|
|
38
|
+
* @twin.org/nameof-vitest-plugin bumped from 0.0.3-next.14 to 0.0.3-next.15
|
|
39
|
+
* @twin.org/validate-locales bumped from 0.0.3-next.14 to 0.0.3-next.15
|
|
40
|
+
|
|
3
41
|
## [0.0.3-next.14](https://github.com/twinfoundation/framework/compare/crypto-v0.0.3-next.13...crypto-v0.0.3-next.14) (2026-01-22)
|
|
4
42
|
|
|
5
43
|
|
|
@@ -12,6 +12,14 @@ Generate random passwords.
|
|
|
12
12
|
|
|
13
13
|
`PasswordGenerator`
|
|
14
14
|
|
|
15
|
+
## Properties
|
|
16
|
+
|
|
17
|
+
### CLASS\_NAME
|
|
18
|
+
|
|
19
|
+
> `readonly` `static` **CLASS\_NAME**: `string`
|
|
20
|
+
|
|
21
|
+
Runtime name for the class.
|
|
22
|
+
|
|
15
23
|
## Methods
|
|
16
24
|
|
|
17
25
|
### generate()
|
|
@@ -24,12 +32,40 @@ Generate a password of given length.
|
|
|
24
32
|
|
|
25
33
|
##### length
|
|
26
34
|
|
|
27
|
-
`number`
|
|
35
|
+
`number` = `PasswordGenerator._DEFAULT_MIN_PASSWORD_LENGTH`
|
|
28
36
|
|
|
29
|
-
The length of the password to generate.
|
|
37
|
+
The length of the password to generate, default to 15.
|
|
30
38
|
|
|
31
39
|
#### Returns
|
|
32
40
|
|
|
33
41
|
`string`
|
|
34
42
|
|
|
35
43
|
The random password.
|
|
44
|
+
|
|
45
|
+
***
|
|
46
|
+
|
|
47
|
+
### hashPassword()
|
|
48
|
+
|
|
49
|
+
> `static` **hashPassword**(`passwordBytes`, `saltBytes`): `Promise`\<`string`\>
|
|
50
|
+
|
|
51
|
+
Hash the password for the user.
|
|
52
|
+
|
|
53
|
+
#### Parameters
|
|
54
|
+
|
|
55
|
+
##### passwordBytes
|
|
56
|
+
|
|
57
|
+
`Uint8Array`
|
|
58
|
+
|
|
59
|
+
The password bytes.
|
|
60
|
+
|
|
61
|
+
##### saltBytes
|
|
62
|
+
|
|
63
|
+
`Uint8Array`
|
|
64
|
+
|
|
65
|
+
The salt bytes.
|
|
66
|
+
|
|
67
|
+
#### Returns
|
|
68
|
+
|
|
69
|
+
`Promise`\<`string`\>
|
|
70
|
+
|
|
71
|
+
The hashed password.
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
# Class: PasswordValidator
|
|
2
2
|
|
|
3
3
|
Test password strength.
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
## See
|
|
6
|
+
|
|
7
|
+
https://www.owasp.org/index.php/Authentication_Cheat_Sheet#Implement_Proper_Password_Strength_Controls .
|
|
5
8
|
|
|
6
9
|
## Constructors
|
|
7
10
|
|
|
@@ -13,6 +16,14 @@ Ref https://www.owasp.org/index.php/Authentication_Cheat_Sheet#Implement_Proper_
|
|
|
13
16
|
|
|
14
17
|
`PasswordValidator`
|
|
15
18
|
|
|
19
|
+
## Properties
|
|
20
|
+
|
|
21
|
+
### CLASS\_NAME
|
|
22
|
+
|
|
23
|
+
> `readonly` `static` **CLASS\_NAME**: `string`
|
|
24
|
+
|
|
25
|
+
Runtime name for the class.
|
|
26
|
+
|
|
16
27
|
## Methods
|
|
17
28
|
|
|
18
29
|
### validate()
|
|
@@ -49,7 +60,49 @@ Options to configure the testing.
|
|
|
49
60
|
|
|
50
61
|
`number`
|
|
51
62
|
|
|
52
|
-
The minimum length of the password, defaults to 8.
|
|
63
|
+
The minimum length of the password, defaults to 15, can be 8 if MFA is enabled.
|
|
64
|
+
|
|
65
|
+
###### maxLength?
|
|
66
|
+
|
|
67
|
+
`number`
|
|
68
|
+
|
|
69
|
+
The minimum length of the password, defaults to 128.
|
|
70
|
+
|
|
71
|
+
###### minPhraseLength?
|
|
72
|
+
|
|
73
|
+
`number`
|
|
74
|
+
|
|
75
|
+
The minimum length of the password for it to be considered a pass phrase.
|
|
76
|
+
|
|
77
|
+
#### Returns
|
|
78
|
+
|
|
79
|
+
`void`
|
|
80
|
+
|
|
81
|
+
***
|
|
82
|
+
|
|
83
|
+
### validatePassword()
|
|
84
|
+
|
|
85
|
+
> `static` **validatePassword**(`password`, `options?`): `void`
|
|
86
|
+
|
|
87
|
+
Validate the password against security policy.
|
|
88
|
+
|
|
89
|
+
#### Parameters
|
|
90
|
+
|
|
91
|
+
##### password
|
|
92
|
+
|
|
93
|
+
`string`
|
|
94
|
+
|
|
95
|
+
The password to validate.
|
|
96
|
+
|
|
97
|
+
##### options?
|
|
98
|
+
|
|
99
|
+
Options to configure the testing.
|
|
100
|
+
|
|
101
|
+
###### minLength?
|
|
102
|
+
|
|
103
|
+
`number`
|
|
104
|
+
|
|
105
|
+
The minimum length of the password, defaults to 15.
|
|
53
106
|
|
|
54
107
|
###### maxLength?
|
|
55
108
|
|
|
@@ -66,3 +119,63 @@ The minimum length of the password for it to be considered a pass phrase.
|
|
|
66
119
|
#### Returns
|
|
67
120
|
|
|
68
121
|
`void`
|
|
122
|
+
|
|
123
|
+
#### Throws
|
|
124
|
+
|
|
125
|
+
Error if the password does not meet the requirements.
|
|
126
|
+
|
|
127
|
+
***
|
|
128
|
+
|
|
129
|
+
### comparePasswordBytes()
|
|
130
|
+
|
|
131
|
+
> `static` **comparePasswordBytes**(`hashedPasswordBytes`, `storedPasswordBytes`): `boolean`
|
|
132
|
+
|
|
133
|
+
Compare two password byte arrays in constant time to prevent timing attacks.
|
|
134
|
+
|
|
135
|
+
#### Parameters
|
|
136
|
+
|
|
137
|
+
##### hashedPasswordBytes
|
|
138
|
+
|
|
139
|
+
`Uint8Array`
|
|
140
|
+
|
|
141
|
+
The computed password bytes to compare.
|
|
142
|
+
|
|
143
|
+
##### storedPasswordBytes
|
|
144
|
+
|
|
145
|
+
`Uint8Array`
|
|
146
|
+
|
|
147
|
+
The stored password bytes to compare against.
|
|
148
|
+
|
|
149
|
+
#### Returns
|
|
150
|
+
|
|
151
|
+
`boolean`
|
|
152
|
+
|
|
153
|
+
True if the bytes match, false otherwise.
|
|
154
|
+
|
|
155
|
+
***
|
|
156
|
+
|
|
157
|
+
### comparePasswordHashes()
|
|
158
|
+
|
|
159
|
+
> `static` **comparePasswordHashes**(`hashedPassword`, `storedPassword`): `boolean`
|
|
160
|
+
|
|
161
|
+
Compare two hashed passwords in constant time to prevent timing attacks.
|
|
162
|
+
|
|
163
|
+
#### Parameters
|
|
164
|
+
|
|
165
|
+
##### hashedPassword
|
|
166
|
+
|
|
167
|
+
`string`
|
|
168
|
+
|
|
169
|
+
The computed hash to compare.
|
|
170
|
+
|
|
171
|
+
##### storedPassword
|
|
172
|
+
|
|
173
|
+
`string`
|
|
174
|
+
|
|
175
|
+
The stored hash to compare against.
|
|
176
|
+
|
|
177
|
+
#### Returns
|
|
178
|
+
|
|
179
|
+
`boolean`
|
|
180
|
+
|
|
181
|
+
True if the hashes match, false otherwise.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@twin.org/crypto",
|
|
3
|
-
"version": "0.0.3-next.
|
|
3
|
+
"version": "0.0.3-next.16",
|
|
4
4
|
"description": "Contains helper methods and classes which implement cryptographic functions",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
"@scure/base": "2.0.0",
|
|
21
21
|
"@scure/bip32": "2.0.1",
|
|
22
22
|
"@scure/bip39": "2.0.1",
|
|
23
|
-
"@twin.org/core": "0.0.3-next.
|
|
24
|
-
"@twin.org/nameof": "0.0.3-next.
|
|
23
|
+
"@twin.org/core": "0.0.3-next.16",
|
|
24
|
+
"@twin.org/nameof": "0.0.3-next.16",
|
|
25
25
|
"crypto-browserify": "3.12.1",
|
|
26
26
|
"micro-key-producer": "0.8.2"
|
|
27
27
|
},
|