51 lines
1.2 KiB
TypeScript
51 lines
1.2 KiB
TypeScript
import {
|
|
ENTROPY_CLASSES,
|
|
MAX_PASSWORD_LENGTH,
|
|
MIN_PASSWORD_ENTROPY,
|
|
MIN_PASSWORD_LENGTH
|
|
} from "./const"
|
|
|
|
export const isPasswordValid = (password: string): [boolean, string] => {
|
|
if (password.length < MIN_PASSWORD_LENGTH) {
|
|
return [false, `Password cannot be shorter than ${MIN_PASSWORD_LENGTH} characters`]
|
|
}
|
|
|
|
if (password.length > MAX_PASSWORD_LENGTH) {
|
|
return [false, `Password cannot be longer than ${MAX_PASSWORD_LENGTH} characters`]
|
|
}
|
|
|
|
const entropy = calculateEntropy(password)
|
|
console.log(`entropy: ${entropy}`)
|
|
if (entropy < MIN_PASSWORD_ENTROPY) {
|
|
return [
|
|
false,
|
|
"Password is not complex enough (add uppercase, lowercase, numbers, and symbols)"
|
|
]
|
|
}
|
|
|
|
return [true, ""]
|
|
}
|
|
|
|
const calculateEntropy = (password: string) => {
|
|
let poolSize = 0
|
|
|
|
for (const [eClass, poolPlus] of ENTROPY_CLASSES) {
|
|
if (eClass.test(password)) {
|
|
poolSize += poolPlus
|
|
}
|
|
}
|
|
|
|
// Empty password exception
|
|
if (poolSize === 0) {
|
|
return 0
|
|
}
|
|
|
|
const uniqueChars = new Set(password.split("")).size
|
|
|
|
const basicEntropy = password.length * Math.log2(poolSize)
|
|
const diversityAdjustedEntropy =
|
|
Math.log2(poolSize) + (password.length - 1) * Math.log2(uniqueChars)
|
|
|
|
return Math.min(basicEntropy, diversityAdjustedEntropy)
|
|
}
|