Password Policy

PasswordPolicy and PasswordScore — check()/enforce() a password against configurable PasswordPolicyOptions with a PasswordCheckContext, returning a PasswordCheckResult of PasswordWeakness entries keyed by PasswordWeaknessCode.

PasswordPolicy grades a password and, on request, gates on it. It is the same measurement the library applies to the secrets that protect a workspace or account, exposed so you can screen a password before you ever hand it to Workspace.open or accounts.create. A check returns a structured PasswordCheckResult — a numeric score, an entropy estimate, and a list of typed PasswordWeakness entries — rather than a bare boolean, so your UI can explain why a password was refused.

password-policy.ts
import { PasswordPolicy, PasswordScore } from "wative-core";

const policy = new PasswordPolicy({ minLength: 12, minScore: PasswordScore.Fair });

const result = policy.check("correct horse battery staple");
result.passable; // true
result.summary;  // "Password OK (score 3, ~132 bits)."

// Or gate on it — throws WativeError("WEAK_PASSWORD") when not passable.
policy.enforce("hunter2");

Constructing a policy

new PasswordPolicy(opts?: PasswordPolicyOptions). Every option is optional; an omitted option takes its default, and passing undefined for an option is treated as "not supplied" (so new PasswordPolicy({ minLength: undefined }) keeps the default floor rather than removing it). Construct one policy and reuse it — instances are stateless between calls.

check vs enforce

Both take the password plus an optional PasswordCheckContext. They differ only in what they do with a failing result.

MethodSignatureBehavior
checkcheck(password: string, context?: PasswordCheckContext): PasswordCheckResultAlways returns the full result. Never throws for a weak password.
enforceenforce(password: string, context?: PasswordCheckContext): voidRuns check; returns nothing when passable, otherwise throws.

enforce throws WativeError("WEAK_PASSWORD", summary, { details: { result } }) — the details.result field carries the same PasswordCheckResult check would have returned, so a handler can render the specific weaknesses. check itself throws only when its input is not a string (WativeError("PROVIDER_IO")).

Options

PasswordPolicyOptions
interface PasswordPolicyOptions {
  minScore?: PasswordScore;
  minLength?: number;
  requireUppercase?: boolean;
  requireLowercase?: boolean;
  requireDigit?: boolean;
  requireSymbol?: boolean;
  forbiddenWords?: string[];
}
OptionDefaultEffect
minScorePasswordScore.Fair (2)Lowest PasswordScore that still counts as passable.
minLength12Fewer code points than this adds TOO_SHORT (a critical weakness).
requireUppercasefalseWhen true, a password with no A–Z adds NO_UPPERCASE.
requireLowercasefalseWhen true, a password with no a–z adds NO_LOWERCASE.
requireDigitfalseWhen true, a password with no 0–9 adds NO_DIGIT.
requireSymbolfalseWhen true, a password with no non-alphanumeric character adds NO_SYMBOL.
forbiddenWords[]Case-insensitive substrings; a match adds FORBIDDEN_WORD (a critical weakness).

PasswordCheckContext

The optional second argument supplies per-user context the policy cannot know on its own.

PasswordCheckContext
interface PasswordCheckContext {
  username?: string;
  history?: string[];
}
FieldTypeEffect
usernamestringIf the password contains it (case-insensitive), adds USERNAME_SUBSTRING.
historystring[]Prior passwords. A match adds REUSED_FROM_HISTORY.

Result and weakness codes

PasswordCheckResult
interface PasswordCheckResult {
  readonly score: PasswordScore;
  readonly entropyBits: number;
  readonly weaknesses: ReadonlyArray<PasswordWeakness>;
  readonly passable: boolean;
  readonly summary: string;
}
FieldTypeMeaning
scorePasswordScoreThe graded strength, 04.
entropyBitsnumberRough Shannon-style entropy estimate from length and character pool.
weaknessesReadonlyArray<PasswordWeakness>Every issue found, in check order.
passablebooleanWhether the password meets this policy.
summarystringA one-line human-readable summary.

PasswordScore is a named enum that replaces bare 0..4 magic numbers:

MemberValue
PasswordScore.VeryWeak0
PasswordScore.Weak1
PasswordScore.Fair2
PasswordScore.Strong3
PasswordScore.VeryStrong4

Each PasswordWeakness is { code, severity, message, fix? }, where severity is one of "critical" | "warning" | "info" and fix is an optional remediation hint:

PasswordWeakness
interface PasswordWeakness {
  readonly code: PasswordWeaknessCode;
  readonly severity: "critical" | "warning" | "info";
  readonly message: string;
  readonly fix?: string;
}

PasswordWeaknessCode is the closed union below. Only critical codes can flip passable to false; warning and info codes lower the score but never reject on their own.

PasswordWeaknessCodeSeverityRaised when
TOO_SHORTcriticalShorter than minLength (measured after NFC normalization).
USERNAME_SUBSTRINGcriticalContains the context.username.
FORBIDDEN_WORDcriticalContains one of forbiddenWords.
REUSED_FROM_HISTORYcriticalMatches an entry in context.history.
NO_UPPERCASEwarningrequireUppercase set and no uppercase letter.
NO_LOWERCASEwarningrequireLowercase set and no lowercase letter.
NO_DIGITwarningrequireDigit set and no digit.
NO_SYMBOLwarningrequireSymbol set and no symbol.
LEADING_TRAILING_WHITESPACEwarningSurrounding whitespace clipboard managers often add silently.
MIXED_UNICODE_NORMALIZATIONwarningNot in NFC form; some IMEs re-normalize on paste.
INVISIBLE_CHARACTERSwarningZero-width or BIDI control characters that aren't visible.
REPEATED_CHARSinfoFour or more of the same character in a row.
SEQUENTIAL_CHARSinfoA run of four or more sequential characters.

NFC-normalized measurement

The KDF normalizes a password to NFC before deriving a key, so check measures the NFC form too — length, character-class rules, entropy, username and history comparisons all run against password.normalize("NFC"). A decomposed password is longer than its composed form; measuring the raw string would report a stronger secret than the one actually protecting the wallet, and could pass a length rule the derived form fails.

See also

Last updated on