@sentinel-password/core
Advanced tools
@@ -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 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":"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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\n// Stryker disable all: the values below are GENERATED data, not logic. Mutating\n// individual table entries (e.g. flipping one int's sign) produces equivalent\n// mutants — altering a single bucket in a 12,000-bit filter never changes the\n// pass/fail outcome for any password. Filter integrity is verified instead by\n// the full-wordlist test in tests/validators/common-password.test.ts.\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// Stryker restore all\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":"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","names":[]} |
@@ -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 * 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":[]} | ||
| {"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\n// Stryker disable all: the values below are GENERATED data, not logic. Mutating\n// individual table entries (e.g. flipping one int's sign) produces equivalent\n// mutants — altering a single bucket in a 12,000-bit filter never changes the\n// pass/fail outcome for any password. Filter integrity is verified instead by\n// the full-wordlist test in tests/validators/common-password.test.ts.\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// Stryker restore all\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":[]} |
+4
-1
| { | ||
| "name": "@sentinel-password/core", | ||
| "version": "1.3.0", | ||
| "version": "1.3.1", | ||
| "type": "module", | ||
@@ -58,2 +58,4 @@ "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).", | ||
| "devDependencies": { | ||
| "@stryker-mutator/core": "^9.6.1", | ||
| "@stryker-mutator/vitest-runner": "^9.6.1", | ||
| "@vitest/coverage-v8": "^4.1.8", | ||
@@ -72,2 +74,3 @@ "check-password-strength": "^3.0.0", | ||
| "test:coverage": "vitest run --coverage", | ||
| "test:mutation": "stryker run", | ||
| "bench": "vitest bench", | ||
@@ -74,0 +77,0 @@ "generate:bloom": "node ../../scripts/generate-bloom-filter.cjs", |
216496
0.45%9
28.57%