UNPKG

pwdkit

Version:

A lightweight utility to evaluate password strength and generate password suggestions.

256 lines (255 loc) 8.98 kB
// src/index.ts var PasswordToolkit = class { constructor(options) { this.minimum_characters = (options == null ? void 0 : options.minimum_characters) || 8; this.containsUpperCase = (options == null ? void 0 : options.containsUpperCase) === false ? false : true; this.containsLowerCase = (options == null ? void 0 : options.containsLowerCase) === false ? false : true; this.containsNumbers = (options == null ? void 0 : options.containsNumbers) === false ? false : true; this.containsSpecialCharacters = (options == null ? void 0 : options.containsSpecialCharacters) === false ? false : true; this.defaultAllowedSpecialCharacters = [ "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "_", "+", "[", "]", "{", "}", "<", ">", "?", ",", "." ]; let given_special_chars = (options == null ? void 0 : options.allowedSpecialCharacters) || []; this.allowedSpecialCharacters = Array.isArray(given_special_chars) && given_special_chars.length > 0 ? given_special_chars : [...this.defaultAllowedSpecialCharacters]; } countDigitsAndSpecialExcludingEnds(input) { let count = 0; const sliced = input.slice(1, -1); for (const char of sliced) { if (/[0-9]/.test(char) || /[^a-zA-Z0-9]/.test(char)) { count++; } } return count; } getCharCounts(input) { let lowercase = 0; let uppercase = 0; let digits = 0; let special_characters = 0; let consecutiveUppercase = 0; let consecutiveLowercase = 0; let consecutiveDigits = 0; for (let i = 0; i < input.length; i++) { const char = input[i]; if (/[a-z]/.test(char)) { lowercase++; if (i > 0 && /[a-z]/.test(input[i - 1])) { consecutiveLowercase++; } } else if (/[A-Z]/.test(char)) { uppercase++; if (i > 0 && /[A-Z]/.test(input[i - 1])) { consecutiveUppercase++; } } else if (/[0-9]/.test(char)) { digits++; if (i > 0 && /[0-9]/.test(input[i - 1])) { consecutiveDigits++; } } else { special_characters++; } } return { lowercase, uppercase, digits, special_characters, consecutiveUppercase, consecutiveLowercase, consecutiveDigits }; } areSpecialCharsEqual(a, b) { if (a.length !== b.length) return false; const sortedA = [...a].sort(); const sortedB = [...b].sort(); return sortedA.every((val, i) => val === sortedB[i]); } getRequirementMatchCount(passwordTxt, char_counts) { let match_count = 0; const pwd_length = passwordTxt.length; if (pwd_length >= this.minimum_characters) { match_count += 1; } if (this.containsUpperCase && char_counts.uppercase > 0) { match_count += 1; } if (this.containsLowerCase && char_counts.lowercase > 0) { match_count += 1; } if (this.containsNumbers && char_counts.digits > 0) { match_count += 1; } if (this.containsSpecialCharacters && char_counts.special_characters > 0) { match_count += 1; if (this.allowedSpecialCharacters && !this.areSpecialCharsEqual( this.allowedSpecialCharacters, this.defaultAllowedSpecialCharacters )) { const containsAllowedSpecialChar = this.allowedSpecialCharacters.some( (char) => passwordTxt.includes(char) ); if (containsAllowedSpecialChar) { match_count += 1; } } } return match_count; } analyse(passwordTxt) { let score = 0; let deductions = 0; let pwd_length = passwordTxt.length; let char_counts = this.getCharCounts(passwordTxt); let characters_score = pwd_length * 4; let uppercase_score = (pwd_length - char_counts.uppercase) * 2; let lowercase_score = (pwd_length - char_counts.lowercase) * 2; let number_score = char_counts.digits * 4; let special_characters_score = char_counts.special_characters * 6; let requirement_match_score = this.getRequirementMatchCount(passwordTxt, char_counts) * 2; let middle_numbers_symbols_score = this.countDigitsAndSpecialExcludingEnds(passwordTxt) * 2; let only_letters_count = char_counts.digits == 0 && char_counts.special_characters == 0 ? char_counts.uppercase + char_counts.lowercase : 0; let only_numbers_count = char_counts.uppercase == 0 && char_counts.lowercase == 0 && char_counts.special_characters == 0 ? char_counts.digits : 0; score = characters_score + uppercase_score + lowercase_score + number_score + special_characters_score + requirement_match_score + middle_numbers_symbols_score; deductions = only_letters_count + only_numbers_count + char_counts.consecutiveUppercase * 2 + char_counts.consecutiveLowercase * 2 + char_counts.consecutiveDigits * 2; let final_score = score - deductions; if (final_score > 100) { final_score = 100; } return { score: final_score }; } generatePassword() { const lowerChars = "abcdefghijklmnopqrstuvwxyz"; const upperChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; const numberChars = "0123456789"; const specialChars = this.allowedSpecialCharacters.join(""); let charPool = ""; let requiredChars = []; const allowedForEdges = []; if (this.containsLowerCase) { charPool += lowerChars; requiredChars.push(this.getRandomChar(lowerChars)); allowedForEdges.push(...lowerChars); } if (this.containsUpperCase) { charPool += upperChars; requiredChars.push(this.getRandomChar(upperChars)); allowedForEdges.push(...upperChars); } if (this.containsNumbers) { charPool += numberChars; requiredChars.push(this.getRandomChar(numberChars)); } if (this.containsSpecialCharacters) { charPool += specialChars; requiredChars.push(this.getRandomChar(specialChars)); } const minLen = this.minimum_characters; const remainingLen = Math.max(minLen - requiredChars.length, 2); let middleChars = []; for (let i = 0; i < remainingLen; i++) { middleChars.push(this.getRandomChar(charPool)); } let combined = [...requiredChars, ...middleChars]; combined = this.shuffleArray(combined); const notAllowedAtEdges = (this.containsNumbers ? "" : numberChars) + (this.containsSpecialCharacters ? "" : specialChars); const safeEdgeChars = combined.filter( (c) => !notAllowedAtEdges.includes(c) && allowedForEdges.includes(c) ); let firstChar = ""; let lastChar = ""; if (safeEdgeChars.length > 0 || allowedForEdges.length > 0) { firstChar = safeEdgeChars[0] || this.getRandomChar(allowedForEdges.join("")); lastChar = safeEdgeChars[safeEdgeChars.length - 1] || this.getRandomChar(allowedForEdges.join("")); } const middle = combined.filter((c) => c !== firstChar && c !== lastChar); return firstChar + middle.join("") + lastChar; } getRandomChar(pool) { return pool[Math.floor(Math.random() * pool.length)]; } shuffleArray(array) { for (let i = array.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [array[i], array[j]] = [array[j], array[i]]; } return array; } getSuggestions(n) { let results = []; for (let i = 0; i < n; i++) { let password = this.generatePassword(); let score = this.analyse(password); results.push({ password, score: score.score }); } return results; } getPolicy() { let policy = { minimum_characters: this.minimum_characters, containsUpperCase: this.containsUpperCase, containsLowerCase: this.containsLowerCase, containsNumbers: this.containsNumbers, containsSpecialCharacters: this.containsSpecialCharacters, allowedSpecialCharacters: this.allowedSpecialCharacters }; return policy; } isPolicySatisfied(passwordTxt) { let result = true; let char_counts = this.getCharCounts(passwordTxt); const pwd_length = passwordTxt.length; if (pwd_length < this.minimum_characters) { result = false; } if (this.containsUpperCase && char_counts.uppercase === 0) { result = false; } if (this.containsLowerCase && char_counts.lowercase === 0) { result = false; } if (this.containsNumbers && char_counts.digits === 0) { result = false; } if (this.containsSpecialCharacters && char_counts.special_characters === 0) { result = false; } if (this.allowedSpecialCharacters && !this.areSpecialCharsEqual( this.allowedSpecialCharacters, this.defaultAllowedSpecialCharacters )) { const containsAllowedSpecialChar = this.allowedSpecialCharacters.some( (char) => passwordTxt.includes(char) ); if (!containsAllowedSpecialChar) { result = false; } } return result; } }; export { PasswordToolkit };