Skip to content

Core Package API

The @sentinel-password/core package provides the foundational password validation functionality. It is zero-dependency, isomorphic, and ships both ESM and CommonJS builds.

Installation

bash
npm install @sentinel-password/core

Main Functions

validatePassword()

Run all built-in checks against a password and get a structured result with strength, feedback, and per-check booleans.

Signature:

typescript
function validatePassword(
  password: string,
  options?: ValidatorOptions
): ValidationResult

Parameters:

ParameterTypeDescription
passwordstringThe password to validate
optionsValidatorOptionsOptional validation configuration (all fields optional)

Returns: ValidationResult

Example:

typescript
import { validatePassword } from '@sentinel-password/core'

const result = validatePassword('MyP@ssw0rd!', {
  minLength: 8,
  maxLength: 128,
  requireUppercase: true,
  requireLowercase: true,
  requireDigit: true,
  requireSymbol: true,
})

console.log(result.valid)              // true
console.log(result.strength)           // 'very-strong'
console.log(result.score)              // 4
console.log(result.feedback.warning)   // undefined when valid
console.log(result.feedback.suggestions) // []
console.log(result.checks)
// {
//   length: true, characterTypes: true, repetition: true,
//   sequential: true, keyboardPattern: true,
//   commonPassword: true, personalInfo: true,
// }

Individual Validators

Each built-in check is also exported on its own for targeted use and tree-shaking.

typescript
import {
  validateLength,
  validateCharacterTypes,
  validateRepetition,
  validateSequential,
  validateKeyboardPattern,
  validateCommonPassword,
  validatePersonalInfo,
  hasUppercase,
  hasLowercase,
  hasDigit,
  hasSymbol,
} from '@sentinel-password/core'

const lengthCheck = validateLength('abc', { minLength: 8 })
// {
//   passed: false,
//   code: 'length.tooShort',
//   params: { minLength: 8 },
//   message: 'Password must be at least 8 characters',
// }

Every validator returns a ValidatorCheckpassed plus code / params / message when the check fails.

Types

ValidatorOptions

A flat configuration object — every field is optional.

typescript
interface ValidatorOptions {
  minLength?: number              // Default: 8
  maxLength?: number              // Default: 128
  requireUppercase?: boolean      // Default: false
  requireLowercase?: boolean      // Default: false
  requireDigit?: boolean          // Default: false
  requireSymbol?: boolean         // Default: false
  maxRepeatedChars?: number       // Default: 3
  checkSequential?: boolean       // Default: true
  checkKeyboardPatterns?: boolean // Default: true
  checkCommonPasswords?: boolean  // Default: true
  personalInfo?: string[]         // Default: undefined (disabled)
  messages?: Partial<Record<MessageCode, string>>  // Default: undefined — English
  formatMessage?: MessageFormatter                  // Default: undefined
}
OptionDefaultDescription
minLength8Minimum password length
maxLength128Maximum password length
requireUppercasefalseRequire at least one uppercase letter
requireLowercasefalseRequire at least one lowercase letter
requireDigitfalseRequire at least one digit
requireSymbolfalseRequire at least one symbol
maxRepeatedChars3Max consecutive repeated characters allowed
checkSequentialtrueReject any three characters whose charCodeAt values are consecutive ascending or descending — abc, xyz, 123, 987, plus less-obvious runs like !"#, ,-., or 9:;. See Sequential.
checkKeyboardPatternstrueReject keyboard runs (qwerty, asdfgh)
checkCommonPasswordstrueReject the top 1,000 common passwords. The check uses a Bloom filter with no false negatives but a ~0.84% false-positive rate — see Common Password.
personalInfoArray of strings the password must not contain (substring match, case-insensitive; entries containing @ are reduced to the local part before matching)
messagesPartial map of MessageCode → template string for localization. {placeholder} tokens are substituted with params values. Missing codes fall back to the built-in English. See i18n guide.
formatMessageCallback (code, params, defaultMessage) => string. Takes precedence over messages. Use to plug in react-intl, i18next, FormatJS/ICU, etc. See i18n guide.

ValidationResult

typescript
interface ValidationResult {
  valid: boolean
  score: StrengthScore
  strength: StrengthLabel
  feedback: {
    warning?: string
    suggestions: readonly string[]
  }
  checks: Record<CheckId, boolean>
}
FieldTypeDescription
validbooleantrue only if every check passed
scoreStrengthScore (04)Strength score derived from passed-check ratio
strengthStrengthLabelHuman-readable label (see below)
feedback.warningstring | undefinedFirst failure message, if any
feedback.suggestionsreadonly string[]All failure messages, in check order
checksRecord<CheckId, boolean>Per-check pass/fail map

StrengthScore and StrengthLabel

Five strength tiers — score and label are linked.

typescript
type StrengthScore = 0 | 1 | 2 | 3 | 4
type StrengthLabel = 'very-weak' | 'weak' | 'medium' | 'strong' | 'very-strong'
ScoreLabel
0'very-weak'
1'weak'
2'medium'
3'strong'
4'very-strong'

CheckId

Identifiers for the seven built-in checks.

typescript
type CheckId =
  | 'length'
  | 'characterTypes'
  | 'repetition'
  | 'sequential'
  | 'keyboardPattern'
  | 'commonPassword'
  | 'personalInfo'

ValidatorCheck and Validator

typescript
interface ValidatorCheck {
  passed: boolean
  message?: string       // default English rendering (or override result)
  code?: MessageCode     // stable identifier; undefined when passed
  params?: MessageParams // interpolation values; undefined when passed
}

type Validator = (password: string, options?: ValidatorOptions) => ValidatorCheck

MessageCode, MessageParams, MessageFormatter

Stable identifiers for validator messages plus the templating contract.

typescript
type MessageCode =
  | 'length.tooShort'
  | 'length.tooLong'
  | 'characterTypes.missing'
  | 'repetition.tooMany'
  | 'sequential.found'
  | 'keyboardPattern.found'
  | 'commonPassword.found'
  | 'personalInfo.found'

type MessageParams = Readonly<Record<string, string | number>>

type MessageFormatter = (
  code: MessageCode,
  params: MessageParams,
  defaultMessage: string
) => string
CodeParamsDefault template
length.tooShort{ minLength }Password must be at least {minLength} characters
length.tooLong{ maxLength }Password must be at most {maxLength} characters
characterTypes.missing{ missing, missingTypes }Password must contain at least one {missing}
repetition.tooMany{ maxRepeatedChars }Password contains too many repeated characters (max {maxRepeatedChars})
sequential.found{}Password contains sequential characters (e.g., abc, 123)
keyboardPattern.found{}Password contains common keyboard patterns
commonPassword.found{}Password is too common. Please choose a more unique password.
personalInfo.found{}Password contains personal information

For characterTypes.missing, params.missing is the joined English (uppercase letter, digit) and params.missingTypes is the raw comma-separated token list (uppercase,digit) — use the latter to translate type names individually. See i18n guide.

DEFAULT_TEMPLATES

Runtime constant exposing the built-in English templates keyed by MessageCode. Useful for tooling that needs to display defaults or build locale catalogs on top.

typescript
import { DEFAULT_TEMPLATES, type MessageCode } from '@sentinel-password/core'

DEFAULT_TEMPLATES['length.tooShort']
// → 'Password must be at least {minLength} characters'

Advanced Usage

Strength-Based Logic

Use score and strength for tiered acceptance:

typescript
const result = validatePassword(password, options)

if (!result.valid) {
  // Reject; show suggestions
  return { error: result.feedback.warning, suggestions: result.feedback.suggestions }
}
if (result.strength === 'medium') {
  // Allow but encourage strengthening
  console.warn('Password meets minimum requirements but could be stronger')
}

Disabling Specific Checks

Make validation more permissive:

typescript
const result = validatePassword(password, {
  checkKeyboardPatterns: false,
  checkSequential: false,
  checkCommonPasswords: false,
})

Personal Info

Pass user-identifying strings — name, username, email — to reject passwords that contain them. Matching is case-insensitive and substring-based.

typescript
validatePassword('john1234!', {
  personalInfo: ['john@example.com', 'John', 'Doe'],
})
// {
//   valid: false,
//   feedback: {
//     warning: 'Password contains sequential characters (e.g., abc, 123)',
//     suggestions: [
//       'Password contains sequential characters (e.g., abc, 123)',
//       'Password contains personal information',
//       'Password contains common keyboard patterns',
//     ],
//   },
//   checks: { ..., sequential: false, keyboardPattern: false, personalInfo: false },
// }

This input fails three checks at once — personalInfo (matches "john" from the email's local part), sequential (the 234 substring), and keyboardPattern (1234 is on the numeric row). warning is always the first suggestion in aggregator order (length → characterTypes → repetition → sequential → commonPassword → personalInfo → keyboardPattern), which is why the warning here is the sequential message, not the personal-info one. To surface specifically which check rejected the password, inspect result.checks.

Identifiers under 3 characters are ignored. To match a domain, pass it separately (e.g. add 'example' to the list).

Email handling: values containing @ are reduced to the part before @ before matching, so 'john.doe@example.com' is treated as 'john.doe'. The domain is not checked. Add it as a separate entry if you want it rejected too.

Tree-Shaking Individual Validators

Bundlers will tree-shake unused validators because the package is sideEffects: false:

typescript
import { validateLength, validateCharacterTypes } from '@sentinel-password/core'

const lengthCheck = validateLength(password, { minLength: 12 })
const charCheck = validateCharacterTypes(password, {
  requireUppercase: true,
  requireDigit: true,
})

See Also

Released under the MIT License.