@sentinel-password/core
Advanced tools
+30
-88
@@ -104,65 +104,4 @@ "use strict"; | ||
| var hasDigit = (password) => /\d/.test(password); | ||
| var hasSymbol = (password) => /[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/.test(password); | ||
| var SYMBOL_CODES = /* @__PURE__ */ new Set([ | ||
| 33, | ||
| // ! | ||
| 34, | ||
| // " | ||
| 35, | ||
| // # | ||
| 36, | ||
| // $ | ||
| 37, | ||
| // % | ||
| 38, | ||
| // & | ||
| 39, | ||
| // ' | ||
| 40, | ||
| // ( | ||
| 41, | ||
| // ) | ||
| 42, | ||
| // * | ||
| 43, | ||
| // + | ||
| 44, | ||
| // , | ||
| 45, | ||
| // - | ||
| 46, | ||
| // . | ||
| 47, | ||
| // / | ||
| 58, | ||
| // : | ||
| 59, | ||
| // ; | ||
| 60, | ||
| // < | ||
| 61, | ||
| // = | ||
| 62, | ||
| // > | ||
| 63, | ||
| // ? | ||
| 64, | ||
| // @ | ||
| 91, | ||
| // [ | ||
| 92, | ||
| // \ | ||
| 93, | ||
| // ] | ||
| 94, | ||
| // ^ | ||
| 95, | ||
| // _ | ||
| 123, | ||
| // { | ||
| 124, | ||
| // | | ||
| 125 | ||
| // } | ||
| ]); | ||
| var hasSymbol = (password) => /[\x20-\x2F\x3A-\x40\x5B-\x60\x7B-\x7E]/.test(password); | ||
| var isSymbolCode = (c) => c >= 32 && c <= 47 || c >= 58 && c <= 64 || c >= 91 && c <= 96 || c >= 123 && c <= 126; | ||
| var validateCharacterTypes = (password, options = {}) => { | ||
@@ -191,3 +130,3 @@ const { | ||
| foundDigit = true; | ||
| } else if (!foundSymbol && SYMBOL_CODES.has(c)) { | ||
| } else if (!foundSymbol && isSymbolCode(c)) { | ||
| foundSymbol = true; | ||
@@ -232,9 +171,5 @@ } | ||
| const { maxRepeatedChars = 3 } = options; | ||
| if (password.length === 0) { | ||
| return { passed: true }; | ||
| } | ||
| let currentChar = password.charAt(0); | ||
| let count = 1; | ||
| for (let i = 1; i < password.length; i++) { | ||
| const char = password.charAt(i); | ||
| let currentChar; | ||
| let count = 0; | ||
| for (const char of password) { | ||
| if (char === currentChar) { | ||
@@ -906,2 +841,3 @@ count++; | ||
| var EMPTY_SUGGESTIONS = Object.freeze([]); | ||
| var EMPTY_FAILURES = Object.freeze([]); | ||
| var TOTAL_CHECKS = 7; | ||
@@ -926,5 +862,4 @@ function validatePassword(password, options = {}) { | ||
| let passedChecks = 0; | ||
| let suggestions; | ||
| let firstSuggestion; | ||
| const record = (result) => { | ||
| let failures; | ||
| const record = (check, result) => { | ||
| if (result.passed) { | ||
@@ -934,17 +869,21 @@ passedChecks++; | ||
| } | ||
| if (result.message === void 0) return; | ||
| if (suggestions === void 0) { | ||
| suggestions = [result.message]; | ||
| firstSuggestion = result.message; | ||
| const failure = { | ||
| check, | ||
| code: result.code, | ||
| params: result.params, | ||
| message: result.message | ||
| }; | ||
| if (failures === void 0) { | ||
| failures = [failure]; | ||
| } else { | ||
| suggestions.push(result.message); | ||
| failures.push(failure); | ||
| } | ||
| }; | ||
| record(lengthResult); | ||
| record(charTypesResult); | ||
| record(repetitionResult); | ||
| record(sequentialResult); | ||
| record(commonPasswordResult); | ||
| record(personalInfoResult); | ||
| record(keyboardPatternResult); | ||
| record("length", lengthResult); | ||
| record("characterTypes", charTypesResult); | ||
| record("repetition", repetitionResult); | ||
| record("sequential", sequentialResult); | ||
| record("commonPassword", commonPasswordResult); | ||
| record("personalInfo", personalInfoResult); | ||
| record("keyboardPattern", keyboardPatternResult); | ||
| const score = Math.min( | ||
@@ -954,2 +893,4 @@ 4, | ||
| ); | ||
| const suggestions = failures ? failures.map((failure) => failure.message) : EMPTY_SUGGESTIONS; | ||
| const firstSuggestion = failures?.[0]?.message; | ||
| return { | ||
@@ -962,5 +903,6 @@ valid: passedChecks === TOTAL_CHECKS, | ||
| ...firstSuggestion !== void 0 && { warning: firstSuggestion }, | ||
| suggestions: suggestions ?? EMPTY_SUGGESTIONS | ||
| suggestions | ||
| }, | ||
| checks | ||
| checks, | ||
| failures: failures ?? EMPTY_FAILURES | ||
| }; | ||
@@ -967,0 +909,0 @@ } |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../src/index.ts","../src/messages.ts","../src/validators/length.ts","../src/validators/character-types.ts","../src/validators/repetition.ts","../src/validators/sequential.ts","../src/validators/keyboard-pattern.ts","../src/validators/common-password.ts","../src/validators/personal-info.ts"],"sourcesContent":["/**\n * @sentinel-password/core\n * Zero-dependency TypeScript password validation library\n */\n\nexport type {\n StrengthScore,\n StrengthLabel,\n ValidationResult,\n ValidatorOptions,\n ValidatorCheck,\n Validator,\n CheckId,\n MessageCode,\n MessageParams,\n MessageFormatter,\n} from './types'\n\nexport { DEFAULT_TEMPLATES } from './messages'\n\nexport { validateLength } from './validators/length'\nexport {\n hasUppercase,\n hasLowercase,\n hasDigit,\n hasSymbol,\n validateCharacterTypes,\n} from './validators/character-types'\nexport { validateRepetition } from './validators/repetition'\nexport { validateSequential } from './validators/sequential'\nexport { validateKeyboardPattern } from './validators/keyboard-pattern'\nexport { validateCommonPassword } from './validators/common-password'\nexport { validatePersonalInfo } from './validators/personal-info'\n\nimport type {\n ValidationResult,\n ValidatorOptions,\n StrengthScore,\n StrengthLabel,\n ValidatorCheck,\n CheckId,\n} from './types'\nimport { validateLength } from './validators/length'\nimport { validateCharacterTypes } from './validators/character-types'\nimport { validateRepetition } from './validators/repetition'\nimport { validateSequential } from './validators/sequential'\nimport { validateKeyboardPattern } from './validators/keyboard-pattern'\nimport { validateCommonPassword } from './validators/common-password'\nimport { validatePersonalInfo } from './validators/personal-info'\n\nconst STRENGTH_LABELS: readonly StrengthLabel[] = [\n 'very-weak',\n 'weak',\n 'medium',\n 'strong',\n 'very-strong',\n] as const\n\n/**\n * Validate a password with comprehensive security checks\n *\n * Performs multiple validation checks including length, character types, repetition,\n * sequential patterns, keyboard patterns, common passwords, and personal information.\n * Returns detailed feedback with strength score and actionable suggestions.\n *\n * @param password - The password string to validate\n * @param options - Optional validation configuration\n * @returns Validation result with score, strength label, feedback, and individual check results\n *\n * @example\n * **Basic usage (zero-config)**\n * ```typescript\n * import { validatePassword } from '@sentinel-password/core'\n *\n * const result = validatePassword('MySecure!Pass_w0rd')\n * console.log(result.valid) // true\n * console.log(result.score) // 4 (0-4 scale)\n * console.log(result.strength) // 'very-strong'\n * console.log(result.feedback.warning) // undefined (no issues)\n * console.log(result.feedback.suggestions) // []\n * ```\n *\n * @example\n * **With a known-common password**\n * ```typescript\n * const result = validatePassword('password')\n * console.log(result.valid) // false (commonPassword check rejected it)\n * console.log(result.score) // 4\n * console.log(result.strength) // 'very-strong'\n * console.log(result.feedback.warning)\n * // \"Password is too common. Please choose a more unique password.\"\n * console.log(result.feedback.suggestions)\n * // [\"Password is too common. Please choose a more unique password.\"]\n * console.log(result.checks)\n * // { length: true, characterTypes: true, repetition: true, sequential: true,\n * // keyboardPattern: true, commonPassword: false, personalInfo: true }\n * // ↑ 6 of 7 checks pass, so score is 4 (\"very-strong\") even though valid is false.\n * // Always use `valid` (or `result.checks`) for acceptance decisions, not `strength`.\n * ```\n *\n * @example\n * **Custom length requirements**\n * ```typescript\n * const result = validatePassword('MyP@ss', {\n * minLength: 12,\n * maxLength: 64\n * })\n * console.log(result.valid) // false\n * console.log(result.feedback.warning) // \"Password must be at least 12 characters\"\n * ```\n *\n * @example\n * **Require specific character types**\n * ```typescript\n * const result = validatePassword('password123', {\n * requireUppercase: true,\n * requireLowercase: true,\n * requireDigit: true,\n * requireSymbol: true\n * })\n * console.log(result.valid) // false\n * console.log(result.feedback.warning) // \"Password must contain at least one uppercase letter, symbol\"\n * ```\n *\n * @example\n * **Prevent personal information**\n * ```typescript\n * const result = validatePassword('john1234!', {\n * personalInfo: ['john', 'john.doe@example.com', 'Doe']\n * })\n * console.log(result.valid) // false\n * console.log(result.feedback.warning) // \"Password contains personal information\"\n * ```\n *\n * @example\n * **Disable specific checks**\n * ```typescript\n * const result = validatePassword('qwerty123', {\n * checkKeyboardPatterns: false, // Allow keyboard patterns\n * checkSequential: false, // Allow sequential chars\n * checkCommonPasswords: false // Allow common passwords\n * })\n * // More permissive validation\n * ```\n *\n * @example\n * **Comprehensive configuration**\n * ```typescript\n * const result = validatePassword('MyP@ssw0rd2024!', {\n * minLength: 12,\n * maxLength: 128,\n * requireUppercase: true,\n * requireLowercase: true,\n * requireDigit: true,\n * requireSymbol: true,\n * maxRepeatedChars: 2,\n * checkSequential: true,\n * checkKeyboardPatterns: true,\n * checkCommonPasswords: true,\n * personalInfo: ['user', 'admin', 'test']\n * })\n * ```\n *\n * @remarks\n * **Default behavior:**\n * - Minimum length: 8 characters\n * - Maximum length: 128 characters\n * - No character type requirements (but recommended to enable)\n * - Max repeated characters: 3\n * - Sequential check: enabled\n * - Keyboard pattern check: enabled\n * - Common password check: enabled (top 1,000 passwords)\n * - Personal info check: disabled (provide personalInfo array to enable)\n *\n * **Scoring:**\n * - `score` = `Math.min(4, Math.floor((passedChecks / 7) * 5))` — purely a\n * passed-check ratio.\n * - `strength` is the human label for that score (`very-weak` … `very-strong`).\n * - Because scoring is ratio-based, a password that fails *only* the\n * common-password (or personal-info, or sequential, etc.) check still passes\n * 6 of 7 checks and lands on `score: 4 / strength: 'very-strong'` while\n * `valid` is `false`. Use `valid` (or inspect `result.checks`) for\n * acceptance decisions; use `strength` for UX cues like progress bars.\n *\n * **Performance:**\n * - All validators run in O(n) time or better\n * - Typical validation: < 1ms for passwords up to 128 characters\n * - Bloom filter for common passwords: O(1) lookup\n *\n * **Security:**\n * - All checks are case-insensitive where applicable\n * - No password data is logged or stored\n * - Runs purely in-process — no network calls, the password never leaves the\n * caller's runtime\n * - This is a *strength* validator, not a password-comparison primitive. The\n * validators use early-return `includes()`/loops and are not constant-time,\n * but timing is not a relevant attack surface here: the patterns being\n * checked (length, character types, common-password list, keyboard layouts)\n * are all public — there's no secret to leak via timing. When you compare\n * a password against a stored hash, use a library like Argon2/bcrypt that\n * provides constant-time verification — that's a separate concern from\n * strength validation.\n */\n/** Frozen empty array shared across success-path callers to avoid the\n * per-call `suggestions: []` allocation when no validator fails. */\nconst EMPTY_SUGGESTIONS: readonly string[] = Object.freeze([])\n\n/** Total number of validators run by `validatePassword`. Compile-time constant. */\nconst TOTAL_CHECKS: number = 7\n\nexport function validatePassword(\n password: string,\n options: ValidatorOptions = {}\n): ValidationResult {\n // Run all 7 validators. Captured into individual locals (not an intermediate\n // record) so we can read `.passed` and `.message` once each without going\n // through Object.values/Object.keys iterations.\n const lengthResult: ValidatorCheck = validateLength(password, options)\n const charTypesResult: ValidatorCheck = validateCharacterTypes(password, options)\n const repetitionResult: ValidatorCheck = validateRepetition(password, options)\n const sequentialResult: ValidatorCheck = validateSequential(password, options)\n const commonPasswordResult: ValidatorCheck = validateCommonPassword(password, options)\n const personalInfoResult: ValidatorCheck = validatePersonalInfo(password, options)\n const keyboardPatternResult: ValidatorCheck = validateKeyboardPattern(password, options)\n\n // Build `checks` and `passedChecks` in a single pass. Lazy-allocate\n // `suggestions` only when a validator actually fails — most calls in\n // practice produce all-passing results and pay no allocation cost.\n const checks: Record<CheckId, boolean> = {\n length: lengthResult.passed,\n characterTypes: charTypesResult.passed,\n repetition: repetitionResult.passed,\n sequential: sequentialResult.passed,\n keyboardPattern: keyboardPatternResult.passed,\n commonPassword: commonPasswordResult.passed,\n personalInfo: personalInfoResult.passed,\n }\n\n let passedChecks: number = 0\n let suggestions: string[] | undefined\n let firstSuggestion: string | undefined\n\n /**\n * Accumulate a validator's pass/fail outcome. Increments `passedChecks` on\n * success; otherwise lazy-allocates `suggestions` (so the success-path call\n * pays no array allocation) and records the first message for `feedback.warning`.\n */\n const record = (result: ValidatorCheck): void => {\n if (result.passed) {\n passedChecks++\n return\n }\n /* v8 ignore next */\n if (result.message === undefined) return\n if (suggestions === undefined) {\n suggestions = [result.message]\n firstSuggestion = result.message\n } else {\n suggestions.push(result.message)\n }\n }\n\n record(lengthResult)\n record(charTypesResult)\n record(repetitionResult)\n record(sequentialResult)\n record(commonPasswordResult)\n record(personalInfoResult)\n record(keyboardPatternResult)\n\n const score: StrengthScore = Math.min(\n 4,\n Math.floor((passedChecks / TOTAL_CHECKS) * 5)\n ) as StrengthScore\n\n return {\n valid: passedChecks === TOTAL_CHECKS,\n score,\n /* v8 ignore next */\n strength: STRENGTH_LABELS[score] ?? 'very-weak',\n feedback: {\n ...(firstSuggestion !== undefined && { warning: firstSuggestion }),\n suggestions: suggestions ?? EMPTY_SUGGESTIONS,\n },\n checks,\n }\n}\n","import type { MessageCode, MessageParams, ValidatorOptions } from './types'\n\n/**\n * Built-in English templates for every {@link MessageCode}.\n *\n * The default rendering of a failed `ValidatorCheck.message` comes from this\n * map. Strings are stable across patch and minor releases — consumers can\n * still use them as translation keys with the legacy lookup-table pattern.\n * Prefer the `messages` / `formatMessage` options on {@link ValidatorOptions}.\n *\n * Placeholders use `{name}` syntax and are substituted by {@link formatTemplate}.\n */\nexport const DEFAULT_TEMPLATES: Readonly<Record<MessageCode, string>> = {\n 'length.tooShort': 'Password must be at least {minLength} characters',\n 'length.tooLong': 'Password must be at most {maxLength} characters',\n 'characterTypes.missing': 'Password must contain at least one {missing}',\n 'repetition.tooMany': 'Password contains too many repeated characters (max {maxRepeatedChars})',\n 'sequential.found': 'Password contains sequential characters (e.g., abc, 123)',\n 'keyboardPattern.found': 'Password contains common keyboard patterns',\n 'commonPassword.found': 'Password is too common. Please choose a more unique password.',\n 'personalInfo.found': 'Password contains personal information',\n} as const\n\nconst PLACEHOLDER_PATTERN: RegExp = /\\{(\\w+)\\}/g\n\n/**\n * Substitute `{name}` placeholders in `template` with values from `params`.\n *\n * Unknown placeholders are left intact (visible in the output) so missing\n * data surfaces as a noticeable bug rather than a silent omission. Values\n * are coerced to strings via the `String` constructor.\n *\n * @example\n * formatTemplate('Min {n} chars', { n: 8 }) // → \"Min 8 chars\"\n * formatTemplate('Need {a} and {b}', { a: 'X' }) // → \"Need X and {b}\"\n */\nexport function formatTemplate(template: string, params: MessageParams): string {\n return template.replace(PLACEHOLDER_PATTERN, (match, key: string): string => {\n const value: string | number | undefined = params[key]\n return value === undefined ? match : String(value)\n })\n}\n\n/**\n * Render a message via the fallback chain:\n * 1. `options.formatMessage(code, params, defaultMessage)` if provided\n * 2. `formatTemplate(options.messages[code], params)` if that override is provided\n * 3. `formatTemplate(DEFAULT_TEMPLATES[code], params)` (built-in English)\n *\n * Used by every validator's failure branch. Validators stay declarative —\n * they describe *what* failed (`code` + `params`) and delegate rendering.\n *\n * If `options.formatMessage` throws, this function swallows the error and\n * returns the default English rendering instead. Validators in this library\n * promise never to throw (see `Validator` in `./types`), and that promise\n * holds even when consumer-provided formatters misbehave.\n */\nexport function resolveMessage(\n code: MessageCode,\n params: MessageParams,\n options: ValidatorOptions\n): string {\n const defaultMessage: string = formatTemplate(DEFAULT_TEMPLATES[code], params)\n\n if (options.formatMessage) {\n try {\n return options.formatMessage(code, params, defaultMessage)\n } catch {\n return defaultMessage\n }\n }\n\n const override: string | undefined = options.messages?.[code]\n if (override !== undefined) {\n return formatTemplate(override, params)\n }\n\n return defaultMessage\n}\n","import type { MessageParams, Validator } from '../types'\nimport { resolveMessage } from '../messages'\n\n/**\n * Validates password length against minimum and maximum requirements\n *\n * @param password - Password to validate\n * @param options - Validation options containing minLength and maxLength\n * @returns Validator check result with passed status and optional error message\n *\n * @example\n * ```typescript\n * import { validateLength } from '@sentinel-password/core'\n *\n * // Default: min 8, max 128 characters\n * validateLength('short') // { passed: false, message: \"Password must be at least 8 characters\" }\n * validateLength('longenough') // { passed: true }\n *\n * // Custom length requirements\n * validateLength('password', { minLength: 12 }) // { passed: false, message: \"...\" }\n * validateLength('verylongpassword', { minLength: 12, maxLength: 20 }) // { passed: true }\n * ```\n *\n * @remarks\n * Default minimum length is 8 characters (OWASP recommendation).\n * Default maximum length is 128 characters (prevents DoS attacks).\n */\nexport const validateLength: Validator = (password, options = {}) => {\n const { minLength = 8, maxLength = 128 }: Partial<{ minLength: number; maxLength: number }> =\n options\n\n const length: number = password.length\n\n if (length < minLength) {\n const params: MessageParams = { minLength }\n return {\n passed: false,\n code: 'length.tooShort',\n params,\n message: resolveMessage('length.tooShort', params, options),\n }\n }\n\n if (length > maxLength) {\n const params: MessageParams = { maxLength }\n return {\n passed: false,\n code: 'length.tooLong',\n params,\n message: resolveMessage('length.tooLong', params, options),\n }\n }\n\n return {\n passed: true,\n }\n}\n","import type { MessageParams, Validator } from '../types'\nimport { resolveMessage } from '../messages'\n\n/**\n * Check if password contains at least one uppercase letter (A-Z)\n *\n * @param password - Password to check\n * @returns True if password contains at least one uppercase letter\n *\n * @example\n * ```typescript\n * hasUppercase('password') // false\n * hasUppercase('Password') // true\n * hasUppercase('PASSWORD') // true\n * ```\n */\nexport const hasUppercase = (password: string): boolean => /[A-Z]/.test(password)\n\n/**\n * Check if password contains at least one lowercase letter (a-z)\n *\n * @param password - Password to check\n * @returns True if password contains at least one lowercase letter\n *\n * @example\n * ```typescript\n * hasLowercase('PASSWORD') // false\n * hasLowercase('Password') // true\n * hasLowercase('password') // true\n * ```\n */\nexport const hasLowercase = (password: string): boolean => /[a-z]/.test(password)\n\n/**\n * Check if password contains at least one digit (0-9)\n *\n * @param password - Password to check\n * @returns True if password contains at least one digit\n *\n * @example\n * ```typescript\n * hasDigit('password') // false\n * hasDigit('password1') // true\n * hasDigit('123') // true\n * ```\n */\nexport const hasDigit = (password: string): boolean => /\\d/.test(password)\n\n/**\n * Check if password contains at least one symbol/special character\n *\n * @param password - Password to check\n * @returns True if password contains at least one special character\n *\n * @example\n * ```typescript\n * hasSymbol('password') // false\n * hasSymbol('password!') // true\n * hasSymbol('p@ssw0rd') // true\n * ```\n *\n * @remarks\n * Accepted symbols: ! @ # $ % ^ & * ( ) _ + - = [ ] { } ; ' : \" \\ | , . < > / ?\n */\nexport const hasSymbol = (password: string): boolean =>\n /[!@#$%^&*()_+\\-=[\\]{};':\"\\\\|,.<>/?]/.test(password)\n\n/**\n * Char codes for the symbol set above, packed into a Set for O(1) lookup during\n * the single-pass scan in {@link validateCharacterTypes}. Built once at module\n * load. Must stay in sync with the regex character class in {@link hasSymbol}.\n *\n * Backtick (0x60) is intentionally NOT in this set — it isn't in `hasSymbol`'s\n * regex either.\n */\nconst SYMBOL_CODES: ReadonlySet<number> = new Set<number>([\n 33, // !\n 34, // \"\n 35, // #\n 36, // $\n 37, // %\n 38, // &\n 39, // '\n 40, // (\n 41, // )\n 42, // *\n 43, // +\n 44, // ,\n 45, // -\n 46, // .\n 47, // /\n 58, // :\n 59, // ;\n 60, // <\n 61, // =\n 62, // >\n 63, // ?\n 64, // @\n 91, // [\n 92, // \\\n 93, // ]\n 94, // ^\n 95, // _\n 123, // {\n 124, // |\n 125, // }\n])\n\n/**\n * Validates character type requirements (uppercase, lowercase, digits, symbols)\n *\n * @param password - Password to validate\n * @param options - Validation options containing character type requirements\n * @returns Validator check result with passed status and optional error message\n *\n * @example\n * ```typescript\n * import { validateCharacterTypes } from '@sentinel-password/core'\n *\n * // No requirements by default\n * validateCharacterTypes('password') // { passed: true }\n *\n * // Require uppercase\n * validateCharacterTypes('password', { requireUppercase: true })\n * // { passed: false, message: \"Password must contain at least one uppercase letter\" }\n *\n * validateCharacterTypes('Password', { requireUppercase: true }) // { passed: true }\n *\n * // Require multiple types\n * validateCharacterTypes('password', {\n * requireUppercase: true,\n * requireDigit: true,\n * requireSymbol: true\n * })\n * // { passed: false, message: \"Password must contain at least one uppercase letter, digit, symbol\" }\n *\n * validateCharacterTypes('Password1!', {\n * requireUppercase: true,\n * requireDigit: true,\n * requireSymbol: true\n * }) // { passed: true }\n * ```\n *\n * @remarks\n * By default, no character types are required. Enable specific requirements via options.\n *\n * Implementation note: scans the password ONCE with `charCodeAt`, classifying\n * each char into uppercase / lowercase / digit / symbol, with early exit as\n * soon as every required class is satisfied. Faster than the previous\n * implementation that ran up to 4 separate `RegExp.test()` scans. The\n * individual `hasUppercase` / `hasLowercase` / `hasDigit` / `hasSymbol`\n * helpers above stay regex-based — they're independently exported and the\n * regex form is clearer for one-off calls.\n */\nexport const validateCharacterTypes: Validator = (password, options = {}) => {\n const {\n requireUppercase = false,\n requireLowercase = false,\n requireDigit = false,\n requireSymbol = false,\n }: Partial<{\n requireUppercase: boolean\n requireLowercase: boolean\n requireDigit: boolean\n requireSymbol: boolean\n }> = options\n\n // No requirements → no scan, no allocations.\n if (!requireUppercase && !requireLowercase && !requireDigit && !requireSymbol) {\n return { passed: true }\n }\n\n let foundUpper: boolean = !requireUppercase\n let foundLower: boolean = !requireLowercase\n let foundDigit: boolean = !requireDigit\n let foundSymbol: boolean = !requireSymbol\n\n for (let i: number = 0; i < password.length; i++) {\n if (foundUpper && foundLower && foundDigit && foundSymbol) break\n const c: number = password.charCodeAt(i)\n if (!foundUpper && c >= 65 && c <= 90) {\n foundUpper = true\n } else if (!foundLower && c >= 97 && c <= 122) {\n foundLower = true\n } else if (!foundDigit && c >= 48 && c <= 57) {\n foundDigit = true\n } else if (!foundSymbol && SYMBOL_CODES.has(c)) {\n foundSymbol = true\n }\n }\n\n if (foundUpper && foundLower && foundDigit && foundSymbol) {\n return { passed: true }\n }\n\n // Failure path: lazy-build the missing-types lists. Order MUST match the\n // pre-existing implementation: uppercase, lowercase, digit, symbol.\n const missing: string[] = []\n const missingTypes: string[] = []\n if (requireUppercase && !foundUpper) {\n missing.push('uppercase letter')\n missingTypes.push('uppercase')\n }\n if (requireLowercase && !foundLower) {\n missing.push('lowercase letter')\n missingTypes.push('lowercase')\n }\n if (requireDigit && !foundDigit) {\n missing.push('digit')\n missingTypes.push('digit')\n }\n if (requireSymbol && !foundSymbol) {\n missing.push('symbol')\n missingTypes.push('symbol')\n }\n\n const params: MessageParams = {\n missing: missing.join(', '),\n missingTypes: missingTypes.join(','),\n }\n return {\n passed: false,\n code: 'characterTypes.missing',\n params,\n message: resolveMessage('characterTypes.missing', params, options),\n }\n}\n","import type { MessageParams, Validator } from '../types'\nimport { resolveMessage } from '../messages'\n\n/**\n * Validates that password doesn't contain excessive repeated characters\n *\n * Uses a single-pass algorithm to detect consecutive repeated characters.\n * Helps prevent weak passwords like \"aaaa1111\" or \"passwordddd\".\n *\n * @param password - Password to validate\n * @param options - Validation options containing maxRepeatedChars\n * @returns Validator check result with passed status and optional error message\n *\n * @example\n * ```typescript\n * import { validateRepetition } from '@sentinel-password/core'\n *\n * // Default: max 3 repeated characters\n * validateRepetition('password') // { passed: true }\n * validateRepetition('passsword') // { passed: true } (3 s's)\n * validateRepetition('passssword') // { passed: false } (4 s's)\n *\n * // Custom limit\n * validateRepetition('passssword', { maxRepeatedChars: 5 }) // { passed: true }\n * validateRepetition('aaa') // { passed: true }\n * validateRepetition('aaaa') // { passed: false, message: \"Password contains too many repeated characters (max 3)\" }\n * ```\n *\n * @remarks\n * Default maximum is 3 consecutive repeated characters.\n * Only checks for consecutive repetition, not overall character frequency.\n */\nexport const validateRepetition: Validator = (password, options = {}) => {\n const { maxRepeatedChars = 3 }: Partial<{ maxRepeatedChars: number }> = options\n\n if (password.length === 0) {\n return { passed: true }\n }\n\n let currentChar: string = password.charAt(0)\n let count: number = 1\n\n for (let i: number = 1; i < password.length; i++) {\n const char: string = password.charAt(i)\n if (char === currentChar) {\n count++\n if (count > maxRepeatedChars) {\n const params: MessageParams = { maxRepeatedChars }\n return {\n passed: false,\n code: 'repetition.tooMany',\n params,\n message: resolveMessage('repetition.tooMany', params, options),\n }\n }\n } else {\n currentChar = char\n count = 1\n }\n }\n\n return {\n passed: true,\n }\n}\n","import type { MessageParams, Validator } from '../types'\nimport { resolveMessage } from '../messages'\n\n/**\n * Detects sequential character patterns (ascending or descending)\n * Checks for sequences of 3 or more consecutive characters\n *\n * @param str - String to check for sequences\n * @returns true if sequential pattern found, false otherwise\n */\nconst hasSequentialPattern = (str: string): boolean => {\n const minSequenceLength: number = 3\n\n for (let i: number = 0; i <= str.length - minSequenceLength; i++) {\n const charCode1: number = str.charCodeAt(i)\n const charCode2: number = str.charCodeAt(i + 1)\n const charCode3: number = str.charCodeAt(i + 2)\n\n // Check ascending sequence (e.g., abc, 123)\n if (charCode2 === charCode1 + 1 && charCode3 === charCode2 + 1) {\n return true\n }\n\n // Check descending sequence (e.g., cba, 321)\n if (charCode2 === charCode1 - 1 && charCode3 === charCode2 - 1) {\n return true\n }\n }\n\n return false\n}\n\n/**\n * Validates that password doesn't contain sequential character patterns\n *\n * Detects sequences like: abc, ABC, 123, 321, xyz, etc.\n * Uses character code comparison for efficient detection.\n * Helps prevent predictable passwords with keyboard sequences.\n *\n * @param password - Password to validate\n * @param options - Validation options containing checkSequential flag\n * @returns Validator check result with passed status and optional error message\n *\n * @example\n * ```typescript\n * import { validateSequential } from '@sentinel-password/core'\n *\n * // Detects ascending sequences\n * validateSequential('password') // { passed: true }\n * validateSequential('abc123') // { passed: false } (contains \"abc\" and \"123\")\n * validateSequential('xyz') // { passed: false } (contains \"xyz\")\n *\n * // Detects descending sequences\n * validateSequential('cba321') // { passed: false } (contains \"cba\" and \"321\")\n *\n * // Disable check\n * validateSequential('abc123', { checkSequential: false }) // { passed: true }\n * ```\n *\n * @remarks\n * Enabled by default. Checks for 3 or more consecutive characters in sequence.\n * Case-sensitive: detects both \"abc\" and \"ABC\" as separate patterns.\n *\n * **Overlap with `validateKeyboardPattern`:** The numeric runs `123`, `456`,\n * `789` (and their reverses) are also matched by the keyboard-pattern\n * validator's numeric-keypad list. To allow simple numeric runs in a\n * password you must set BOTH `checkSequential: false` AND\n * `checkKeyboardPatterns: false` — disabling either alone will not let\n * `password123` through. The two checks are deliberately independent\n * defences (code-point runs vs. keyboard-locality runs).\n */\nexport const validateSequential: Validator = (password, options = {}) => {\n const { checkSequential = true }: Partial<{ checkSequential: boolean }> = options\n\n if (!checkSequential) {\n return { passed: true }\n }\n\n if (hasSequentialPattern(password)) {\n const params: MessageParams = {}\n return {\n passed: false,\n code: 'sequential.found',\n params,\n message: resolveMessage('sequential.found', params, options),\n }\n }\n\n return {\n passed: true,\n }\n}\n","import type { MessageParams, ValidatorCheck, ValidatorOptions } from '../types'\nimport { resolveMessage } from '../messages'\n\n/**\n * Common keyboard patterns to detect across multiple layouts\n * Supports: QWERTY, AZERTY, QWERTZ, Dvorak, Colemak, and Cyrillic\n */\nconst KEYBOARD_PATTERNS: readonly string[] = [\n // === QWERTY (English, US, UK) ===\n // Full rows\n 'qwertyuiop',\n 'asdfghjkl',\n 'zxcvbnm',\n // Common typing patterns (adjacent keys)\n 'qwert',\n 'werty',\n 'asdfg',\n 'sdfgh',\n 'zxcvb',\n 'xcvbn',\n // Short sequences (3+ chars)\n 'qwe',\n 'asd',\n 'zxc',\n 'rty',\n 'fgh',\n 'cvb',\n 'poi',\n 'lkj',\n 'mnb',\n // Columns (top to bottom)\n '1qaz',\n '2wsx',\n '3edc',\n '4rfv',\n '5tgb',\n '6yhn',\n '7ujm',\n '8ik',\n '9ol',\n '0p',\n // Diagonals\n 'qaz',\n 'wsx',\n 'edc',\n 'zaq',\n 'xsw',\n 'cde',\n\n // === AZERTY (French, Belgian) ===\n // Full rows\n 'azertyuiop',\n 'qsdfghjklm',\n 'wxcvbn',\n // Common patterns\n 'azert',\n 'zerty',\n 'qsdfg',\n 'sdfgh',\n 'wxcvb',\n // Short sequences\n 'aze',\n 'qsd',\n 'wxc',\n\n // === QWERTZ (German, Central European) ===\n // Full rows\n 'qwertzuiop',\n 'yxcvbnm',\n // Common patterns\n 'qwertz',\n 'yxcvb',\n // Short sequences\n 'yxc',\n // Note: Patterns 'qwe', 'asd', 'asdfg', and 'asdfghjkl' are shared with QWERTY and listed above for efficiency.\n\n // === Dvorak ===\n // Full rows\n 'pyfgcrl',\n 'aoeuidhtns',\n 'qjkxbmwvz',\n // Common patterns\n 'aoeu',\n 'htns',\n 'qjkx',\n\n // === Colemak ===\n // Full rows\n 'qwfpgjluy',\n 'arstdhneio',\n 'zxcvbkm',\n // Common patterns\n 'arst',\n 'dhne',\n 'zxcv',\n\n // === Cyrillic (ЙЦУКЕН - Russian) ===\n // Full rows (Cyrillic characters)\n 'йцукенгшщзхъ',\n 'фывапролджэ',\n 'ячсмитьбю',\n // Common patterns\n 'йцукен',\n 'фывап',\n 'ячсми',\n 'цукен',\n\n // === Numeric patterns (universal) ===\n // Number row\n '1234567890',\n '0987654321',\n // Numeric keypad (rows)\n '789',\n '456',\n '123',\n // Numeric keypad (columns)\n '741',\n '852',\n '963',\n '7410',\n '8520',\n '9630',\n] as const\n\n/**\n * Escape regex metacharacters in a string so it matches literally.\n * Defensive: none of the current patterns contain metacharacters, but this\n * keeps future additions safe (e.g., if a layout's pattern contains `+` or `$`).\n */\nfunction escapeRegex(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\n/**\n * Single regex matching ANY keyboard pattern in either forward or reverse\n * direction, case-insensitively. Built once at module load.\n *\n * Performance note: this replaced an earlier implementation that ran a\n * `for` loop over `KEYBOARD_PATTERNS`, calling `pattern.split('').reverse().join('')`\n * to build the reverse string AND `lowercase.includes(pattern)` on every iteration\n * (~480 allocations per call). A single regex test against an NFA-compiled\n * alternation is ~12× faster on typical passwords on V8/Node 22.\n *\n * If a future V8 regex bug surfaces (e.g., a Unicode case-folding regression\n * affecting Cyrillic with the `/i` flag), there's a side-by-side comparison\n * against a precomputed-array loop variant in\n * `packages/core/tests/performance.bench.ts` → `Keyboard pattern: implementation comparison`.\n * The loop variant is ~2.5× faster than the original split/reverse/join code\n * and ~5× slower than the regex, so it's a usable rollback target.\n */\nconst KEYBOARD_REGEX: RegExp = new RegExp(\n KEYBOARD_PATTERNS.flatMap((p: string): readonly string[] => {\n const reversed: string = p.split('').reverse().join('')\n return [escapeRegex(p), escapeRegex(reversed)]\n }).join('|'),\n 'i'\n)\n\n/**\n * Validates that password does not contain common keyboard patterns\n *\n * Detects patterns across multiple keyboard layouts:\n * - QWERTY (English, US, UK): qwerty, asdfgh, 1qaz2wsx\n * - AZERTY (French, Belgian): azerty, qsdfg\n * - QWERTZ (German, Central European): qwertz, yxcvb\n * - Dvorak (Alternative English): aoeu, htns\n * - Colemak (Alternative English): arst, dhne\n * - Cyrillic (Russian ЙЦУКЕН): йцукен, фывап\n * - Universal numeric: 123, 789, numeric keypad patterns\n * - Both forward and reverse patterns\n *\n * @param password - The password to validate\n * @param options - Validation options\n * @returns Validation result\n *\n * @example\n * ```typescript\n * validateKeyboardPattern('qwerty123') // { passed: false, message: '...' }\n * validateKeyboardPattern('azerty456') // { passed: false, message: '...' } (AZERTY)\n * validateKeyboardPattern('йцукен') // { passed: false, message: '...' } (Cyrillic)\n * validateKeyboardPattern('MyP@ssw0rd') // { passed: true }\n * validateKeyboardPattern('asdfgh', { checkKeyboardPatterns: false }) // { passed: true }\n * ```\n *\n * @remarks\n * **Overlap with `validateSequential`:** The numeric-keypad rows `123`,\n * `456`, `789` (and their reverses) are also caught by the sequential\n * validator's `charCodeAt`-consecutive check. To allow simple numeric\n * runs in a password you must set BOTH `checkKeyboardPatterns: false`\n * AND `checkSequential: false` — disabling either alone will not let\n * `password123` through. The two checks are deliberately independent\n * defences (keyboard-locality runs vs. code-point runs).\n */\nexport function validateKeyboardPattern(\n password: string,\n options: ValidatorOptions = {}\n): ValidatorCheck {\n const { checkKeyboardPatterns = true }: Partial<{ checkKeyboardPatterns: boolean }> = options\n\n if (!checkKeyboardPatterns) {\n return { passed: true }\n }\n\n if (KEYBOARD_REGEX.test(password)) {\n const emptyParams: MessageParams = {}\n return {\n passed: false,\n code: 'keyboardPattern.found',\n params: emptyParams,\n message: resolveMessage('keyboardPattern.found', emptyParams, options),\n }\n }\n\n return { passed: true }\n}\n","import type { MessageParams, Validator, ValidatorOptions } from '../types'\nimport { resolveMessage } from '../messages'\n\n/**\n * Bloom filter for common passwords\n *\n * Source: SecLists https://github.com/danielmiessler/SecLists\n * File: Passwords/Common-Credentials/10k-most-common.txt (top 1,000)\n * Local: packages/core/data/common-passwords.txt\n *\n * Regenerate with: pnpm --filter @sentinel-password/core generate:bloom\n */\n\n// --- BEGIN GENERATED BLOOM FILTER ---\n// Generated from: packages/core/data/common-passwords.txt\n// Passwords: 1000 | Bloom size: 12000 bits | Hash functions: 7\nconst BLOOM_SIZE: number = 12000\nconst BLOOM_HASH_COUNT: number = 7\n\nconst BLOOM_BUCKETS: Int32Array = new Int32Array([\n -1274142440, -1983615455, -2110842727, -1440077142, 1782964852, 956870738, 48445478, 290080800,\n -1961772502, -1869995330, 787111091, 140549329, -1508237141, -800970204, -1987272632, -1439602637,\n 1837731885, 240419586, 2143496680, 547359246, -435494235, -1549227926, 151650466, -1742731903,\n -1535043550, -1607849886, -464376123, 1858481975, -1339118942, 410034491, -1397081975, 1217017604,\n -1934873593, -1769402149, -1107107031, -1098029453, 4380674, -393017311, -1465113568, 183557329,\n 1713935653, -99073116, 179529869, -1372675933, 78050105, 313074313, -1696462848, 404392594,\n -252409893, -1320442777, -1474295681, -1360120173, -743767380, 135925863, -1462755274, 562596610,\n -483730736, -2002210214, 1803176, 448811114, -1408849403, -735411494, -1872045334, 914538658,\n -374794234, 710617115, -1909840320, -2119695701, -1979682099, -1671944535, -1872492857, 570434164,\n -1334628885, 438020137, -1087528694, 464528967, -359521705, -1407023569, -1759335432, -1181701078,\n -1959746004, -1910872796, 1771191076, -1699312926, -1837137785, 132360706, -1002778102,\n 1384163571, 1786784659, -1354832568, -1364547569, -269830616, -1970133308, 848887818, -827577818,\n -1103500629, 2071996096, 1760287282, 1768617098, -1845466197, -1877054343, -223851966,\n -1037686738, 581441802, -761237365, -1282923217, 1328736770, 557363947, 369254184, -1708971466,\n -874268952, -2010768709, -1735196024, -1023761881, 772024867, -328714721, -125771638, 1644593802,\n 1762927155, -1509621238, 671919842, -1102294492, -2010455282, 302591302, -2136446493, -1986908626,\n -2018566016, 671347107, 842040354, 243352234, -534627656, -2011035123, -350834429, -844096647,\n -1598189902, -1833872134, -2021613470, -1968994020, -1297357224, 1210222837, -399712104,\n 906129314, -1430115578, -2113618941, -906361552, 551676011, 2819256, -1710063476, 1099626796,\n 1026172967, 640491579, -1440579096, -1887270648, -1710185214, 1721813836, 984101107, 1944258615,\n 724137993, -947645533, 1913070606, 44829860, 101219306, 1011556358, -1432173338, -482828238,\n -2103243204, 447353024, -329610536, 1398671502, 1623196423, 348309551, -211221782, 1980906030,\n 778830798, 968125066, -1458011638, -1337934774, 671089894, 52600876, -120418625, -1163255178,\n 680202936, -1597565152, 36835260, 1845733400, 153924331, 40509970, 411221088, 2114095983,\n 171043813, 1488604610, 172814850, -1702382524, 1502872105, 747635458, -501052624, 411301928,\n 731823798, -1333221971, -398552509, 134621864, -1872819037, 1822818465, -1893684382, -1316869470,\n -931632595, -1951260093, 598498019, -1290239178, -989323246, 573220082, 675809232, 169874120,\n -559880256, -910750974, 194548323, 736774690, -719202768, 1814594698, -1155290520, 237063072,\n -488083322, -924144083, 36385696, 715956226, -1568572256, 635709488, 1336062594, 320907784,\n 1124501765, -1406433280, -1963307926, -391509877, 1063369670, -1566479450, -1472058776,\n -1985331186, 1377869964, 942987938, 1109713448, 831578699, 853962256, 92835849, 2035317046,\n -2001761759, 216178811, 463827086, 1675819625, -1301764214, -234217277, -1459402461, 579365994,\n -1878875352, -1973245719, -1598478333, 1611084451, 1659513348, -1539122450, 837944629, 154749747,\n -500636954, 1746899746, 714271018, -1150679461, -1538612630, 1814069472, -492197206, -192202641,\n -756936190, -939252957, -1240063770, 1279410916, -1302054220, 1410991882, 1000401066, -198048178,\n -1419228998, -1742550398, -1991360862, -2111688732, -1700640206, 2061912618, -2103274201,\n -288731098, 578232, 53128594, 1755908256, 1116379816, 797411910, -1589060869, -1072342375,\n 845812237, 1749465810, 872549054, -1498077050, -1766137746, -376831803, -2084952921, 1202203194,\n 1216389770, -1104576382, 818061341, 942317755, -1578565146, 2083914250, -1012266815, 579371193,\n -461075653, 710981226, 1578192039, 681770739, -376523066, 1244735807, -490842006, -781193759,\n 556277799, 1984167471, -2000977760, -1265456598, 151665664, -1842697570, 579340290, 1082298021,\n 245211512, -1867881849, -1341630964, -90545664, 1806344898, 47088170, 992486842, -1558631407,\n -1449508691, -2006144829, 547374756, 912919200, 708120146, 616106151, 1172449414, 2124751394,\n -2073427929, 1009261122, 1210336770, -1973924734, 144458353, -1155209078, -1962399613, 897229836,\n 12357639, -1299101013, 312943930, 708920290, -1310023134, 639040320, -821838839, -2014506238,\n -274610648, -1442508704, 177432331, 1721041571, 414065321, -1272924654, 756208161, 1772773934,\n -1484380630, 1101004848, -1054523758, -2116504793, 1118667297, 86058657, 975856680, 749906058,\n 1937961650, 620890537, 918765730, -1366546774,\n])\n// --- END GENERATED BLOOM FILTER ---\n\n/**\n * Hash function for bloom filter\n */\nfunction hashString(str: string, seed: number): number {\n let hash: number = seed\n\n for (let i: number = 0; i < str.length; i++) {\n const char: number = str.charCodeAt(i)\n hash = (hash << 5) - hash + char\n hash = hash | 0 // Convert to 32-bit integer\n }\n\n return Math.abs(hash)\n}\n\n/**\n * Get multiple hash positions for a password\n */\nfunction getHashes(password: string): number[] {\n const hashes: number[] = []\n const hash1: number = hashString(password, 0)\n const hash2: number = hashString(password, 1)\n\n for (let i: number = 0; i < BLOOM_HASH_COUNT; i++) {\n const hash: number = (hash1 + i * hash2) >>> 0\n hashes.push(hash % BLOOM_SIZE)\n }\n\n return hashes\n}\n\n/**\n * Check if password might be in the common password list\n * Note: Bloom filters can have false positives (~0.84%) but never false negatives\n */\nfunction mightBeCommon(password: string): boolean {\n const hashes: number[] = getHashes(password.toLowerCase())\n\n for (const hash of hashes) {\n const arrayIndex: number = Math.floor(hash / 32)\n const bitIndex: number = hash % 32\n\n // Bounds check for TypeScript strict mode\n const bucket: number | undefined = BLOOM_BUCKETS[arrayIndex]\n if (bucket === undefined || (bucket & (1 << bitIndex)) === 0) {\n return false\n }\n }\n\n return true\n}\n\n/**\n * Validates that a password is not in the common password list\n *\n * Uses a Bloom filter to efficiently check against the top 1,000 most common passwords.\n * Case-insensitive matching prevents simple case variations of common passwords.\n *\n * @param password - Password to validate\n * @param options - Validation options containing checkCommonPasswords flag\n * @returns Validator check result with passed status and optional error message\n *\n * @example\n * ```typescript\n * import { validateCommonPassword } from '@sentinel-password/core'\n *\n * // Detects common passwords\n * validateCommonPassword('password')\n * // { passed: false, message: \"Password is too common. Please choose a more unique password.\" }\n *\n * validateCommonPassword('123456')\n * // { passed: false }\n *\n * // Case-insensitive\n * validateCommonPassword('PASSWORD')\n * // { passed: false }\n *\n * // Unique passwords pass\n * validateCommonPassword('MyUn1qu3P@ssw0rd!')\n * // { passed: true }\n *\n * // Disable check\n * validateCommonPassword('password', { checkCommonPasswords: false })\n * // { passed: true }\n * ```\n *\n * @remarks\n * Checks against top 1,000 most common passwords from SecLists.\n * Uses Bloom filter for space efficiency (~1.5KB vs ~8KB for raw array).\n * False positive rate: ~0.84% (may rarely flag uncommon passwords).\n * Enabled by default for security.\n */\nexport const validateCommonPassword: Validator = (\n password: string,\n options: ValidatorOptions = {}\n) => {\n const { checkCommonPasswords = true }: { checkCommonPasswords?: boolean } = options\n\n if (!checkCommonPasswords || password.length === 0) {\n return { passed: true }\n }\n\n // Case-insensitive check using bloom filter\n if (mightBeCommon(password)) {\n const params: MessageParams = {}\n return {\n passed: false,\n code: 'commonPassword.found',\n params,\n message: resolveMessage('commonPassword.found', params, options),\n }\n }\n\n return { passed: true }\n}\n","import type { MessageParams, Validator } from '../types'\nimport { resolveMessage } from '../messages'\n\n/**\n * Extracts username from email address\n * @param email - Email address\n * @returns Username part before @ symbol\n */\nconst extractUsername = (email: string): string => {\n const atIndex: number = email.indexOf('@')\n /* v8 ignore next */\n return atIndex > 0 ? email.substring(0, atIndex) : email\n}\n\n/**\n * Normalizes personal info strings for comparison\n * Converts to lowercase and extracts username from emails\n *\n * @param info - Personal information string\n * @returns Normalized string for comparison\n */\nconst normalizePersonalInfo = (info: string): string => {\n const normalized: string = info.toLowerCase().trim()\n // Extract username from email if it looks like an email\n return normalized.includes('@') ? extractUsername(normalized) : normalized\n}\n\n/**\n * Validates that password doesn't contain personal information\n *\n * Checks against provided personal info (username, email, name, etc.)\n * Uses case-insensitive comparison and extracts usernames from email addresses.\n * Ignores very short strings (< 3 characters) to avoid false positives.\n *\n * @param password - Password to validate\n * @param options - Validation options containing personalInfo array\n * @returns Validator check result with passed status and optional error message\n *\n * @example\n * ```typescript\n * import { validatePersonalInfo } from '@sentinel-password/core'\n *\n * // No personal info by default\n * validatePersonalInfo('password') // { passed: true }\n *\n * // Detects username in password\n * validatePersonalInfo('johnpassword', { personalInfo: ['john'] })\n * // { passed: false, message: \"Password contains personal information\" }\n *\n * // Emails are reduced to the *entire local part* (everything before `@`),\n * // matched as a literal substring — not split into name fragments.\n * validatePersonalInfo('john.doe123', { personalInfo: ['john.doe@example.com'] })\n * // { passed: false } (matches the full local part \"john.doe\")\n *\n * validatePersonalInfo('john123', { personalInfo: ['john.doe@example.com'] })\n * // { passed: true } (the local part is \"john.doe\", not \"john\" — no match)\n *\n * // To reject passwords containing just \"john\", pass it explicitly:\n * validatePersonalInfo('john123', { personalInfo: ['john', 'john.doe@example.com'] })\n * // { passed: false } (matches the literal \"john\")\n *\n * // Case-insensitive\n * validatePersonalInfo('JOHN123', { personalInfo: ['john'] })\n * // { passed: false }\n *\n * // Ignores short strings\n * validatePersonalInfo('password', { personalInfo: ['pw'] }) // { passed: true } (too short)\n *\n * // Multiple personal info items\n * validatePersonalInfo('secretpass', {\n * personalInfo: ['john', 'doe', 'john.doe@example.com']\n * }) // { passed: true }\n * ```\n *\n * @remarks\n * Best practice: Provide username, email, first name, last name, and company name.\n * Strings shorter than 3 characters are ignored to prevent false positives.\n */\nexport const validatePersonalInfo: Validator = (password, options = {}) => {\n const { personalInfo = [] }: Partial<{ personalInfo: string[] }> = options\n\n if (personalInfo.length === 0 || password.length === 0) {\n return { passed: true }\n }\n\n const lowerPassword: string = password.toLowerCase()\n\n for (const info of personalInfo) {\n const normalized: string = normalizePersonalInfo(info)\n\n // Skip very short strings to avoid false positives\n if (normalized.length < 3) {\n continue\n }\n\n // Check if password contains this personal information\n if (lowerPassword.includes(normalized)) {\n const params: MessageParams = {}\n return {\n passed: false,\n code: 'personalInfo.found',\n params,\n message: resolveMessage('personalInfo.found', params, options),\n }\n }\n }\n\n return {\n passed: true,\n }\n}\n"],"mappings":"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","names":[]} | ||
| {"version":3,"sources":["../src/index.ts","../src/messages.ts","../src/validators/length.ts","../src/validators/character-types.ts","../src/validators/repetition.ts","../src/validators/sequential.ts","../src/validators/keyboard-pattern.ts","../src/validators/common-password.ts","../src/validators/personal-info.ts"],"sourcesContent":["/**\n * @sentinel-password/core\n * Zero-dependency TypeScript password validation library\n */\n\nexport type {\n StrengthScore,\n StrengthLabel,\n ValidationResult,\n ValidationFailure,\n ValidatorOptions,\n ValidatorCheck,\n Validator,\n CheckId,\n MessageCode,\n MessageParams,\n MessageFormatter,\n} from './types'\n\nexport { DEFAULT_TEMPLATES } from './messages'\n\nexport { validateLength } from './validators/length'\nexport {\n hasUppercase,\n hasLowercase,\n hasDigit,\n hasSymbol,\n validateCharacterTypes,\n} from './validators/character-types'\nexport { validateRepetition } from './validators/repetition'\nexport { validateSequential } from './validators/sequential'\nexport { validateKeyboardPattern } from './validators/keyboard-pattern'\nexport { validateCommonPassword } from './validators/common-password'\nexport { validatePersonalInfo } from './validators/personal-info'\n\nimport type {\n ValidationResult,\n ValidationFailure,\n ValidatorOptions,\n StrengthScore,\n StrengthLabel,\n ValidatorCheck,\n CheckId,\n} from './types'\nimport { validateLength } from './validators/length'\nimport { validateCharacterTypes } from './validators/character-types'\nimport { validateRepetition } from './validators/repetition'\nimport { validateSequential } from './validators/sequential'\nimport { validateKeyboardPattern } from './validators/keyboard-pattern'\nimport { validateCommonPassword } from './validators/common-password'\nimport { validatePersonalInfo } from './validators/personal-info'\n\nconst STRENGTH_LABELS: readonly StrengthLabel[] = [\n 'very-weak',\n 'weak',\n 'medium',\n 'strong',\n 'very-strong',\n] as const\n\n/**\n * Validate a password with comprehensive security checks\n *\n * Performs multiple validation checks including length, character types, repetition,\n * sequential patterns, keyboard patterns, common passwords, and personal information.\n * Returns detailed feedback with strength score and actionable suggestions.\n *\n * @param password - The password string to validate\n * @param options - Optional validation configuration\n * @returns Validation result with score, strength label, feedback, and individual check results\n *\n * @example\n * **Basic usage (zero-config)**\n * ```typescript\n * import { validatePassword } from '@sentinel-password/core'\n *\n * const result = validatePassword('MySecure!Pass_w0rd')\n * console.log(result.valid) // true\n * console.log(result.score) // 4 (0-4 scale)\n * console.log(result.strength) // 'very-strong'\n * console.log(result.feedback.warning) // undefined (no issues)\n * console.log(result.feedback.suggestions) // []\n * ```\n *\n * @example\n * **With a known-common password**\n * ```typescript\n * const result = validatePassword('password')\n * console.log(result.valid) // false (commonPassword check rejected it)\n * console.log(result.score) // 4\n * console.log(result.strength) // 'very-strong'\n * console.log(result.feedback.warning)\n * // \"Password is too common. Please choose a more unique password.\"\n * console.log(result.feedback.suggestions)\n * // [\"Password is too common. Please choose a more unique password.\"]\n * console.log(result.checks)\n * // { length: true, characterTypes: true, repetition: true, sequential: true,\n * // keyboardPattern: true, commonPassword: false, personalInfo: true }\n * // ↑ 6 of 7 checks pass, so score is 4 (\"very-strong\") even though valid is false.\n * // Always use `valid` (or `result.checks`) for acceptance decisions, not `strength`.\n * ```\n *\n * @example\n * **Custom length requirements**\n * ```typescript\n * const result = validatePassword('MyP@ss', {\n * minLength: 12,\n * maxLength: 64\n * })\n * console.log(result.valid) // false\n * console.log(result.feedback.warning) // \"Password must be at least 12 characters\"\n * ```\n *\n * @example\n * **Require specific character types**\n * ```typescript\n * const result = validatePassword('password123', {\n * requireUppercase: true,\n * requireLowercase: true,\n * requireDigit: true,\n * requireSymbol: true\n * })\n * console.log(result.valid) // false\n * console.log(result.feedback.warning) // \"Password must contain at least one uppercase letter, symbol\"\n * ```\n *\n * @example\n * **Prevent personal information**\n * ```typescript\n * const result = validatePassword('john1234!', {\n * personalInfo: ['john', 'john.doe@example.com', 'Doe']\n * })\n * console.log(result.valid) // false\n * console.log(result.feedback.warning) // \"Password contains personal information\"\n * ```\n *\n * @example\n * **Disable specific checks**\n * ```typescript\n * const result = validatePassword('qwerty123', {\n * checkKeyboardPatterns: false, // Allow keyboard patterns\n * checkSequential: false, // Allow sequential chars\n * checkCommonPasswords: false // Allow common passwords\n * })\n * // More permissive validation\n * ```\n *\n * @example\n * **Comprehensive configuration**\n * ```typescript\n * const result = validatePassword('MyP@ssw0rd2024!', {\n * minLength: 12,\n * maxLength: 128,\n * requireUppercase: true,\n * requireLowercase: true,\n * requireDigit: true,\n * requireSymbol: true,\n * maxRepeatedChars: 2,\n * checkSequential: true,\n * checkKeyboardPatterns: true,\n * checkCommonPasswords: true,\n * personalInfo: ['user', 'admin', 'test']\n * })\n * ```\n *\n * @remarks\n * **Default behavior:**\n * - Minimum length: 8 characters\n * - Maximum length: 128 characters\n * - No character type requirements (but recommended to enable)\n * - Max repeated characters: 3\n * - Sequential check: enabled\n * - Keyboard pattern check: enabled\n * - Common password check: enabled (top 1,000 passwords)\n * - Personal info check: disabled (provide personalInfo array to enable)\n *\n * **Scoring:**\n * - `score` = `Math.min(4, Math.floor((passedChecks / 7) * 5))` — purely a\n * passed-check ratio.\n * - `strength` is the human label for that score (`very-weak` … `very-strong`).\n * - Because scoring is ratio-based, a password that fails *only* the\n * common-password (or personal-info, or sequential, etc.) check still passes\n * 6 of 7 checks and lands on `score: 4 / strength: 'very-strong'` while\n * `valid` is `false`. Use `valid` (or inspect `result.checks`) for\n * acceptance decisions; use `strength` for UX cues like progress bars.\n *\n * **Performance:**\n * - All validators run in O(n) time or better\n * - Typical validation: < 1ms for passwords up to 128 characters\n * - Bloom filter for common passwords: O(1) lookup\n *\n * **Security:**\n * - All checks are case-insensitive where applicable\n * - No password data is logged or stored\n * - Runs purely in-process — no network calls, the password never leaves the\n * caller's runtime\n * - This is a *strength* validator, not a password-comparison primitive. The\n * validators use early-return `includes()`/loops and are not constant-time,\n * but timing is not a relevant attack surface here: the patterns being\n * checked (length, character types, common-password list, keyboard layouts)\n * are all public — there's no secret to leak via timing. When you compare\n * a password against a stored hash, use a library like Argon2/bcrypt that\n * provides constant-time verification — that's a separate concern from\n * strength validation.\n */\n/** Frozen empty array shared across success-path callers to avoid the\n * per-call `suggestions: []` allocation when no validator fails. */\nconst EMPTY_SUGGESTIONS: readonly string[] = Object.freeze([])\n\n/** Frozen empty array shared by the all-passing path (no per-call allocation). */\nconst EMPTY_FAILURES: readonly ValidationFailure[] = Object.freeze([])\n\n/** Total number of validators run by `validatePassword`. Compile-time constant. */\nconst TOTAL_CHECKS: number = 7\n\nexport function validatePassword(\n password: string,\n options: ValidatorOptions = {}\n): ValidationResult {\n // Run all 7 validators. Captured into individual locals (not an intermediate\n // record) so we can read `.passed` and `.message` once each without going\n // through Object.values/Object.keys iterations.\n const lengthResult: ValidatorCheck = validateLength(password, options)\n const charTypesResult: ValidatorCheck = validateCharacterTypes(password, options)\n const repetitionResult: ValidatorCheck = validateRepetition(password, options)\n const sequentialResult: ValidatorCheck = validateSequential(password, options)\n const commonPasswordResult: ValidatorCheck = validateCommonPassword(password, options)\n const personalInfoResult: ValidatorCheck = validatePersonalInfo(password, options)\n const keyboardPatternResult: ValidatorCheck = validateKeyboardPattern(password, options)\n\n // Build `checks` and `passedChecks` in a single pass. Lazy-allocate\n // `suggestions` only when a validator actually fails — most calls in\n // practice produce all-passing results and pay no allocation cost.\n const checks: Record<CheckId, boolean> = {\n length: lengthResult.passed,\n characterTypes: charTypesResult.passed,\n repetition: repetitionResult.passed,\n sequential: sequentialResult.passed,\n keyboardPattern: keyboardPatternResult.passed,\n commonPassword: commonPasswordResult.passed,\n personalInfo: personalInfoResult.passed,\n }\n\n let passedChecks: number = 0\n let failures: ValidationFailure[] | undefined\n\n /**\n * Accumulate a validator's pass/fail outcome. Increments `passedChecks` on\n * success; otherwise lazy-allocates `failures` (so the all-passing path pays\n * no array allocation) with the stable code/params. The discriminated\n * `ValidatorCheck` guarantees message/code/params are present once `passed` is\n * `false`, so no presence guard is needed. `feedback` is derived from\n * `failures` below — a single source of truth for warning/suggestions.\n */\n const record = (check: CheckId, result: ValidatorCheck): void => {\n if (result.passed) {\n passedChecks++\n return\n }\n const failure: ValidationFailure = {\n check,\n code: result.code,\n params: result.params,\n message: result.message,\n }\n if (failures === undefined) {\n failures = [failure]\n } else {\n failures.push(failure)\n }\n }\n\n record('length', lengthResult)\n record('characterTypes', charTypesResult)\n record('repetition', repetitionResult)\n record('sequential', sequentialResult)\n record('commonPassword', commonPasswordResult)\n record('personalInfo', personalInfoResult)\n record('keyboardPattern', keyboardPatternResult)\n\n const score: StrengthScore = Math.min(\n 4,\n Math.floor((passedChecks / TOTAL_CHECKS) * 5)\n ) as StrengthScore\n\n const suggestions: readonly string[] = failures\n ? failures.map((failure) => failure.message)\n : EMPTY_SUGGESTIONS\n const firstSuggestion: string | undefined = failures?.[0]?.message\n\n return {\n valid: passedChecks === TOTAL_CHECKS,\n score,\n /* v8 ignore next */\n strength: STRENGTH_LABELS[score] ?? 'very-weak',\n feedback: {\n ...(firstSuggestion !== undefined && { warning: firstSuggestion }),\n suggestions,\n },\n checks,\n failures: failures ?? EMPTY_FAILURES,\n }\n}\n","import type { MessageCode, MessageParams, ValidatorOptions } from './types'\n\n/**\n * Built-in English templates for every {@link MessageCode}.\n *\n * The default rendering of a failed `ValidatorCheck.message` comes from this\n * map. Strings are stable across patch and minor releases — consumers can\n * still use them as translation keys with the legacy lookup-table pattern.\n * Prefer the `messages` / `formatMessage` options on {@link ValidatorOptions}.\n *\n * Placeholders use `{name}` syntax and are substituted by {@link formatTemplate}.\n */\nexport const DEFAULT_TEMPLATES: Readonly<Record<MessageCode, string>> = {\n 'length.tooShort': 'Password must be at least {minLength} characters',\n 'length.tooLong': 'Password must be at most {maxLength} characters',\n 'characterTypes.missing': 'Password must contain at least one {missing}',\n 'repetition.tooMany': 'Password contains too many repeated characters (max {maxRepeatedChars})',\n 'sequential.found': 'Password contains sequential characters (e.g., abc, 123)',\n 'keyboardPattern.found': 'Password contains common keyboard patterns',\n 'commonPassword.found': 'Password is too common. Please choose a more unique password.',\n 'personalInfo.found': 'Password contains personal information',\n} as const\n\nconst PLACEHOLDER_PATTERN: RegExp = /\\{(\\w+)\\}/g\n\n/**\n * Substitute `{name}` placeholders in `template` with values from `params`.\n *\n * Unknown placeholders are left intact (visible in the output) so missing\n * data surfaces as a noticeable bug rather than a silent omission. Values\n * are coerced to strings via the `String` constructor.\n *\n * @example\n * formatTemplate('Min {n} chars', { n: 8 }) // → \"Min 8 chars\"\n * formatTemplate('Need {a} and {b}', { a: 'X' }) // → \"Need X and {b}\"\n */\nexport function formatTemplate(template: string, params: MessageParams): string {\n return template.replace(PLACEHOLDER_PATTERN, (match, key: string): string => {\n const value: string | number | undefined = params[key]\n return value === undefined ? match : String(value)\n })\n}\n\n/**\n * Render a message via the fallback chain:\n * 1. `options.formatMessage(code, params, defaultMessage)` if provided\n * 2. `formatTemplate(options.messages[code], params)` if that override is provided\n * 3. `formatTemplate(DEFAULT_TEMPLATES[code], params)` (built-in English)\n *\n * Used by every validator's failure branch. Validators stay declarative —\n * they describe *what* failed (`code` + `params`) and delegate rendering.\n *\n * If `options.formatMessage` throws, this function swallows the error and\n * returns the default English rendering instead. Validators in this library\n * promise never to throw (see `Validator` in `./types`), and that promise\n * holds even when consumer-provided formatters misbehave.\n */\nexport function resolveMessage(\n code: MessageCode,\n params: MessageParams,\n options: ValidatorOptions\n): string {\n const defaultMessage: string = formatTemplate(DEFAULT_TEMPLATES[code], params)\n\n if (options.formatMessage) {\n try {\n return options.formatMessage(code, params, defaultMessage)\n } catch {\n return defaultMessage\n }\n }\n\n const override: string | undefined = options.messages?.[code]\n if (override !== undefined) {\n return formatTemplate(override, params)\n }\n\n return defaultMessage\n}\n","import type { MessageParams, Validator } from '../types'\nimport { resolveMessage } from '../messages'\n\n/**\n * Validates password length against minimum and maximum requirements\n *\n * @param password - Password to validate\n * @param options - Validation options containing minLength and maxLength\n * @returns Validator check result with passed status and optional error message\n *\n * @example\n * ```typescript\n * import { validateLength } from '@sentinel-password/core'\n *\n * // Default: min 8, max 128 characters\n * validateLength('short') // { passed: false, message: \"Password must be at least 8 characters\" }\n * validateLength('longenough') // { passed: true }\n *\n * // Custom length requirements\n * validateLength('password', { minLength: 12 }) // { passed: false, message: \"...\" }\n * validateLength('verylongpassword', { minLength: 12, maxLength: 20 }) // { passed: true }\n * ```\n *\n * @remarks\n * Default minimum length is 8 characters (OWASP recommendation).\n * Default maximum length is 128 characters (prevents DoS attacks).\n */\nexport const validateLength: Validator = (password, options = {}) => {\n const { minLength = 8, maxLength = 128 }: Partial<{ minLength: number; maxLength: number }> =\n options\n\n const length: number = password.length\n\n if (length < minLength) {\n const params: MessageParams = { minLength }\n return {\n passed: false,\n code: 'length.tooShort',\n params,\n message: resolveMessage('length.tooShort', params, options),\n }\n }\n\n if (length > maxLength) {\n const params: MessageParams = { maxLength }\n return {\n passed: false,\n code: 'length.tooLong',\n params,\n message: resolveMessage('length.tooLong', params, options),\n }\n }\n\n return {\n passed: true,\n }\n}\n","import type { MessageParams, Validator } from '../types'\nimport { resolveMessage } from '../messages'\n\n/**\n * Check if password contains at least one uppercase letter (A-Z)\n *\n * @param password - Password to check\n * @returns True if password contains at least one uppercase letter\n *\n * @example\n * ```typescript\n * hasUppercase('password') // false\n * hasUppercase('Password') // true\n * hasUppercase('PASSWORD') // true\n * ```\n */\nexport const hasUppercase = (password: string): boolean => /[A-Z]/.test(password)\n\n/**\n * Check if password contains at least one lowercase letter (a-z)\n *\n * @param password - Password to check\n * @returns True if password contains at least one lowercase letter\n *\n * @example\n * ```typescript\n * hasLowercase('PASSWORD') // false\n * hasLowercase('Password') // true\n * hasLowercase('password') // true\n * ```\n */\nexport const hasLowercase = (password: string): boolean => /[a-z]/.test(password)\n\n/**\n * Check if password contains at least one digit (0-9)\n *\n * @param password - Password to check\n * @returns True if password contains at least one digit\n *\n * @example\n * ```typescript\n * hasDigit('password') // false\n * hasDigit('password1') // true\n * hasDigit('123') // true\n * ```\n */\nexport const hasDigit = (password: string): boolean => /\\d/.test(password)\n\n/**\n * Check if password contains at least one symbol/special character\n *\n * @param password - Password to check\n * @returns True if password contains at least one special character\n *\n * @example\n * ```typescript\n * hasSymbol('password') // false\n * hasSymbol('password!') // true\n * hasSymbol('p@ssw0rd') // true\n * ```\n *\n * @remarks\n * A symbol is any printable ASCII character that is not a letter or digit —\n * code points 0x20–0x7E excluding 0-9, A-Z, a-z. This includes space, backtick\n * (`` ` ``) and tilde (`~`), matching the character-class counting in\n * `@sentinel-password/entropy`.\n */\nexport const hasSymbol = (password: string): boolean =>\n /[\\x20-\\x2F\\x3A-\\x40\\x5B-\\x60\\x7B-\\x7E]/.test(password)\n\n/**\n * Char-code counterpart of {@link hasSymbol}, used by the single-pass scan in\n * {@link validateCharacterTypes}: true for any printable ASCII code point that\n * is not a letter or digit (0x20-0x2F, 0x3A-0x40, 0x5B-0x60, 0x7B-0x7E). Covers\n * space (0x20), backtick (0x60) and tilde (0x7E). Must stay in sync with the\n * regex character class in {@link hasSymbol}.\n */\nconst isSymbolCode = (c: number): boolean =>\n (c >= 0x20 && c <= 0x2f) ||\n (c >= 0x3a && c <= 0x40) ||\n (c >= 0x5b && c <= 0x60) ||\n (c >= 0x7b && c <= 0x7e)\n\n/**\n * Validates character type requirements (uppercase, lowercase, digits, symbols)\n *\n * @param password - Password to validate\n * @param options - Validation options containing character type requirements\n * @returns Validator check result with passed status and optional error message\n *\n * @example\n * ```typescript\n * import { validateCharacterTypes } from '@sentinel-password/core'\n *\n * // No requirements by default\n * validateCharacterTypes('password') // { passed: true }\n *\n * // Require uppercase\n * validateCharacterTypes('password', { requireUppercase: true })\n * // { passed: false, message: \"Password must contain at least one uppercase letter\" }\n *\n * validateCharacterTypes('Password', { requireUppercase: true }) // { passed: true }\n *\n * // Require multiple types\n * validateCharacterTypes('password', {\n * requireUppercase: true,\n * requireDigit: true,\n * requireSymbol: true\n * })\n * // { passed: false, message: \"Password must contain at least one uppercase letter, digit, symbol\" }\n *\n * validateCharacterTypes('Password1!', {\n * requireUppercase: true,\n * requireDigit: true,\n * requireSymbol: true\n * }) // { passed: true }\n * ```\n *\n * @remarks\n * By default, no character types are required. Enable specific requirements via options.\n *\n * Implementation note: scans the password ONCE with `charCodeAt`, classifying\n * each char into uppercase / lowercase / digit / symbol, with early exit as\n * soon as every required class is satisfied. Faster than the previous\n * implementation that ran up to 4 separate `RegExp.test()` scans. The\n * individual `hasUppercase` / `hasLowercase` / `hasDigit` / `hasSymbol`\n * helpers above stay regex-based — they're independently exported and the\n * regex form is clearer for one-off calls.\n */\nexport const validateCharacterTypes: Validator = (password, options = {}) => {\n const {\n requireUppercase = false,\n requireLowercase = false,\n requireDigit = false,\n requireSymbol = false,\n }: Partial<{\n requireUppercase: boolean\n requireLowercase: boolean\n requireDigit: boolean\n requireSymbol: boolean\n }> = options\n\n // No requirements → no scan, no allocations.\n if (!requireUppercase && !requireLowercase && !requireDigit && !requireSymbol) {\n return { passed: true }\n }\n\n let foundUpper: boolean = !requireUppercase\n let foundLower: boolean = !requireLowercase\n let foundDigit: boolean = !requireDigit\n let foundSymbol: boolean = !requireSymbol\n\n for (let i: number = 0; i < password.length; i++) {\n if (foundUpper && foundLower && foundDigit && foundSymbol) break\n const c: number = password.charCodeAt(i)\n if (!foundUpper && c >= 65 && c <= 90) {\n foundUpper = true\n } else if (!foundLower && c >= 97 && c <= 122) {\n foundLower = true\n } else if (!foundDigit && c >= 48 && c <= 57) {\n foundDigit = true\n } else if (!foundSymbol && isSymbolCode(c)) {\n foundSymbol = true\n }\n }\n\n if (foundUpper && foundLower && foundDigit && foundSymbol) {\n return { passed: true }\n }\n\n // Failure path: lazy-build the missing-types lists. Order MUST match the\n // pre-existing implementation: uppercase, lowercase, digit, symbol.\n const missing: string[] = []\n const missingTypes: string[] = []\n if (requireUppercase && !foundUpper) {\n missing.push('uppercase letter')\n missingTypes.push('uppercase')\n }\n if (requireLowercase && !foundLower) {\n missing.push('lowercase letter')\n missingTypes.push('lowercase')\n }\n if (requireDigit && !foundDigit) {\n missing.push('digit')\n missingTypes.push('digit')\n }\n if (requireSymbol && !foundSymbol) {\n missing.push('symbol')\n missingTypes.push('symbol')\n }\n\n const params: MessageParams = {\n missing: missing.join(', '),\n missingTypes: missingTypes.join(','),\n }\n return {\n passed: false,\n code: 'characterTypes.missing',\n params,\n message: resolveMessage('characterTypes.missing', params, options),\n }\n}\n","import type { MessageParams, Validator } from '../types'\nimport { resolveMessage } from '../messages'\n\n/**\n * Validates that password doesn't contain excessive repeated characters\n *\n * Uses a single-pass algorithm to detect consecutive repeated characters.\n * Helps prevent weak passwords like \"aaaa1111\" or \"passwordddd\".\n *\n * @param password - Password to validate\n * @param options - Validation options containing maxRepeatedChars\n * @returns Validator check result with passed status and optional error message\n *\n * @example\n * ```typescript\n * import { validateRepetition } from '@sentinel-password/core'\n *\n * // Default: max 3 repeated characters\n * validateRepetition('password') // { passed: true }\n * validateRepetition('passsword') // { passed: true } (3 s's)\n * validateRepetition('passssword') // { passed: false } (4 s's)\n *\n * // Custom limit\n * validateRepetition('passssword', { maxRepeatedChars: 5 }) // { passed: true }\n * validateRepetition('aaa') // { passed: true }\n * validateRepetition('aaaa') // { passed: false, message: \"Password contains too many repeated characters (max 3)\" }\n * ```\n *\n * @remarks\n * Default maximum is 3 consecutive repeated characters.\n * Only checks for consecutive repetition, not overall character frequency.\n *\n * Iterates by Unicode code point (via `for...of`), so a run of identical astral\n * characters — e.g. repeated emoji — is counted as user-perceived characters\n * rather than UTF-16 code units (which would let `😀😀😀😀` slip through).\n */\nexport const validateRepetition: Validator = (password, options = {}) => {\n const { maxRepeatedChars = 3 }: Partial<{ maxRepeatedChars: number }> = options\n\n let currentChar: string | undefined\n let count: number = 0\n\n for (const char of password) {\n if (char === currentChar) {\n count++\n if (count > maxRepeatedChars) {\n const params: MessageParams = { maxRepeatedChars }\n return {\n passed: false,\n code: 'repetition.tooMany',\n params,\n message: resolveMessage('repetition.tooMany', params, options),\n }\n }\n } else {\n currentChar = char\n count = 1\n }\n }\n\n return {\n passed: true,\n }\n}\n","import type { MessageParams, Validator } from '../types'\nimport { resolveMessage } from '../messages'\n\n/**\n * Detects sequential character patterns (ascending or descending)\n * Checks for sequences of 3 or more consecutive characters\n *\n * @param str - String to check for sequences\n * @returns true if sequential pattern found, false otherwise\n */\nconst hasSequentialPattern = (str: string): boolean => {\n const minSequenceLength: number = 3\n\n for (let i: number = 0; i <= str.length - minSequenceLength; i++) {\n const charCode1: number = str.charCodeAt(i)\n const charCode2: number = str.charCodeAt(i + 1)\n const charCode3: number = str.charCodeAt(i + 2)\n\n // Check ascending sequence (e.g., abc, 123)\n if (charCode2 === charCode1 + 1 && charCode3 === charCode2 + 1) {\n return true\n }\n\n // Check descending sequence (e.g., cba, 321)\n if (charCode2 === charCode1 - 1 && charCode3 === charCode2 - 1) {\n return true\n }\n }\n\n return false\n}\n\n/**\n * Validates that password doesn't contain sequential character patterns\n *\n * Detects sequences like: abc, ABC, 123, 321, xyz, etc.\n * Uses character code comparison for efficient detection.\n * Helps prevent predictable passwords with keyboard sequences.\n *\n * @param password - Password to validate\n * @param options - Validation options containing checkSequential flag\n * @returns Validator check result with passed status and optional error message\n *\n * @example\n * ```typescript\n * import { validateSequential } from '@sentinel-password/core'\n *\n * // Detects ascending sequences\n * validateSequential('password') // { passed: true }\n * validateSequential('abc123') // { passed: false } (contains \"abc\" and \"123\")\n * validateSequential('xyz') // { passed: false } (contains \"xyz\")\n *\n * // Detects descending sequences\n * validateSequential('cba321') // { passed: false } (contains \"cba\" and \"321\")\n *\n * // Disable check\n * validateSequential('abc123', { checkSequential: false }) // { passed: true }\n * ```\n *\n * @remarks\n * Enabled by default. Checks for 3 or more consecutive characters in sequence.\n * Case-sensitive: detects both \"abc\" and \"ABC\" as separate patterns.\n *\n * **Overlap with `validateKeyboardPattern`:** The numeric runs `123`, `456`,\n * `789` (and their reverses) are also matched by the keyboard-pattern\n * validator's numeric-keypad list. To allow simple numeric runs in a\n * password you must set BOTH `checkSequential: false` AND\n * `checkKeyboardPatterns: false` — disabling either alone will not let\n * `password123` through. The two checks are deliberately independent\n * defences (code-point runs vs. keyboard-locality runs).\n */\nexport const validateSequential: Validator = (password, options = {}) => {\n const { checkSequential = true }: Partial<{ checkSequential: boolean }> = options\n\n if (!checkSequential) {\n return { passed: true }\n }\n\n if (hasSequentialPattern(password)) {\n const params: MessageParams = {}\n return {\n passed: false,\n code: 'sequential.found',\n params,\n message: resolveMessage('sequential.found', params, options),\n }\n }\n\n return {\n passed: true,\n }\n}\n","import type { MessageParams, ValidatorCheck, ValidatorOptions } from '../types'\nimport { resolveMessage } from '../messages'\n\n/**\n * Common keyboard patterns to detect across multiple layouts\n * Supports: QWERTY, AZERTY, QWERTZ, Dvorak, Colemak, and Cyrillic\n */\nconst KEYBOARD_PATTERNS: readonly string[] = [\n // === QWERTY (English, US, UK) ===\n // Full rows\n 'qwertyuiop',\n 'asdfghjkl',\n 'zxcvbnm',\n // Common typing patterns (adjacent keys)\n 'qwert',\n 'werty',\n 'asdfg',\n 'sdfgh',\n 'zxcvb',\n 'xcvbn',\n // Short sequences (3+ chars)\n 'qwe',\n 'asd',\n 'zxc',\n 'rty',\n 'fgh',\n 'cvb',\n 'poi',\n 'lkj',\n 'mnb',\n // Columns (top to bottom)\n '1qaz',\n '2wsx',\n '3edc',\n '4rfv',\n '5tgb',\n '6yhn',\n '7ujm',\n '8ik',\n '9ol',\n '0p',\n // Diagonals\n 'qaz',\n 'wsx',\n 'edc',\n 'zaq',\n 'xsw',\n 'cde',\n\n // === AZERTY (French, Belgian) ===\n // Full rows\n 'azertyuiop',\n 'qsdfghjklm',\n 'wxcvbn',\n // Common patterns\n 'azert',\n 'zerty',\n 'qsdfg',\n 'sdfgh',\n 'wxcvb',\n // Short sequences\n 'aze',\n 'qsd',\n 'wxc',\n\n // === QWERTZ (German, Central European) ===\n // Full rows\n 'qwertzuiop',\n 'yxcvbnm',\n // Common patterns\n 'qwertz',\n 'yxcvb',\n // Short sequences\n 'yxc',\n // Note: Patterns 'qwe', 'asd', 'asdfg', and 'asdfghjkl' are shared with QWERTY and listed above for efficiency.\n\n // === Dvorak ===\n // Full rows\n 'pyfgcrl',\n 'aoeuidhtns',\n 'qjkxbmwvz',\n // Common patterns\n 'aoeu',\n 'htns',\n 'qjkx',\n\n // === Colemak ===\n // Full rows\n 'qwfpgjluy',\n 'arstdhneio',\n 'zxcvbkm',\n // Common patterns\n 'arst',\n 'dhne',\n 'zxcv',\n\n // === Cyrillic (ЙЦУКЕН - Russian) ===\n // Full rows (Cyrillic characters)\n 'йцукенгшщзхъ',\n 'фывапролджэ',\n 'ячсмитьбю',\n // Common patterns\n 'йцукен',\n 'фывап',\n 'ячсми',\n 'цукен',\n\n // === Numeric patterns (universal) ===\n // Number row\n '1234567890',\n '0987654321',\n // Numeric keypad (rows)\n '789',\n '456',\n '123',\n // Numeric keypad (columns)\n '741',\n '852',\n '963',\n '7410',\n '8520',\n '9630',\n] as const\n\n/**\n * Escape regex metacharacters in a string so it matches literally.\n * Defensive: none of the current patterns contain metacharacters, but this\n * keeps future additions safe (e.g., if a layout's pattern contains `+` or `$`).\n */\nfunction escapeRegex(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\n/**\n * Single regex matching ANY keyboard pattern in either forward or reverse\n * direction, case-insensitively. Built once at module load.\n *\n * Performance note: this replaced an earlier implementation that ran a\n * `for` loop over `KEYBOARD_PATTERNS`, calling `pattern.split('').reverse().join('')`\n * to build the reverse string AND `lowercase.includes(pattern)` on every iteration\n * (~480 allocations per call). A single regex test against an NFA-compiled\n * alternation is ~12× faster on typical passwords on V8/Node 22.\n *\n * If a future V8 regex bug surfaces (e.g., a Unicode case-folding regression\n * affecting Cyrillic with the `/i` flag), there's a side-by-side comparison\n * against a precomputed-array loop variant in\n * `packages/core/tests/performance.bench.ts` → `Keyboard pattern: implementation comparison`.\n * The loop variant is ~2.5× faster than the original split/reverse/join code\n * and ~5× slower than the regex, so it's a usable rollback target.\n */\nconst KEYBOARD_REGEX: RegExp = new RegExp(\n KEYBOARD_PATTERNS.flatMap((p: string): readonly string[] => {\n const reversed: string = p.split('').reverse().join('')\n return [escapeRegex(p), escapeRegex(reversed)]\n }).join('|'),\n 'i'\n)\n\n/**\n * Validates that password does not contain common keyboard patterns\n *\n * Detects patterns across multiple keyboard layouts:\n * - QWERTY (English, US, UK): qwerty, asdfgh, 1qaz2wsx\n * - AZERTY (French, Belgian): azerty, qsdfg\n * - QWERTZ (German, Central European): qwertz, yxcvb\n * - Dvorak (Alternative English): aoeu, htns\n * - Colemak (Alternative English): arst, dhne\n * - Cyrillic (Russian ЙЦУКЕН): йцукен, фывап\n * - Universal numeric: 123, 789, numeric keypad patterns\n * - Both forward and reverse patterns\n *\n * @param password - The password to validate\n * @param options - Validation options\n * @returns Validation result\n *\n * @example\n * ```typescript\n * validateKeyboardPattern('qwerty123') // { passed: false, message: '...' }\n * validateKeyboardPattern('azerty456') // { passed: false, message: '...' } (AZERTY)\n * validateKeyboardPattern('йцукен') // { passed: false, message: '...' } (Cyrillic)\n * validateKeyboardPattern('MyP@ssw0rd') // { passed: true }\n * validateKeyboardPattern('asdfgh', { checkKeyboardPatterns: false }) // { passed: true }\n * ```\n *\n * @remarks\n * **Overlap with `validateSequential`:** The numeric-keypad rows `123`,\n * `456`, `789` (and their reverses) are also caught by the sequential\n * validator's `charCodeAt`-consecutive check. To allow simple numeric\n * runs in a password you must set BOTH `checkKeyboardPatterns: false`\n * AND `checkSequential: false` — disabling either alone will not let\n * `password123` through. The two checks are deliberately independent\n * defences (keyboard-locality runs vs. code-point runs).\n */\nexport function validateKeyboardPattern(\n password: string,\n options: ValidatorOptions = {}\n): ValidatorCheck {\n const { checkKeyboardPatterns = true }: Partial<{ checkKeyboardPatterns: boolean }> = options\n\n if (!checkKeyboardPatterns) {\n return { passed: true }\n }\n\n if (KEYBOARD_REGEX.test(password)) {\n const emptyParams: MessageParams = {}\n return {\n passed: false,\n code: 'keyboardPattern.found',\n params: emptyParams,\n message: resolveMessage('keyboardPattern.found', emptyParams, options),\n }\n }\n\n return { passed: true }\n}\n","import type { MessageParams, Validator, ValidatorOptions } from '../types'\nimport { resolveMessage } from '../messages'\n\n/**\n * Bloom filter for common passwords\n *\n * Source: SecLists https://github.com/danielmiessler/SecLists\n * File: Passwords/Common-Credentials/10k-most-common.txt (top 1,000)\n * Local: packages/core/data/common-passwords.txt\n *\n * Regenerate with: pnpm --filter @sentinel-password/core generate:bloom\n */\n\n// --- BEGIN GENERATED BLOOM FILTER ---\n// Generated from: packages/core/data/common-passwords.txt\n// Passwords: 1000 | Bloom size: 12000 bits | Hash functions: 7\nconst BLOOM_SIZE: number = 12000\nconst BLOOM_HASH_COUNT: number = 7\n\nconst BLOOM_BUCKETS: Int32Array = new Int32Array([\n -1274142440, -1983615455, -2110842727, -1440077142, 1782964852, 956870738, 48445478, 290080800,\n -1961772502, -1869995330, 787111091, 140549329, -1508237141, -800970204, -1987272632, -1439602637,\n 1837731885, 240419586, 2143496680, 547359246, -435494235, -1549227926, 151650466, -1742731903,\n -1535043550, -1607849886, -464376123, 1858481975, -1339118942, 410034491, -1397081975, 1217017604,\n -1934873593, -1769402149, -1107107031, -1098029453, 4380674, -393017311, -1465113568, 183557329,\n 1713935653, -99073116, 179529869, -1372675933, 78050105, 313074313, -1696462848, 404392594,\n -252409893, -1320442777, -1474295681, -1360120173, -743767380, 135925863, -1462755274, 562596610,\n -483730736, -2002210214, 1803176, 448811114, -1408849403, -735411494, -1872045334, 914538658,\n -374794234, 710617115, -1909840320, -2119695701, -1979682099, -1671944535, -1872492857, 570434164,\n -1334628885, 438020137, -1087528694, 464528967, -359521705, -1407023569, -1759335432, -1181701078,\n -1959746004, -1910872796, 1771191076, -1699312926, -1837137785, 132360706, -1002778102,\n 1384163571, 1786784659, -1354832568, -1364547569, -269830616, -1970133308, 848887818, -827577818,\n -1103500629, 2071996096, 1760287282, 1768617098, -1845466197, -1877054343, -223851966,\n -1037686738, 581441802, -761237365, -1282923217, 1328736770, 557363947, 369254184, -1708971466,\n -874268952, -2010768709, -1735196024, -1023761881, 772024867, -328714721, -125771638, 1644593802,\n 1762927155, -1509621238, 671919842, -1102294492, -2010455282, 302591302, -2136446493, -1986908626,\n -2018566016, 671347107, 842040354, 243352234, -534627656, -2011035123, -350834429, -844096647,\n -1598189902, -1833872134, -2021613470, -1968994020, -1297357224, 1210222837, -399712104,\n 906129314, -1430115578, -2113618941, -906361552, 551676011, 2819256, -1710063476, 1099626796,\n 1026172967, 640491579, -1440579096, -1887270648, -1710185214, 1721813836, 984101107, 1944258615,\n 724137993, -947645533, 1913070606, 44829860, 101219306, 1011556358, -1432173338, -482828238,\n -2103243204, 447353024, -329610536, 1398671502, 1623196423, 348309551, -211221782, 1980906030,\n 778830798, 968125066, -1458011638, -1337934774, 671089894, 52600876, -120418625, -1163255178,\n 680202936, -1597565152, 36835260, 1845733400, 153924331, 40509970, 411221088, 2114095983,\n 171043813, 1488604610, 172814850, -1702382524, 1502872105, 747635458, -501052624, 411301928,\n 731823798, -1333221971, -398552509, 134621864, -1872819037, 1822818465, -1893684382, -1316869470,\n -931632595, -1951260093, 598498019, -1290239178, -989323246, 573220082, 675809232, 169874120,\n -559880256, -910750974, 194548323, 736774690, -719202768, 1814594698, -1155290520, 237063072,\n -488083322, -924144083, 36385696, 715956226, -1568572256, 635709488, 1336062594, 320907784,\n 1124501765, -1406433280, -1963307926, -391509877, 1063369670, -1566479450, -1472058776,\n -1985331186, 1377869964, 942987938, 1109713448, 831578699, 853962256, 92835849, 2035317046,\n -2001761759, 216178811, 463827086, 1675819625, -1301764214, -234217277, -1459402461, 579365994,\n -1878875352, -1973245719, -1598478333, 1611084451, 1659513348, -1539122450, 837944629, 154749747,\n -500636954, 1746899746, 714271018, -1150679461, -1538612630, 1814069472, -492197206, -192202641,\n -756936190, -939252957, -1240063770, 1279410916, -1302054220, 1410991882, 1000401066, -198048178,\n -1419228998, -1742550398, -1991360862, -2111688732, -1700640206, 2061912618, -2103274201,\n -288731098, 578232, 53128594, 1755908256, 1116379816, 797411910, -1589060869, -1072342375,\n 845812237, 1749465810, 872549054, -1498077050, -1766137746, -376831803, -2084952921, 1202203194,\n 1216389770, -1104576382, 818061341, 942317755, -1578565146, 2083914250, -1012266815, 579371193,\n -461075653, 710981226, 1578192039, 681770739, -376523066, 1244735807, -490842006, -781193759,\n 556277799, 1984167471, -2000977760, -1265456598, 151665664, -1842697570, 579340290, 1082298021,\n 245211512, -1867881849, -1341630964, -90545664, 1806344898, 47088170, 992486842, -1558631407,\n -1449508691, -2006144829, 547374756, 912919200, 708120146, 616106151, 1172449414, 2124751394,\n -2073427929, 1009261122, 1210336770, -1973924734, 144458353, -1155209078, -1962399613, 897229836,\n 12357639, -1299101013, 312943930, 708920290, -1310023134, 639040320, -821838839, -2014506238,\n -274610648, -1442508704, 177432331, 1721041571, 414065321, -1272924654, 756208161, 1772773934,\n -1484380630, 1101004848, -1054523758, -2116504793, 1118667297, 86058657, 975856680, 749906058,\n 1937961650, 620890537, 918765730, -1366546774,\n])\n// --- END GENERATED BLOOM FILTER ---\n\n/**\n * Hash function for bloom filter\n */\nfunction hashString(str: string, seed: number): number {\n let hash: number = seed\n\n for (let i: number = 0; i < str.length; i++) {\n const char: number = str.charCodeAt(i)\n hash = (hash << 5) - hash + char\n hash = hash | 0 // Convert to 32-bit integer\n }\n\n return Math.abs(hash)\n}\n\n/**\n * Get multiple hash positions for a password\n */\nfunction getHashes(password: string): number[] {\n const hashes: number[] = []\n const hash1: number = hashString(password, 0)\n const hash2: number = hashString(password, 1)\n\n for (let i: number = 0; i < BLOOM_HASH_COUNT; i++) {\n const hash: number = (hash1 + i * hash2) >>> 0\n hashes.push(hash % BLOOM_SIZE)\n }\n\n return hashes\n}\n\n/**\n * Check if password might be in the common password list\n * Note: Bloom filters can have false positives (~0.84%) but never false negatives\n */\nfunction mightBeCommon(password: string): boolean {\n const hashes: number[] = getHashes(password.toLowerCase())\n\n for (const hash of hashes) {\n const arrayIndex: number = Math.floor(hash / 32)\n const bitIndex: number = hash % 32\n\n // Bounds check for TypeScript strict mode\n const bucket: number | undefined = BLOOM_BUCKETS[arrayIndex]\n if (bucket === undefined || (bucket & (1 << bitIndex)) === 0) {\n return false\n }\n }\n\n return true\n}\n\n/**\n * Validates that a password is not in the common password list\n *\n * Uses a Bloom filter to efficiently check against the top 1,000 most common passwords.\n * Case-insensitive matching prevents simple case variations of common passwords.\n *\n * @param password - Password to validate\n * @param options - Validation options containing checkCommonPasswords flag\n * @returns Validator check result with passed status and optional error message\n *\n * @example\n * ```typescript\n * import { validateCommonPassword } from '@sentinel-password/core'\n *\n * // Detects common passwords\n * validateCommonPassword('password')\n * // { passed: false, message: \"Password is too common. Please choose a more unique password.\" }\n *\n * validateCommonPassword('123456')\n * // { passed: false }\n *\n * // Case-insensitive\n * validateCommonPassword('PASSWORD')\n * // { passed: false }\n *\n * // Unique passwords pass\n * validateCommonPassword('MyUn1qu3P@ssw0rd!')\n * // { passed: true }\n *\n * // Disable check\n * validateCommonPassword('password', { checkCommonPasswords: false })\n * // { passed: true }\n * ```\n *\n * @remarks\n * Checks against top 1,000 most common passwords from SecLists.\n * Uses Bloom filter for space efficiency (~1.5KB vs ~8KB for raw array).\n * False positive rate: ~0.84% (may rarely flag uncommon passwords).\n * Enabled by default for security.\n */\nexport const validateCommonPassword: Validator = (\n password: string,\n options: ValidatorOptions = {}\n) => {\n const { checkCommonPasswords = true }: { checkCommonPasswords?: boolean } = options\n\n if (!checkCommonPasswords || password.length === 0) {\n return { passed: true }\n }\n\n // Case-insensitive check using bloom filter\n if (mightBeCommon(password)) {\n const params: MessageParams = {}\n return {\n passed: false,\n code: 'commonPassword.found',\n params,\n message: resolveMessage('commonPassword.found', params, options),\n }\n }\n\n return { passed: true }\n}\n","import type { MessageParams, Validator } from '../types'\nimport { resolveMessage } from '../messages'\n\n/**\n * Extracts username from email address\n * @param email - Email address\n * @returns Username part before @ symbol\n */\nconst extractUsername = (email: string): string => {\n const atIndex: number = email.indexOf('@')\n // `normalizePersonalInfo` only calls this when the string contains '@', so\n // `atIndex` is never -1; the `: email` fallback covers a leading-'@' entry\n // (e.g. '@admin'), where the whole string is matched as a literal substring.\n return atIndex > 0 ? email.substring(0, atIndex) : email\n}\n\n/**\n * Normalizes personal info strings for comparison\n * Converts to lowercase and extracts username from emails\n *\n * @param info - Personal information string\n * @returns Normalized string for comparison\n */\nconst normalizePersonalInfo = (info: string): string => {\n const normalized: string = info.toLowerCase().trim()\n // Extract username from email if it looks like an email\n return normalized.includes('@') ? extractUsername(normalized) : normalized\n}\n\n/**\n * Validates that password doesn't contain personal information\n *\n * Checks against provided personal info (username, email, name, etc.)\n * Uses case-insensitive comparison and extracts usernames from email addresses.\n * Ignores very short strings (< 3 characters) to avoid false positives.\n *\n * @param password - Password to validate\n * @param options - Validation options containing personalInfo array\n * @returns Validator check result with passed status and optional error message\n *\n * @example\n * ```typescript\n * import { validatePersonalInfo } from '@sentinel-password/core'\n *\n * // No personal info by default\n * validatePersonalInfo('password') // { passed: true }\n *\n * // Detects username in password\n * validatePersonalInfo('johnpassword', { personalInfo: ['john'] })\n * // { passed: false, message: \"Password contains personal information\" }\n *\n * // Emails are reduced to the *entire local part* (everything before `@`),\n * // matched as a literal substring — not split into name fragments.\n * validatePersonalInfo('john.doe123', { personalInfo: ['john.doe@example.com'] })\n * // { passed: false } (matches the full local part \"john.doe\")\n *\n * validatePersonalInfo('john123', { personalInfo: ['john.doe@example.com'] })\n * // { passed: true } (the local part is \"john.doe\", not \"john\" — no match)\n *\n * // To reject passwords containing just \"john\", pass it explicitly:\n * validatePersonalInfo('john123', { personalInfo: ['john', 'john.doe@example.com'] })\n * // { passed: false } (matches the literal \"john\")\n *\n * // Case-insensitive\n * validatePersonalInfo('JOHN123', { personalInfo: ['john'] })\n * // { passed: false }\n *\n * // Ignores short strings\n * validatePersonalInfo('password', { personalInfo: ['pw'] }) // { passed: true } (too short)\n *\n * // Multiple personal info items\n * validatePersonalInfo('secretpass', {\n * personalInfo: ['john', 'doe', 'john.doe@example.com']\n * }) // { passed: true }\n * ```\n *\n * @remarks\n * Best practice: Provide username, email, first name, last name, and company name.\n * Strings shorter than 3 characters are ignored to prevent false positives.\n */\nexport const validatePersonalInfo: Validator = (password, options = {}) => {\n const { personalInfo = [] }: Partial<{ personalInfo: string[] }> = options\n\n if (personalInfo.length === 0 || password.length === 0) {\n return { passed: true }\n }\n\n const lowerPassword: string = password.toLowerCase()\n\n for (const info of personalInfo) {\n const normalized: string = normalizePersonalInfo(info)\n\n // Skip very short strings to avoid false positives\n if (normalized.length < 3) {\n continue\n }\n\n // Check if password contains this personal information\n if (lowerPassword.includes(normalized)) {\n const params: MessageParams = {}\n return {\n passed: false,\n code: 'personalInfo.found',\n params,\n message: resolveMessage('personalInfo.found', params, options),\n }\n }\n }\n\n return {\n passed: true,\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACYO,IAAM,oBAA2D;AAAA,EACtE,mBAAmB;AAAA,EACnB,kBAAkB;AAAA,EAClB,0BAA0B;AAAA,EAC1B,sBAAsB;AAAA,EACtB,oBAAoB;AAAA,EACpB,yBAAyB;AAAA,EACzB,wBAAwB;AAAA,EACxB,sBAAsB;AACxB;AAEA,IAAM,sBAA8B;AAa7B,SAAS,eAAe,UAAkB,QAA+B;AAC9E,SAAO,SAAS,QAAQ,qBAAqB,CAAC,OAAO,QAAwB;AAC3E,UAAM,QAAqC,OAAO,GAAG;AACrD,WAAO,UAAU,SAAY,QAAQ,OAAO,KAAK;AAAA,EACnD,CAAC;AACH;AAgBO,SAAS,eACd,MACA,QACA,SACQ;AACR,QAAM,iBAAyB,eAAe,kBAAkB,IAAI,GAAG,MAAM;AAE7E,MAAI,QAAQ,eAAe;AACzB,QAAI;AACF,aAAO,QAAQ,cAAc,MAAM,QAAQ,cAAc;AAAA,IAC3D,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAEA,QAAM,WAA+B,QAAQ,WAAW,IAAI;AAC5D,MAAI,aAAa,QAAW;AAC1B,WAAO,eAAe,UAAU,MAAM;AAAA,EACxC;AAEA,SAAO;AACT;;;ACnDO,IAAM,iBAA4B,CAAC,UAAU,UAAU,CAAC,MAAM;AACnE,QAAM,EAAE,YAAY,GAAG,YAAY,IAAI,IACrC;AAEF,QAAM,SAAiB,SAAS;AAEhC,MAAI,SAAS,WAAW;AACtB,UAAM,SAAwB,EAAE,UAAU;AAC1C,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,MAAM;AAAA,MACN;AAAA,MACA,SAAS,eAAe,mBAAmB,QAAQ,OAAO;AAAA,IAC5D;AAAA,EACF;AAEA,MAAI,SAAS,WAAW;AACtB,UAAM,SAAwB,EAAE,UAAU;AAC1C,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,MAAM;AAAA,MACN;AAAA,MACA,SAAS,eAAe,kBAAkB,QAAQ,OAAO;AAAA,IAC3D;AAAA,EACF;AAEA,SAAO;AAAA,IACL,QAAQ;AAAA,EACV;AACF;;;ACxCO,IAAM,eAAe,CAAC,aAA8B,QAAQ,KAAK,QAAQ;AAezE,IAAM,eAAe,CAAC,aAA8B,QAAQ,KAAK,QAAQ;AAezE,IAAM,WAAW,CAAC,aAA8B,KAAK,KAAK,QAAQ;AAqBlE,IAAM,YAAY,CAAC,aACxB,yCAAyC,KAAK,QAAQ;AASxD,IAAM,eAAe,CAAC,MACnB,KAAK,MAAQ,KAAK,MAClB,KAAK,MAAQ,KAAK,MAClB,KAAK,MAAQ,KAAK,MAClB,KAAK,OAAQ,KAAK;AAgDd,IAAM,yBAAoC,CAAC,UAAU,UAAU,CAAC,MAAM;AAC3E,QAAM;AAAA,IACJ,mBAAmB;AAAA,IACnB,mBAAmB;AAAA,IACnB,eAAe;AAAA,IACf,gBAAgB;AAAA,EAClB,IAKK;AAGL,MAAI,CAAC,oBAAoB,CAAC,oBAAoB,CAAC,gBAAgB,CAAC,eAAe;AAC7E,WAAO,EAAE,QAAQ,KAAK;AAAA,EACxB;AAEA,MAAI,aAAsB,CAAC;AAC3B,MAAI,aAAsB,CAAC;AAC3B,MAAI,aAAsB,CAAC;AAC3B,MAAI,cAAuB,CAAC;AAE5B,WAAS,IAAY,GAAG,IAAI,SAAS,QAAQ,KAAK;AAChD,QAAI,cAAc,cAAc,cAAc,YAAa;AAC3D,UAAM,IAAY,SAAS,WAAW,CAAC;AACvC,QAAI,CAAC,cAAc,KAAK,MAAM,KAAK,IAAI;AACrC,mBAAa;AAAA,IACf,WAAW,CAAC,cAAc,KAAK,MAAM,KAAK,KAAK;AAC7C,mBAAa;AAAA,IACf,WAAW,CAAC,cAAc,KAAK,MAAM,KAAK,IAAI;AAC5C,mBAAa;AAAA,IACf,WAAW,CAAC,eAAe,aAAa,CAAC,GAAG;AAC1C,oBAAc;AAAA,IAChB;AAAA,EACF;AAEA,MAAI,cAAc,cAAc,cAAc,aAAa;AACzD,WAAO,EAAE,QAAQ,KAAK;AAAA,EACxB;AAIA,QAAM,UAAoB,CAAC;AAC3B,QAAM,eAAyB,CAAC;AAChC,MAAI,oBAAoB,CAAC,YAAY;AACnC,YAAQ,KAAK,kBAAkB;AAC/B,iBAAa,KAAK,WAAW;AAAA,EAC/B;AACA,MAAI,oBAAoB,CAAC,YAAY;AACnC,YAAQ,KAAK,kBAAkB;AAC/B,iBAAa,KAAK,WAAW;AAAA,EAC/B;AACA,MAAI,gBAAgB,CAAC,YAAY;AAC/B,YAAQ,KAAK,OAAO;AACpB,iBAAa,KAAK,OAAO;AAAA,EAC3B;AACA,MAAI,iBAAiB,CAAC,aAAa;AACjC,YAAQ,KAAK,QAAQ;AACrB,iBAAa,KAAK,QAAQ;AAAA,EAC5B;AAEA,QAAM,SAAwB;AAAA,IAC5B,SAAS,QAAQ,KAAK,IAAI;AAAA,IAC1B,cAAc,aAAa,KAAK,GAAG;AAAA,EACrC;AACA,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,MAAM;AAAA,IACN;AAAA,IACA,SAAS,eAAe,0BAA0B,QAAQ,OAAO;AAAA,EACnE;AACF;;;ACrKO,IAAM,qBAAgC,CAAC,UAAU,UAAU,CAAC,MAAM;AACvE,QAAM,EAAE,mBAAmB,EAAE,IAA2C;AAExE,MAAI;AACJ,MAAI,QAAgB;AAEpB,aAAW,QAAQ,UAAU;AAC3B,QAAI,SAAS,aAAa;AACxB;AACA,UAAI,QAAQ,kBAAkB;AAC5B,cAAM,SAAwB,EAAE,iBAAiB;AACjD,eAAO;AAAA,UACL,QAAQ;AAAA,UACR,MAAM;AAAA,UACN;AAAA,UACA,SAAS,eAAe,sBAAsB,QAAQ,OAAO;AAAA,QAC/D;AAAA,MACF;AAAA,IACF,OAAO;AACL,oBAAc;AACd,cAAQ;AAAA,IACV;AAAA,EACF;AAEA,SAAO;AAAA,IACL,QAAQ;AAAA,EACV;AACF;;;ACrDA,IAAM,uBAAuB,CAAC,QAAyB;AACrD,QAAM,oBAA4B;AAElC,WAAS,IAAY,GAAG,KAAK,IAAI,SAAS,mBAAmB,KAAK;AAChE,UAAM,YAAoB,IAAI,WAAW,CAAC;AAC1C,UAAM,YAAoB,IAAI,WAAW,IAAI,CAAC;AAC9C,UAAM,YAAoB,IAAI,WAAW,IAAI,CAAC;AAG9C,QAAI,cAAc,YAAY,KAAK,cAAc,YAAY,GAAG;AAC9D,aAAO;AAAA,IACT;AAGA,QAAI,cAAc,YAAY,KAAK,cAAc,YAAY,GAAG;AAC9D,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO;AACT;AAyCO,IAAM,qBAAgC,CAAC,UAAU,UAAU,CAAC,MAAM;AACvE,QAAM,EAAE,kBAAkB,KAAK,IAA2C;AAE1E,MAAI,CAAC,iBAAiB;AACpB,WAAO,EAAE,QAAQ,KAAK;AAAA,EACxB;AAEA,MAAI,qBAAqB,QAAQ,GAAG;AAClC,UAAM,SAAwB,CAAC;AAC/B,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,MAAM;AAAA,MACN;AAAA,MACA,SAAS,eAAe,oBAAoB,QAAQ,OAAO;AAAA,IAC7D;AAAA,EACF;AAEA,SAAO;AAAA,IACL,QAAQ;AAAA,EACV;AACF;;;ACpFA,IAAM,oBAAuC;AAAA;AAAA;AAAA,EAG3C;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA,EAIA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA,EAIA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA;AAAA;AAAA;AAAA,EAKA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA,EAIA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA,EAIA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA,EAIA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAOA,SAAS,YAAY,GAAmB;AACtC,SAAO,EAAE,QAAQ,uBAAuB,MAAM;AAChD;AAmBA,IAAM,iBAAyB,IAAI;AAAA,EACjC,kBAAkB,QAAQ,CAAC,MAAiC;AAC1D,UAAM,WAAmB,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE;AACtD,WAAO,CAAC,YAAY,CAAC,GAAG,YAAY,QAAQ,CAAC;AAAA,EAC/C,CAAC,EAAE,KAAK,GAAG;AAAA,EACX;AACF;AAqCO,SAAS,wBACd,UACA,UAA4B,CAAC,GACb;AAChB,QAAM,EAAE,wBAAwB,KAAK,IAAiD;AAEtF,MAAI,CAAC,uBAAuB;AAC1B,WAAO,EAAE,QAAQ,KAAK;AAAA,EACxB;AAEA,MAAI,eAAe,KAAK,QAAQ,GAAG;AACjC,UAAM,cAA6B,CAAC;AACpC,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,SAAS,eAAe,yBAAyB,aAAa,OAAO;AAAA,IACvE;AAAA,EACF;AAEA,SAAO,EAAE,QAAQ,KAAK;AACxB;;;ACtMA,IAAM,aAAqB;AAC3B,IAAM,mBAA2B;AAEjC,IAAM,gBAA4B,IAAI,WAAW;AAAA,EAC/C;AAAA,EAAa;AAAA,EAAa;AAAA,EAAa;AAAA,EAAa;AAAA,EAAY;AAAA,EAAW;AAAA,EAAU;AAAA,EACrF;AAAA,EAAa;AAAA,EAAa;AAAA,EAAW;AAAA,EAAW;AAAA,EAAa;AAAA,EAAY;AAAA,EAAa;AAAA,EACtF;AAAA,EAAY;AAAA,EAAW;AAAA,EAAY;AAAA,EAAW;AAAA,EAAY;AAAA,EAAa;AAAA,EAAW;AAAA,EAClF;AAAA,EAAa;AAAA,EAAa;AAAA,EAAY;AAAA,EAAY;AAAA,EAAa;AAAA,EAAW;AAAA,EAAa;AAAA,EACvF;AAAA,EAAa;AAAA,EAAa;AAAA,EAAa;AAAA,EAAa;AAAA,EAAS;AAAA,EAAY;AAAA,EAAa;AAAA,EACtF;AAAA,EAAY;AAAA,EAAW;AAAA,EAAW;AAAA,EAAa;AAAA,EAAU;AAAA,EAAW;AAAA,EAAa;AAAA,EACjF;AAAA,EAAY;AAAA,EAAa;AAAA,EAAa;AAAA,EAAa;AAAA,EAAY;AAAA,EAAW;AAAA,EAAa;AAAA,EACvF;AAAA,EAAY;AAAA,EAAa;AAAA,EAAS;AAAA,EAAW;AAAA,EAAa;AAAA,EAAY;AAAA,EAAa;AAAA,EACnF;AAAA,EAAY;AAAA,EAAW;AAAA,EAAa;AAAA,EAAa;AAAA,EAAa;AAAA,EAAa;AAAA,EAAa;AAAA,EACxF;AAAA,EAAa;AAAA,EAAW;AAAA,EAAa;AAAA,EAAW;AAAA,EAAY;AAAA,EAAa;AAAA,EAAa;AAAA,EACtF;AAAA,EAAa;AAAA,EAAa;AAAA,EAAY;AAAA,EAAa;AAAA,EAAa;AAAA,EAAW;AAAA,EAC3E;AAAA,EAAY;AAAA,EAAY;AAAA,EAAa;AAAA,EAAa;AAAA,EAAY;AAAA,EAAa;AAAA,EAAW;AAAA,EACtF;AAAA,EAAa;AAAA,EAAY;AAAA,EAAY;AAAA,EAAY;AAAA,EAAa;AAAA,EAAa;AAAA,EAC3E;AAAA,EAAa;AAAA,EAAW;AAAA,EAAY;AAAA,EAAa;AAAA,EAAY;AAAA,EAAW;AAAA,EAAW;AAAA,EACnF;AAAA,EAAY;AAAA,EAAa;AAAA,EAAa;AAAA,EAAa;AAAA,EAAW;AAAA,EAAY;AAAA,EAAY;AAAA,EACtF;AAAA,EAAY;AAAA,EAAa;AAAA,EAAW;AAAA,EAAa;AAAA,EAAa;AAAA,EAAW;AAAA,EAAa;AAAA,EACtF;AAAA,EAAa;AAAA,EAAW;AAAA,EAAW;AAAA,EAAW;AAAA,EAAY;AAAA,EAAa;AAAA,EAAY;AAAA,EACnF;AAAA,EAAa;AAAA,EAAa;AAAA,EAAa;AAAA,EAAa;AAAA,EAAa;AAAA,EAAY;AAAA,EAC7E;AAAA,EAAW;AAAA,EAAa;AAAA,EAAa;AAAA,EAAY;AAAA,EAAW;AAAA,EAAS;AAAA,EAAa;AAAA,EAClF;AAAA,EAAY;AAAA,EAAW;AAAA,EAAa;AAAA,EAAa;AAAA,EAAa;AAAA,EAAY;AAAA,EAAW;AAAA,EACrF;AAAA,EAAW;AAAA,EAAY;AAAA,EAAY;AAAA,EAAU;AAAA,EAAW;AAAA,EAAY;AAAA,EAAa;AAAA,EACjF;AAAA,EAAa;AAAA,EAAW;AAAA,EAAY;AAAA,EAAY;AAAA,EAAY;AAAA,EAAW;AAAA,EAAY;AAAA,EACnF;AAAA,EAAW;AAAA,EAAW;AAAA,EAAa;AAAA,EAAa;AAAA,EAAW;AAAA,EAAU;AAAA,EAAY;AAAA,EACjF;AAAA,EAAW;AAAA,EAAa;AAAA,EAAU;AAAA,EAAY;AAAA,EAAW;AAAA,EAAU;AAAA,EAAW;AAAA,EAC9E;AAAA,EAAW;AAAA,EAAY;AAAA,EAAW;AAAA,EAAa;AAAA,EAAY;AAAA,EAAW;AAAA,EAAY;AAAA,EAClF;AAAA,EAAW;AAAA,EAAa;AAAA,EAAY;AAAA,EAAW;AAAA,EAAa;AAAA,EAAY;AAAA,EAAa;AAAA,EACrF;AAAA,EAAY;AAAA,EAAa;AAAA,EAAW;AAAA,EAAa;AAAA,EAAY;AAAA,EAAW;AAAA,EAAW;AAAA,EACnF;AAAA,EAAY;AAAA,EAAY;AAAA,EAAW;AAAA,EAAW;AAAA,EAAY;AAAA,EAAY;AAAA,EAAa;AAAA,EACnF;AAAA,EAAY;AAAA,EAAY;AAAA,EAAU;AAAA,EAAW;AAAA,EAAa;AAAA,EAAW;AAAA,EAAY;AAAA,EACjF;AAAA,EAAY;AAAA,EAAa;AAAA,EAAa;AAAA,EAAY;AAAA,EAAY;AAAA,EAAa;AAAA,EAC3E;AAAA,EAAa;AAAA,EAAY;AAAA,EAAW;AAAA,EAAY;AAAA,EAAW;AAAA,EAAW;AAAA,EAAU;AAAA,EAChF;AAAA,EAAa;AAAA,EAAW;AAAA,EAAW;AAAA,EAAY;AAAA,EAAa;AAAA,EAAY;AAAA,EAAa;AAAA,EACrF;AAAA,EAAa;AAAA,EAAa;AAAA,EAAa;AAAA,EAAY;AAAA,EAAY;AAAA,EAAa;AAAA,EAAW;AAAA,EACvF;AAAA,EAAY;AAAA,EAAY;AAAA,EAAW;AAAA,EAAa;AAAA,EAAa;AAAA,EAAY;AAAA,EAAY;AAAA,EACrF;AAAA,EAAY;AAAA,EAAY;AAAA,EAAa;AAAA,EAAY;AAAA,EAAa;AAAA,EAAY;AAAA,EAAY;AAAA,EACtF;AAAA,EAAa;AAAA,EAAa;AAAA,EAAa;AAAA,EAAa;AAAA,EAAa;AAAA,EAAY;AAAA,EAC7E;AAAA,EAAY;AAAA,EAAQ;AAAA,EAAU;AAAA,EAAY;AAAA,EAAY;AAAA,EAAW;AAAA,EAAa;AAAA,EAC9E;AAAA,EAAW;AAAA,EAAY;AAAA,EAAW;AAAA,EAAa;AAAA,EAAa;AAAA,EAAY;AAAA,EAAa;AAAA,EACrF;AAAA,EAAY;AAAA,EAAa;AAAA,EAAW;AAAA,EAAW;AAAA,EAAa;AAAA,EAAY;AAAA,EAAa;AAAA,EACrF;AAAA,EAAY;AAAA,EAAW;AAAA,EAAY;AAAA,EAAW;AAAA,EAAY;AAAA,EAAY;AAAA,EAAY;AAAA,EAClF;AAAA,EAAW;AAAA,EAAY;AAAA,EAAa;AAAA,EAAa;AAAA,EAAW;AAAA,EAAa;AAAA,EAAW;AAAA,EACpF;AAAA,EAAW;AAAA,EAAa;AAAA,EAAa;AAAA,EAAW;AAAA,EAAY;AAAA,EAAU;AAAA,EAAW;AAAA,EACjF;AAAA,EAAa;AAAA,EAAa;AAAA,EAAW;AAAA,EAAW;AAAA,EAAW;AAAA,EAAW;AAAA,EAAY;AAAA,EAClF;AAAA,EAAa;AAAA,EAAY;AAAA,EAAY;AAAA,EAAa;AAAA,EAAW;AAAA,EAAa;AAAA,EAAa;AAAA,EACvF;AAAA,EAAU;AAAA,EAAa;AAAA,EAAW;AAAA,EAAW;AAAA,EAAa;AAAA,EAAW;AAAA,EAAY;AAAA,EACjF;AAAA,EAAY;AAAA,EAAa;AAAA,EAAW;AAAA,EAAY;AAAA,EAAW;AAAA,EAAa;AAAA,EAAW;AAAA,EACnF;AAAA,EAAa;AAAA,EAAY;AAAA,EAAa;AAAA,EAAa;AAAA,EAAY;AAAA,EAAU;AAAA,EAAW;AAAA,EACpF;AAAA,EAAY;AAAA,EAAW;AAAA,EAAW;AACpC,CAAC;AAMD,SAAS,WAAW,KAAa,MAAsB;AACrD,MAAI,OAAe;AAEnB,WAAS,IAAY,GAAG,IAAI,IAAI,QAAQ,KAAK;AAC3C,UAAM,OAAe,IAAI,WAAW,CAAC;AACrC,YAAQ,QAAQ,KAAK,OAAO;AAC5B,WAAO,OAAO;AAAA,EAChB;AAEA,SAAO,KAAK,IAAI,IAAI;AACtB;AAKA,SAAS,UAAU,UAA4B;AAC7C,QAAM,SAAmB,CAAC;AAC1B,QAAM,QAAgB,WAAW,UAAU,CAAC;AAC5C,QAAM,QAAgB,WAAW,UAAU,CAAC;AAE5C,WAAS,IAAY,GAAG,IAAI,kBAAkB,KAAK;AACjD,UAAM,OAAgB,QAAQ,IAAI,UAAW;AAC7C,WAAO,KAAK,OAAO,UAAU;AAAA,EAC/B;AAEA,SAAO;AACT;AAMA,SAAS,cAAc,UAA2B;AAChD,QAAM,SAAmB,UAAU,SAAS,YAAY,CAAC;AAEzD,aAAW,QAAQ,QAAQ;AACzB,UAAM,aAAqB,KAAK,MAAM,OAAO,EAAE;AAC/C,UAAM,WAAmB,OAAO;AAGhC,UAAM,SAA6B,cAAc,UAAU;AAC3D,QAAI,WAAW,WAAc,SAAU,KAAK,cAAe,GAAG;AAC5D,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO;AACT;AA0CO,IAAM,yBAAoC,CAC/C,UACA,UAA4B,CAAC,MAC1B;AACH,QAAM,EAAE,uBAAuB,KAAK,IAAwC;AAE5E,MAAI,CAAC,wBAAwB,SAAS,WAAW,GAAG;AAClD,WAAO,EAAE,QAAQ,KAAK;AAAA,EACxB;AAGA,MAAI,cAAc,QAAQ,GAAG;AAC3B,UAAM,SAAwB,CAAC;AAC/B,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,MAAM;AAAA,MACN;AAAA,MACA,SAAS,eAAe,wBAAwB,QAAQ,OAAO;AAAA,IACjE;AAAA,EACF;AAEA,SAAO,EAAE,QAAQ,KAAK;AACxB;;;ACjLA,IAAM,kBAAkB,CAAC,UAA0B;AACjD,QAAM,UAAkB,MAAM,QAAQ,GAAG;AAIzC,SAAO,UAAU,IAAI,MAAM,UAAU,GAAG,OAAO,IAAI;AACrD;AASA,IAAM,wBAAwB,CAAC,SAAyB;AACtD,QAAM,aAAqB,KAAK,YAAY,EAAE,KAAK;AAEnD,SAAO,WAAW,SAAS,GAAG,IAAI,gBAAgB,UAAU,IAAI;AAClE;AAqDO,IAAM,uBAAkC,CAAC,UAAU,UAAU,CAAC,MAAM;AACzE,QAAM,EAAE,eAAe,CAAC,EAAE,IAAyC;AAEnE,MAAI,aAAa,WAAW,KAAK,SAAS,WAAW,GAAG;AACtD,WAAO,EAAE,QAAQ,KAAK;AAAA,EACxB;AAEA,QAAM,gBAAwB,SAAS,YAAY;AAEnD,aAAW,QAAQ,cAAc;AAC/B,UAAM,aAAqB,sBAAsB,IAAI;AAGrD,QAAI,WAAW,SAAS,GAAG;AACzB;AAAA,IACF;AAGA,QAAI,cAAc,SAAS,UAAU,GAAG;AACtC,YAAM,SAAwB,CAAC;AAC/B,aAAO;AAAA,QACL,QAAQ;AAAA,QACR,MAAM;AAAA,QACN;AAAA,QACA,SAAS,eAAe,sBAAsB,QAAQ,OAAO;AAAA,MAC/D;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,QAAQ;AAAA,EACV;AACF;;;AR5DA,IAAM,kBAA4C;AAAA,EAChD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAqJA,IAAM,oBAAuC,OAAO,OAAO,CAAC,CAAC;AAG7D,IAAM,iBAA+C,OAAO,OAAO,CAAC,CAAC;AAGrE,IAAM,eAAuB;AAEtB,SAAS,iBACd,UACA,UAA4B,CAAC,GACX;AAIlB,QAAM,eAA+B,eAAe,UAAU,OAAO;AACrE,QAAM,kBAAkC,uBAAuB,UAAU,OAAO;AAChF,QAAM,mBAAmC,mBAAmB,UAAU,OAAO;AAC7E,QAAM,mBAAmC,mBAAmB,UAAU,OAAO;AAC7E,QAAM,uBAAuC,uBAAuB,UAAU,OAAO;AACrF,QAAM,qBAAqC,qBAAqB,UAAU,OAAO;AACjF,QAAM,wBAAwC,wBAAwB,UAAU,OAAO;AAKvF,QAAM,SAAmC;AAAA,IACvC,QAAQ,aAAa;AAAA,IACrB,gBAAgB,gBAAgB;AAAA,IAChC,YAAY,iBAAiB;AAAA,IAC7B,YAAY,iBAAiB;AAAA,IAC7B,iBAAiB,sBAAsB;AAAA,IACvC,gBAAgB,qBAAqB;AAAA,IACrC,cAAc,mBAAmB;AAAA,EACnC;AAEA,MAAI,eAAuB;AAC3B,MAAI;AAUJ,QAAM,SAAS,CAAC,OAAgB,WAAiC;AAC/D,QAAI,OAAO,QAAQ;AACjB;AACA;AAAA,IACF;AACA,UAAM,UAA6B;AAAA,MACjC;AAAA,MACA,MAAM,OAAO;AAAA,MACb,QAAQ,OAAO;AAAA,MACf,SAAS,OAAO;AAAA,IAClB;AACA,QAAI,aAAa,QAAW;AAC1B,iBAAW,CAAC,OAAO;AAAA,IACrB,OAAO;AACL,eAAS,KAAK,OAAO;AAAA,IACvB;AAAA,EACF;AAEA,SAAO,UAAU,YAAY;AAC7B,SAAO,kBAAkB,eAAe;AACxC,SAAO,cAAc,gBAAgB;AACrC,SAAO,cAAc,gBAAgB;AACrC,SAAO,kBAAkB,oBAAoB;AAC7C,SAAO,gBAAgB,kBAAkB;AACzC,SAAO,mBAAmB,qBAAqB;AAE/C,QAAM,QAAuB,KAAK;AAAA,IAChC;AAAA,IACA,KAAK,MAAO,eAAe,eAAgB,CAAC;AAAA,EAC9C;AAEA,QAAM,cAAiC,WACnC,SAAS,IAAI,CAAC,YAAY,QAAQ,OAAO,IACzC;AACJ,QAAM,kBAAsC,WAAW,CAAC,GAAG;AAE3D,SAAO;AAAA,IACL,OAAO,iBAAiB;AAAA,IACxB;AAAA;AAAA,IAEA,UAAU,gBAAgB,KAAK,KAAK;AAAA,IACpC,UAAU;AAAA,MACR,GAAI,oBAAoB,UAAa,EAAE,SAAS,gBAAgB;AAAA,MAChE;AAAA,IACF;AAAA,IACA;AAAA,IACA,UAAU,YAAY;AAAA,EACxB;AACF;","names":[]} |
+45
-12
@@ -29,14 +29,18 @@ /** | ||
| /** | ||
| * Individual validator check result | ||
| * Individual validator check result. A discriminated union on `passed`: the | ||
| * failure branch guarantees `message`, `code`, and `params` are present, so | ||
| * consumers can read them after a `passed === false` narrow without | ||
| * optional-chaining or non-null assertions. | ||
| */ | ||
| interface ValidatorCheck { | ||
| /** Whether this check passed */ | ||
| passed: boolean; | ||
| type ValidatorCheck = { | ||
| passed: true; | ||
| } | { | ||
| passed: false; | ||
| /** Default rendering of the message (English unless overridden via options) */ | ||
| message?: string; | ||
| /** Stable, locale-independent identifier for the failure (`undefined` when `passed: true`) */ | ||
| code?: MessageCode; | ||
| /** Interpolation values for the message template (`undefined` when `passed: true`) */ | ||
| params?: MessageParams; | ||
| } | ||
| message: string; | ||
| /** Stable, locale-independent identifier for the failure */ | ||
| code: MessageCode; | ||
| /** Interpolation values for the message template */ | ||
| params: MessageParams; | ||
| }; | ||
| /** | ||
@@ -47,2 +51,17 @@ * Identifiers for individual validation checks | ||
| /** | ||
| * A single failed check, surfaced on {@link ValidationResult.failures} so | ||
| * consumers can localize per-check failures via the stable `code`/`params` | ||
| * without re-running the individual validators. | ||
| */ | ||
| interface ValidationFailure { | ||
| /** Which check failed. */ | ||
| check: CheckId; | ||
| /** Stable, locale-independent failure identifier. */ | ||
| code: MessageCode; | ||
| /** Interpolation values for the message template. */ | ||
| params: MessageParams; | ||
| /** Default (English) rendering, or the consumer's `formatMessage` output. */ | ||
| message: string; | ||
| } | ||
| /** | ||
| * Result of password validation | ||
@@ -66,2 +85,9 @@ */ | ||
| checks: Record<CheckId, boolean>; | ||
| /** | ||
| * Structured per-check failures in evaluation order (empty when every check | ||
| * passes). Carries the stable `code`/`params` that `feedback.suggestions` | ||
| * (pre-rendered strings) and `checks` (booleans) don't expose — use these to | ||
| * localize per failing check from the zero-config `validatePassword` call. | ||
| */ | ||
| failures: readonly ValidationFailure[]; | ||
| } | ||
@@ -205,3 +231,6 @@ /** | ||
| * @remarks | ||
| * Accepted symbols: ! @ # $ % ^ & * ( ) _ + - = [ ] { } ; ' : " \ | , . < > / ? | ||
| * A symbol is any printable ASCII character that is not a letter or digit — | ||
| * code points 0x20–0x7E excluding 0-9, A-Z, a-z. This includes space, backtick | ||
| * (`` ` ``) and tilde (`~`), matching the character-class counting in | ||
| * `@sentinel-password/entropy`. | ||
| */ | ||
@@ -285,2 +314,6 @@ declare const hasSymbol: (password: string) => boolean; | ||
| * Only checks for consecutive repetition, not overall character frequency. | ||
| * | ||
| * Iterates by Unicode code point (via `for...of`), so a run of identical astral | ||
| * characters — e.g. repeated emoji — is counted as user-perceived characters | ||
| * rather than UTF-16 code units (which would let `😀😀😀😀` slip through). | ||
| */ | ||
@@ -469,2 +502,2 @@ declare const validateRepetition: Validator; | ||
| export { type CheckId, DEFAULT_TEMPLATES, type MessageCode, type MessageFormatter, type MessageParams, type StrengthLabel, type StrengthScore, type ValidationResult, type Validator, type ValidatorCheck, type ValidatorOptions, hasDigit, hasLowercase, hasSymbol, hasUppercase, validateCharacterTypes, validateCommonPassword, validateKeyboardPattern, validateLength, validatePassword, validatePersonalInfo, validateRepetition, validateSequential }; | ||
| export { type CheckId, DEFAULT_TEMPLATES, type MessageCode, type MessageFormatter, type MessageParams, type StrengthLabel, type StrengthScore, type ValidationFailure, type ValidationResult, type Validator, type ValidatorCheck, type ValidatorOptions, hasDigit, hasLowercase, hasSymbol, hasUppercase, validateCharacterTypes, validateCommonPassword, validateKeyboardPattern, validateLength, validatePassword, validatePersonalInfo, validateRepetition, validateSequential }; |
+45
-12
@@ -29,14 +29,18 @@ /** | ||
| /** | ||
| * Individual validator check result | ||
| * Individual validator check result. A discriminated union on `passed`: the | ||
| * failure branch guarantees `message`, `code`, and `params` are present, so | ||
| * consumers can read them after a `passed === false` narrow without | ||
| * optional-chaining or non-null assertions. | ||
| */ | ||
| interface ValidatorCheck { | ||
| /** Whether this check passed */ | ||
| passed: boolean; | ||
| type ValidatorCheck = { | ||
| passed: true; | ||
| } | { | ||
| passed: false; | ||
| /** Default rendering of the message (English unless overridden via options) */ | ||
| message?: string; | ||
| /** Stable, locale-independent identifier for the failure (`undefined` when `passed: true`) */ | ||
| code?: MessageCode; | ||
| /** Interpolation values for the message template (`undefined` when `passed: true`) */ | ||
| params?: MessageParams; | ||
| } | ||
| message: string; | ||
| /** Stable, locale-independent identifier for the failure */ | ||
| code: MessageCode; | ||
| /** Interpolation values for the message template */ | ||
| params: MessageParams; | ||
| }; | ||
| /** | ||
@@ -47,2 +51,17 @@ * Identifiers for individual validation checks | ||
| /** | ||
| * A single failed check, surfaced on {@link ValidationResult.failures} so | ||
| * consumers can localize per-check failures via the stable `code`/`params` | ||
| * without re-running the individual validators. | ||
| */ | ||
| interface ValidationFailure { | ||
| /** Which check failed. */ | ||
| check: CheckId; | ||
| /** Stable, locale-independent failure identifier. */ | ||
| code: MessageCode; | ||
| /** Interpolation values for the message template. */ | ||
| params: MessageParams; | ||
| /** Default (English) rendering, or the consumer's `formatMessage` output. */ | ||
| message: string; | ||
| } | ||
| /** | ||
| * Result of password validation | ||
@@ -66,2 +85,9 @@ */ | ||
| checks: Record<CheckId, boolean>; | ||
| /** | ||
| * Structured per-check failures in evaluation order (empty when every check | ||
| * passes). Carries the stable `code`/`params` that `feedback.suggestions` | ||
| * (pre-rendered strings) and `checks` (booleans) don't expose — use these to | ||
| * localize per failing check from the zero-config `validatePassword` call. | ||
| */ | ||
| failures: readonly ValidationFailure[]; | ||
| } | ||
@@ -205,3 +231,6 @@ /** | ||
| * @remarks | ||
| * Accepted symbols: ! @ # $ % ^ & * ( ) _ + - = [ ] { } ; ' : " \ | , . < > / ? | ||
| * A symbol is any printable ASCII character that is not a letter or digit — | ||
| * code points 0x20–0x7E excluding 0-9, A-Z, a-z. This includes space, backtick | ||
| * (`` ` ``) and tilde (`~`), matching the character-class counting in | ||
| * `@sentinel-password/entropy`. | ||
| */ | ||
@@ -285,2 +314,6 @@ declare const hasSymbol: (password: string) => boolean; | ||
| * Only checks for consecutive repetition, not overall character frequency. | ||
| * | ||
| * Iterates by Unicode code point (via `for...of`), so a run of identical astral | ||
| * characters — e.g. repeated emoji — is counted as user-perceived characters | ||
| * rather than UTF-16 code units (which would let `😀😀😀😀` slip through). | ||
| */ | ||
@@ -469,2 +502,2 @@ declare const validateRepetition: Validator; | ||
| export { type CheckId, DEFAULT_TEMPLATES, type MessageCode, type MessageFormatter, type MessageParams, type StrengthLabel, type StrengthScore, type ValidationResult, type Validator, type ValidatorCheck, type ValidatorOptions, hasDigit, hasLowercase, hasSymbol, hasUppercase, validateCharacterTypes, validateCommonPassword, validateKeyboardPattern, validateLength, validatePassword, validatePersonalInfo, validateRepetition, validateSequential }; | ||
| export { type CheckId, DEFAULT_TEMPLATES, type MessageCode, type MessageFormatter, type MessageParams, type StrengthLabel, type StrengthScore, type ValidationFailure, type ValidationResult, type Validator, type ValidatorCheck, type ValidatorOptions, hasDigit, hasLowercase, hasSymbol, hasUppercase, validateCharacterTypes, validateCommonPassword, validateKeyboardPattern, validateLength, validatePassword, validatePersonalInfo, validateRepetition, validateSequential }; |
+30
-88
@@ -66,65 +66,4 @@ // src/messages.ts | ||
| var hasDigit = (password) => /\d/.test(password); | ||
| var hasSymbol = (password) => /[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/.test(password); | ||
| var SYMBOL_CODES = /* @__PURE__ */ new Set([ | ||
| 33, | ||
| // ! | ||
| 34, | ||
| // " | ||
| 35, | ||
| // # | ||
| 36, | ||
| // $ | ||
| 37, | ||
| // % | ||
| 38, | ||
| // & | ||
| 39, | ||
| // ' | ||
| 40, | ||
| // ( | ||
| 41, | ||
| // ) | ||
| 42, | ||
| // * | ||
| 43, | ||
| // + | ||
| 44, | ||
| // , | ||
| 45, | ||
| // - | ||
| 46, | ||
| // . | ||
| 47, | ||
| // / | ||
| 58, | ||
| // : | ||
| 59, | ||
| // ; | ||
| 60, | ||
| // < | ||
| 61, | ||
| // = | ||
| 62, | ||
| // > | ||
| 63, | ||
| // ? | ||
| 64, | ||
| // @ | ||
| 91, | ||
| // [ | ||
| 92, | ||
| // \ | ||
| 93, | ||
| // ] | ||
| 94, | ||
| // ^ | ||
| 95, | ||
| // _ | ||
| 123, | ||
| // { | ||
| 124, | ||
| // | | ||
| 125 | ||
| // } | ||
| ]); | ||
| var hasSymbol = (password) => /[\x20-\x2F\x3A-\x40\x5B-\x60\x7B-\x7E]/.test(password); | ||
| var isSymbolCode = (c) => c >= 32 && c <= 47 || c >= 58 && c <= 64 || c >= 91 && c <= 96 || c >= 123 && c <= 126; | ||
| var validateCharacterTypes = (password, options = {}) => { | ||
@@ -153,3 +92,3 @@ const { | ||
| foundDigit = true; | ||
| } else if (!foundSymbol && SYMBOL_CODES.has(c)) { | ||
| } else if (!foundSymbol && isSymbolCode(c)) { | ||
| foundSymbol = true; | ||
@@ -194,9 +133,5 @@ } | ||
| const { maxRepeatedChars = 3 } = options; | ||
| if (password.length === 0) { | ||
| return { passed: true }; | ||
| } | ||
| let currentChar = password.charAt(0); | ||
| let count = 1; | ||
| for (let i = 1; i < password.length; i++) { | ||
| const char = password.charAt(i); | ||
| let currentChar; | ||
| let count = 0; | ||
| for (const char of password) { | ||
| if (char === currentChar) { | ||
@@ -868,2 +803,3 @@ count++; | ||
| var EMPTY_SUGGESTIONS = Object.freeze([]); | ||
| var EMPTY_FAILURES = Object.freeze([]); | ||
| var TOTAL_CHECKS = 7; | ||
@@ -888,5 +824,4 @@ function validatePassword(password, options = {}) { | ||
| let passedChecks = 0; | ||
| let suggestions; | ||
| let firstSuggestion; | ||
| const record = (result) => { | ||
| let failures; | ||
| const record = (check, result) => { | ||
| if (result.passed) { | ||
@@ -896,17 +831,21 @@ passedChecks++; | ||
| } | ||
| if (result.message === void 0) return; | ||
| if (suggestions === void 0) { | ||
| suggestions = [result.message]; | ||
| firstSuggestion = result.message; | ||
| const failure = { | ||
| check, | ||
| code: result.code, | ||
| params: result.params, | ||
| message: result.message | ||
| }; | ||
| if (failures === void 0) { | ||
| failures = [failure]; | ||
| } else { | ||
| suggestions.push(result.message); | ||
| failures.push(failure); | ||
| } | ||
| }; | ||
| record(lengthResult); | ||
| record(charTypesResult); | ||
| record(repetitionResult); | ||
| record(sequentialResult); | ||
| record(commonPasswordResult); | ||
| record(personalInfoResult); | ||
| record(keyboardPatternResult); | ||
| record("length", lengthResult); | ||
| record("characterTypes", charTypesResult); | ||
| record("repetition", repetitionResult); | ||
| record("sequential", sequentialResult); | ||
| record("commonPassword", commonPasswordResult); | ||
| record("personalInfo", personalInfoResult); | ||
| record("keyboardPattern", keyboardPatternResult); | ||
| const score = Math.min( | ||
@@ -916,2 +855,4 @@ 4, | ||
| ); | ||
| const suggestions = failures ? failures.map((failure) => failure.message) : EMPTY_SUGGESTIONS; | ||
| const firstSuggestion = failures?.[0]?.message; | ||
| return { | ||
@@ -924,5 +865,6 @@ valid: passedChecks === TOTAL_CHECKS, | ||
| ...firstSuggestion !== void 0 && { warning: firstSuggestion }, | ||
| suggestions: suggestions ?? EMPTY_SUGGESTIONS | ||
| suggestions | ||
| }, | ||
| checks | ||
| checks, | ||
| failures: failures ?? EMPTY_FAILURES | ||
| }; | ||
@@ -929,0 +871,0 @@ } |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../src/messages.ts","../src/validators/length.ts","../src/validators/character-types.ts","../src/validators/repetition.ts","../src/validators/sequential.ts","../src/validators/keyboard-pattern.ts","../src/validators/common-password.ts","../src/validators/personal-info.ts","../src/index.ts"],"sourcesContent":["import type { MessageCode, MessageParams, ValidatorOptions } from './types'\n\n/**\n * Built-in English templates for every {@link MessageCode}.\n *\n * The default rendering of a failed `ValidatorCheck.message` comes from this\n * map. Strings are stable across patch and minor releases — consumers can\n * still use them as translation keys with the legacy lookup-table pattern.\n * Prefer the `messages` / `formatMessage` options on {@link ValidatorOptions}.\n *\n * Placeholders use `{name}` syntax and are substituted by {@link formatTemplate}.\n */\nexport const DEFAULT_TEMPLATES: Readonly<Record<MessageCode, string>> = {\n 'length.tooShort': 'Password must be at least {minLength} characters',\n 'length.tooLong': 'Password must be at most {maxLength} characters',\n 'characterTypes.missing': 'Password must contain at least one {missing}',\n 'repetition.tooMany': 'Password contains too many repeated characters (max {maxRepeatedChars})',\n 'sequential.found': 'Password contains sequential characters (e.g., abc, 123)',\n 'keyboardPattern.found': 'Password contains common keyboard patterns',\n 'commonPassword.found': 'Password is too common. Please choose a more unique password.',\n 'personalInfo.found': 'Password contains personal information',\n} as const\n\nconst PLACEHOLDER_PATTERN: RegExp = /\\{(\\w+)\\}/g\n\n/**\n * Substitute `{name}` placeholders in `template` with values from `params`.\n *\n * Unknown placeholders are left intact (visible in the output) so missing\n * data surfaces as a noticeable bug rather than a silent omission. Values\n * are coerced to strings via the `String` constructor.\n *\n * @example\n * formatTemplate('Min {n} chars', { n: 8 }) // → \"Min 8 chars\"\n * formatTemplate('Need {a} and {b}', { a: 'X' }) // → \"Need X and {b}\"\n */\nexport function formatTemplate(template: string, params: MessageParams): string {\n return template.replace(PLACEHOLDER_PATTERN, (match, key: string): string => {\n const value: string | number | undefined = params[key]\n return value === undefined ? match : String(value)\n })\n}\n\n/**\n * Render a message via the fallback chain:\n * 1. `options.formatMessage(code, params, defaultMessage)` if provided\n * 2. `formatTemplate(options.messages[code], params)` if that override is provided\n * 3. `formatTemplate(DEFAULT_TEMPLATES[code], params)` (built-in English)\n *\n * Used by every validator's failure branch. Validators stay declarative —\n * they describe *what* failed (`code` + `params`) and delegate rendering.\n *\n * If `options.formatMessage` throws, this function swallows the error and\n * returns the default English rendering instead. Validators in this library\n * promise never to throw (see `Validator` in `./types`), and that promise\n * holds even when consumer-provided formatters misbehave.\n */\nexport function resolveMessage(\n code: MessageCode,\n params: MessageParams,\n options: ValidatorOptions\n): string {\n const defaultMessage: string = formatTemplate(DEFAULT_TEMPLATES[code], params)\n\n if (options.formatMessage) {\n try {\n return options.formatMessage(code, params, defaultMessage)\n } catch {\n return defaultMessage\n }\n }\n\n const override: string | undefined = options.messages?.[code]\n if (override !== undefined) {\n return formatTemplate(override, params)\n }\n\n return defaultMessage\n}\n","import type { MessageParams, Validator } from '../types'\nimport { resolveMessage } from '../messages'\n\n/**\n * Validates password length against minimum and maximum requirements\n *\n * @param password - Password to validate\n * @param options - Validation options containing minLength and maxLength\n * @returns Validator check result with passed status and optional error message\n *\n * @example\n * ```typescript\n * import { validateLength } from '@sentinel-password/core'\n *\n * // Default: min 8, max 128 characters\n * validateLength('short') // { passed: false, message: \"Password must be at least 8 characters\" }\n * validateLength('longenough') // { passed: true }\n *\n * // Custom length requirements\n * validateLength('password', { minLength: 12 }) // { passed: false, message: \"...\" }\n * validateLength('verylongpassword', { minLength: 12, maxLength: 20 }) // { passed: true }\n * ```\n *\n * @remarks\n * Default minimum length is 8 characters (OWASP recommendation).\n * Default maximum length is 128 characters (prevents DoS attacks).\n */\nexport const validateLength: Validator = (password, options = {}) => {\n const { minLength = 8, maxLength = 128 }: Partial<{ minLength: number; maxLength: number }> =\n options\n\n const length: number = password.length\n\n if (length < minLength) {\n const params: MessageParams = { minLength }\n return {\n passed: false,\n code: 'length.tooShort',\n params,\n message: resolveMessage('length.tooShort', params, options),\n }\n }\n\n if (length > maxLength) {\n const params: MessageParams = { maxLength }\n return {\n passed: false,\n code: 'length.tooLong',\n params,\n message: resolveMessage('length.tooLong', params, options),\n }\n }\n\n return {\n passed: true,\n }\n}\n","import type { MessageParams, Validator } from '../types'\nimport { resolveMessage } from '../messages'\n\n/**\n * Check if password contains at least one uppercase letter (A-Z)\n *\n * @param password - Password to check\n * @returns True if password contains at least one uppercase letter\n *\n * @example\n * ```typescript\n * hasUppercase('password') // false\n * hasUppercase('Password') // true\n * hasUppercase('PASSWORD') // true\n * ```\n */\nexport const hasUppercase = (password: string): boolean => /[A-Z]/.test(password)\n\n/**\n * Check if password contains at least one lowercase letter (a-z)\n *\n * @param password - Password to check\n * @returns True if password contains at least one lowercase letter\n *\n * @example\n * ```typescript\n * hasLowercase('PASSWORD') // false\n * hasLowercase('Password') // true\n * hasLowercase('password') // true\n * ```\n */\nexport const hasLowercase = (password: string): boolean => /[a-z]/.test(password)\n\n/**\n * Check if password contains at least one digit (0-9)\n *\n * @param password - Password to check\n * @returns True if password contains at least one digit\n *\n * @example\n * ```typescript\n * hasDigit('password') // false\n * hasDigit('password1') // true\n * hasDigit('123') // true\n * ```\n */\nexport const hasDigit = (password: string): boolean => /\\d/.test(password)\n\n/**\n * Check if password contains at least one symbol/special character\n *\n * @param password - Password to check\n * @returns True if password contains at least one special character\n *\n * @example\n * ```typescript\n * hasSymbol('password') // false\n * hasSymbol('password!') // true\n * hasSymbol('p@ssw0rd') // true\n * ```\n *\n * @remarks\n * Accepted symbols: ! @ # $ % ^ & * ( ) _ + - = [ ] { } ; ' : \" \\ | , . < > / ?\n */\nexport const hasSymbol = (password: string): boolean =>\n /[!@#$%^&*()_+\\-=[\\]{};':\"\\\\|,.<>/?]/.test(password)\n\n/**\n * Char codes for the symbol set above, packed into a Set for O(1) lookup during\n * the single-pass scan in {@link validateCharacterTypes}. Built once at module\n * load. Must stay in sync with the regex character class in {@link hasSymbol}.\n *\n * Backtick (0x60) is intentionally NOT in this set — it isn't in `hasSymbol`'s\n * regex either.\n */\nconst SYMBOL_CODES: ReadonlySet<number> = new Set<number>([\n 33, // !\n 34, // \"\n 35, // #\n 36, // $\n 37, // %\n 38, // &\n 39, // '\n 40, // (\n 41, // )\n 42, // *\n 43, // +\n 44, // ,\n 45, // -\n 46, // .\n 47, // /\n 58, // :\n 59, // ;\n 60, // <\n 61, // =\n 62, // >\n 63, // ?\n 64, // @\n 91, // [\n 92, // \\\n 93, // ]\n 94, // ^\n 95, // _\n 123, // {\n 124, // |\n 125, // }\n])\n\n/**\n * Validates character type requirements (uppercase, lowercase, digits, symbols)\n *\n * @param password - Password to validate\n * @param options - Validation options containing character type requirements\n * @returns Validator check result with passed status and optional error message\n *\n * @example\n * ```typescript\n * import { validateCharacterTypes } from '@sentinel-password/core'\n *\n * // No requirements by default\n * validateCharacterTypes('password') // { passed: true }\n *\n * // Require uppercase\n * validateCharacterTypes('password', { requireUppercase: true })\n * // { passed: false, message: \"Password must contain at least one uppercase letter\" }\n *\n * validateCharacterTypes('Password', { requireUppercase: true }) // { passed: true }\n *\n * // Require multiple types\n * validateCharacterTypes('password', {\n * requireUppercase: true,\n * requireDigit: true,\n * requireSymbol: true\n * })\n * // { passed: false, message: \"Password must contain at least one uppercase letter, digit, symbol\" }\n *\n * validateCharacterTypes('Password1!', {\n * requireUppercase: true,\n * requireDigit: true,\n * requireSymbol: true\n * }) // { passed: true }\n * ```\n *\n * @remarks\n * By default, no character types are required. Enable specific requirements via options.\n *\n * Implementation note: scans the password ONCE with `charCodeAt`, classifying\n * each char into uppercase / lowercase / digit / symbol, with early exit as\n * soon as every required class is satisfied. Faster than the previous\n * implementation that ran up to 4 separate `RegExp.test()` scans. The\n * individual `hasUppercase` / `hasLowercase` / `hasDigit` / `hasSymbol`\n * helpers above stay regex-based — they're independently exported and the\n * regex form is clearer for one-off calls.\n */\nexport const validateCharacterTypes: Validator = (password, options = {}) => {\n const {\n requireUppercase = false,\n requireLowercase = false,\n requireDigit = false,\n requireSymbol = false,\n }: Partial<{\n requireUppercase: boolean\n requireLowercase: boolean\n requireDigit: boolean\n requireSymbol: boolean\n }> = options\n\n // No requirements → no scan, no allocations.\n if (!requireUppercase && !requireLowercase && !requireDigit && !requireSymbol) {\n return { passed: true }\n }\n\n let foundUpper: boolean = !requireUppercase\n let foundLower: boolean = !requireLowercase\n let foundDigit: boolean = !requireDigit\n let foundSymbol: boolean = !requireSymbol\n\n for (let i: number = 0; i < password.length; i++) {\n if (foundUpper && foundLower && foundDigit && foundSymbol) break\n const c: number = password.charCodeAt(i)\n if (!foundUpper && c >= 65 && c <= 90) {\n foundUpper = true\n } else if (!foundLower && c >= 97 && c <= 122) {\n foundLower = true\n } else if (!foundDigit && c >= 48 && c <= 57) {\n foundDigit = true\n } else if (!foundSymbol && SYMBOL_CODES.has(c)) {\n foundSymbol = true\n }\n }\n\n if (foundUpper && foundLower && foundDigit && foundSymbol) {\n return { passed: true }\n }\n\n // Failure path: lazy-build the missing-types lists. Order MUST match the\n // pre-existing implementation: uppercase, lowercase, digit, symbol.\n const missing: string[] = []\n const missingTypes: string[] = []\n if (requireUppercase && !foundUpper) {\n missing.push('uppercase letter')\n missingTypes.push('uppercase')\n }\n if (requireLowercase && !foundLower) {\n missing.push('lowercase letter')\n missingTypes.push('lowercase')\n }\n if (requireDigit && !foundDigit) {\n missing.push('digit')\n missingTypes.push('digit')\n }\n if (requireSymbol && !foundSymbol) {\n missing.push('symbol')\n missingTypes.push('symbol')\n }\n\n const params: MessageParams = {\n missing: missing.join(', '),\n missingTypes: missingTypes.join(','),\n }\n return {\n passed: false,\n code: 'characterTypes.missing',\n params,\n message: resolveMessage('characterTypes.missing', params, options),\n }\n}\n","import type { MessageParams, Validator } from '../types'\nimport { resolveMessage } from '../messages'\n\n/**\n * Validates that password doesn't contain excessive repeated characters\n *\n * Uses a single-pass algorithm to detect consecutive repeated characters.\n * Helps prevent weak passwords like \"aaaa1111\" or \"passwordddd\".\n *\n * @param password - Password to validate\n * @param options - Validation options containing maxRepeatedChars\n * @returns Validator check result with passed status and optional error message\n *\n * @example\n * ```typescript\n * import { validateRepetition } from '@sentinel-password/core'\n *\n * // Default: max 3 repeated characters\n * validateRepetition('password') // { passed: true }\n * validateRepetition('passsword') // { passed: true } (3 s's)\n * validateRepetition('passssword') // { passed: false } (4 s's)\n *\n * // Custom limit\n * validateRepetition('passssword', { maxRepeatedChars: 5 }) // { passed: true }\n * validateRepetition('aaa') // { passed: true }\n * validateRepetition('aaaa') // { passed: false, message: \"Password contains too many repeated characters (max 3)\" }\n * ```\n *\n * @remarks\n * Default maximum is 3 consecutive repeated characters.\n * Only checks for consecutive repetition, not overall character frequency.\n */\nexport const validateRepetition: Validator = (password, options = {}) => {\n const { maxRepeatedChars = 3 }: Partial<{ maxRepeatedChars: number }> = options\n\n if (password.length === 0) {\n return { passed: true }\n }\n\n let currentChar: string = password.charAt(0)\n let count: number = 1\n\n for (let i: number = 1; i < password.length; i++) {\n const char: string = password.charAt(i)\n if (char === currentChar) {\n count++\n if (count > maxRepeatedChars) {\n const params: MessageParams = { maxRepeatedChars }\n return {\n passed: false,\n code: 'repetition.tooMany',\n params,\n message: resolveMessage('repetition.tooMany', params, options),\n }\n }\n } else {\n currentChar = char\n count = 1\n }\n }\n\n return {\n passed: true,\n }\n}\n","import type { MessageParams, Validator } from '../types'\nimport { resolveMessage } from '../messages'\n\n/**\n * Detects sequential character patterns (ascending or descending)\n * Checks for sequences of 3 or more consecutive characters\n *\n * @param str - String to check for sequences\n * @returns true if sequential pattern found, false otherwise\n */\nconst hasSequentialPattern = (str: string): boolean => {\n const minSequenceLength: number = 3\n\n for (let i: number = 0; i <= str.length - minSequenceLength; i++) {\n const charCode1: number = str.charCodeAt(i)\n const charCode2: number = str.charCodeAt(i + 1)\n const charCode3: number = str.charCodeAt(i + 2)\n\n // Check ascending sequence (e.g., abc, 123)\n if (charCode2 === charCode1 + 1 && charCode3 === charCode2 + 1) {\n return true\n }\n\n // Check descending sequence (e.g., cba, 321)\n if (charCode2 === charCode1 - 1 && charCode3 === charCode2 - 1) {\n return true\n }\n }\n\n return false\n}\n\n/**\n * Validates that password doesn't contain sequential character patterns\n *\n * Detects sequences like: abc, ABC, 123, 321, xyz, etc.\n * Uses character code comparison for efficient detection.\n * Helps prevent predictable passwords with keyboard sequences.\n *\n * @param password - Password to validate\n * @param options - Validation options containing checkSequential flag\n * @returns Validator check result with passed status and optional error message\n *\n * @example\n * ```typescript\n * import { validateSequential } from '@sentinel-password/core'\n *\n * // Detects ascending sequences\n * validateSequential('password') // { passed: true }\n * validateSequential('abc123') // { passed: false } (contains \"abc\" and \"123\")\n * validateSequential('xyz') // { passed: false } (contains \"xyz\")\n *\n * // Detects descending sequences\n * validateSequential('cba321') // { passed: false } (contains \"cba\" and \"321\")\n *\n * // Disable check\n * validateSequential('abc123', { checkSequential: false }) // { passed: true }\n * ```\n *\n * @remarks\n * Enabled by default. Checks for 3 or more consecutive characters in sequence.\n * Case-sensitive: detects both \"abc\" and \"ABC\" as separate patterns.\n *\n * **Overlap with `validateKeyboardPattern`:** The numeric runs `123`, `456`,\n * `789` (and their reverses) are also matched by the keyboard-pattern\n * validator's numeric-keypad list. To allow simple numeric runs in a\n * password you must set BOTH `checkSequential: false` AND\n * `checkKeyboardPatterns: false` — disabling either alone will not let\n * `password123` through. The two checks are deliberately independent\n * defences (code-point runs vs. keyboard-locality runs).\n */\nexport const validateSequential: Validator = (password, options = {}) => {\n const { checkSequential = true }: Partial<{ checkSequential: boolean }> = options\n\n if (!checkSequential) {\n return { passed: true }\n }\n\n if (hasSequentialPattern(password)) {\n const params: MessageParams = {}\n return {\n passed: false,\n code: 'sequential.found',\n params,\n message: resolveMessage('sequential.found', params, options),\n }\n }\n\n return {\n passed: true,\n }\n}\n","import type { MessageParams, ValidatorCheck, ValidatorOptions } from '../types'\nimport { resolveMessage } from '../messages'\n\n/**\n * Common keyboard patterns to detect across multiple layouts\n * Supports: QWERTY, AZERTY, QWERTZ, Dvorak, Colemak, and Cyrillic\n */\nconst KEYBOARD_PATTERNS: readonly string[] = [\n // === QWERTY (English, US, UK) ===\n // Full rows\n 'qwertyuiop',\n 'asdfghjkl',\n 'zxcvbnm',\n // Common typing patterns (adjacent keys)\n 'qwert',\n 'werty',\n 'asdfg',\n 'sdfgh',\n 'zxcvb',\n 'xcvbn',\n // Short sequences (3+ chars)\n 'qwe',\n 'asd',\n 'zxc',\n 'rty',\n 'fgh',\n 'cvb',\n 'poi',\n 'lkj',\n 'mnb',\n // Columns (top to bottom)\n '1qaz',\n '2wsx',\n '3edc',\n '4rfv',\n '5tgb',\n '6yhn',\n '7ujm',\n '8ik',\n '9ol',\n '0p',\n // Diagonals\n 'qaz',\n 'wsx',\n 'edc',\n 'zaq',\n 'xsw',\n 'cde',\n\n // === AZERTY (French, Belgian) ===\n // Full rows\n 'azertyuiop',\n 'qsdfghjklm',\n 'wxcvbn',\n // Common patterns\n 'azert',\n 'zerty',\n 'qsdfg',\n 'sdfgh',\n 'wxcvb',\n // Short sequences\n 'aze',\n 'qsd',\n 'wxc',\n\n // === QWERTZ (German, Central European) ===\n // Full rows\n 'qwertzuiop',\n 'yxcvbnm',\n // Common patterns\n 'qwertz',\n 'yxcvb',\n // Short sequences\n 'yxc',\n // Note: Patterns 'qwe', 'asd', 'asdfg', and 'asdfghjkl' are shared with QWERTY and listed above for efficiency.\n\n // === Dvorak ===\n // Full rows\n 'pyfgcrl',\n 'aoeuidhtns',\n 'qjkxbmwvz',\n // Common patterns\n 'aoeu',\n 'htns',\n 'qjkx',\n\n // === Colemak ===\n // Full rows\n 'qwfpgjluy',\n 'arstdhneio',\n 'zxcvbkm',\n // Common patterns\n 'arst',\n 'dhne',\n 'zxcv',\n\n // === Cyrillic (ЙЦУКЕН - Russian) ===\n // Full rows (Cyrillic characters)\n 'йцукенгшщзхъ',\n 'фывапролджэ',\n 'ячсмитьбю',\n // Common patterns\n 'йцукен',\n 'фывап',\n 'ячсми',\n 'цукен',\n\n // === Numeric patterns (universal) ===\n // Number row\n '1234567890',\n '0987654321',\n // Numeric keypad (rows)\n '789',\n '456',\n '123',\n // Numeric keypad (columns)\n '741',\n '852',\n '963',\n '7410',\n '8520',\n '9630',\n] as const\n\n/**\n * Escape regex metacharacters in a string so it matches literally.\n * Defensive: none of the current patterns contain metacharacters, but this\n * keeps future additions safe (e.g., if a layout's pattern contains `+` or `$`).\n */\nfunction escapeRegex(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\n/**\n * Single regex matching ANY keyboard pattern in either forward or reverse\n * direction, case-insensitively. Built once at module load.\n *\n * Performance note: this replaced an earlier implementation that ran a\n * `for` loop over `KEYBOARD_PATTERNS`, calling `pattern.split('').reverse().join('')`\n * to build the reverse string AND `lowercase.includes(pattern)` on every iteration\n * (~480 allocations per call). A single regex test against an NFA-compiled\n * alternation is ~12× faster on typical passwords on V8/Node 22.\n *\n * If a future V8 regex bug surfaces (e.g., a Unicode case-folding regression\n * affecting Cyrillic with the `/i` flag), there's a side-by-side comparison\n * against a precomputed-array loop variant in\n * `packages/core/tests/performance.bench.ts` → `Keyboard pattern: implementation comparison`.\n * The loop variant is ~2.5× faster than the original split/reverse/join code\n * and ~5× slower than the regex, so it's a usable rollback target.\n */\nconst KEYBOARD_REGEX: RegExp = new RegExp(\n KEYBOARD_PATTERNS.flatMap((p: string): readonly string[] => {\n const reversed: string = p.split('').reverse().join('')\n return [escapeRegex(p), escapeRegex(reversed)]\n }).join('|'),\n 'i'\n)\n\n/**\n * Validates that password does not contain common keyboard patterns\n *\n * Detects patterns across multiple keyboard layouts:\n * - QWERTY (English, US, UK): qwerty, asdfgh, 1qaz2wsx\n * - AZERTY (French, Belgian): azerty, qsdfg\n * - QWERTZ (German, Central European): qwertz, yxcvb\n * - Dvorak (Alternative English): aoeu, htns\n * - Colemak (Alternative English): arst, dhne\n * - Cyrillic (Russian ЙЦУКЕН): йцукен, фывап\n * - Universal numeric: 123, 789, numeric keypad patterns\n * - Both forward and reverse patterns\n *\n * @param password - The password to validate\n * @param options - Validation options\n * @returns Validation result\n *\n * @example\n * ```typescript\n * validateKeyboardPattern('qwerty123') // { passed: false, message: '...' }\n * validateKeyboardPattern('azerty456') // { passed: false, message: '...' } (AZERTY)\n * validateKeyboardPattern('йцукен') // { passed: false, message: '...' } (Cyrillic)\n * validateKeyboardPattern('MyP@ssw0rd') // { passed: true }\n * validateKeyboardPattern('asdfgh', { checkKeyboardPatterns: false }) // { passed: true }\n * ```\n *\n * @remarks\n * **Overlap with `validateSequential`:** The numeric-keypad rows `123`,\n * `456`, `789` (and their reverses) are also caught by the sequential\n * validator's `charCodeAt`-consecutive check. To allow simple numeric\n * runs in a password you must set BOTH `checkKeyboardPatterns: false`\n * AND `checkSequential: false` — disabling either alone will not let\n * `password123` through. The two checks are deliberately independent\n * defences (keyboard-locality runs vs. code-point runs).\n */\nexport function validateKeyboardPattern(\n password: string,\n options: ValidatorOptions = {}\n): ValidatorCheck {\n const { checkKeyboardPatterns = true }: Partial<{ checkKeyboardPatterns: boolean }> = options\n\n if (!checkKeyboardPatterns) {\n return { passed: true }\n }\n\n if (KEYBOARD_REGEX.test(password)) {\n const emptyParams: MessageParams = {}\n return {\n passed: false,\n code: 'keyboardPattern.found',\n params: emptyParams,\n message: resolveMessage('keyboardPattern.found', emptyParams, options),\n }\n }\n\n return { passed: true }\n}\n","import type { MessageParams, Validator, ValidatorOptions } from '../types'\nimport { resolveMessage } from '../messages'\n\n/**\n * Bloom filter for common passwords\n *\n * Source: SecLists https://github.com/danielmiessler/SecLists\n * File: Passwords/Common-Credentials/10k-most-common.txt (top 1,000)\n * Local: packages/core/data/common-passwords.txt\n *\n * Regenerate with: pnpm --filter @sentinel-password/core generate:bloom\n */\n\n// --- BEGIN GENERATED BLOOM FILTER ---\n// Generated from: packages/core/data/common-passwords.txt\n// Passwords: 1000 | Bloom size: 12000 bits | Hash functions: 7\nconst BLOOM_SIZE: number = 12000\nconst BLOOM_HASH_COUNT: number = 7\n\nconst BLOOM_BUCKETS: Int32Array = new Int32Array([\n -1274142440, -1983615455, -2110842727, -1440077142, 1782964852, 956870738, 48445478, 290080800,\n -1961772502, -1869995330, 787111091, 140549329, -1508237141, -800970204, -1987272632, -1439602637,\n 1837731885, 240419586, 2143496680, 547359246, -435494235, -1549227926, 151650466, -1742731903,\n -1535043550, -1607849886, -464376123, 1858481975, -1339118942, 410034491, -1397081975, 1217017604,\n -1934873593, -1769402149, -1107107031, -1098029453, 4380674, -393017311, -1465113568, 183557329,\n 1713935653, -99073116, 179529869, -1372675933, 78050105, 313074313, -1696462848, 404392594,\n -252409893, -1320442777, -1474295681, -1360120173, -743767380, 135925863, -1462755274, 562596610,\n -483730736, -2002210214, 1803176, 448811114, -1408849403, -735411494, -1872045334, 914538658,\n -374794234, 710617115, -1909840320, -2119695701, -1979682099, -1671944535, -1872492857, 570434164,\n -1334628885, 438020137, -1087528694, 464528967, -359521705, -1407023569, -1759335432, -1181701078,\n -1959746004, -1910872796, 1771191076, -1699312926, -1837137785, 132360706, -1002778102,\n 1384163571, 1786784659, -1354832568, -1364547569, -269830616, -1970133308, 848887818, -827577818,\n -1103500629, 2071996096, 1760287282, 1768617098, -1845466197, -1877054343, -223851966,\n -1037686738, 581441802, -761237365, -1282923217, 1328736770, 557363947, 369254184, -1708971466,\n -874268952, -2010768709, -1735196024, -1023761881, 772024867, -328714721, -125771638, 1644593802,\n 1762927155, -1509621238, 671919842, -1102294492, -2010455282, 302591302, -2136446493, -1986908626,\n -2018566016, 671347107, 842040354, 243352234, -534627656, -2011035123, -350834429, -844096647,\n -1598189902, -1833872134, -2021613470, -1968994020, -1297357224, 1210222837, -399712104,\n 906129314, -1430115578, -2113618941, -906361552, 551676011, 2819256, -1710063476, 1099626796,\n 1026172967, 640491579, -1440579096, -1887270648, -1710185214, 1721813836, 984101107, 1944258615,\n 724137993, -947645533, 1913070606, 44829860, 101219306, 1011556358, -1432173338, -482828238,\n -2103243204, 447353024, -329610536, 1398671502, 1623196423, 348309551, -211221782, 1980906030,\n 778830798, 968125066, -1458011638, -1337934774, 671089894, 52600876, -120418625, -1163255178,\n 680202936, -1597565152, 36835260, 1845733400, 153924331, 40509970, 411221088, 2114095983,\n 171043813, 1488604610, 172814850, -1702382524, 1502872105, 747635458, -501052624, 411301928,\n 731823798, -1333221971, -398552509, 134621864, -1872819037, 1822818465, -1893684382, -1316869470,\n -931632595, -1951260093, 598498019, -1290239178, -989323246, 573220082, 675809232, 169874120,\n -559880256, -910750974, 194548323, 736774690, -719202768, 1814594698, -1155290520, 237063072,\n -488083322, -924144083, 36385696, 715956226, -1568572256, 635709488, 1336062594, 320907784,\n 1124501765, -1406433280, -1963307926, -391509877, 1063369670, -1566479450, -1472058776,\n -1985331186, 1377869964, 942987938, 1109713448, 831578699, 853962256, 92835849, 2035317046,\n -2001761759, 216178811, 463827086, 1675819625, -1301764214, -234217277, -1459402461, 579365994,\n -1878875352, -1973245719, -1598478333, 1611084451, 1659513348, -1539122450, 837944629, 154749747,\n -500636954, 1746899746, 714271018, -1150679461, -1538612630, 1814069472, -492197206, -192202641,\n -756936190, -939252957, -1240063770, 1279410916, -1302054220, 1410991882, 1000401066, -198048178,\n -1419228998, -1742550398, -1991360862, -2111688732, -1700640206, 2061912618, -2103274201,\n -288731098, 578232, 53128594, 1755908256, 1116379816, 797411910, -1589060869, -1072342375,\n 845812237, 1749465810, 872549054, -1498077050, -1766137746, -376831803, -2084952921, 1202203194,\n 1216389770, -1104576382, 818061341, 942317755, -1578565146, 2083914250, -1012266815, 579371193,\n -461075653, 710981226, 1578192039, 681770739, -376523066, 1244735807, -490842006, -781193759,\n 556277799, 1984167471, -2000977760, -1265456598, 151665664, -1842697570, 579340290, 1082298021,\n 245211512, -1867881849, -1341630964, -90545664, 1806344898, 47088170, 992486842, -1558631407,\n -1449508691, -2006144829, 547374756, 912919200, 708120146, 616106151, 1172449414, 2124751394,\n -2073427929, 1009261122, 1210336770, -1973924734, 144458353, -1155209078, -1962399613, 897229836,\n 12357639, -1299101013, 312943930, 708920290, -1310023134, 639040320, -821838839, -2014506238,\n -274610648, -1442508704, 177432331, 1721041571, 414065321, -1272924654, 756208161, 1772773934,\n -1484380630, 1101004848, -1054523758, -2116504793, 1118667297, 86058657, 975856680, 749906058,\n 1937961650, 620890537, 918765730, -1366546774,\n])\n// --- END GENERATED BLOOM FILTER ---\n\n/**\n * Hash function for bloom filter\n */\nfunction hashString(str: string, seed: number): number {\n let hash: number = seed\n\n for (let i: number = 0; i < str.length; i++) {\n const char: number = str.charCodeAt(i)\n hash = (hash << 5) - hash + char\n hash = hash | 0 // Convert to 32-bit integer\n }\n\n return Math.abs(hash)\n}\n\n/**\n * Get multiple hash positions for a password\n */\nfunction getHashes(password: string): number[] {\n const hashes: number[] = []\n const hash1: number = hashString(password, 0)\n const hash2: number = hashString(password, 1)\n\n for (let i: number = 0; i < BLOOM_HASH_COUNT; i++) {\n const hash: number = (hash1 + i * hash2) >>> 0\n hashes.push(hash % BLOOM_SIZE)\n }\n\n return hashes\n}\n\n/**\n * Check if password might be in the common password list\n * Note: Bloom filters can have false positives (~0.84%) but never false negatives\n */\nfunction mightBeCommon(password: string): boolean {\n const hashes: number[] = getHashes(password.toLowerCase())\n\n for (const hash of hashes) {\n const arrayIndex: number = Math.floor(hash / 32)\n const bitIndex: number = hash % 32\n\n // Bounds check for TypeScript strict mode\n const bucket: number | undefined = BLOOM_BUCKETS[arrayIndex]\n if (bucket === undefined || (bucket & (1 << bitIndex)) === 0) {\n return false\n }\n }\n\n return true\n}\n\n/**\n * Validates that a password is not in the common password list\n *\n * Uses a Bloom filter to efficiently check against the top 1,000 most common passwords.\n * Case-insensitive matching prevents simple case variations of common passwords.\n *\n * @param password - Password to validate\n * @param options - Validation options containing checkCommonPasswords flag\n * @returns Validator check result with passed status and optional error message\n *\n * @example\n * ```typescript\n * import { validateCommonPassword } from '@sentinel-password/core'\n *\n * // Detects common passwords\n * validateCommonPassword('password')\n * // { passed: false, message: \"Password is too common. Please choose a more unique password.\" }\n *\n * validateCommonPassword('123456')\n * // { passed: false }\n *\n * // Case-insensitive\n * validateCommonPassword('PASSWORD')\n * // { passed: false }\n *\n * // Unique passwords pass\n * validateCommonPassword('MyUn1qu3P@ssw0rd!')\n * // { passed: true }\n *\n * // Disable check\n * validateCommonPassword('password', { checkCommonPasswords: false })\n * // { passed: true }\n * ```\n *\n * @remarks\n * Checks against top 1,000 most common passwords from SecLists.\n * Uses Bloom filter for space efficiency (~1.5KB vs ~8KB for raw array).\n * False positive rate: ~0.84% (may rarely flag uncommon passwords).\n * Enabled by default for security.\n */\nexport const validateCommonPassword: Validator = (\n password: string,\n options: ValidatorOptions = {}\n) => {\n const { checkCommonPasswords = true }: { checkCommonPasswords?: boolean } = options\n\n if (!checkCommonPasswords || password.length === 0) {\n return { passed: true }\n }\n\n // Case-insensitive check using bloom filter\n if (mightBeCommon(password)) {\n const params: MessageParams = {}\n return {\n passed: false,\n code: 'commonPassword.found',\n params,\n message: resolveMessage('commonPassword.found', params, options),\n }\n }\n\n return { passed: true }\n}\n","import type { MessageParams, Validator } from '../types'\nimport { resolveMessage } from '../messages'\n\n/**\n * Extracts username from email address\n * @param email - Email address\n * @returns Username part before @ symbol\n */\nconst extractUsername = (email: string): string => {\n const atIndex: number = email.indexOf('@')\n /* v8 ignore next */\n return atIndex > 0 ? email.substring(0, atIndex) : email\n}\n\n/**\n * Normalizes personal info strings for comparison\n * Converts to lowercase and extracts username from emails\n *\n * @param info - Personal information string\n * @returns Normalized string for comparison\n */\nconst normalizePersonalInfo = (info: string): string => {\n const normalized: string = info.toLowerCase().trim()\n // Extract username from email if it looks like an email\n return normalized.includes('@') ? extractUsername(normalized) : normalized\n}\n\n/**\n * Validates that password doesn't contain personal information\n *\n * Checks against provided personal info (username, email, name, etc.)\n * Uses case-insensitive comparison and extracts usernames from email addresses.\n * Ignores very short strings (< 3 characters) to avoid false positives.\n *\n * @param password - Password to validate\n * @param options - Validation options containing personalInfo array\n * @returns Validator check result with passed status and optional error message\n *\n * @example\n * ```typescript\n * import { validatePersonalInfo } from '@sentinel-password/core'\n *\n * // No personal info by default\n * validatePersonalInfo('password') // { passed: true }\n *\n * // Detects username in password\n * validatePersonalInfo('johnpassword', { personalInfo: ['john'] })\n * // { passed: false, message: \"Password contains personal information\" }\n *\n * // Emails are reduced to the *entire local part* (everything before `@`),\n * // matched as a literal substring — not split into name fragments.\n * validatePersonalInfo('john.doe123', { personalInfo: ['john.doe@example.com'] })\n * // { passed: false } (matches the full local part \"john.doe\")\n *\n * validatePersonalInfo('john123', { personalInfo: ['john.doe@example.com'] })\n * // { passed: true } (the local part is \"john.doe\", not \"john\" — no match)\n *\n * // To reject passwords containing just \"john\", pass it explicitly:\n * validatePersonalInfo('john123', { personalInfo: ['john', 'john.doe@example.com'] })\n * // { passed: false } (matches the literal \"john\")\n *\n * // Case-insensitive\n * validatePersonalInfo('JOHN123', { personalInfo: ['john'] })\n * // { passed: false }\n *\n * // Ignores short strings\n * validatePersonalInfo('password', { personalInfo: ['pw'] }) // { passed: true } (too short)\n *\n * // Multiple personal info items\n * validatePersonalInfo('secretpass', {\n * personalInfo: ['john', 'doe', 'john.doe@example.com']\n * }) // { passed: true }\n * ```\n *\n * @remarks\n * Best practice: Provide username, email, first name, last name, and company name.\n * Strings shorter than 3 characters are ignored to prevent false positives.\n */\nexport const validatePersonalInfo: Validator = (password, options = {}) => {\n const { personalInfo = [] }: Partial<{ personalInfo: string[] }> = options\n\n if (personalInfo.length === 0 || password.length === 0) {\n return { passed: true }\n }\n\n const lowerPassword: string = password.toLowerCase()\n\n for (const info of personalInfo) {\n const normalized: string = normalizePersonalInfo(info)\n\n // Skip very short strings to avoid false positives\n if (normalized.length < 3) {\n continue\n }\n\n // Check if password contains this personal information\n if (lowerPassword.includes(normalized)) {\n const params: MessageParams = {}\n return {\n passed: false,\n code: 'personalInfo.found',\n params,\n message: resolveMessage('personalInfo.found', params, options),\n }\n }\n }\n\n return {\n passed: true,\n }\n}\n","/**\n * @sentinel-password/core\n * Zero-dependency TypeScript password validation library\n */\n\nexport type {\n StrengthScore,\n StrengthLabel,\n ValidationResult,\n ValidatorOptions,\n ValidatorCheck,\n Validator,\n CheckId,\n MessageCode,\n MessageParams,\n MessageFormatter,\n} from './types'\n\nexport { DEFAULT_TEMPLATES } from './messages'\n\nexport { validateLength } from './validators/length'\nexport {\n hasUppercase,\n hasLowercase,\n hasDigit,\n hasSymbol,\n validateCharacterTypes,\n} from './validators/character-types'\nexport { validateRepetition } from './validators/repetition'\nexport { validateSequential } from './validators/sequential'\nexport { validateKeyboardPattern } from './validators/keyboard-pattern'\nexport { validateCommonPassword } from './validators/common-password'\nexport { validatePersonalInfo } from './validators/personal-info'\n\nimport type {\n ValidationResult,\n ValidatorOptions,\n StrengthScore,\n StrengthLabel,\n ValidatorCheck,\n CheckId,\n} from './types'\nimport { validateLength } from './validators/length'\nimport { validateCharacterTypes } from './validators/character-types'\nimport { validateRepetition } from './validators/repetition'\nimport { validateSequential } from './validators/sequential'\nimport { validateKeyboardPattern } from './validators/keyboard-pattern'\nimport { validateCommonPassword } from './validators/common-password'\nimport { validatePersonalInfo } from './validators/personal-info'\n\nconst STRENGTH_LABELS: readonly StrengthLabel[] = [\n 'very-weak',\n 'weak',\n 'medium',\n 'strong',\n 'very-strong',\n] as const\n\n/**\n * Validate a password with comprehensive security checks\n *\n * Performs multiple validation checks including length, character types, repetition,\n * sequential patterns, keyboard patterns, common passwords, and personal information.\n * Returns detailed feedback with strength score and actionable suggestions.\n *\n * @param password - The password string to validate\n * @param options - Optional validation configuration\n * @returns Validation result with score, strength label, feedback, and individual check results\n *\n * @example\n * **Basic usage (zero-config)**\n * ```typescript\n * import { validatePassword } from '@sentinel-password/core'\n *\n * const result = validatePassword('MySecure!Pass_w0rd')\n * console.log(result.valid) // true\n * console.log(result.score) // 4 (0-4 scale)\n * console.log(result.strength) // 'very-strong'\n * console.log(result.feedback.warning) // undefined (no issues)\n * console.log(result.feedback.suggestions) // []\n * ```\n *\n * @example\n * **With a known-common password**\n * ```typescript\n * const result = validatePassword('password')\n * console.log(result.valid) // false (commonPassword check rejected it)\n * console.log(result.score) // 4\n * console.log(result.strength) // 'very-strong'\n * console.log(result.feedback.warning)\n * // \"Password is too common. Please choose a more unique password.\"\n * console.log(result.feedback.suggestions)\n * // [\"Password is too common. Please choose a more unique password.\"]\n * console.log(result.checks)\n * // { length: true, characterTypes: true, repetition: true, sequential: true,\n * // keyboardPattern: true, commonPassword: false, personalInfo: true }\n * // ↑ 6 of 7 checks pass, so score is 4 (\"very-strong\") even though valid is false.\n * // Always use `valid` (or `result.checks`) for acceptance decisions, not `strength`.\n * ```\n *\n * @example\n * **Custom length requirements**\n * ```typescript\n * const result = validatePassword('MyP@ss', {\n * minLength: 12,\n * maxLength: 64\n * })\n * console.log(result.valid) // false\n * console.log(result.feedback.warning) // \"Password must be at least 12 characters\"\n * ```\n *\n * @example\n * **Require specific character types**\n * ```typescript\n * const result = validatePassword('password123', {\n * requireUppercase: true,\n * requireLowercase: true,\n * requireDigit: true,\n * requireSymbol: true\n * })\n * console.log(result.valid) // false\n * console.log(result.feedback.warning) // \"Password must contain at least one uppercase letter, symbol\"\n * ```\n *\n * @example\n * **Prevent personal information**\n * ```typescript\n * const result = validatePassword('john1234!', {\n * personalInfo: ['john', 'john.doe@example.com', 'Doe']\n * })\n * console.log(result.valid) // false\n * console.log(result.feedback.warning) // \"Password contains personal information\"\n * ```\n *\n * @example\n * **Disable specific checks**\n * ```typescript\n * const result = validatePassword('qwerty123', {\n * checkKeyboardPatterns: false, // Allow keyboard patterns\n * checkSequential: false, // Allow sequential chars\n * checkCommonPasswords: false // Allow common passwords\n * })\n * // More permissive validation\n * ```\n *\n * @example\n * **Comprehensive configuration**\n * ```typescript\n * const result = validatePassword('MyP@ssw0rd2024!', {\n * minLength: 12,\n * maxLength: 128,\n * requireUppercase: true,\n * requireLowercase: true,\n * requireDigit: true,\n * requireSymbol: true,\n * maxRepeatedChars: 2,\n * checkSequential: true,\n * checkKeyboardPatterns: true,\n * checkCommonPasswords: true,\n * personalInfo: ['user', 'admin', 'test']\n * })\n * ```\n *\n * @remarks\n * **Default behavior:**\n * - Minimum length: 8 characters\n * - Maximum length: 128 characters\n * - No character type requirements (but recommended to enable)\n * - Max repeated characters: 3\n * - Sequential check: enabled\n * - Keyboard pattern check: enabled\n * - Common password check: enabled (top 1,000 passwords)\n * - Personal info check: disabled (provide personalInfo array to enable)\n *\n * **Scoring:**\n * - `score` = `Math.min(4, Math.floor((passedChecks / 7) * 5))` — purely a\n * passed-check ratio.\n * - `strength` is the human label for that score (`very-weak` … `very-strong`).\n * - Because scoring is ratio-based, a password that fails *only* the\n * common-password (or personal-info, or sequential, etc.) check still passes\n * 6 of 7 checks and lands on `score: 4 / strength: 'very-strong'` while\n * `valid` is `false`. Use `valid` (or inspect `result.checks`) for\n * acceptance decisions; use `strength` for UX cues like progress bars.\n *\n * **Performance:**\n * - All validators run in O(n) time or better\n * - Typical validation: < 1ms for passwords up to 128 characters\n * - Bloom filter for common passwords: O(1) lookup\n *\n * **Security:**\n * - All checks are case-insensitive where applicable\n * - No password data is logged or stored\n * - Runs purely in-process — no network calls, the password never leaves the\n * caller's runtime\n * - This is a *strength* validator, not a password-comparison primitive. The\n * validators use early-return `includes()`/loops and are not constant-time,\n * but timing is not a relevant attack surface here: the patterns being\n * checked (length, character types, common-password list, keyboard layouts)\n * are all public — there's no secret to leak via timing. When you compare\n * a password against a stored hash, use a library like Argon2/bcrypt that\n * provides constant-time verification — that's a separate concern from\n * strength validation.\n */\n/** Frozen empty array shared across success-path callers to avoid the\n * per-call `suggestions: []` allocation when no validator fails. */\nconst EMPTY_SUGGESTIONS: readonly string[] = Object.freeze([])\n\n/** Total number of validators run by `validatePassword`. Compile-time constant. */\nconst TOTAL_CHECKS: number = 7\n\nexport function validatePassword(\n password: string,\n options: ValidatorOptions = {}\n): ValidationResult {\n // Run all 7 validators. Captured into individual locals (not an intermediate\n // record) so we can read `.passed` and `.message` once each without going\n // through Object.values/Object.keys iterations.\n const lengthResult: ValidatorCheck = validateLength(password, options)\n const charTypesResult: ValidatorCheck = validateCharacterTypes(password, options)\n const repetitionResult: ValidatorCheck = validateRepetition(password, options)\n const sequentialResult: ValidatorCheck = validateSequential(password, options)\n const commonPasswordResult: ValidatorCheck = validateCommonPassword(password, options)\n const personalInfoResult: ValidatorCheck = validatePersonalInfo(password, options)\n const keyboardPatternResult: ValidatorCheck = validateKeyboardPattern(password, options)\n\n // Build `checks` and `passedChecks` in a single pass. Lazy-allocate\n // `suggestions` only when a validator actually fails — most calls in\n // practice produce all-passing results and pay no allocation cost.\n const checks: Record<CheckId, boolean> = {\n length: lengthResult.passed,\n characterTypes: charTypesResult.passed,\n repetition: repetitionResult.passed,\n sequential: sequentialResult.passed,\n keyboardPattern: keyboardPatternResult.passed,\n commonPassword: commonPasswordResult.passed,\n personalInfo: personalInfoResult.passed,\n }\n\n let passedChecks: number = 0\n let suggestions: string[] | undefined\n let firstSuggestion: string | undefined\n\n /**\n * Accumulate a validator's pass/fail outcome. Increments `passedChecks` on\n * success; otherwise lazy-allocates `suggestions` (so the success-path call\n * pays no array allocation) and records the first message for `feedback.warning`.\n */\n const record = (result: ValidatorCheck): void => {\n if (result.passed) {\n passedChecks++\n return\n }\n /* v8 ignore next */\n if (result.message === undefined) return\n if (suggestions === undefined) {\n suggestions = [result.message]\n firstSuggestion = result.message\n } else {\n suggestions.push(result.message)\n }\n }\n\n record(lengthResult)\n record(charTypesResult)\n record(repetitionResult)\n record(sequentialResult)\n record(commonPasswordResult)\n record(personalInfoResult)\n record(keyboardPatternResult)\n\n const score: StrengthScore = Math.min(\n 4,\n Math.floor((passedChecks / TOTAL_CHECKS) * 5)\n ) as StrengthScore\n\n return {\n valid: passedChecks === TOTAL_CHECKS,\n score,\n /* v8 ignore next */\n strength: STRENGTH_LABELS[score] ?? 'very-weak',\n feedback: {\n ...(firstSuggestion !== undefined && { warning: firstSuggestion }),\n suggestions: suggestions ?? EMPTY_SUGGESTIONS,\n },\n checks,\n }\n}\n"],"mappings":"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","names":[]} | ||
| {"version":3,"sources":["../src/messages.ts","../src/validators/length.ts","../src/validators/character-types.ts","../src/validators/repetition.ts","../src/validators/sequential.ts","../src/validators/keyboard-pattern.ts","../src/validators/common-password.ts","../src/validators/personal-info.ts","../src/index.ts"],"sourcesContent":["import type { MessageCode, MessageParams, ValidatorOptions } from './types'\n\n/**\n * Built-in English templates for every {@link MessageCode}.\n *\n * The default rendering of a failed `ValidatorCheck.message` comes from this\n * map. Strings are stable across patch and minor releases — consumers can\n * still use them as translation keys with the legacy lookup-table pattern.\n * Prefer the `messages` / `formatMessage` options on {@link ValidatorOptions}.\n *\n * Placeholders use `{name}` syntax and are substituted by {@link formatTemplate}.\n */\nexport const DEFAULT_TEMPLATES: Readonly<Record<MessageCode, string>> = {\n 'length.tooShort': 'Password must be at least {minLength} characters',\n 'length.tooLong': 'Password must be at most {maxLength} characters',\n 'characterTypes.missing': 'Password must contain at least one {missing}',\n 'repetition.tooMany': 'Password contains too many repeated characters (max {maxRepeatedChars})',\n 'sequential.found': 'Password contains sequential characters (e.g., abc, 123)',\n 'keyboardPattern.found': 'Password contains common keyboard patterns',\n 'commonPassword.found': 'Password is too common. Please choose a more unique password.',\n 'personalInfo.found': 'Password contains personal information',\n} as const\n\nconst PLACEHOLDER_PATTERN: RegExp = /\\{(\\w+)\\}/g\n\n/**\n * Substitute `{name}` placeholders in `template` with values from `params`.\n *\n * Unknown placeholders are left intact (visible in the output) so missing\n * data surfaces as a noticeable bug rather than a silent omission. Values\n * are coerced to strings via the `String` constructor.\n *\n * @example\n * formatTemplate('Min {n} chars', { n: 8 }) // → \"Min 8 chars\"\n * formatTemplate('Need {a} and {b}', { a: 'X' }) // → \"Need X and {b}\"\n */\nexport function formatTemplate(template: string, params: MessageParams): string {\n return template.replace(PLACEHOLDER_PATTERN, (match, key: string): string => {\n const value: string | number | undefined = params[key]\n return value === undefined ? match : String(value)\n })\n}\n\n/**\n * Render a message via the fallback chain:\n * 1. `options.formatMessage(code, params, defaultMessage)` if provided\n * 2. `formatTemplate(options.messages[code], params)` if that override is provided\n * 3. `formatTemplate(DEFAULT_TEMPLATES[code], params)` (built-in English)\n *\n * Used by every validator's failure branch. Validators stay declarative —\n * they describe *what* failed (`code` + `params`) and delegate rendering.\n *\n * If `options.formatMessage` throws, this function swallows the error and\n * returns the default English rendering instead. Validators in this library\n * promise never to throw (see `Validator` in `./types`), and that promise\n * holds even when consumer-provided formatters misbehave.\n */\nexport function resolveMessage(\n code: MessageCode,\n params: MessageParams,\n options: ValidatorOptions\n): string {\n const defaultMessage: string = formatTemplate(DEFAULT_TEMPLATES[code], params)\n\n if (options.formatMessage) {\n try {\n return options.formatMessage(code, params, defaultMessage)\n } catch {\n return defaultMessage\n }\n }\n\n const override: string | undefined = options.messages?.[code]\n if (override !== undefined) {\n return formatTemplate(override, params)\n }\n\n return defaultMessage\n}\n","import type { MessageParams, Validator } from '../types'\nimport { resolveMessage } from '../messages'\n\n/**\n * Validates password length against minimum and maximum requirements\n *\n * @param password - Password to validate\n * @param options - Validation options containing minLength and maxLength\n * @returns Validator check result with passed status and optional error message\n *\n * @example\n * ```typescript\n * import { validateLength } from '@sentinel-password/core'\n *\n * // Default: min 8, max 128 characters\n * validateLength('short') // { passed: false, message: \"Password must be at least 8 characters\" }\n * validateLength('longenough') // { passed: true }\n *\n * // Custom length requirements\n * validateLength('password', { minLength: 12 }) // { passed: false, message: \"...\" }\n * validateLength('verylongpassword', { minLength: 12, maxLength: 20 }) // { passed: true }\n * ```\n *\n * @remarks\n * Default minimum length is 8 characters (OWASP recommendation).\n * Default maximum length is 128 characters (prevents DoS attacks).\n */\nexport const validateLength: Validator = (password, options = {}) => {\n const { minLength = 8, maxLength = 128 }: Partial<{ minLength: number; maxLength: number }> =\n options\n\n const length: number = password.length\n\n if (length < minLength) {\n const params: MessageParams = { minLength }\n return {\n passed: false,\n code: 'length.tooShort',\n params,\n message: resolveMessage('length.tooShort', params, options),\n }\n }\n\n if (length > maxLength) {\n const params: MessageParams = { maxLength }\n return {\n passed: false,\n code: 'length.tooLong',\n params,\n message: resolveMessage('length.tooLong', params, options),\n }\n }\n\n return {\n passed: true,\n }\n}\n","import type { MessageParams, Validator } from '../types'\nimport { resolveMessage } from '../messages'\n\n/**\n * Check if password contains at least one uppercase letter (A-Z)\n *\n * @param password - Password to check\n * @returns True if password contains at least one uppercase letter\n *\n * @example\n * ```typescript\n * hasUppercase('password') // false\n * hasUppercase('Password') // true\n * hasUppercase('PASSWORD') // true\n * ```\n */\nexport const hasUppercase = (password: string): boolean => /[A-Z]/.test(password)\n\n/**\n * Check if password contains at least one lowercase letter (a-z)\n *\n * @param password - Password to check\n * @returns True if password contains at least one lowercase letter\n *\n * @example\n * ```typescript\n * hasLowercase('PASSWORD') // false\n * hasLowercase('Password') // true\n * hasLowercase('password') // true\n * ```\n */\nexport const hasLowercase = (password: string): boolean => /[a-z]/.test(password)\n\n/**\n * Check if password contains at least one digit (0-9)\n *\n * @param password - Password to check\n * @returns True if password contains at least one digit\n *\n * @example\n * ```typescript\n * hasDigit('password') // false\n * hasDigit('password1') // true\n * hasDigit('123') // true\n * ```\n */\nexport const hasDigit = (password: string): boolean => /\\d/.test(password)\n\n/**\n * Check if password contains at least one symbol/special character\n *\n * @param password - Password to check\n * @returns True if password contains at least one special character\n *\n * @example\n * ```typescript\n * hasSymbol('password') // false\n * hasSymbol('password!') // true\n * hasSymbol('p@ssw0rd') // true\n * ```\n *\n * @remarks\n * A symbol is any printable ASCII character that is not a letter or digit —\n * code points 0x20–0x7E excluding 0-9, A-Z, a-z. This includes space, backtick\n * (`` ` ``) and tilde (`~`), matching the character-class counting in\n * `@sentinel-password/entropy`.\n */\nexport const hasSymbol = (password: string): boolean =>\n /[\\x20-\\x2F\\x3A-\\x40\\x5B-\\x60\\x7B-\\x7E]/.test(password)\n\n/**\n * Char-code counterpart of {@link hasSymbol}, used by the single-pass scan in\n * {@link validateCharacterTypes}: true for any printable ASCII code point that\n * is not a letter or digit (0x20-0x2F, 0x3A-0x40, 0x5B-0x60, 0x7B-0x7E). Covers\n * space (0x20), backtick (0x60) and tilde (0x7E). Must stay in sync with the\n * regex character class in {@link hasSymbol}.\n */\nconst isSymbolCode = (c: number): boolean =>\n (c >= 0x20 && c <= 0x2f) ||\n (c >= 0x3a && c <= 0x40) ||\n (c >= 0x5b && c <= 0x60) ||\n (c >= 0x7b && c <= 0x7e)\n\n/**\n * Validates character type requirements (uppercase, lowercase, digits, symbols)\n *\n * @param password - Password to validate\n * @param options - Validation options containing character type requirements\n * @returns Validator check result with passed status and optional error message\n *\n * @example\n * ```typescript\n * import { validateCharacterTypes } from '@sentinel-password/core'\n *\n * // No requirements by default\n * validateCharacterTypes('password') // { passed: true }\n *\n * // Require uppercase\n * validateCharacterTypes('password', { requireUppercase: true })\n * // { passed: false, message: \"Password must contain at least one uppercase letter\" }\n *\n * validateCharacterTypes('Password', { requireUppercase: true }) // { passed: true }\n *\n * // Require multiple types\n * validateCharacterTypes('password', {\n * requireUppercase: true,\n * requireDigit: true,\n * requireSymbol: true\n * })\n * // { passed: false, message: \"Password must contain at least one uppercase letter, digit, symbol\" }\n *\n * validateCharacterTypes('Password1!', {\n * requireUppercase: true,\n * requireDigit: true,\n * requireSymbol: true\n * }) // { passed: true }\n * ```\n *\n * @remarks\n * By default, no character types are required. Enable specific requirements via options.\n *\n * Implementation note: scans the password ONCE with `charCodeAt`, classifying\n * each char into uppercase / lowercase / digit / symbol, with early exit as\n * soon as every required class is satisfied. Faster than the previous\n * implementation that ran up to 4 separate `RegExp.test()` scans. The\n * individual `hasUppercase` / `hasLowercase` / `hasDigit` / `hasSymbol`\n * helpers above stay regex-based — they're independently exported and the\n * regex form is clearer for one-off calls.\n */\nexport const validateCharacterTypes: Validator = (password, options = {}) => {\n const {\n requireUppercase = false,\n requireLowercase = false,\n requireDigit = false,\n requireSymbol = false,\n }: Partial<{\n requireUppercase: boolean\n requireLowercase: boolean\n requireDigit: boolean\n requireSymbol: boolean\n }> = options\n\n // No requirements → no scan, no allocations.\n if (!requireUppercase && !requireLowercase && !requireDigit && !requireSymbol) {\n return { passed: true }\n }\n\n let foundUpper: boolean = !requireUppercase\n let foundLower: boolean = !requireLowercase\n let foundDigit: boolean = !requireDigit\n let foundSymbol: boolean = !requireSymbol\n\n for (let i: number = 0; i < password.length; i++) {\n if (foundUpper && foundLower && foundDigit && foundSymbol) break\n const c: number = password.charCodeAt(i)\n if (!foundUpper && c >= 65 && c <= 90) {\n foundUpper = true\n } else if (!foundLower && c >= 97 && c <= 122) {\n foundLower = true\n } else if (!foundDigit && c >= 48 && c <= 57) {\n foundDigit = true\n } else if (!foundSymbol && isSymbolCode(c)) {\n foundSymbol = true\n }\n }\n\n if (foundUpper && foundLower && foundDigit && foundSymbol) {\n return { passed: true }\n }\n\n // Failure path: lazy-build the missing-types lists. Order MUST match the\n // pre-existing implementation: uppercase, lowercase, digit, symbol.\n const missing: string[] = []\n const missingTypes: string[] = []\n if (requireUppercase && !foundUpper) {\n missing.push('uppercase letter')\n missingTypes.push('uppercase')\n }\n if (requireLowercase && !foundLower) {\n missing.push('lowercase letter')\n missingTypes.push('lowercase')\n }\n if (requireDigit && !foundDigit) {\n missing.push('digit')\n missingTypes.push('digit')\n }\n if (requireSymbol && !foundSymbol) {\n missing.push('symbol')\n missingTypes.push('symbol')\n }\n\n const params: MessageParams = {\n missing: missing.join(', '),\n missingTypes: missingTypes.join(','),\n }\n return {\n passed: false,\n code: 'characterTypes.missing',\n params,\n message: resolveMessage('characterTypes.missing', params, options),\n }\n}\n","import type { MessageParams, Validator } from '../types'\nimport { resolveMessage } from '../messages'\n\n/**\n * Validates that password doesn't contain excessive repeated characters\n *\n * Uses a single-pass algorithm to detect consecutive repeated characters.\n * Helps prevent weak passwords like \"aaaa1111\" or \"passwordddd\".\n *\n * @param password - Password to validate\n * @param options - Validation options containing maxRepeatedChars\n * @returns Validator check result with passed status and optional error message\n *\n * @example\n * ```typescript\n * import { validateRepetition } from '@sentinel-password/core'\n *\n * // Default: max 3 repeated characters\n * validateRepetition('password') // { passed: true }\n * validateRepetition('passsword') // { passed: true } (3 s's)\n * validateRepetition('passssword') // { passed: false } (4 s's)\n *\n * // Custom limit\n * validateRepetition('passssword', { maxRepeatedChars: 5 }) // { passed: true }\n * validateRepetition('aaa') // { passed: true }\n * validateRepetition('aaaa') // { passed: false, message: \"Password contains too many repeated characters (max 3)\" }\n * ```\n *\n * @remarks\n * Default maximum is 3 consecutive repeated characters.\n * Only checks for consecutive repetition, not overall character frequency.\n *\n * Iterates by Unicode code point (via `for...of`), so a run of identical astral\n * characters — e.g. repeated emoji — is counted as user-perceived characters\n * rather than UTF-16 code units (which would let `😀😀😀😀` slip through).\n */\nexport const validateRepetition: Validator = (password, options = {}) => {\n const { maxRepeatedChars = 3 }: Partial<{ maxRepeatedChars: number }> = options\n\n let currentChar: string | undefined\n let count: number = 0\n\n for (const char of password) {\n if (char === currentChar) {\n count++\n if (count > maxRepeatedChars) {\n const params: MessageParams = { maxRepeatedChars }\n return {\n passed: false,\n code: 'repetition.tooMany',\n params,\n message: resolveMessage('repetition.tooMany', params, options),\n }\n }\n } else {\n currentChar = char\n count = 1\n }\n }\n\n return {\n passed: true,\n }\n}\n","import type { MessageParams, Validator } from '../types'\nimport { resolveMessage } from '../messages'\n\n/**\n * Detects sequential character patterns (ascending or descending)\n * Checks for sequences of 3 or more consecutive characters\n *\n * @param str - String to check for sequences\n * @returns true if sequential pattern found, false otherwise\n */\nconst hasSequentialPattern = (str: string): boolean => {\n const minSequenceLength: number = 3\n\n for (let i: number = 0; i <= str.length - minSequenceLength; i++) {\n const charCode1: number = str.charCodeAt(i)\n const charCode2: number = str.charCodeAt(i + 1)\n const charCode3: number = str.charCodeAt(i + 2)\n\n // Check ascending sequence (e.g., abc, 123)\n if (charCode2 === charCode1 + 1 && charCode3 === charCode2 + 1) {\n return true\n }\n\n // Check descending sequence (e.g., cba, 321)\n if (charCode2 === charCode1 - 1 && charCode3 === charCode2 - 1) {\n return true\n }\n }\n\n return false\n}\n\n/**\n * Validates that password doesn't contain sequential character patterns\n *\n * Detects sequences like: abc, ABC, 123, 321, xyz, etc.\n * Uses character code comparison for efficient detection.\n * Helps prevent predictable passwords with keyboard sequences.\n *\n * @param password - Password to validate\n * @param options - Validation options containing checkSequential flag\n * @returns Validator check result with passed status and optional error message\n *\n * @example\n * ```typescript\n * import { validateSequential } from '@sentinel-password/core'\n *\n * // Detects ascending sequences\n * validateSequential('password') // { passed: true }\n * validateSequential('abc123') // { passed: false } (contains \"abc\" and \"123\")\n * validateSequential('xyz') // { passed: false } (contains \"xyz\")\n *\n * // Detects descending sequences\n * validateSequential('cba321') // { passed: false } (contains \"cba\" and \"321\")\n *\n * // Disable check\n * validateSequential('abc123', { checkSequential: false }) // { passed: true }\n * ```\n *\n * @remarks\n * Enabled by default. Checks for 3 or more consecutive characters in sequence.\n * Case-sensitive: detects both \"abc\" and \"ABC\" as separate patterns.\n *\n * **Overlap with `validateKeyboardPattern`:** The numeric runs `123`, `456`,\n * `789` (and their reverses) are also matched by the keyboard-pattern\n * validator's numeric-keypad list. To allow simple numeric runs in a\n * password you must set BOTH `checkSequential: false` AND\n * `checkKeyboardPatterns: false` — disabling either alone will not let\n * `password123` through. The two checks are deliberately independent\n * defences (code-point runs vs. keyboard-locality runs).\n */\nexport const validateSequential: Validator = (password, options = {}) => {\n const { checkSequential = true }: Partial<{ checkSequential: boolean }> = options\n\n if (!checkSequential) {\n return { passed: true }\n }\n\n if (hasSequentialPattern(password)) {\n const params: MessageParams = {}\n return {\n passed: false,\n code: 'sequential.found',\n params,\n message: resolveMessage('sequential.found', params, options),\n }\n }\n\n return {\n passed: true,\n }\n}\n","import type { MessageParams, ValidatorCheck, ValidatorOptions } from '../types'\nimport { resolveMessage } from '../messages'\n\n/**\n * Common keyboard patterns to detect across multiple layouts\n * Supports: QWERTY, AZERTY, QWERTZ, Dvorak, Colemak, and Cyrillic\n */\nconst KEYBOARD_PATTERNS: readonly string[] = [\n // === QWERTY (English, US, UK) ===\n // Full rows\n 'qwertyuiop',\n 'asdfghjkl',\n 'zxcvbnm',\n // Common typing patterns (adjacent keys)\n 'qwert',\n 'werty',\n 'asdfg',\n 'sdfgh',\n 'zxcvb',\n 'xcvbn',\n // Short sequences (3+ chars)\n 'qwe',\n 'asd',\n 'zxc',\n 'rty',\n 'fgh',\n 'cvb',\n 'poi',\n 'lkj',\n 'mnb',\n // Columns (top to bottom)\n '1qaz',\n '2wsx',\n '3edc',\n '4rfv',\n '5tgb',\n '6yhn',\n '7ujm',\n '8ik',\n '9ol',\n '0p',\n // Diagonals\n 'qaz',\n 'wsx',\n 'edc',\n 'zaq',\n 'xsw',\n 'cde',\n\n // === AZERTY (French, Belgian) ===\n // Full rows\n 'azertyuiop',\n 'qsdfghjklm',\n 'wxcvbn',\n // Common patterns\n 'azert',\n 'zerty',\n 'qsdfg',\n 'sdfgh',\n 'wxcvb',\n // Short sequences\n 'aze',\n 'qsd',\n 'wxc',\n\n // === QWERTZ (German, Central European) ===\n // Full rows\n 'qwertzuiop',\n 'yxcvbnm',\n // Common patterns\n 'qwertz',\n 'yxcvb',\n // Short sequences\n 'yxc',\n // Note: Patterns 'qwe', 'asd', 'asdfg', and 'asdfghjkl' are shared with QWERTY and listed above for efficiency.\n\n // === Dvorak ===\n // Full rows\n 'pyfgcrl',\n 'aoeuidhtns',\n 'qjkxbmwvz',\n // Common patterns\n 'aoeu',\n 'htns',\n 'qjkx',\n\n // === Colemak ===\n // Full rows\n 'qwfpgjluy',\n 'arstdhneio',\n 'zxcvbkm',\n // Common patterns\n 'arst',\n 'dhne',\n 'zxcv',\n\n // === Cyrillic (ЙЦУКЕН - Russian) ===\n // Full rows (Cyrillic characters)\n 'йцукенгшщзхъ',\n 'фывапролджэ',\n 'ячсмитьбю',\n // Common patterns\n 'йцукен',\n 'фывап',\n 'ячсми',\n 'цукен',\n\n // === Numeric patterns (universal) ===\n // Number row\n '1234567890',\n '0987654321',\n // Numeric keypad (rows)\n '789',\n '456',\n '123',\n // Numeric keypad (columns)\n '741',\n '852',\n '963',\n '7410',\n '8520',\n '9630',\n] as const\n\n/**\n * Escape regex metacharacters in a string so it matches literally.\n * Defensive: none of the current patterns contain metacharacters, but this\n * keeps future additions safe (e.g., if a layout's pattern contains `+` or `$`).\n */\nfunction escapeRegex(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\n/**\n * Single regex matching ANY keyboard pattern in either forward or reverse\n * direction, case-insensitively. Built once at module load.\n *\n * Performance note: this replaced an earlier implementation that ran a\n * `for` loop over `KEYBOARD_PATTERNS`, calling `pattern.split('').reverse().join('')`\n * to build the reverse string AND `lowercase.includes(pattern)` on every iteration\n * (~480 allocations per call). A single regex test against an NFA-compiled\n * alternation is ~12× faster on typical passwords on V8/Node 22.\n *\n * If a future V8 regex bug surfaces (e.g., a Unicode case-folding regression\n * affecting Cyrillic with the `/i` flag), there's a side-by-side comparison\n * against a precomputed-array loop variant in\n * `packages/core/tests/performance.bench.ts` → `Keyboard pattern: implementation comparison`.\n * The loop variant is ~2.5× faster than the original split/reverse/join code\n * and ~5× slower than the regex, so it's a usable rollback target.\n */\nconst KEYBOARD_REGEX: RegExp = new RegExp(\n KEYBOARD_PATTERNS.flatMap((p: string): readonly string[] => {\n const reversed: string = p.split('').reverse().join('')\n return [escapeRegex(p), escapeRegex(reversed)]\n }).join('|'),\n 'i'\n)\n\n/**\n * Validates that password does not contain common keyboard patterns\n *\n * Detects patterns across multiple keyboard layouts:\n * - QWERTY (English, US, UK): qwerty, asdfgh, 1qaz2wsx\n * - AZERTY (French, Belgian): azerty, qsdfg\n * - QWERTZ (German, Central European): qwertz, yxcvb\n * - Dvorak (Alternative English): aoeu, htns\n * - Colemak (Alternative English): arst, dhne\n * - Cyrillic (Russian ЙЦУКЕН): йцукен, фывап\n * - Universal numeric: 123, 789, numeric keypad patterns\n * - Both forward and reverse patterns\n *\n * @param password - The password to validate\n * @param options - Validation options\n * @returns Validation result\n *\n * @example\n * ```typescript\n * validateKeyboardPattern('qwerty123') // { passed: false, message: '...' }\n * validateKeyboardPattern('azerty456') // { passed: false, message: '...' } (AZERTY)\n * validateKeyboardPattern('йцукен') // { passed: false, message: '...' } (Cyrillic)\n * validateKeyboardPattern('MyP@ssw0rd') // { passed: true }\n * validateKeyboardPattern('asdfgh', { checkKeyboardPatterns: false }) // { passed: true }\n * ```\n *\n * @remarks\n * **Overlap with `validateSequential`:** The numeric-keypad rows `123`,\n * `456`, `789` (and their reverses) are also caught by the sequential\n * validator's `charCodeAt`-consecutive check. To allow simple numeric\n * runs in a password you must set BOTH `checkKeyboardPatterns: false`\n * AND `checkSequential: false` — disabling either alone will not let\n * `password123` through. The two checks are deliberately independent\n * defences (keyboard-locality runs vs. code-point runs).\n */\nexport function validateKeyboardPattern(\n password: string,\n options: ValidatorOptions = {}\n): ValidatorCheck {\n const { checkKeyboardPatterns = true }: Partial<{ checkKeyboardPatterns: boolean }> = options\n\n if (!checkKeyboardPatterns) {\n return { passed: true }\n }\n\n if (KEYBOARD_REGEX.test(password)) {\n const emptyParams: MessageParams = {}\n return {\n passed: false,\n code: 'keyboardPattern.found',\n params: emptyParams,\n message: resolveMessage('keyboardPattern.found', emptyParams, options),\n }\n }\n\n return { passed: true }\n}\n","import type { MessageParams, Validator, ValidatorOptions } from '../types'\nimport { resolveMessage } from '../messages'\n\n/**\n * Bloom filter for common passwords\n *\n * Source: SecLists https://github.com/danielmiessler/SecLists\n * File: Passwords/Common-Credentials/10k-most-common.txt (top 1,000)\n * Local: packages/core/data/common-passwords.txt\n *\n * Regenerate with: pnpm --filter @sentinel-password/core generate:bloom\n */\n\n// --- BEGIN GENERATED BLOOM FILTER ---\n// Generated from: packages/core/data/common-passwords.txt\n// Passwords: 1000 | Bloom size: 12000 bits | Hash functions: 7\nconst BLOOM_SIZE: number = 12000\nconst BLOOM_HASH_COUNT: number = 7\n\nconst BLOOM_BUCKETS: Int32Array = new Int32Array([\n -1274142440, -1983615455, -2110842727, -1440077142, 1782964852, 956870738, 48445478, 290080800,\n -1961772502, -1869995330, 787111091, 140549329, -1508237141, -800970204, -1987272632, -1439602637,\n 1837731885, 240419586, 2143496680, 547359246, -435494235, -1549227926, 151650466, -1742731903,\n -1535043550, -1607849886, -464376123, 1858481975, -1339118942, 410034491, -1397081975, 1217017604,\n -1934873593, -1769402149, -1107107031, -1098029453, 4380674, -393017311, -1465113568, 183557329,\n 1713935653, -99073116, 179529869, -1372675933, 78050105, 313074313, -1696462848, 404392594,\n -252409893, -1320442777, -1474295681, -1360120173, -743767380, 135925863, -1462755274, 562596610,\n -483730736, -2002210214, 1803176, 448811114, -1408849403, -735411494, -1872045334, 914538658,\n -374794234, 710617115, -1909840320, -2119695701, -1979682099, -1671944535, -1872492857, 570434164,\n -1334628885, 438020137, -1087528694, 464528967, -359521705, -1407023569, -1759335432, -1181701078,\n -1959746004, -1910872796, 1771191076, -1699312926, -1837137785, 132360706, -1002778102,\n 1384163571, 1786784659, -1354832568, -1364547569, -269830616, -1970133308, 848887818, -827577818,\n -1103500629, 2071996096, 1760287282, 1768617098, -1845466197, -1877054343, -223851966,\n -1037686738, 581441802, -761237365, -1282923217, 1328736770, 557363947, 369254184, -1708971466,\n -874268952, -2010768709, -1735196024, -1023761881, 772024867, -328714721, -125771638, 1644593802,\n 1762927155, -1509621238, 671919842, -1102294492, -2010455282, 302591302, -2136446493, -1986908626,\n -2018566016, 671347107, 842040354, 243352234, -534627656, -2011035123, -350834429, -844096647,\n -1598189902, -1833872134, -2021613470, -1968994020, -1297357224, 1210222837, -399712104,\n 906129314, -1430115578, -2113618941, -906361552, 551676011, 2819256, -1710063476, 1099626796,\n 1026172967, 640491579, -1440579096, -1887270648, -1710185214, 1721813836, 984101107, 1944258615,\n 724137993, -947645533, 1913070606, 44829860, 101219306, 1011556358, -1432173338, -482828238,\n -2103243204, 447353024, -329610536, 1398671502, 1623196423, 348309551, -211221782, 1980906030,\n 778830798, 968125066, -1458011638, -1337934774, 671089894, 52600876, -120418625, -1163255178,\n 680202936, -1597565152, 36835260, 1845733400, 153924331, 40509970, 411221088, 2114095983,\n 171043813, 1488604610, 172814850, -1702382524, 1502872105, 747635458, -501052624, 411301928,\n 731823798, -1333221971, -398552509, 134621864, -1872819037, 1822818465, -1893684382, -1316869470,\n -931632595, -1951260093, 598498019, -1290239178, -989323246, 573220082, 675809232, 169874120,\n -559880256, -910750974, 194548323, 736774690, -719202768, 1814594698, -1155290520, 237063072,\n -488083322, -924144083, 36385696, 715956226, -1568572256, 635709488, 1336062594, 320907784,\n 1124501765, -1406433280, -1963307926, -391509877, 1063369670, -1566479450, -1472058776,\n -1985331186, 1377869964, 942987938, 1109713448, 831578699, 853962256, 92835849, 2035317046,\n -2001761759, 216178811, 463827086, 1675819625, -1301764214, -234217277, -1459402461, 579365994,\n -1878875352, -1973245719, -1598478333, 1611084451, 1659513348, -1539122450, 837944629, 154749747,\n -500636954, 1746899746, 714271018, -1150679461, -1538612630, 1814069472, -492197206, -192202641,\n -756936190, -939252957, -1240063770, 1279410916, -1302054220, 1410991882, 1000401066, -198048178,\n -1419228998, -1742550398, -1991360862, -2111688732, -1700640206, 2061912618, -2103274201,\n -288731098, 578232, 53128594, 1755908256, 1116379816, 797411910, -1589060869, -1072342375,\n 845812237, 1749465810, 872549054, -1498077050, -1766137746, -376831803, -2084952921, 1202203194,\n 1216389770, -1104576382, 818061341, 942317755, -1578565146, 2083914250, -1012266815, 579371193,\n -461075653, 710981226, 1578192039, 681770739, -376523066, 1244735807, -490842006, -781193759,\n 556277799, 1984167471, -2000977760, -1265456598, 151665664, -1842697570, 579340290, 1082298021,\n 245211512, -1867881849, -1341630964, -90545664, 1806344898, 47088170, 992486842, -1558631407,\n -1449508691, -2006144829, 547374756, 912919200, 708120146, 616106151, 1172449414, 2124751394,\n -2073427929, 1009261122, 1210336770, -1973924734, 144458353, -1155209078, -1962399613, 897229836,\n 12357639, -1299101013, 312943930, 708920290, -1310023134, 639040320, -821838839, -2014506238,\n -274610648, -1442508704, 177432331, 1721041571, 414065321, -1272924654, 756208161, 1772773934,\n -1484380630, 1101004848, -1054523758, -2116504793, 1118667297, 86058657, 975856680, 749906058,\n 1937961650, 620890537, 918765730, -1366546774,\n])\n// --- END GENERATED BLOOM FILTER ---\n\n/**\n * Hash function for bloom filter\n */\nfunction hashString(str: string, seed: number): number {\n let hash: number = seed\n\n for (let i: number = 0; i < str.length; i++) {\n const char: number = str.charCodeAt(i)\n hash = (hash << 5) - hash + char\n hash = hash | 0 // Convert to 32-bit integer\n }\n\n return Math.abs(hash)\n}\n\n/**\n * Get multiple hash positions for a password\n */\nfunction getHashes(password: string): number[] {\n const hashes: number[] = []\n const hash1: number = hashString(password, 0)\n const hash2: number = hashString(password, 1)\n\n for (let i: number = 0; i < BLOOM_HASH_COUNT; i++) {\n const hash: number = (hash1 + i * hash2) >>> 0\n hashes.push(hash % BLOOM_SIZE)\n }\n\n return hashes\n}\n\n/**\n * Check if password might be in the common password list\n * Note: Bloom filters can have false positives (~0.84%) but never false negatives\n */\nfunction mightBeCommon(password: string): boolean {\n const hashes: number[] = getHashes(password.toLowerCase())\n\n for (const hash of hashes) {\n const arrayIndex: number = Math.floor(hash / 32)\n const bitIndex: number = hash % 32\n\n // Bounds check for TypeScript strict mode\n const bucket: number | undefined = BLOOM_BUCKETS[arrayIndex]\n if (bucket === undefined || (bucket & (1 << bitIndex)) === 0) {\n return false\n }\n }\n\n return true\n}\n\n/**\n * Validates that a password is not in the common password list\n *\n * Uses a Bloom filter to efficiently check against the top 1,000 most common passwords.\n * Case-insensitive matching prevents simple case variations of common passwords.\n *\n * @param password - Password to validate\n * @param options - Validation options containing checkCommonPasswords flag\n * @returns Validator check result with passed status and optional error message\n *\n * @example\n * ```typescript\n * import { validateCommonPassword } from '@sentinel-password/core'\n *\n * // Detects common passwords\n * validateCommonPassword('password')\n * // { passed: false, message: \"Password is too common. Please choose a more unique password.\" }\n *\n * validateCommonPassword('123456')\n * // { passed: false }\n *\n * // Case-insensitive\n * validateCommonPassword('PASSWORD')\n * // { passed: false }\n *\n * // Unique passwords pass\n * validateCommonPassword('MyUn1qu3P@ssw0rd!')\n * // { passed: true }\n *\n * // Disable check\n * validateCommonPassword('password', { checkCommonPasswords: false })\n * // { passed: true }\n * ```\n *\n * @remarks\n * Checks against top 1,000 most common passwords from SecLists.\n * Uses Bloom filter for space efficiency (~1.5KB vs ~8KB for raw array).\n * False positive rate: ~0.84% (may rarely flag uncommon passwords).\n * Enabled by default for security.\n */\nexport const validateCommonPassword: Validator = (\n password: string,\n options: ValidatorOptions = {}\n) => {\n const { checkCommonPasswords = true }: { checkCommonPasswords?: boolean } = options\n\n if (!checkCommonPasswords || password.length === 0) {\n return { passed: true }\n }\n\n // Case-insensitive check using bloom filter\n if (mightBeCommon(password)) {\n const params: MessageParams = {}\n return {\n passed: false,\n code: 'commonPassword.found',\n params,\n message: resolveMessage('commonPassword.found', params, options),\n }\n }\n\n return { passed: true }\n}\n","import type { MessageParams, Validator } from '../types'\nimport { resolveMessage } from '../messages'\n\n/**\n * Extracts username from email address\n * @param email - Email address\n * @returns Username part before @ symbol\n */\nconst extractUsername = (email: string): string => {\n const atIndex: number = email.indexOf('@')\n // `normalizePersonalInfo` only calls this when the string contains '@', so\n // `atIndex` is never -1; the `: email` fallback covers a leading-'@' entry\n // (e.g. '@admin'), where the whole string is matched as a literal substring.\n return atIndex > 0 ? email.substring(0, atIndex) : email\n}\n\n/**\n * Normalizes personal info strings for comparison\n * Converts to lowercase and extracts username from emails\n *\n * @param info - Personal information string\n * @returns Normalized string for comparison\n */\nconst normalizePersonalInfo = (info: string): string => {\n const normalized: string = info.toLowerCase().trim()\n // Extract username from email if it looks like an email\n return normalized.includes('@') ? extractUsername(normalized) : normalized\n}\n\n/**\n * Validates that password doesn't contain personal information\n *\n * Checks against provided personal info (username, email, name, etc.)\n * Uses case-insensitive comparison and extracts usernames from email addresses.\n * Ignores very short strings (< 3 characters) to avoid false positives.\n *\n * @param password - Password to validate\n * @param options - Validation options containing personalInfo array\n * @returns Validator check result with passed status and optional error message\n *\n * @example\n * ```typescript\n * import { validatePersonalInfo } from '@sentinel-password/core'\n *\n * // No personal info by default\n * validatePersonalInfo('password') // { passed: true }\n *\n * // Detects username in password\n * validatePersonalInfo('johnpassword', { personalInfo: ['john'] })\n * // { passed: false, message: \"Password contains personal information\" }\n *\n * // Emails are reduced to the *entire local part* (everything before `@`),\n * // matched as a literal substring — not split into name fragments.\n * validatePersonalInfo('john.doe123', { personalInfo: ['john.doe@example.com'] })\n * // { passed: false } (matches the full local part \"john.doe\")\n *\n * validatePersonalInfo('john123', { personalInfo: ['john.doe@example.com'] })\n * // { passed: true } (the local part is \"john.doe\", not \"john\" — no match)\n *\n * // To reject passwords containing just \"john\", pass it explicitly:\n * validatePersonalInfo('john123', { personalInfo: ['john', 'john.doe@example.com'] })\n * // { passed: false } (matches the literal \"john\")\n *\n * // Case-insensitive\n * validatePersonalInfo('JOHN123', { personalInfo: ['john'] })\n * // { passed: false }\n *\n * // Ignores short strings\n * validatePersonalInfo('password', { personalInfo: ['pw'] }) // { passed: true } (too short)\n *\n * // Multiple personal info items\n * validatePersonalInfo('secretpass', {\n * personalInfo: ['john', 'doe', 'john.doe@example.com']\n * }) // { passed: true }\n * ```\n *\n * @remarks\n * Best practice: Provide username, email, first name, last name, and company name.\n * Strings shorter than 3 characters are ignored to prevent false positives.\n */\nexport const validatePersonalInfo: Validator = (password, options = {}) => {\n const { personalInfo = [] }: Partial<{ personalInfo: string[] }> = options\n\n if (personalInfo.length === 0 || password.length === 0) {\n return { passed: true }\n }\n\n const lowerPassword: string = password.toLowerCase()\n\n for (const info of personalInfo) {\n const normalized: string = normalizePersonalInfo(info)\n\n // Skip very short strings to avoid false positives\n if (normalized.length < 3) {\n continue\n }\n\n // Check if password contains this personal information\n if (lowerPassword.includes(normalized)) {\n const params: MessageParams = {}\n return {\n passed: false,\n code: 'personalInfo.found',\n params,\n message: resolveMessage('personalInfo.found', params, options),\n }\n }\n }\n\n return {\n passed: true,\n }\n}\n","/**\n * @sentinel-password/core\n * Zero-dependency TypeScript password validation library\n */\n\nexport type {\n StrengthScore,\n StrengthLabel,\n ValidationResult,\n ValidationFailure,\n ValidatorOptions,\n ValidatorCheck,\n Validator,\n CheckId,\n MessageCode,\n MessageParams,\n MessageFormatter,\n} from './types'\n\nexport { DEFAULT_TEMPLATES } from './messages'\n\nexport { validateLength } from './validators/length'\nexport {\n hasUppercase,\n hasLowercase,\n hasDigit,\n hasSymbol,\n validateCharacterTypes,\n} from './validators/character-types'\nexport { validateRepetition } from './validators/repetition'\nexport { validateSequential } from './validators/sequential'\nexport { validateKeyboardPattern } from './validators/keyboard-pattern'\nexport { validateCommonPassword } from './validators/common-password'\nexport { validatePersonalInfo } from './validators/personal-info'\n\nimport type {\n ValidationResult,\n ValidationFailure,\n ValidatorOptions,\n StrengthScore,\n StrengthLabel,\n ValidatorCheck,\n CheckId,\n} from './types'\nimport { validateLength } from './validators/length'\nimport { validateCharacterTypes } from './validators/character-types'\nimport { validateRepetition } from './validators/repetition'\nimport { validateSequential } from './validators/sequential'\nimport { validateKeyboardPattern } from './validators/keyboard-pattern'\nimport { validateCommonPassword } from './validators/common-password'\nimport { validatePersonalInfo } from './validators/personal-info'\n\nconst STRENGTH_LABELS: readonly StrengthLabel[] = [\n 'very-weak',\n 'weak',\n 'medium',\n 'strong',\n 'very-strong',\n] as const\n\n/**\n * Validate a password with comprehensive security checks\n *\n * Performs multiple validation checks including length, character types, repetition,\n * sequential patterns, keyboard patterns, common passwords, and personal information.\n * Returns detailed feedback with strength score and actionable suggestions.\n *\n * @param password - The password string to validate\n * @param options - Optional validation configuration\n * @returns Validation result with score, strength label, feedback, and individual check results\n *\n * @example\n * **Basic usage (zero-config)**\n * ```typescript\n * import { validatePassword } from '@sentinel-password/core'\n *\n * const result = validatePassword('MySecure!Pass_w0rd')\n * console.log(result.valid) // true\n * console.log(result.score) // 4 (0-4 scale)\n * console.log(result.strength) // 'very-strong'\n * console.log(result.feedback.warning) // undefined (no issues)\n * console.log(result.feedback.suggestions) // []\n * ```\n *\n * @example\n * **With a known-common password**\n * ```typescript\n * const result = validatePassword('password')\n * console.log(result.valid) // false (commonPassword check rejected it)\n * console.log(result.score) // 4\n * console.log(result.strength) // 'very-strong'\n * console.log(result.feedback.warning)\n * // \"Password is too common. Please choose a more unique password.\"\n * console.log(result.feedback.suggestions)\n * // [\"Password is too common. Please choose a more unique password.\"]\n * console.log(result.checks)\n * // { length: true, characterTypes: true, repetition: true, sequential: true,\n * // keyboardPattern: true, commonPassword: false, personalInfo: true }\n * // ↑ 6 of 7 checks pass, so score is 4 (\"very-strong\") even though valid is false.\n * // Always use `valid` (or `result.checks`) for acceptance decisions, not `strength`.\n * ```\n *\n * @example\n * **Custom length requirements**\n * ```typescript\n * const result = validatePassword('MyP@ss', {\n * minLength: 12,\n * maxLength: 64\n * })\n * console.log(result.valid) // false\n * console.log(result.feedback.warning) // \"Password must be at least 12 characters\"\n * ```\n *\n * @example\n * **Require specific character types**\n * ```typescript\n * const result = validatePassword('password123', {\n * requireUppercase: true,\n * requireLowercase: true,\n * requireDigit: true,\n * requireSymbol: true\n * })\n * console.log(result.valid) // false\n * console.log(result.feedback.warning) // \"Password must contain at least one uppercase letter, symbol\"\n * ```\n *\n * @example\n * **Prevent personal information**\n * ```typescript\n * const result = validatePassword('john1234!', {\n * personalInfo: ['john', 'john.doe@example.com', 'Doe']\n * })\n * console.log(result.valid) // false\n * console.log(result.feedback.warning) // \"Password contains personal information\"\n * ```\n *\n * @example\n * **Disable specific checks**\n * ```typescript\n * const result = validatePassword('qwerty123', {\n * checkKeyboardPatterns: false, // Allow keyboard patterns\n * checkSequential: false, // Allow sequential chars\n * checkCommonPasswords: false // Allow common passwords\n * })\n * // More permissive validation\n * ```\n *\n * @example\n * **Comprehensive configuration**\n * ```typescript\n * const result = validatePassword('MyP@ssw0rd2024!', {\n * minLength: 12,\n * maxLength: 128,\n * requireUppercase: true,\n * requireLowercase: true,\n * requireDigit: true,\n * requireSymbol: true,\n * maxRepeatedChars: 2,\n * checkSequential: true,\n * checkKeyboardPatterns: true,\n * checkCommonPasswords: true,\n * personalInfo: ['user', 'admin', 'test']\n * })\n * ```\n *\n * @remarks\n * **Default behavior:**\n * - Minimum length: 8 characters\n * - Maximum length: 128 characters\n * - No character type requirements (but recommended to enable)\n * - Max repeated characters: 3\n * - Sequential check: enabled\n * - Keyboard pattern check: enabled\n * - Common password check: enabled (top 1,000 passwords)\n * - Personal info check: disabled (provide personalInfo array to enable)\n *\n * **Scoring:**\n * - `score` = `Math.min(4, Math.floor((passedChecks / 7) * 5))` — purely a\n * passed-check ratio.\n * - `strength` is the human label for that score (`very-weak` … `very-strong`).\n * - Because scoring is ratio-based, a password that fails *only* the\n * common-password (or personal-info, or sequential, etc.) check still passes\n * 6 of 7 checks and lands on `score: 4 / strength: 'very-strong'` while\n * `valid` is `false`. Use `valid` (or inspect `result.checks`) for\n * acceptance decisions; use `strength` for UX cues like progress bars.\n *\n * **Performance:**\n * - All validators run in O(n) time or better\n * - Typical validation: < 1ms for passwords up to 128 characters\n * - Bloom filter for common passwords: O(1) lookup\n *\n * **Security:**\n * - All checks are case-insensitive where applicable\n * - No password data is logged or stored\n * - Runs purely in-process — no network calls, the password never leaves the\n * caller's runtime\n * - This is a *strength* validator, not a password-comparison primitive. The\n * validators use early-return `includes()`/loops and are not constant-time,\n * but timing is not a relevant attack surface here: the patterns being\n * checked (length, character types, common-password list, keyboard layouts)\n * are all public — there's no secret to leak via timing. When you compare\n * a password against a stored hash, use a library like Argon2/bcrypt that\n * provides constant-time verification — that's a separate concern from\n * strength validation.\n */\n/** Frozen empty array shared across success-path callers to avoid the\n * per-call `suggestions: []` allocation when no validator fails. */\nconst EMPTY_SUGGESTIONS: readonly string[] = Object.freeze([])\n\n/** Frozen empty array shared by the all-passing path (no per-call allocation). */\nconst EMPTY_FAILURES: readonly ValidationFailure[] = Object.freeze([])\n\n/** Total number of validators run by `validatePassword`. Compile-time constant. */\nconst TOTAL_CHECKS: number = 7\n\nexport function validatePassword(\n password: string,\n options: ValidatorOptions = {}\n): ValidationResult {\n // Run all 7 validators. Captured into individual locals (not an intermediate\n // record) so we can read `.passed` and `.message` once each without going\n // through Object.values/Object.keys iterations.\n const lengthResult: ValidatorCheck = validateLength(password, options)\n const charTypesResult: ValidatorCheck = validateCharacterTypes(password, options)\n const repetitionResult: ValidatorCheck = validateRepetition(password, options)\n const sequentialResult: ValidatorCheck = validateSequential(password, options)\n const commonPasswordResult: ValidatorCheck = validateCommonPassword(password, options)\n const personalInfoResult: ValidatorCheck = validatePersonalInfo(password, options)\n const keyboardPatternResult: ValidatorCheck = validateKeyboardPattern(password, options)\n\n // Build `checks` and `passedChecks` in a single pass. Lazy-allocate\n // `suggestions` only when a validator actually fails — most calls in\n // practice produce all-passing results and pay no allocation cost.\n const checks: Record<CheckId, boolean> = {\n length: lengthResult.passed,\n characterTypes: charTypesResult.passed,\n repetition: repetitionResult.passed,\n sequential: sequentialResult.passed,\n keyboardPattern: keyboardPatternResult.passed,\n commonPassword: commonPasswordResult.passed,\n personalInfo: personalInfoResult.passed,\n }\n\n let passedChecks: number = 0\n let failures: ValidationFailure[] | undefined\n\n /**\n * Accumulate a validator's pass/fail outcome. Increments `passedChecks` on\n * success; otherwise lazy-allocates `failures` (so the all-passing path pays\n * no array allocation) with the stable code/params. The discriminated\n * `ValidatorCheck` guarantees message/code/params are present once `passed` is\n * `false`, so no presence guard is needed. `feedback` is derived from\n * `failures` below — a single source of truth for warning/suggestions.\n */\n const record = (check: CheckId, result: ValidatorCheck): void => {\n if (result.passed) {\n passedChecks++\n return\n }\n const failure: ValidationFailure = {\n check,\n code: result.code,\n params: result.params,\n message: result.message,\n }\n if (failures === undefined) {\n failures = [failure]\n } else {\n failures.push(failure)\n }\n }\n\n record('length', lengthResult)\n record('characterTypes', charTypesResult)\n record('repetition', repetitionResult)\n record('sequential', sequentialResult)\n record('commonPassword', commonPasswordResult)\n record('personalInfo', personalInfoResult)\n record('keyboardPattern', keyboardPatternResult)\n\n const score: StrengthScore = Math.min(\n 4,\n Math.floor((passedChecks / TOTAL_CHECKS) * 5)\n ) as StrengthScore\n\n const suggestions: readonly string[] = failures\n ? failures.map((failure) => failure.message)\n : EMPTY_SUGGESTIONS\n const firstSuggestion: string | undefined = failures?.[0]?.message\n\n return {\n valid: passedChecks === TOTAL_CHECKS,\n score,\n /* v8 ignore next */\n strength: STRENGTH_LABELS[score] ?? 'very-weak',\n feedback: {\n ...(firstSuggestion !== undefined && { warning: firstSuggestion }),\n suggestions,\n },\n checks,\n failures: failures ?? EMPTY_FAILURES,\n }\n}\n"],"mappings":"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","names":[]} |
+8
-4
| { | ||
| "name": "@sentinel-password/core", | ||
| "version": "1.2.5", | ||
| "version": "1.3.0", | ||
| "type": "module", | ||
| "description": "Modern, zero-dependency TypeScript password validation with bloom filter-based common password detection. 100% test coverage (enforced); ~5.5 KB gzipped (10 KB CI limit).", | ||
| "description": "Modern, zero-dependency TypeScript password validation with bloom filter-based common password detection. 100% test coverage (enforced); ~6.3 KB gzipped (10 KB CI limit).", | ||
| "main": "./dist/index.cjs", | ||
@@ -25,3 +25,4 @@ "module": "./dist/index.js", | ||
| "publishConfig": { | ||
| "access": "public" | ||
| "access": "public", | ||
| "provenance": true | ||
| }, | ||
@@ -45,5 +46,8 @@ "sideEffects": false, | ||
| "license": "MIT", | ||
| "engines": { | ||
| "node": ">=20" | ||
| }, | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "https://github.com/akankov/sentinel-password", | ||
| "url": "git+https://github.com/akankov/sentinel-password.git", | ||
| "directory": "packages/core" | ||
@@ -50,0 +54,0 @@ }, |
215533
2.01%2264
-3.54%