/** * Passphrase validation — phrase format (per the three-tier session-tiers * design, locked 2026-05-04). * * Passphrases are **phrases**: multiple simple words, easy to remember, * structurally constrained so a weak choice cannot silently collapse the * security floor. The format is intentionally narrow: lowercase letters * and single spaces only, no punctuation, no symbols, no digits. * * - Default minimum: 6 words (~77 bits with the 7,776-word EFF list). * - Strict minimum: 8 words (~103 bits). * - Per-word minimum: 3 characters (excludes "a", "is", "of"). * - Adjacent repeats rejected ("the the"). * * The hub runs validation default-on at every passphrase ingress * (`createOwnerKeyring`, `grant`, `rotatePassphrase`); test fixtures and * CLI scripts override via `{ allowWeakPassphrase: true }`. * * @module */ import { NoydbError } from './errors.js'; /** All reasons a phrase can be rejected. */ export type WeakPassphraseReason = 'empty' | 'invalid-chars' | 'leading-or-trailing-space' | 'double-space' | 'too-few-words' | 'word-too-short' | 'repeated-adjacent'; /** Per-vault knobs. Aligns with `VaultPolicy.passphrase`. */ export interface PassphrasePolicy { /** Minimum number of words. Default 6. Strict policy uses 8. */ readonly minWords?: number; /** Minimum characters per word. Default 3. */ readonly minWordLength?: number; /** Reject adjacent identical words ("the the"). Default true. */ readonly rejectRepeatedAdjacent?: boolean; /** * Override the default character-class rule (`/^[a-z]+( [a-z]+)*$/`). * * The hub's strict default is lowercase-letters-and-single-spaces * because that's what the EFF wordlist generator emits and what * most attacker password lists are keyed on. Use this knob to allow * digits, uppercase, hyphens, or non-Latin scripts when the * consumer's audience needs them — e.g.: * * ```ts * // Thai + English mix with digits permitted * pattern: /^[\p{L}0-9 ]+( [\p{L}0-9 ]+)*$/u * * // Allow uppercase + hyphens (passphrase-with-hyphens style) * pattern: /^[A-Za-z]+([- ][A-Za-z]+)*$/ * ``` * * The OTHER structural rules still apply (min-words split by space, * min-word-length, repeated-adjacent, leading/trailing whitespace, * double-space). For non-space-delimited word semantics, use * {@link customValidator} instead. * */ readonly pattern?: RegExp; /** * Replace ALL validation entirely with a custom function. When set, * none of the other PassphrasePolicy fields apply — the consumer * owns every rule (word splitting, character classes, entropy * thresholds, allowlist/denylist). Use sparingly; this is the * escape hatch for domain-specific phrase formats: * * - Localized wordlists with non-space word boundaries * - BIP-39 seed phrases (24 words, fixed wordlist, etc.) * - Organization-specific HR password policies * * The returned `PassphraseValidationResult` is what * {@link assertStrongPassphrase} dispatches on — `ok: true` accepts; * `ok: false` throws `WeakPassphraseError` with the supplied reason. * */ readonly customValidator?: (phrase: string) => PassphraseValidationResult; } /** Result of a check. Discriminated union — compile-time exhaustive. */ export type PassphraseValidationResult = { readonly ok: true; readonly words: number; } | { readonly ok: false; readonly reason: WeakPassphraseReason; readonly minimum?: number; readonly got?: number; }; /** * Thrown by `assertStrongPassphrase()` and by every hub ingress * point (`createOwnerKeyring`, `grant`, `rotatePassphrase`) when a * supplied phrase fails the structural rules above. */ export declare class WeakPassphraseError extends NoydbError { readonly reason: WeakPassphraseReason; readonly suggestion: string; constructor(reason: WeakPassphraseReason, suggestion: string); } /** * Inspect a phrase against the format rules and return a structured * verdict. Never throws — callers either branch on `ok` or pass the * result to {@link assertStrongPassphrase} for the throwing flavour. */ export declare function validatePassphrase(s: string, opts?: PassphrasePolicy): PassphraseValidationResult; /** * Throw {@link WeakPassphraseError} when the phrase fails. Used by * `createOwnerKeyring`, `grant`, and `rotatePassphrase` at ingress. * * Pass `{ allowWeakPassphrase: true }` to bypass — intended for test * fixtures, CLI scripts, and dev environments. The override never * loosens the cryptographic key derivation; it only relaxes the * structural-strength gate. */ export declare function assertStrongPassphrase(s: string, opts?: PassphrasePolicy & { allowWeakPassphrase?: boolean; }): void; /** * Estimate the entropy of a phrase, given the EFF 7,776-word list as * the assumed wordlist. ~12.9 bits per word. * * Returns 0 for any input that fails the phrase format — character-class * estimates aren't comparable to phrase entropy, and surfacing 0 makes * weak inputs visible in any UI that displays an entropy meter. */ export declare function estimateEntropy(passphrase: string): number; /** * Internal compatibility shim. Older code paths used the throwing * `validatePassphrase(s)` directly; some still do via re-exports. Routes * to the new `assertStrongPassphrase` so the contract holds for both * shapes during the transition. New code should call * {@link assertStrongPassphrase} directly. * * @internal */ export declare function legacyAssertPassphrase(s: string): void;