@nxgt/janus 0.13.0 → 0.14.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/README.md +56 -15
- package/dist/auth/config/index.d.ts +1 -1
- package/dist/auth/config/index.d.ts.map +1 -1
- package/dist/auth/config/janus-config.d.ts +23 -0
- package/dist/auth/config/janus-config.d.ts.map +1 -1
- package/dist/auth/config/resolve-config.d.ts.map +1 -1
- package/dist/auth/config/resolve-sign-in.d.ts +5 -0
- package/dist/auth/config/resolve-sign-in.d.ts.map +1 -0
- package/dist/auth/config/resolved-config.d.ts +5 -0
- package/dist/auth/config/resolved-config.d.ts.map +1 -1
- package/dist/auth/derived-keys.d.ts +9 -0
- package/dist/auth/derived-keys.d.ts.map +1 -0
- package/dist/auth/events.d.ts +19 -4
- package/dist/auth/events.d.ts.map +1 -1
- package/dist/auth/index.d.ts +1 -1
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/one-time/index.d.ts +3 -1
- package/dist/auth/one-time/index.d.ts.map +1 -1
- package/dist/auth/one-time/window.d.ts +50 -0
- package/dist/auth/one-time/window.d.ts.map +1 -0
- package/dist/auth/password-written.d.ts +7 -0
- package/dist/auth/password-written.d.ts.map +1 -1
- package/dist/auth/second-factor/app-code-attempts.d.ts +5 -8
- package/dist/auth/second-factor/app-code-attempts.d.ts.map +1 -1
- package/dist/auth/second-factor/challenge.d.ts.map +1 -1
- package/dist/auth/second-factor/recovery-codes.d.ts.map +1 -1
- package/dist/auth/second-factor/recovery.d.ts.map +1 -1
- package/dist/auth/sign-in-attempts.d.ts +43 -0
- package/dist/auth/sign-in-attempts.d.ts.map +1 -0
- package/dist/auth/types/password.d.ts +8 -0
- package/dist/auth/types/password.d.ts.map +1 -1
- package/dist/auth/users/fields-patch.d.ts +8 -2
- package/dist/auth/users/fields-patch.d.ts.map +1 -1
- package/dist/auth/users/record-flows.d.ts.map +1 -1
- package/dist/auth/users/sign-in.d.ts +2 -2
- package/dist/auth/users/sign-in.d.ts.map +1 -1
- package/dist/auth/users/update.d.ts +12 -0
- package/dist/auth/users/update.d.ts.map +1 -0
- package/dist/chunks/{index-km7ffctx.js → index-6c9fbtv2.js} +2 -2
- package/dist/chunks/{index-rd2rddvv.js → index-baazjyvc.js} +2 -2
- package/dist/chunks/{index-j2gd8fry.js → index-hnwggyxs.js} +3 -2
- package/dist/chunks/{index-j2gd8fry.js.map → index-hnwggyxs.js.map} +3 -3
- package/dist/chunks/{index-jp2kka6d.js → index-kj3st57z.js} +2 -2
- package/dist/conformance/index.js +3 -3
- package/dist/errors/janus-error/base.d.ts +1 -0
- package/dist/errors/janus-error/base.d.ts.map +1 -1
- package/dist/errors/janus-error/codes.d.ts +3 -1
- package/dist/errors/janus-error/codes.d.ts.map +1 -1
- package/dist/errors/janus-error/options.d.ts +9 -2
- package/dist/errors/janus-error/options.d.ts.map +1 -1
- package/dist/index.js +226 -86
- package/dist/index.js.map +20 -15
- package/dist/permissions/index.js +3 -3
- package/docs/README.md +2 -2
- package/docs/guide/errors.md +10 -2
- package/docs/guide/events.md +73 -12
- package/docs/guide/passwords.md +115 -17
- package/docs/guide/second-factor.md +4 -2
- package/docs/guide/users.md +11 -6
- package/docs/guide/vocabulary.md +1 -1
- package/docs/roadmap.md +27 -18
- package/docs/troubleshooting.md +55 -2
- package/package.json +1 -1
- /package/dist/chunks/{index-km7ffctx.js.map → index-6c9fbtv2.js.map} +0 -0
- /package/dist/chunks/{index-rd2rddvv.js.map → index-baazjyvc.js.map} +0 -0
- /package/dist/chunks/{index-jp2kka6d.js.map → index-kj3st57z.js.map} +0 -0
package/docs/troubleshooting.md
CHANGED
|
@@ -40,6 +40,7 @@ How the messages are shaped:
|
|
|
40
40
|
- [`janus: secondFactor.keys: the key "<id>" is not 32 bytes in base64 …`](#janus-secondfactorkeys-the-key-id-is-not-32-bytes-in-base64--make-one-with-openssl-rand--base64-32)
|
|
41
41
|
- [`"hasSecondFactor" is a field janus sets itself; rename it`](#hassecondfactor-is-a-field-janus-sets-itself-rename-it)
|
|
42
42
|
- [`janus: events must be a function that takes a user event …`](#janus-events-must-be-a-function-that-takes-a-user-event--webhooks---from-nxgtjanus-webhooks-or-your-own)
|
|
43
|
+
- [`janus: signIn.throttle …` wiring messages](#janus-signinthrottle-wiring-messages)
|
|
43
44
|
- [Other `janus:` wiring messages](#other-janus-wiring-messages)
|
|
44
45
|
|
|
45
46
|
**Users, sessions and tokens**
|
|
@@ -51,6 +52,8 @@ How the messages are shaped:
|
|
|
51
52
|
- [`USER_INVALID` — `<call>: the fields do not match the <type> schema …`](#user_invalid--call-the-fields-do-not-match-the-type-schema-n-issues-at-paths)
|
|
52
53
|
- [`PASSWORD_TOO_SHORT` — `<call>: the password is shorter than the policy's <n> characters`](#password_too_short--call-the-password-is-shorter-than-the-policys-n-characters)
|
|
53
54
|
- [`CREDENTIALS_INVALID` — `<call>: the login and the password do not match`](#credentials_invalid--call-the-login-and-the-password-do-not-match)
|
|
55
|
+
- [`CREDENTIALS_INVALID` — `<call>: too many passwords tried at this login …`](#credentials_invalid--call-too-many-passwords-tried-at-this-login--wait-for-the-next-window)
|
|
56
|
+
- [`STORE_FAILED` — `<call>: the store dropped the attempts it had just stored`](#store_failed--call-the-store-dropped-the-attempts-it-had-just-stored)
|
|
54
57
|
- [`HASH_UNSUPPORTED` — `<call>: no wired verifier claims the prefix "<prefix>"`](#hash_unsupported--call-no-wired-verifier-claims-the-prefix-prefix)
|
|
55
58
|
- [`scryptHasher: the stored hash has the $scrypt$ prefix and not its format`](#scrypthasher-the-stored-hash-has-the-scrypt-prefix-and-not-its-format)
|
|
56
59
|
- [`USER_INACTIVE` — `<call>: the user is inactive`](#user_inactive--call-the-user-is-inactive)
|
|
@@ -328,7 +331,7 @@ Also `janus: "<name>" cannot name a user type — janus() answers a method of th
|
|
|
328
331
|
|
|
329
332
|
### `janus: session.lifespan: "<value>" is not a duration; write a number followed by ms, s, m, h or d — for example "15m" or "720h"`
|
|
330
333
|
|
|
331
|
-
The same for `session.renewAfter`, `tokens.verifyEmail`, `tokens.resetPassword`, `tokens.signInCode`, `tokens.stepUp` and `
|
|
334
|
+
The same for `session.renewAfter`, `tokens.verifyEmail`, `tokens.resetPassword`, `tokens.signInCode`, `tokens.stepUp`, `secondFactor.challenge` and `signIn.throttle.window` — and, at call time rather than in `janus()`, `assertFresh: maxAge: "<value>" is not a duration; …`, from a `maxAge` written wrong. Also `<option>: a duration must be above zero` and `<option>: a duration in milliseconds must be a finite number above zero`.
|
|
332
335
|
|
|
333
336
|
**When:** `janus({...})`.
|
|
334
337
|
**Why:** a duration is a number of milliseconds, or a number followed by one unit. `'30 m'` compiles — TypeScript's `${number}` accepts the space — and is refused here.
|
|
@@ -403,6 +406,15 @@ janus({
|
|
|
403
406
|
});
|
|
404
407
|
```
|
|
405
408
|
|
|
409
|
+
### `janus: signIn.throttle` wiring messages
|
|
410
|
+
|
|
411
|
+
`TypeError`, at `janus()`: `janus: signIn must be an object — { throttle }`,
|
|
412
|
+
`janus: signIn.throttle must be { attempts, window }, or false to count
|
|
413
|
+
nothing`, `janus: signIn.throttle.attempts must be a whole number above
|
|
414
|
+
zero`, and a `janus: signIn.throttle.window` that is not a duration.
|
|
415
|
+
**Fix:** `signIn: { throttle: { attempts: 10, window: '15m' } }` — the
|
|
416
|
+
defaults — or `signIn: { throttle: false }` to count nothing.
|
|
417
|
+
|
|
406
418
|
### Other `janus:` wiring messages
|
|
407
419
|
|
|
408
420
|
| Message | Fix |
|
|
@@ -532,7 +544,7 @@ if (error instanceof UserInvalidError) {
|
|
|
532
544
|
Also `changePassword: the current password does not match`.
|
|
533
545
|
|
|
534
546
|
**When:** `signIn`, `changePassword`.
|
|
535
|
-
**Why:** no user holds the login, the user has no password, or the password is wrong — **one code for the three**. Also a sign-in that verified a password written over while it ran (`reason: 'wrongPassword'`): its session is revoked, or its challenge spent, before the refusal. `error.reason` (`unknownLogin`, `noPassword`, `wrongPassword`) tells them apart for your logs and your rate limiter. A login holding a NUL character or a lone surrogate is `unknownLogin`: no user can hold one.
|
|
547
|
+
**Why:** no user holds the login, the user has no password, or the password is wrong — **one code for the three**. Also a sign-in that verified a password written over while it ran (`reason: 'wrongPassword'`): its session is revoked, or its challenge spent, before the refusal. `error.reason` (`unknownLogin`, `noPassword`, `wrongPassword`) tells them apart for your logs and your rate limiter; a login past its attempts has its own message, below. A login holding a NUL character or a lone surrogate is `unknownLogin`: no user can hold one.
|
|
536
548
|
**Fix:** answer 401 with the same body whatever the reason:
|
|
537
549
|
|
|
538
550
|
```ts
|
|
@@ -540,6 +552,47 @@ Also `changePassword: the current password does not match`.
|
|
|
540
552
|
return new Response('Wrong e-mail or password', { status: 401 });
|
|
541
553
|
```
|
|
542
554
|
|
|
555
|
+
### `CREDENTIALS_INVALID` — `<call>: too many passwords tried at this login — wait for the next window`
|
|
556
|
+
|
|
557
|
+
`CredentialError`, with `reason: 'throttled'` and `retryAfter`.
|
|
558
|
+
|
|
559
|
+
**When:** `signIn`, after ten passwords were tried at this login in the
|
|
560
|
+
current 15-minute window — whatever this password is, **the right one
|
|
561
|
+
included**, and whether or not anybody holds the login. Nothing is compared.
|
|
562
|
+
**Why:** `signIn` throttles password guessing per login, on by default:
|
|
563
|
+
counted by the store, in fixed windows of the clock, shared by every process
|
|
564
|
+
over the same store. Nothing locks: the next window signs in, and a sign-in
|
|
565
|
+
that succeeds starts the count again. In tests with `fixedClock`, the window
|
|
566
|
+
never ends: advance the clock past `retryAfter`, or wire
|
|
567
|
+
`signIn: { throttle: false }`.
|
|
568
|
+
**Fix:** tell the visitor to wait `retryAfter` seconds — not that the
|
|
569
|
+
password is wrong — and answer 401, as `@nxgt/janus-hono`'s `janusErrors()`
|
|
570
|
+
does, with a `Retry-After` header. If nobody you know tried ten passwords,
|
|
571
|
+
somebody is guessing: look for `janus.signIn.throttled` in your logs. To
|
|
572
|
+
change the limit or turn it off:
|
|
573
|
+
|
|
574
|
+
```ts
|
|
575
|
+
if (error instanceof JanusError && error.retryAfter !== undefined) {
|
|
576
|
+
const headers = { 'retry-after': String(error.retryAfter) };
|
|
577
|
+
return Response.json({ code: error.code, retryAfter: error.retryAfter }, { status: 401, headers });
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
janus({ ..., signIn: { throttle: { attempts: 20, window: '15m' } } });
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
### `STORE_FAILED` — `<call>: the store dropped the attempts it had just stored`
|
|
584
|
+
|
|
585
|
+
`StoreFailure`, with `slot: 'tokens'` and `operation: 'countAttempt'`.
|
|
586
|
+
|
|
587
|
+
**When:** `signIn`, when the tokens store answers `countAttempt` with `null`
|
|
588
|
+
for the count it stored an instant before.
|
|
589
|
+
**Why:** the store lost it — a Redis evicting keys under memory pressure, a
|
|
590
|
+
flush between the two calls, or an adapter whose `countAttempt` does not
|
|
591
|
+
find what `insertToken` wrote. The throttle cannot count, so the sign-in
|
|
592
|
+
fails rather than pass uncounted.
|
|
593
|
+
**Fix:** answer 503. Run the conformance suite against the adapter, and give
|
|
594
|
+
a Redis tokens store `maxmemory-policy noeviction`.
|
|
595
|
+
|
|
543
596
|
### `HASH_UNSUPPORTED` — `<call>: no wired verifier claims the prefix "<prefix>"`
|
|
544
597
|
|
|
545
598
|
**When:** `signIn` or `changePassword`, for a user whose stored hash was written by another system — typically after an import.
|
package/package.json
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|