use-password-policy 3.0.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/README.md +40 -18
- package/dist/core.d.mts +112 -28
- package/dist/core.d.ts +112 -28
- package/dist/core.js +176 -15
- package/dist/core.mjs +169 -14
- package/dist/index.d.mts +174 -86
- package/dist/index.d.ts +174 -86
- package/dist/index.js +52 -456
- package/dist/index.mjs +49 -444
- package/dist/styles.css +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.1.0
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- **`patternCheck`** rejects predictable passwords: repeated characters (`aaaaaaaaaaaaaaa`), repeated chunks (`qwertyqwertyqwerty`, `dragon dragon dragon`), sequences and keyboard runs (`123456789012345`, `abcdefghijk`), and passwords made of only a few distinct characters, including all spaces. It's on in `presets.nist` and `presets.nistMfa`.
|
|
7
|
+
- **`breachCheck`** puts Have I Been Pwned into the normal result. The hook checks once the other rules pass and adds a `notBreached` requirement that is `pending` until answered. `isValid` waits for it, and the component shows it automatically. `failOpen` decides what happens when the service is down (default: let through).
|
|
8
|
+
- **`validatePasswordAsync()`** for servers, with the same result plus the breach check. Also `zodPasswordRuleAsync()`, `passwordValidatorAsync()` and `applyBreachResult()`.
|
|
9
|
+
- Results include `breach: { status, count }`, and requirements can be `pending`.
|
|
10
|
+
- `isPredictablePattern()` and `passwordLength()` helpers.
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- Length rules count Unicode code points, as NIST specifies. 8 emoji no longer pass a 15-character minimum.
|
|
14
|
+
- The common-password check now catches list words joined together (`passwordpassword`, `Summer2024!Summer`).
|
|
15
|
+
- `<PasswordPolicyInput />` calls `onPasswordChange` again when an async check settles.
|
|
16
|
+
- Repeated breach checks for the same hash prefix reuse the previous response.
|
|
17
|
+
|
|
3
18
|
## 3.0.0
|
|
4
19
|
|
|
5
20
|
### Added
|
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
- A **NIST SP 800-63B** preset, a common-password blocklist and **Have I Been Pwned** breach checks
|
|
13
13
|
- Optional **zxcvbn** scoring, so "strength" means how hard a password is to guess, not how many boxes it ticks
|
|
14
14
|
- **Zod** and **react-hook-form** helpers
|
|
15
|
-
- No runtime dependencies. About
|
|
15
|
+
- No runtime dependencies. About 4 KB gzipped for the core, about 6.5 KB with the React parts.
|
|
16
16
|
|
|
17
17
|
### [➡️ Live demo & playground](https://rahulpatwa1303.github.io/use-password-policy/)
|
|
18
18
|
|
|
@@ -78,18 +78,20 @@ const { isValid, requirements, strengthLabel, strengthPercent } = usePasswordPol
|
|
|
78
78
|
```ts
|
|
79
79
|
// password-policy.ts — shared by client and server
|
|
80
80
|
import { presets, type PasswordPolicyOptions } from 'use-password-policy/core';
|
|
81
|
-
export const policy: PasswordPolicyOptions = { ...presets.nist };
|
|
81
|
+
export const policy: PasswordPolicyOptions = { ...presets.nist, breachCheck: true };
|
|
82
82
|
```
|
|
83
83
|
|
|
84
84
|
```ts
|
|
85
85
|
// api/sign-up.ts (Node, Next.js route handler, Express, Cloudflare Worker…)
|
|
86
|
-
import {
|
|
86
|
+
import { validatePasswordAsync } from 'use-password-policy/core';
|
|
87
87
|
import { policy } from './password-policy';
|
|
88
88
|
|
|
89
|
-
const { isValid, errors } =
|
|
89
|
+
const { isValid, errors } = await validatePasswordAsync(body.password, policy);
|
|
90
90
|
if (!isValid) return Response.json({ errors }, { status: 400 });
|
|
91
91
|
```
|
|
92
92
|
|
|
93
|
+
Use `validatePasswordAsync` when the policy has `breachCheck`. Without it, the synchronous `validatePassword` returns the same result.
|
|
94
|
+
|
|
93
95
|
`use-password-policy/core` doesn't import React, so it's safe in server bundles.
|
|
94
96
|
|
|
95
97
|
## Presets
|
|
@@ -102,13 +104,13 @@ usePasswordPolicy({ ...presets.nistMfa, password }); // NIST, password is
|
|
|
102
104
|
usePasswordPolicy({ ...presets.classic, password }); // 8+ chars, upper, lower, number, symbol (the default)
|
|
103
105
|
```
|
|
104
106
|
|
|
105
|
-
| Preset | Min | Max | Composition rules | Blocks common passwords |
|
|
106
|
-
| --- | --- | --- | --- | --- |
|
|
107
|
-
| `classic` (default) | 8 | – | upper, lower, number, symbol | no |
|
|
108
|
-
| `nist` | 15 | 64 | none | yes |
|
|
109
|
-
| `nistMfa` | 8 | 64 | none | yes |
|
|
107
|
+
| Preset | Min | Max | Composition rules | Blocks common passwords | Blocks patterns |
|
|
108
|
+
| --- | --- | --- | --- | --- | --- |
|
|
109
|
+
| `classic` (default) | 8 | – | upper, lower, number, symbol | no | no |
|
|
110
|
+
| `nist` | 15 | 64 | none | yes | yes |
|
|
111
|
+
| `nistMfa` | 8 | 64 | none | yes | yes |
|
|
110
112
|
|
|
111
|
-
The NIST presets follow [SP 800-63B-4](https://pages.nist.gov/800-63-4/sp800-63b.html). It asks for length and a blocklist check, and says not to require "mixtures of different character types".
|
|
113
|
+
The NIST presets follow [SP 800-63B-4](https://pages.nist.gov/800-63-4/sp800-63b.html). It asks for length and a blocklist check, and says not to require "mixtures of different character types". Length is counted in Unicode code points, as NIST specifies, so an emoji counts as one character. To also check known breaches, add `breachCheck: true`.
|
|
112
114
|
|
|
113
115
|
## Security add-ons
|
|
114
116
|
|
|
@@ -118,19 +120,34 @@ The NIST presets follow [SP 800-63B-4](https://pages.nist.gov/800-63-4/sp800-63b
|
|
|
118
120
|
usePasswordPolicy({ password, commonPasswordCheck: true });
|
|
119
121
|
```
|
|
120
122
|
|
|
121
|
-
This uses a small built-in list of the most common passwords and base words. It also catches simple variations such as `Password123!`, `P@ssw0rd`, `123qwerty` and `Monkey
|
|
123
|
+
This uses a small built-in list of the most common passwords and base words. It also catches simple variations such as `Password123!`, `P@ssw0rd`, `123qwerty` and `Monkey!!`, and list words joined together such as `passwordpassword` or `Summer2024!Summer`. Pass `commonPasswords: [...]` to use your own list, for example your product name.
|
|
124
|
+
|
|
125
|
+
### Block predictable patterns
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
usePasswordPolicy({ password, patternCheck: true }); // on in the NIST presets
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Rejects repeated characters (`aaaaaaaaaaaaaaa`), repeated chunks (`qwertyqwertyqwerty`, `dragon dragon dragon`), sequences and keyboard runs (`123456789012345`, `abcdefghijk`, `qwertyuiop`), and passwords made of only a few distinct characters, including all spaces.
|
|
122
132
|
|
|
123
133
|
### Check breached passwords (Have I Been Pwned)
|
|
124
134
|
|
|
125
135
|
```tsx
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const pwned = usePwnedPassword(password, { enabled: isValid }); // debounced, cancels stale requests
|
|
129
|
-
// pwned.status: 'idle' | 'checking' | 'safe' | 'pwned' | 'error'
|
|
130
|
-
{pwned.isPwned && <p>Seen {pwned.count.toLocaleString()} times in data breaches. Pick another.</p>}
|
|
136
|
+
const { isValid, requirements, breach } = usePasswordPolicy({ ...presets.nist, breachCheck: true, password });
|
|
131
137
|
```
|
|
132
138
|
|
|
133
|
-
|
|
139
|
+
With `breachCheck`, the check is part of the normal result:
|
|
140
|
+
- Once every other rule passes, the hook checks Have I Been Pwned (debounced, stale requests cancelled).
|
|
141
|
+
- A "Not found in known data breaches" requirement is `pending` until the check answers, and `isValid` stays `false` until then.
|
|
142
|
+
- A breached password fails with that message and is marked Very Weak. `breach` gives `{ status, count }`, e.g. `{ status: 'pwned', count: 10434004 }`.
|
|
143
|
+
- `<PasswordPolicyInput policyOptions={{ breachCheck: true }} />` shows it in the checklist automatically.
|
|
144
|
+
|
|
145
|
+
On the server, `await validatePasswordAsync(password, policy)` does the same.
|
|
146
|
+
|
|
147
|
+
If the service can't be reached, the password is let through by default and `breach.status` is `'error'`. Pass `breachCheck: { failOpen: false }` to block instead. Other settings: `debounceMs` (default 500), `fetch`, `endpoint`, `padding`.
|
|
148
|
+
|
|
149
|
+
For a custom flow, the lower-level `usePwnedPassword(password)` hook and `checkPwnedPassword(password)` (returns the breach count) are still available.
|
|
150
|
+
|
|
134
151
|
Only the first 5 characters of the password's SHA-1 hash are sent ([k-anonymity](https://haveibeenpwned.com/API/v3#SearchingPwnedPasswordsByRange)), never the password. It needs `crypto.subtle`, which means HTTPS or `localhost` in browsers and Node 20+ on the server.
|
|
135
152
|
|
|
136
153
|
### Real strength scoring with zxcvbn
|
|
@@ -168,7 +185,7 @@ const schema = z.object({ password: z.string().superRefine(zodPasswordRule(polic
|
|
|
168
185
|
register('password', { validate: passwordValidator(policy) });
|
|
169
186
|
```
|
|
170
187
|
|
|
171
|
-
|
|
188
|
+
With `breachCheck`, use the async versions, `zodPasswordRuleAsync(policy)` (with `parseAsync`) and `passwordValidatorAsync(policy)`, so the breach check runs too. None of these helpers import Zod or react-hook-form, so they add no dependencies.
|
|
172
189
|
|
|
173
190
|
## Confirm-password field
|
|
174
191
|
|
|
@@ -206,6 +223,8 @@ Rule names: `minLength`, `maxLength`, `uppercase`, `lowercase`, `number`, `speci
|
|
|
206
223
|
| `numberCheck` | `boolean` | `true` | Require a digit. |
|
|
207
224
|
| `specialCharCheck` | `boolean` | `true` | Require a special character. |
|
|
208
225
|
| `commonPasswordCheck` | `boolean` | `false` | Reject common passwords. |
|
|
226
|
+
| `patternCheck` | `boolean` | `false` | Reject repeats, sequences and keyboard patterns. |
|
|
227
|
+
| `breachCheck` | `boolean \| { failOpen, debounceMs, fetch, endpoint, padding }` | `false` | Include the Have I Been Pwned check (hook and `validatePasswordAsync`). |
|
|
209
228
|
| `commonPasswords` | `string[]` | built-in | Replace the blocklist. |
|
|
210
229
|
| `confirmPassword` | `string` | – | Adds a `match` rule when set. |
|
|
211
230
|
| `strengthEstimator` | `(pw) => { score, feedback? }` | – | For example `fromZxcvbn(zxcvbn)`. |
|
|
@@ -226,6 +245,9 @@ Rule names: `minLength`, `maxLength`, `uppercase`, `lowercase`, `number`, `speci
|
|
|
226
245
|
| `strengthPercent` | `number` (0–1) | Fill for a meter. |
|
|
227
246
|
| `strengthScore` | `number` | Number of rules passed. |
|
|
228
247
|
| `estimate` | `{ score, feedback? }` | Only with `strengthEstimator`. |
|
|
248
|
+
| `breach` | `{ status, count }` | Only with `breachCheck`. `status` is `idle`, `checking`, `safe`, `pwned` or `error`. |
|
|
249
|
+
|
|
250
|
+
Each requirement is `{ name, passed, message, pending? }`. `pending` is `true` while the breach check hasn't answered.
|
|
229
251
|
|
|
230
252
|
### `<PasswordPolicyInput />` props
|
|
231
253
|
|
package/dist/core.d.mts
CHANGED
|
@@ -9,6 +9,28 @@
|
|
|
9
9
|
*/
|
|
10
10
|
declare const COMMON_PASSWORDS: readonly string[];
|
|
11
11
|
|
|
12
|
+
interface CheckPwnedOptions {
|
|
13
|
+
/** Abort the request (e.g. when the user keeps typing). */
|
|
14
|
+
signal?: AbortSignal;
|
|
15
|
+
/** Custom fetch implementation. Defaults to the global `fetch`. */
|
|
16
|
+
fetch?: typeof fetch;
|
|
17
|
+
/** Range API base URL. Defaults to Have I Been Pwned. */
|
|
18
|
+
endpoint?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Ask HIBP to pad the response so its size doesn't hint at the result.
|
|
21
|
+
* Sends an `Add-Padding` header. Default `false`.
|
|
22
|
+
*/
|
|
23
|
+
padding?: boolean;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* How many times this password appears in known data breaches, using the
|
|
27
|
+
* Have I Been Pwned range API (k-anonymity). Only the first 5 characters of
|
|
28
|
+
* the password's SHA-1 hash leave the device — never the password itself.
|
|
29
|
+
*
|
|
30
|
+
* Resolves to `0` when the password was not found.
|
|
31
|
+
*/
|
|
32
|
+
declare function checkPwnedPassword(password: string, options?: CheckPwnedOptions): Promise<number>;
|
|
33
|
+
|
|
12
34
|
/** Built-in strength labels, weakest to strongest. */
|
|
13
35
|
type StrengthLabel = 'Very Weak' | 'Weak' | 'Medium' | 'Strong' | 'Very Strong';
|
|
14
36
|
/** Text for a rule: a plain string, or a function of the resolved options (handy for i18n). */
|
|
@@ -56,6 +78,19 @@ interface PasswordPolicyOptions {
|
|
|
56
78
|
commonPasswordCheck?: boolean;
|
|
57
79
|
/** Replace the built-in common-password list. */
|
|
58
80
|
commonPasswords?: readonly string[];
|
|
81
|
+
/**
|
|
82
|
+
* Reject predictable patterns: repeated characters (`aaaaaaaa`), repeated chunks
|
|
83
|
+
* (`abcabcabc`), sequences and keyboard runs (`123456789`, `qwertyuiop`), and passwords
|
|
84
|
+
* made of only a few distinct characters (including all spaces). Default `false`;
|
|
85
|
+
* on in the NIST presets.
|
|
86
|
+
*/
|
|
87
|
+
patternCheck?: boolean;
|
|
88
|
+
/**
|
|
89
|
+
* Check Have I Been Pwned as part of the result. The hook runs it automatically once
|
|
90
|
+
* every other rule passes; on the server use `validatePasswordAsync`. The synchronous
|
|
91
|
+
* `validatePassword` ignores this option. Default `false`.
|
|
92
|
+
*/
|
|
93
|
+
breachCheck?: boolean | BreachCheckOptions;
|
|
59
94
|
/**
|
|
60
95
|
* When set (even to `''`), adds a `match` rule that passes only if
|
|
61
96
|
* `password === confirmPassword`.
|
|
@@ -77,8 +112,34 @@ interface PasswordPolicyOptions {
|
|
|
77
112
|
numberRegex?: RegExp;
|
|
78
113
|
specialCharRegex?: RegExp;
|
|
79
114
|
}
|
|
115
|
+
interface BreachCheckOptions {
|
|
116
|
+
/**
|
|
117
|
+
* What to do when the breach service can't be reached: `true` lets the password
|
|
118
|
+
* through (the requirement passes and `breach.status` is `'error'`), `false` blocks it.
|
|
119
|
+
* Default `true`.
|
|
120
|
+
*/
|
|
121
|
+
failOpen?: boolean;
|
|
122
|
+
/** Hook only: wait this long after the last keystroke. Default `500` ms. */
|
|
123
|
+
debounceMs?: number;
|
|
124
|
+
/** Custom fetch implementation. */
|
|
125
|
+
fetch?: typeof fetch;
|
|
126
|
+
/** Range API base URL. Defaults to Have I Been Pwned. */
|
|
127
|
+
endpoint?: string;
|
|
128
|
+
/** Send the `Add-Padding` header. Default `false`. */
|
|
129
|
+
padding?: boolean;
|
|
130
|
+
}
|
|
131
|
+
type BreachStatus = 'idle' | 'checking' | 'safe' | 'pwned' | 'error';
|
|
132
|
+
interface BreachResult {
|
|
133
|
+
/**
|
|
134
|
+
* `idle`: not checked yet (other rules still failing, or no input).
|
|
135
|
+
* `checking`: request in flight. `safe` / `pwned`: answered. `error`: couldn't reach the service.
|
|
136
|
+
*/
|
|
137
|
+
status: BreachStatus;
|
|
138
|
+
/** Times seen in breaches (0 unless `pwned`). */
|
|
139
|
+
count: number;
|
|
140
|
+
}
|
|
80
141
|
/** Options after defaults are applied. Passed to every rule's `test`. */
|
|
81
|
-
type ResolvedPolicyOptions = Required<Omit<PasswordPolicyOptions, 'password' | 'confirmPassword' | 'strengthEstimator' | 'minStrength'>> & Pick<PasswordPolicyOptions, 'confirmPassword' | 'strengthEstimator' | 'minStrength'>;
|
|
142
|
+
type ResolvedPolicyOptions = Required<Omit<PasswordPolicyOptions, 'password' | 'confirmPassword' | 'strengthEstimator' | 'minStrength' | 'breachCheck'>> & Pick<PasswordPolicyOptions, 'confirmPassword' | 'strengthEstimator' | 'minStrength' | 'breachCheck'>;
|
|
82
143
|
/** @deprecated Use `ResolvedPolicyOptions`. */
|
|
83
144
|
type PolicyDefaults = ResolvedPolicyOptions;
|
|
84
145
|
/** Pass/fail for each active rule, keyed by rule name. */
|
|
@@ -90,6 +151,8 @@ interface Requirement {
|
|
|
90
151
|
name: string;
|
|
91
152
|
passed: boolean;
|
|
92
153
|
message: string;
|
|
154
|
+
/** `true` while the result isn't known yet (the breach check before it has answered). */
|
|
155
|
+
pending?: boolean;
|
|
93
156
|
}
|
|
94
157
|
/** Result of `validatePassword` (and the hook). */
|
|
95
158
|
interface ValidationResult {
|
|
@@ -108,34 +171,14 @@ interface ValidationResult {
|
|
|
108
171
|
strengthLabel: StrengthLabel;
|
|
109
172
|
/** The estimator's result, if `strengthEstimator` is set. */
|
|
110
173
|
estimate?: StrengthEstimate;
|
|
174
|
+
/** Breach-check state, when `breachCheck` is on (hook and `validatePasswordAsync`). */
|
|
175
|
+
breach?: BreachResult;
|
|
111
176
|
}
|
|
112
177
|
/** What `usePasswordPolicy` returns. */
|
|
113
178
|
interface HookReturnValue extends ValidationResult {
|
|
114
179
|
password?: string;
|
|
115
180
|
}
|
|
116
181
|
|
|
117
|
-
interface CheckPwnedOptions {
|
|
118
|
-
/** Abort the request (e.g. when the user keeps typing). */
|
|
119
|
-
signal?: AbortSignal;
|
|
120
|
-
/** Custom fetch implementation. Defaults to the global `fetch`. */
|
|
121
|
-
fetch?: typeof fetch;
|
|
122
|
-
/** Range API base URL. Defaults to Have I Been Pwned. */
|
|
123
|
-
endpoint?: string;
|
|
124
|
-
/**
|
|
125
|
-
* Ask HIBP to pad the response so its size doesn't hint at the result.
|
|
126
|
-
* Sends an `Add-Padding` header. Default `false`.
|
|
127
|
-
*/
|
|
128
|
-
padding?: boolean;
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* How many times this password appears in known data breaches, using the
|
|
132
|
-
* Have I Been Pwned range API (k-anonymity). Only the first 5 characters of
|
|
133
|
-
* the password's SHA-1 hash leave the device — never the password itself.
|
|
134
|
-
*
|
|
135
|
-
* Resolves to `0` when the password was not found.
|
|
136
|
-
*/
|
|
137
|
-
declare function checkPwnedPassword(password: string, options?: CheckPwnedOptions): Promise<number>;
|
|
138
|
-
|
|
139
182
|
/**
|
|
140
183
|
* use-password-policy/core
|
|
141
184
|
*
|
|
@@ -160,7 +203,8 @@ declare const presets: {
|
|
|
160
203
|
};
|
|
161
204
|
/**
|
|
162
205
|
* NIST SP 800-63B-4 (Aug 2025) for passwords used on their own:
|
|
163
|
-
* at least 15 characters, allow up to 64, no composition rules, block common passwords
|
|
206
|
+
* at least 15 characters, allow up to 64, no composition rules, block common passwords
|
|
207
|
+
* and predictable patterns. Add `breachCheck: true` to also check known breaches.
|
|
164
208
|
*/
|
|
165
209
|
nist: {
|
|
166
210
|
minLength: number;
|
|
@@ -170,6 +214,7 @@ declare const presets: {
|
|
|
170
214
|
numberCheck: false;
|
|
171
215
|
specialCharCheck: false;
|
|
172
216
|
commonPasswordCheck: true;
|
|
217
|
+
patternCheck: true;
|
|
173
218
|
};
|
|
174
219
|
/** NIST SP 800-63B-4 when the password is one factor of MFA: at least 8 characters. */
|
|
175
220
|
nistMfa: {
|
|
@@ -180,17 +225,28 @@ declare const presets: {
|
|
|
180
225
|
numberCheck: false;
|
|
181
226
|
specialCharCheck: false;
|
|
182
227
|
commonPasswordCheck: true;
|
|
228
|
+
patternCheck: true;
|
|
183
229
|
};
|
|
184
230
|
};
|
|
185
231
|
declare const DEFAULT_MESSAGES: Record<string, RuleMessage>;
|
|
186
232
|
/** Merge user options over the defaults, ignoring keys set to `undefined`. */
|
|
187
233
|
declare function resolveOptions(options?: PasswordPolicyOptions): ResolvedPolicyOptions;
|
|
234
|
+
/** Length in Unicode code points, so an emoji counts as one character (as NIST specifies). */
|
|
235
|
+
declare const passwordLength: (password: string) => number;
|
|
188
236
|
/**
|
|
189
|
-
* `true` if the password is on the list,
|
|
190
|
-
*
|
|
191
|
-
*
|
|
237
|
+
* `true` if the password is on the list, is a list entry with simple decoration
|
|
238
|
+
* (trailing digits/symbols "password123!", a leading number "123qwerty", leet swaps
|
|
239
|
+
* "p@ssw0rd"), or is only list words joined together ("passwordpassword",
|
|
240
|
+
* "dragon dragon dragon", "Summer2024!Summer").
|
|
192
241
|
*/
|
|
193
242
|
declare function isCommonPassword(password: string, list?: readonly string[]): boolean;
|
|
243
|
+
/**
|
|
244
|
+
* `true` for passwords that follow a predictable pattern: only whitespace, three or
|
|
245
|
+
* fewer distinct characters ("aaaaaaaa", "abababab"), a repeated chunk ("abcabcabc",
|
|
246
|
+
* "qwertyqwertyqwerty"), or mostly sequences and keyboard runs ("123456789012345",
|
|
247
|
+
* "abcdefghijk", "qwertyuiop", "987654321").
|
|
248
|
+
*/
|
|
249
|
+
declare function isPredictablePattern(password: string): boolean;
|
|
194
250
|
/**
|
|
195
251
|
* Validate a password against a policy. Pure and synchronous.
|
|
196
252
|
*
|
|
@@ -200,6 +256,22 @@ declare function isCommonPassword(password: string, list?: readonly string[]): b
|
|
|
200
256
|
* ```
|
|
201
257
|
*/
|
|
202
258
|
declare function validatePassword(password: string, options?: PasswordPolicyOptions): ValidationResult;
|
|
259
|
+
/**
|
|
260
|
+
* Fold a breach-check outcome into a validation result: adds a `notBreached`
|
|
261
|
+
* requirement (pending while unanswered), makes `isValid` depend on it, and marks a
|
|
262
|
+
* breached password Very Weak. The hook and `validatePasswordAsync` use this; you
|
|
263
|
+
* only need it for custom flows.
|
|
264
|
+
*/
|
|
265
|
+
declare function applyBreachResult(result: ValidationResult, breach: BreachResult, options?: PasswordPolicyOptions): ValidationResult;
|
|
266
|
+
/**
|
|
267
|
+
* Like `validatePassword`, plus the Have I Been Pwned check when `breachCheck` is on.
|
|
268
|
+
* The breach lookup only runs once every other rule passes. Use this on the server.
|
|
269
|
+
*
|
|
270
|
+
* ```ts
|
|
271
|
+
* const { isValid, errors, breach } = await validatePasswordAsync(password, { ...presets.nist, breachCheck: true });
|
|
272
|
+
* ```
|
|
273
|
+
*/
|
|
274
|
+
declare function validatePasswordAsync(password: string, options?: PasswordPolicyOptions): Promise<ValidationResult>;
|
|
203
275
|
/** Anything shaped like the result of `zxcvbn(password)` or `@zxcvbn-ts/core`'s `zxcvbn(password)`. */
|
|
204
276
|
interface ZxcvbnLikeResult {
|
|
205
277
|
score: number;
|
|
@@ -241,6 +313,18 @@ interface ZodLikeRefinementCtx {
|
|
|
241
313
|
declare function zodPasswordRule(options?: PasswordPolicyOptions, { allErrors }?: {
|
|
242
314
|
allErrors?: boolean | undefined;
|
|
243
315
|
}): (value: string, ctx: ZodLikeRefinementCtx) => void;
|
|
316
|
+
/**
|
|
317
|
+
* Async version of `zodPasswordRule` that also runs the breach check when
|
|
318
|
+
* `breachCheck` is on. Use it with `safeParseAsync` / `parseAsync`.
|
|
319
|
+
*/
|
|
320
|
+
declare function zodPasswordRuleAsync(options?: PasswordPolicyOptions, { allErrors }?: {
|
|
321
|
+
allErrors?: boolean | undefined;
|
|
322
|
+
}): (value: string, ctx: ZodLikeRefinementCtx) => Promise<void>;
|
|
323
|
+
/**
|
|
324
|
+
* Async version of `passwordValidator` that also runs the breach check when
|
|
325
|
+
* `breachCheck` is on. react-hook-form accepts async `validate` functions.
|
|
326
|
+
*/
|
|
327
|
+
declare function passwordValidatorAsync(options?: PasswordPolicyOptions): (value: string) => Promise<true | string>;
|
|
244
328
|
/**
|
|
245
329
|
* A validate function that returns `true` or the first error message.
|
|
246
330
|
* Drops straight into react-hook-form's `validate`, and works with anything
|
|
@@ -252,4 +336,4 @@ declare function zodPasswordRule(options?: PasswordPolicyOptions, { allErrors }?
|
|
|
252
336
|
*/
|
|
253
337
|
declare function passwordValidator(options?: PasswordPolicyOptions): (value: string) => true | string;
|
|
254
338
|
|
|
255
|
-
export { COMMON_PASSWORDS, type CheckPwnedOptions, DEFAULT_MESSAGES, DEFAULT_OPTIONS, type HookReturnValue, type PasswordPolicyOptions, type PasswordPolicyState, type PolicyDefaults, type PolicyRule, type Requirement, type ResolvedPolicyOptions, type RuleMessage, type StrengthEstimate, type StrengthEstimator, type StrengthLabel, type ValidationResult, type ZodLikeRefinementCtx, type ZxcvbnLikeResult, checkPwnedPassword, fromZxcvbn, isCommonPassword, passwordValidator, presets, resolveOptions, validatePassword, zodPasswordRule };
|
|
339
|
+
export { type BreachCheckOptions, type BreachResult, type BreachStatus, COMMON_PASSWORDS, type CheckPwnedOptions, DEFAULT_MESSAGES, DEFAULT_OPTIONS, type HookReturnValue, type PasswordPolicyOptions, type PasswordPolicyState, type PolicyDefaults, type PolicyRule, type Requirement, type ResolvedPolicyOptions, type RuleMessage, type StrengthEstimate, type StrengthEstimator, type StrengthLabel, type ValidationResult, type ZodLikeRefinementCtx, type ZxcvbnLikeResult, applyBreachResult, checkPwnedPassword, fromZxcvbn, isCommonPassword, isPredictablePattern, passwordLength, passwordValidator, passwordValidatorAsync, presets, resolveOptions, validatePassword, validatePasswordAsync, zodPasswordRule, zodPasswordRuleAsync };
|
package/dist/core.d.ts
CHANGED
|
@@ -9,6 +9,28 @@
|
|
|
9
9
|
*/
|
|
10
10
|
declare const COMMON_PASSWORDS: readonly string[];
|
|
11
11
|
|
|
12
|
+
interface CheckPwnedOptions {
|
|
13
|
+
/** Abort the request (e.g. when the user keeps typing). */
|
|
14
|
+
signal?: AbortSignal;
|
|
15
|
+
/** Custom fetch implementation. Defaults to the global `fetch`. */
|
|
16
|
+
fetch?: typeof fetch;
|
|
17
|
+
/** Range API base URL. Defaults to Have I Been Pwned. */
|
|
18
|
+
endpoint?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Ask HIBP to pad the response so its size doesn't hint at the result.
|
|
21
|
+
* Sends an `Add-Padding` header. Default `false`.
|
|
22
|
+
*/
|
|
23
|
+
padding?: boolean;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* How many times this password appears in known data breaches, using the
|
|
27
|
+
* Have I Been Pwned range API (k-anonymity). Only the first 5 characters of
|
|
28
|
+
* the password's SHA-1 hash leave the device — never the password itself.
|
|
29
|
+
*
|
|
30
|
+
* Resolves to `0` when the password was not found.
|
|
31
|
+
*/
|
|
32
|
+
declare function checkPwnedPassword(password: string, options?: CheckPwnedOptions): Promise<number>;
|
|
33
|
+
|
|
12
34
|
/** Built-in strength labels, weakest to strongest. */
|
|
13
35
|
type StrengthLabel = 'Very Weak' | 'Weak' | 'Medium' | 'Strong' | 'Very Strong';
|
|
14
36
|
/** Text for a rule: a plain string, or a function of the resolved options (handy for i18n). */
|
|
@@ -56,6 +78,19 @@ interface PasswordPolicyOptions {
|
|
|
56
78
|
commonPasswordCheck?: boolean;
|
|
57
79
|
/** Replace the built-in common-password list. */
|
|
58
80
|
commonPasswords?: readonly string[];
|
|
81
|
+
/**
|
|
82
|
+
* Reject predictable patterns: repeated characters (`aaaaaaaa`), repeated chunks
|
|
83
|
+
* (`abcabcabc`), sequences and keyboard runs (`123456789`, `qwertyuiop`), and passwords
|
|
84
|
+
* made of only a few distinct characters (including all spaces). Default `false`;
|
|
85
|
+
* on in the NIST presets.
|
|
86
|
+
*/
|
|
87
|
+
patternCheck?: boolean;
|
|
88
|
+
/**
|
|
89
|
+
* Check Have I Been Pwned as part of the result. The hook runs it automatically once
|
|
90
|
+
* every other rule passes; on the server use `validatePasswordAsync`. The synchronous
|
|
91
|
+
* `validatePassword` ignores this option. Default `false`.
|
|
92
|
+
*/
|
|
93
|
+
breachCheck?: boolean | BreachCheckOptions;
|
|
59
94
|
/**
|
|
60
95
|
* When set (even to `''`), adds a `match` rule that passes only if
|
|
61
96
|
* `password === confirmPassword`.
|
|
@@ -77,8 +112,34 @@ interface PasswordPolicyOptions {
|
|
|
77
112
|
numberRegex?: RegExp;
|
|
78
113
|
specialCharRegex?: RegExp;
|
|
79
114
|
}
|
|
115
|
+
interface BreachCheckOptions {
|
|
116
|
+
/**
|
|
117
|
+
* What to do when the breach service can't be reached: `true` lets the password
|
|
118
|
+
* through (the requirement passes and `breach.status` is `'error'`), `false` blocks it.
|
|
119
|
+
* Default `true`.
|
|
120
|
+
*/
|
|
121
|
+
failOpen?: boolean;
|
|
122
|
+
/** Hook only: wait this long after the last keystroke. Default `500` ms. */
|
|
123
|
+
debounceMs?: number;
|
|
124
|
+
/** Custom fetch implementation. */
|
|
125
|
+
fetch?: typeof fetch;
|
|
126
|
+
/** Range API base URL. Defaults to Have I Been Pwned. */
|
|
127
|
+
endpoint?: string;
|
|
128
|
+
/** Send the `Add-Padding` header. Default `false`. */
|
|
129
|
+
padding?: boolean;
|
|
130
|
+
}
|
|
131
|
+
type BreachStatus = 'idle' | 'checking' | 'safe' | 'pwned' | 'error';
|
|
132
|
+
interface BreachResult {
|
|
133
|
+
/**
|
|
134
|
+
* `idle`: not checked yet (other rules still failing, or no input).
|
|
135
|
+
* `checking`: request in flight. `safe` / `pwned`: answered. `error`: couldn't reach the service.
|
|
136
|
+
*/
|
|
137
|
+
status: BreachStatus;
|
|
138
|
+
/** Times seen in breaches (0 unless `pwned`). */
|
|
139
|
+
count: number;
|
|
140
|
+
}
|
|
80
141
|
/** Options after defaults are applied. Passed to every rule's `test`. */
|
|
81
|
-
type ResolvedPolicyOptions = Required<Omit<PasswordPolicyOptions, 'password' | 'confirmPassword' | 'strengthEstimator' | 'minStrength'>> & Pick<PasswordPolicyOptions, 'confirmPassword' | 'strengthEstimator' | 'minStrength'>;
|
|
142
|
+
type ResolvedPolicyOptions = Required<Omit<PasswordPolicyOptions, 'password' | 'confirmPassword' | 'strengthEstimator' | 'minStrength' | 'breachCheck'>> & Pick<PasswordPolicyOptions, 'confirmPassword' | 'strengthEstimator' | 'minStrength' | 'breachCheck'>;
|
|
82
143
|
/** @deprecated Use `ResolvedPolicyOptions`. */
|
|
83
144
|
type PolicyDefaults = ResolvedPolicyOptions;
|
|
84
145
|
/** Pass/fail for each active rule, keyed by rule name. */
|
|
@@ -90,6 +151,8 @@ interface Requirement {
|
|
|
90
151
|
name: string;
|
|
91
152
|
passed: boolean;
|
|
92
153
|
message: string;
|
|
154
|
+
/** `true` while the result isn't known yet (the breach check before it has answered). */
|
|
155
|
+
pending?: boolean;
|
|
93
156
|
}
|
|
94
157
|
/** Result of `validatePassword` (and the hook). */
|
|
95
158
|
interface ValidationResult {
|
|
@@ -108,34 +171,14 @@ interface ValidationResult {
|
|
|
108
171
|
strengthLabel: StrengthLabel;
|
|
109
172
|
/** The estimator's result, if `strengthEstimator` is set. */
|
|
110
173
|
estimate?: StrengthEstimate;
|
|
174
|
+
/** Breach-check state, when `breachCheck` is on (hook and `validatePasswordAsync`). */
|
|
175
|
+
breach?: BreachResult;
|
|
111
176
|
}
|
|
112
177
|
/** What `usePasswordPolicy` returns. */
|
|
113
178
|
interface HookReturnValue extends ValidationResult {
|
|
114
179
|
password?: string;
|
|
115
180
|
}
|
|
116
181
|
|
|
117
|
-
interface CheckPwnedOptions {
|
|
118
|
-
/** Abort the request (e.g. when the user keeps typing). */
|
|
119
|
-
signal?: AbortSignal;
|
|
120
|
-
/** Custom fetch implementation. Defaults to the global `fetch`. */
|
|
121
|
-
fetch?: typeof fetch;
|
|
122
|
-
/** Range API base URL. Defaults to Have I Been Pwned. */
|
|
123
|
-
endpoint?: string;
|
|
124
|
-
/**
|
|
125
|
-
* Ask HIBP to pad the response so its size doesn't hint at the result.
|
|
126
|
-
* Sends an `Add-Padding` header. Default `false`.
|
|
127
|
-
*/
|
|
128
|
-
padding?: boolean;
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* How many times this password appears in known data breaches, using the
|
|
132
|
-
* Have I Been Pwned range API (k-anonymity). Only the first 5 characters of
|
|
133
|
-
* the password's SHA-1 hash leave the device — never the password itself.
|
|
134
|
-
*
|
|
135
|
-
* Resolves to `0` when the password was not found.
|
|
136
|
-
*/
|
|
137
|
-
declare function checkPwnedPassword(password: string, options?: CheckPwnedOptions): Promise<number>;
|
|
138
|
-
|
|
139
182
|
/**
|
|
140
183
|
* use-password-policy/core
|
|
141
184
|
*
|
|
@@ -160,7 +203,8 @@ declare const presets: {
|
|
|
160
203
|
};
|
|
161
204
|
/**
|
|
162
205
|
* NIST SP 800-63B-4 (Aug 2025) for passwords used on their own:
|
|
163
|
-
* at least 15 characters, allow up to 64, no composition rules, block common passwords
|
|
206
|
+
* at least 15 characters, allow up to 64, no composition rules, block common passwords
|
|
207
|
+
* and predictable patterns. Add `breachCheck: true` to also check known breaches.
|
|
164
208
|
*/
|
|
165
209
|
nist: {
|
|
166
210
|
minLength: number;
|
|
@@ -170,6 +214,7 @@ declare const presets: {
|
|
|
170
214
|
numberCheck: false;
|
|
171
215
|
specialCharCheck: false;
|
|
172
216
|
commonPasswordCheck: true;
|
|
217
|
+
patternCheck: true;
|
|
173
218
|
};
|
|
174
219
|
/** NIST SP 800-63B-4 when the password is one factor of MFA: at least 8 characters. */
|
|
175
220
|
nistMfa: {
|
|
@@ -180,17 +225,28 @@ declare const presets: {
|
|
|
180
225
|
numberCheck: false;
|
|
181
226
|
specialCharCheck: false;
|
|
182
227
|
commonPasswordCheck: true;
|
|
228
|
+
patternCheck: true;
|
|
183
229
|
};
|
|
184
230
|
};
|
|
185
231
|
declare const DEFAULT_MESSAGES: Record<string, RuleMessage>;
|
|
186
232
|
/** Merge user options over the defaults, ignoring keys set to `undefined`. */
|
|
187
233
|
declare function resolveOptions(options?: PasswordPolicyOptions): ResolvedPolicyOptions;
|
|
234
|
+
/** Length in Unicode code points, so an emoji counts as one character (as NIST specifies). */
|
|
235
|
+
declare const passwordLength: (password: string) => number;
|
|
188
236
|
/**
|
|
189
|
-
* `true` if the password is on the list,
|
|
190
|
-
*
|
|
191
|
-
*
|
|
237
|
+
* `true` if the password is on the list, is a list entry with simple decoration
|
|
238
|
+
* (trailing digits/symbols "password123!", a leading number "123qwerty", leet swaps
|
|
239
|
+
* "p@ssw0rd"), or is only list words joined together ("passwordpassword",
|
|
240
|
+
* "dragon dragon dragon", "Summer2024!Summer").
|
|
192
241
|
*/
|
|
193
242
|
declare function isCommonPassword(password: string, list?: readonly string[]): boolean;
|
|
243
|
+
/**
|
|
244
|
+
* `true` for passwords that follow a predictable pattern: only whitespace, three or
|
|
245
|
+
* fewer distinct characters ("aaaaaaaa", "abababab"), a repeated chunk ("abcabcabc",
|
|
246
|
+
* "qwertyqwertyqwerty"), or mostly sequences and keyboard runs ("123456789012345",
|
|
247
|
+
* "abcdefghijk", "qwertyuiop", "987654321").
|
|
248
|
+
*/
|
|
249
|
+
declare function isPredictablePattern(password: string): boolean;
|
|
194
250
|
/**
|
|
195
251
|
* Validate a password against a policy. Pure and synchronous.
|
|
196
252
|
*
|
|
@@ -200,6 +256,22 @@ declare function isCommonPassword(password: string, list?: readonly string[]): b
|
|
|
200
256
|
* ```
|
|
201
257
|
*/
|
|
202
258
|
declare function validatePassword(password: string, options?: PasswordPolicyOptions): ValidationResult;
|
|
259
|
+
/**
|
|
260
|
+
* Fold a breach-check outcome into a validation result: adds a `notBreached`
|
|
261
|
+
* requirement (pending while unanswered), makes `isValid` depend on it, and marks a
|
|
262
|
+
* breached password Very Weak. The hook and `validatePasswordAsync` use this; you
|
|
263
|
+
* only need it for custom flows.
|
|
264
|
+
*/
|
|
265
|
+
declare function applyBreachResult(result: ValidationResult, breach: BreachResult, options?: PasswordPolicyOptions): ValidationResult;
|
|
266
|
+
/**
|
|
267
|
+
* Like `validatePassword`, plus the Have I Been Pwned check when `breachCheck` is on.
|
|
268
|
+
* The breach lookup only runs once every other rule passes. Use this on the server.
|
|
269
|
+
*
|
|
270
|
+
* ```ts
|
|
271
|
+
* const { isValid, errors, breach } = await validatePasswordAsync(password, { ...presets.nist, breachCheck: true });
|
|
272
|
+
* ```
|
|
273
|
+
*/
|
|
274
|
+
declare function validatePasswordAsync(password: string, options?: PasswordPolicyOptions): Promise<ValidationResult>;
|
|
203
275
|
/** Anything shaped like the result of `zxcvbn(password)` or `@zxcvbn-ts/core`'s `zxcvbn(password)`. */
|
|
204
276
|
interface ZxcvbnLikeResult {
|
|
205
277
|
score: number;
|
|
@@ -241,6 +313,18 @@ interface ZodLikeRefinementCtx {
|
|
|
241
313
|
declare function zodPasswordRule(options?: PasswordPolicyOptions, { allErrors }?: {
|
|
242
314
|
allErrors?: boolean | undefined;
|
|
243
315
|
}): (value: string, ctx: ZodLikeRefinementCtx) => void;
|
|
316
|
+
/**
|
|
317
|
+
* Async version of `zodPasswordRule` that also runs the breach check when
|
|
318
|
+
* `breachCheck` is on. Use it with `safeParseAsync` / `parseAsync`.
|
|
319
|
+
*/
|
|
320
|
+
declare function zodPasswordRuleAsync(options?: PasswordPolicyOptions, { allErrors }?: {
|
|
321
|
+
allErrors?: boolean | undefined;
|
|
322
|
+
}): (value: string, ctx: ZodLikeRefinementCtx) => Promise<void>;
|
|
323
|
+
/**
|
|
324
|
+
* Async version of `passwordValidator` that also runs the breach check when
|
|
325
|
+
* `breachCheck` is on. react-hook-form accepts async `validate` functions.
|
|
326
|
+
*/
|
|
327
|
+
declare function passwordValidatorAsync(options?: PasswordPolicyOptions): (value: string) => Promise<true | string>;
|
|
244
328
|
/**
|
|
245
329
|
* A validate function that returns `true` or the first error message.
|
|
246
330
|
* Drops straight into react-hook-form's `validate`, and works with anything
|
|
@@ -252,4 +336,4 @@ declare function zodPasswordRule(options?: PasswordPolicyOptions, { allErrors }?
|
|
|
252
336
|
*/
|
|
253
337
|
declare function passwordValidator(options?: PasswordPolicyOptions): (value: string) => true | string;
|
|
254
338
|
|
|
255
|
-
export { COMMON_PASSWORDS, type CheckPwnedOptions, DEFAULT_MESSAGES, DEFAULT_OPTIONS, type HookReturnValue, type PasswordPolicyOptions, type PasswordPolicyState, type PolicyDefaults, type PolicyRule, type Requirement, type ResolvedPolicyOptions, type RuleMessage, type StrengthEstimate, type StrengthEstimator, type StrengthLabel, type ValidationResult, type ZodLikeRefinementCtx, type ZxcvbnLikeResult, checkPwnedPassword, fromZxcvbn, isCommonPassword, passwordValidator, presets, resolveOptions, validatePassword, zodPasswordRule };
|
|
339
|
+
export { type BreachCheckOptions, type BreachResult, type BreachStatus, COMMON_PASSWORDS, type CheckPwnedOptions, DEFAULT_MESSAGES, DEFAULT_OPTIONS, type HookReturnValue, type PasswordPolicyOptions, type PasswordPolicyState, type PolicyDefaults, type PolicyRule, type Requirement, type ResolvedPolicyOptions, type RuleMessage, type StrengthEstimate, type StrengthEstimator, type StrengthLabel, type ValidationResult, type ZodLikeRefinementCtx, type ZxcvbnLikeResult, applyBreachResult, checkPwnedPassword, fromZxcvbn, isCommonPassword, isPredictablePattern, passwordLength, passwordValidator, passwordValidatorAsync, presets, resolveOptions, validatePassword, validatePasswordAsync, zodPasswordRule, zodPasswordRuleAsync };
|