@nxgt/janus 0.17.2 → 0.19.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.
Files changed (93) hide show
  1. package/README.md +107 -27
  2. package/dist/auth/config/index.d.ts +1 -1
  3. package/dist/auth/config/index.d.ts.map +1 -1
  4. package/dist/auth/config/janus-config.d.ts +31 -0
  5. package/dist/auth/config/janus-config.d.ts.map +1 -1
  6. package/dist/auth/config/resolve-config.d.ts.map +1 -1
  7. package/dist/auth/config/resolve-throttle.d.ts +30 -0
  8. package/dist/auth/config/resolve-throttle.d.ts.map +1 -0
  9. package/dist/auth/config/resolved-config.d.ts +5 -0
  10. package/dist/auth/config/resolved-config.d.ts.map +1 -1
  11. package/dist/auth/config/warn-throttle-window.d.ts +14 -0
  12. package/dist/auth/config/warn-throttle-window.d.ts.map +1 -0
  13. package/dist/auth/counter-key.d.ts +19 -0
  14. package/dist/auth/counter-key.d.ts.map +1 -0
  15. package/dist/auth/email-flows/reset-password.d.ts.map +1 -1
  16. package/dist/auth/email-flows/verify-email.d.ts.map +1 -1
  17. package/dist/auth/index.d.ts +1 -1
  18. package/dist/auth/index.d.ts.map +1 -1
  19. package/dist/auth/janus.d.ts.map +1 -1
  20. package/dist/auth/magic-link.d.ts.map +1 -1
  21. package/dist/auth/mail-requests.d.ts +51 -0
  22. package/dist/auth/mail-requests.d.ts.map +1 -0
  23. package/dist/auth/port/assert-stores.d.ts.map +1 -1
  24. package/dist/auth/port/memory/users.d.ts.map +1 -1
  25. package/dist/auth/port/types/stores.d.ts +5 -0
  26. package/dist/auth/port/types/stores.d.ts.map +1 -1
  27. package/dist/auth/port/types/users.d.ts +17 -0
  28. package/dist/auth/port/types/users.d.ts.map +1 -1
  29. package/dist/auth/sign-in-attempts.d.ts +3 -6
  30. package/dist/auth/sign-in-attempts.d.ts.map +1 -1
  31. package/dist/auth/sign-in-code.d.ts +3 -2
  32. package/dist/auth/sign-in-code.d.ts.map +1 -1
  33. package/dist/auth/step-up/request.d.ts.map +1 -1
  34. package/dist/auth/types/email-flows.d.ts +16 -6
  35. package/dist/auth/types/email-flows.d.ts.map +1 -1
  36. package/dist/auth/types/magic-link.d.ts +4 -2
  37. package/dist/auth/types/magic-link.d.ts.map +1 -1
  38. package/dist/auth/types/step-up.d.ts +4 -1
  39. package/dist/auth/types/step-up.d.ts.map +1 -1
  40. package/dist/auth/types/user-type.d.ts +15 -0
  41. package/dist/auth/types/user-type.d.ts.map +1 -1
  42. package/dist/auth/users/find-many.d.ts +29 -0
  43. package/dist/auth/users/find-many.d.ts.map +1 -0
  44. package/dist/auth/users/record-flows.d.ts.map +1 -1
  45. package/dist/chunks/{index-6fxq0kzf.js → index-4khsm7mw.js} +2 -2
  46. package/dist/chunks/{index-7xe5x3eg.js → index-6bjfv4xv.js} +9 -3
  47. package/dist/chunks/{index-7xe5x3eg.js.map → index-6bjfv4xv.js.map} +3 -3
  48. package/dist/chunks/{index-k136102s.js → index-cntabs9d.js} +2 -2
  49. package/dist/chunks/{index-ztng6018.js → index-kppkcbn5.js} +10 -3
  50. package/dist/chunks/{index-ztng6018.js.map → index-kppkcbn5.js.map} +4 -4
  51. package/dist/conformance/cases/outage.d.ts.map +1 -1
  52. package/dist/conformance/cases/users/batch.d.ts +8 -0
  53. package/dist/conformance/cases/users/batch.d.ts.map +1 -0
  54. package/dist/conformance/cases/users/index.d.ts.map +1 -1
  55. package/dist/conformance/describe.d.ts +2 -1
  56. package/dist/conformance/describe.d.ts.map +1 -1
  57. package/dist/conformance/index.d.ts +1 -1
  58. package/dist/conformance/index.d.ts.map +1 -1
  59. package/dist/conformance/index.js +106 -46
  60. package/dist/conformance/index.js.map +7 -6
  61. package/dist/conformance/types.d.ts +10 -2
  62. package/dist/conformance/types.d.ts.map +1 -1
  63. package/dist/errors/janus-error/codes.d.ts +10 -0
  64. package/dist/errors/janus-error/codes.d.ts.map +1 -1
  65. package/dist/errors/janus-error/index.d.ts +1 -1
  66. package/dist/errors/janus-error/index.d.ts.map +1 -1
  67. package/dist/errors/janus-error/options.d.ts +4 -3
  68. package/dist/errors/janus-error/options.d.ts.map +1 -1
  69. package/dist/errors/janus-error/refusals.d.ts +8 -0
  70. package/dist/errors/janus-error/refusals.d.ts.map +1 -1
  71. package/dist/errors/janus-error/status.d.ts +1 -1
  72. package/dist/errors/janus-error/status.d.ts.map +1 -1
  73. package/dist/index.d.ts +1 -1
  74. package/dist/index.d.ts.map +1 -1
  75. package/dist/index.js +171 -30
  76. package/dist/index.js.map +18 -14
  77. package/dist/permissions/index.js +3 -3
  78. package/docs/README.md +1 -1
  79. package/docs/guide/adapters.md +44 -4
  80. package/docs/guide/email-flows.md +164 -4
  81. package/docs/guide/errors.md +12 -3
  82. package/docs/guide/magic-link.md +41 -5
  83. package/docs/guide/passwords.md +2 -1
  84. package/docs/guide/sign-in-code.md +46 -7
  85. package/docs/guide/step-up.md +38 -7
  86. package/docs/guide/users.md +49 -0
  87. package/docs/roadmap.md +26 -15
  88. package/docs/troubleshooting.md +127 -5
  89. package/package.json +1 -1
  90. package/dist/auth/config/resolve-sign-in.d.ts +0 -5
  91. package/dist/auth/config/resolve-sign-in.d.ts.map +0 -1
  92. /package/dist/chunks/{index-6fxq0kzf.js.map → index-4khsm7mw.js.map} +0 -0
  93. /package/dist/chunks/{index-k136102s.js.map → index-cntabs9d.js.map} +0 -0
package/docs/roadmap.md CHANGED
@@ -16,9 +16,15 @@ Nothing yet.
16
16
  - **Collecting lapsed one-time tokens** — `collectExpired()` deletes lapsed
17
17
  sessions only. On a store with no TTL (PostgreSQL), lapsed tokens stay
18
18
  until you delete them, and the sign-in throttle adds one per login tried
19
- per window. A port method to collect them, as sessions are, would let the
19
+ per window, and the mail throttle one per flow per address or user per
20
+ window. A port method to collect them, as sessions are, would let the
20
21
  core do it.
21
22
 
23
+ - **A batch read across types** — `findMany` reads one user type, as `find`
24
+ does. The shared `findUser(id)` has no batch counterpart yet; one on the
25
+ instance, answering a union narrowed by `user.type`, would serve a list that
26
+ mixes types.
27
+
22
28
  - **More official adapters** — the ports are cut where atomicity is not
23
29
  required, so users, sessions and permission tuples can each live in the
24
30
  database that suits them. MongoDB is the first adapter
@@ -70,6 +76,25 @@ Nothing yet.
70
76
  The last ten, newest first, each with the version it came in. Everything
71
77
  before is in the [CHANGELOG](../CHANGELOG.md).
72
78
 
79
+ - **Several users by id in one query, v0.18.0** — `auth.<type>.findMany(ids)`
80
+ answers the type's users in the order of `ids`, each once, and leaves out an
81
+ id nobody of that type holds — for a list of rows that would otherwise make
82
+ one `find` per row and fill the connection pool. The users store gains an
83
+ optional `findUsers`, implemented by the reference store,
84
+ `@nxgt/janus-drizzle` 0.7.0 and `@nxgt/janus-mongo` 0.8.0; a store without it
85
+ is read with `findUser`, ten at a time, so third-party adapters keep working.
86
+ - **Requests that send e-mail are throttled, v0.18.0** —
87
+ `janus({ mail: { throttle: { attempts, window } } })`, five per ten
88
+ minutes by default and `false` to turn it off. `magicLink.request`,
89
+ `signInCode.request` and `resetPassword.request` count per address;
90
+ `verifyEmail.send` and an e-mailed `stepUp.request` count per user; each
91
+ flow on its own. Past the limit a request throws `MailThrottledError`
92
+ (`MAIL_THROTTLED`, 429) with `retryAfter` and issues, spends and rotates
93
+ nothing: the last link or code sent still works, so a loop on an address
94
+ cannot lock its owner out.
95
+ The window is no longer than the shortest default token lifetime. The counts
96
+ live in the tokens store, with no port change. `MailConfig` and
97
+ `MailThrottleConfig` are exported.
73
98
  - **A sign-in from a new device, v0.17.0** — `janus({ devices: { keys } })`,
74
99
  and `{ device }` as the last argument of `signUp`, `signIn`,
75
100
  `secondFactor.confirm`, `secondFactor.recover`, `signInCode.confirm` and
@@ -140,20 +165,6 @@ before is in the [CHANGELOG](../CHANGELOG.md).
140
165
  user only — or a security settings page; `null` for a user with no active
141
166
  factor. The listener can now tell the user how many codes remain, as
142
167
  `@nxgt/janus-mail`'s `recoveryCodeUsed` e-mail does.
143
- - **Recovery codes, v0.10.0** — a user who loses their authenticator app
144
- still signs in, with no operator resetting the account.
145
- `secondFactor.activate` answers `{ user, recoveryCodes }`: ten single-use
146
- codes, shown once and stored only as keyed hashes (breaking: read `.user`).
147
- `secondFactor.recover(challenge, code)` redeems a sign-in's challenge with
148
- one and answers the session with `recoveryCodesLeft`;
149
- `secondFactor.regenerateRecoveryCodes(user, code)` replaces them all on a
150
- fresh code from the app, and counts its attempts itself — five per user
151
- per 15-minute window, in the store — so a stolen session cannot guess the
152
- code, and the route needs no limiter of its own. Two user events,
153
- `user.recoveryCodesRegenerated` and `user.recoveryCodeUsed`. For adapters:
154
- `SecondFactorRecord.recoveryCodes`,
155
- stored by the users stores of `@nxgt/janus-drizzle` and
156
- `@nxgt/janus-mongo`, with conformance cases.
157
168
  - **Sending the e-mails, `@nxgt/janus-mail` v0.1.0** — a package of its
158
169
  own, built on the `@nxgt/mail` toolkit: `janusMail({ mailer, from, brand,
159
170
  links })` takes what each flow answers —
@@ -42,11 +42,16 @@ How the messages are shaped:
42
42
  - [`janus: devices.keys: …`](#janus-deviceskeys-expected-at-least-one-key---id-key--the-first-seals)
43
43
  - [`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)
44
44
  - [`janus: signIn.throttle …` wiring messages](#janus-signinthrottle-wiring-messages)
45
+ - [`janus: mail.throttle …` wiring messages](#janus-mailthrottle-wiring-messages)
46
+ - [`janus: tokens.<flow> is 5m, shorter than mail.throttle.window, 10m …`](#janus-tokensflow-is-5m-shorter-than-mailthrottlewindow-10m-)
45
47
  - [Other `janus:` wiring messages](#other-janus-wiring-messages)
46
48
 
47
49
  **Users, sessions and tokens**
48
50
  - [`STORE_FAILED` — `<slot>.<method>: the store could not answer`](#store_failed--slotmethod-the-store-could-not-answer)
49
51
  - [`STORE_FAILED` — `<slot>.<method> answered undefined …`](#store_failed--slotmethod-answered-undefined-an-absence-is-null-so-this-store-forgot-to-answer)
52
+ - [`STORE_FAILED` — `users.findUsers answered no list …`](#store_failed--usersfindusers-answered-no-list-an-absence-is-a-shorter-list-so-this-store-forgot-to-answer)
53
+ - [`<call>: ids must be an array of user ids`](#call-ids-must-be-an-array-of-user-ids)
54
+ - [`findMany` answers fewer users than ids](#findmany-answers-fewer-users-than-ids)
50
55
  - [`NOT_FOUND` — `<call>: no <type> has this id`](#not_found--call-no-type-has-this-id)
51
56
  - [`LOGIN_TAKEN` — `<call>: the login is taken by another <type>`](#login_taken--call-the-login-is-taken-by-another-type)
52
57
  - [`VERSION_CONFLICT` — `<call>: expected version <n>, found <m>`](#version_conflict--call-expected-version-n-found-m)
@@ -54,6 +59,7 @@ How the messages are shaped:
54
59
  - [`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)
55
60
  - [`CREDENTIALS_INVALID` — `<call>: the login and the password do not match`](#credentials_invalid--call-the-login-and-the-password-do-not-match)
56
61
  - [`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)
62
+ - [`MAIL_THROTTLED` — `<call>: too many e-mails asked for this address …`, or `… for this user …`](#mail_throttled--call-too-many-e-mails-asked-for-this-address--the-last-one-sent-still-works-use-it-or-wait-for-the-next-window)
57
63
  - [`STORE_FAILED` — `<call>: the store dropped the attempts it had just stored`](#store_failed--call-the-store-dropped-the-attempts-it-had-just-stored)
58
64
  - [`HASH_UNSUPPORTED` — `<call>: no wired verifier claims the prefix "<prefix>"`](#hash_unsupported--call-no-wired-verifier-claims-the-prefix-prefix)
59
65
  - [`scryptHasher: the stored hash has the $scrypt$ prefix and not its format`](#scrypthasher-the-stored-hash-has-the-scrypt-prefix-and-not-its-format)
@@ -280,10 +286,10 @@ janus({ ..., hasher: scryptHasher(), verifiers: [legacyBcrypt] }); // not [scryp
280
286
 
281
287
  ### `janus: store.<slot> has no method <method>, which the port requires`
282
288
 
283
- Also: `janus: store.<slot> is missing`, `janus: store must be an object with users, sessions and tokens`, `janus: store.sessions.deleteExpiredSessions must be a function or absent`.
289
+ Also: `janus: store.<slot> is missing`, `janus: store must be an object with users, sessions and tokens`, `janus: store.sessions.deleteExpiredSessions must be a function or absent`, `janus: store.users.findUsers must be a function or absent`.
284
290
 
285
291
  **When:** `janus({...})`, from JavaScript or with a store typed loosely. TypeScript refuses a partial store at compile time and names the method.
286
- **Why:** `store` is `{ users, sessions, tokens }`, and each slot must answer every method of the port. `deleteExpiredSessions` is the one optional method: absent, or a function.
292
+ **Why:** `store` is `{ users, sessions, tokens }`, and each slot must answer every method of the port. Two methods are optional — `sessions.deleteExpiredSessions` and `users.findUsers` — and each is absent, or a function: a value in its place (`findUsers: true`, a property copied from a config) is a wiring mistake, not an absent capability.
287
293
  An adapter written for an earlier `@nxgt/janus` reports the method a later
288
294
  release added to the port: `store.tokens has no method countAttempt` for one
289
295
  written against 0.3 (the method came in 0.4), and
@@ -465,6 +471,39 @@ zero`, and a `janus: signIn.throttle.window` that is not a duration.
465
471
  **Fix:** `signIn: { throttle: { attempts: 10, window: '15m' } }` — the
466
472
  defaults — or `signIn: { throttle: false }` to count nothing.
467
473
 
474
+ ### `janus: mail.throttle` wiring messages
475
+
476
+ `TypeError`, at `janus()`: `janus: mail must be an object — { throttle }`,
477
+ `janus: mail.throttle must be { attempts, window }, or false to count
478
+ nothing`, `janus: mail.throttle.attempts must be a whole number above zero`,
479
+ and a `janus: mail.throttle.window` that is not a duration.
480
+ **Fix:** `mail: { throttle: { attempts: 5, window: '10m' } }` — the defaults —
481
+ or `mail: { throttle: false }` to count nothing.
482
+
483
+ ### `janus: tokens.<flow> is 5m, shorter than mail.throttle.window, 10m …`
484
+
485
+ A warning, not an error: `janus()` returns, and emits through
486
+ `process.emitWarning` with the code `JANUS_THROTTLE_WINDOW`, once per call. The
487
+ text, here for `tokens.signInCode: '5m'` with the default window:
488
+
489
+ ```
490
+ janus: tokens.signInCode is 5m, shorter than mail.throttle.window, 10m — past the limit the last token sent can expire before the refusal ends. Set mail.throttle.window to 5m or less, or raise tokens.signInCode to 10m or more.
491
+ ```
492
+
493
+ With several flows shorter than the window, one warning lists each
494
+ (`tokens.magicLink is 2m, … ; tokens.stepUp is 30s, …`). **Why:** a request the
495
+ mail throttle refuses spends nothing, so the last link or code sent still works,
496
+ but only until it expires; with a lifetime shorter than the window it can lapse
497
+ while the visitor is still refused. **Fix:** make the window no longer than the
498
+ shortest lifetime, or raise the lifetime:
499
+
500
+ ```ts
501
+ janus({ user, store, tokens: { signInCode: '5m' }, mail: { throttle: { window: '5m' } } });
502
+ ```
503
+
504
+ A runtime without `process.emitWarning` stays silent. Silence it with
505
+ `node --disable-warning=JANUS_THROTTLE_WINDOW`.
506
+
468
507
  ### Other `janus:` wiring messages
469
508
 
470
509
  | Message | Fix |
@@ -507,6 +546,40 @@ try {
507
546
  **Why:** a method that can find nothing must answer `null`. `undefined` is also what a missing `return` produces, so it is treated as a bug in the store, not as "not found".
508
547
  **Fix:** `return doc ?? null;` — and run `@nxgt/janus/conformance` against the store.
509
548
 
549
+ ### `STORE_FAILED` — `users.findUsers answered no list: an absence is a shorter list, so this store forgot to answer`
550
+
551
+ **When:** `findMany`, over a users store you wrote that implements the optional `findUsers`.
552
+ **Why:** `findUsers` answered `null`, `undefined` or something else than an array. An id nobody holds is left out of the list, and nobody at all is `[]`; anything else is a store that forgot to answer, so it fails rather than read as "nobody".
553
+ **Fix:** answer the rows you found, mapped to records — or leave `findUsers` out, and `findMany` reads with `findUser`:
554
+
555
+ ```ts
556
+ findUsers: async (ids) => (await db.users.where('id', 'in', ids)).map(toUser), // [] when none
557
+ ```
558
+
559
+ ### `<call>: ids must be an array of user ids`
560
+
561
+ A bare `TypeError` — `findMany: …`, or `staff.findMany: …` with several user types.
562
+
563
+ **When:** `findMany` was given something other than an array: one id, a `Set`, a comma-separated string.
564
+ **Why:** `findMany` takes a list; one id is `find`. Only your code passes the wrong shape, and TypeScript refuses it.
565
+ **Fix:** pass an array, or use `find` for one id:
566
+
567
+ ```ts
568
+ await auth.staff.findMany([...selectedIds]); // from a Set
569
+ await auth.staff.find(id); // one id
570
+ ```
571
+
572
+ ### `findMany` answers fewer users than ids
573
+
574
+ **When:** always, when an id is malformed, unknown, held by a user of another type, or repeated.
575
+ **Why:** `findMany` leaves out what `find` answers `null` for, and answers a repeated id once — it never puts `null` in a place. Reading `users[i]` as the user of `ids[i]` then shows one row's account on another.
576
+ **Fix:** match by id:
577
+
578
+ ```ts
579
+ const byId = new Map((await auth.staff.findMany(ids)).map((user) => [user.id, user]));
580
+ const member = byId.get(row.staffId); // undefined: nobody of this type holds it
581
+ ```
582
+
510
583
  ### `NOT_FOUND` — `<call>: no <type> has this id`
511
584
 
512
585
  `NotFoundError`, from `get`, `getUser`, `update`, `setActive`, `setPassword`, `verifyEmail.send`, `secondFactor.recoveryCodesLeft`. Also `<call>: the user has no e-mail`.
@@ -641,6 +714,52 @@ if (error instanceof JanusError && error.retryAfter !== undefined) {
641
714
  janus({ ..., signIn: { throttle: { attempts: 20, window: '15m' } } });
642
715
  ```
643
716
 
717
+ ### `MAIL_THROTTLED` — `<call>: too many e-mails asked for this address — the last one sent still works; use it, or wait for the next window`
718
+
719
+ `MailThrottledError`, with `retryAfter` and `userType`. The per-user flows say
720
+ `… too many e-mails asked for this user — the last one sent still works; use
721
+ it, or wait for the next window` and carry `userId`.
722
+
723
+ **When:** `magicLink.request`, `signInCode.request` or
724
+ `resetPassword.request` (per address), or `verifyEmail.send` and an e-mailed
725
+ `stepUp.request` (per user), past five requests of that flow in the current
726
+ 10-minute window — whether or not anybody holds the address. `<call>` is the
727
+ call, such as `magicLink.request` or `patient.signInCode.request`.
728
+ **Why:** requests that send e-mail are throttled, on by default, so a loop
729
+ cannot fill an inbox or cancel its owner's link or code. Each flow counts on
730
+ its own, in fixed windows of the clock, by the store. **A refused request
731
+ spends, invalidates and rotates nothing**: no token, code or challenge is
732
+ issued, and the last link or code sent — by anyone's request, an attacker's
733
+ included — still confirms until it expires, so a loop on a victim's address
734
+ cannot lock them out. Nothing locks: the next window answers again. In tests with `fixedClock`, the window never
735
+ ends.
736
+ **Fix:** answer 429 with a `Retry-After` header, and tell the visitor to use
737
+ the last e-mail they received (it still works), or to wait `retryAfter`
738
+ seconds: "Check your inbox: the last e-mail we sent still works. You can ask for
739
+ a new one in N minutes." It reveals nothing about accounts, so it is safe to
740
+ show.
741
+ In tests, advance the clock past `retryAfter`, or wire `mail: { throttle:
742
+ false }`. If nobody you know asked five times, somebody is looping on the
743
+ route: `janus` never sees IP addresses, so limit it per client, which the
744
+ throttle does not — with `@nxgt/redis` rate limits (`defineRateLimit`,
745
+ `bindRateLimit`; `enforce()` throws `GuardError` `RATE_LIMITED` whose
746
+ `retryAfter` is in milliseconds), as in the
747
+ [e-mail flows guide](guide/email-flows.md#requests-that-send-e-mail-are-throttled).
748
+ If you set a token lifetime shorter than the window (`tokens.signInCode: '5m'`
749
+ with the default), the last code can expire before the window ends: keep
750
+ `mail.throttle.window` no longer than the shortest `tokens.*` you use.
751
+
752
+ ```ts
753
+ import { MailThrottledError } from '@nxgt/janus';
754
+
755
+ if (error instanceof MailThrottledError) {
756
+ const headers = { 'retry-after': String(error.retryAfter) };
757
+ return Response.json({ code: error.code, retryAfter: error.retryAfter }, { status: 429, headers });
758
+ }
759
+
760
+ janus({ ..., mail: { throttle: { attempts: 10, window: '10m' } } });
761
+ ```
762
+
644
763
  ### `STORE_FAILED` — `<call>: the store dropped the attempts it had just stored`
645
764
 
646
765
  `StoreFailure`, with `slot: 'tokens'` and `operation: 'countAttempt'`.
@@ -1153,7 +1272,9 @@ A `TypeError`.
1153
1272
 
1154
1273
  The messages start with `signInCode.confirm` — prefixed by the type with
1155
1274
  several user types: `patient.signInCode.confirm: …`. `signInCode.request`
1156
- throws nothing but `STORE_FAILED`: an e-mail it cannot sign in is `null`.
1275
+ throws nothing but `STORE_FAILED` and
1276
+ [`MAIL_THROTTLED`](#mail_throttled--call-too-many-e-mails-asked-for-this-address--the-last-one-sent-still-works-use-it-or-wait-for-the-next-window):
1277
+ an e-mail it cannot sign in is `null`.
1157
1278
  `signInCode.confirm` also rejects with
1158
1279
  [`CODE_INVALID`](#code_invalid--call-the-code-does-not-match-or-was-already-used),
1159
1280
  [`TOKEN_UNKNOWN`, `TOKEN_SPENT`, `TOKEN_EXPIRED`](#token_unknown-token_spent-token_expired),
@@ -1236,8 +1357,9 @@ janus({
1236
1357
 
1237
1358
  `magicLink.confirm`'s messages start with `magicLink.confirm` — prefixed by
1238
1359
  the type with several user types: `patient.magicLink.confirm: …`.
1239
- `magicLink.request` throws nothing but `STORE_FAILED`: an e-mail it cannot
1240
- sign in is `null`. `magicLink.confirm` rejects with
1360
+ `magicLink.request` throws nothing but `STORE_FAILED` and
1361
+ [`MAIL_THROTTLED`](#mail_throttled--call-too-many-e-mails-asked-for-this-address--the-last-one-sent-still-works-use-it-or-wait-for-the-next-window):
1362
+ an e-mail it cannot sign in is `null`. `magicLink.confirm` rejects with
1241
1363
  [`TOKEN_UNKNOWN`, `TOKEN_SPENT`, `TOKEN_EXPIRED`](#token_unknown-token_spent-token_expired),
1242
1364
  [`TOKEN_STALE`](#token_stale--call-the-token-was-sent-to-an-e-mail-the-user-no-longer-has),
1243
1365
  [`USER_INACTIVE`](#user_inactive--call-the-user-is-inactive) and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxgt/janus",
3
- "version": "0.17.2",
3
+ "version": "0.19.0",
4
4
  "description": "Embeddable, type-safe identities and permissions: bring your own database",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1,5 +0,0 @@
1
- /** Resolves `signIn.throttle`: on unless `false`, ten passwords per login per fifteen minutes. */
2
- import type { SignInConfig } from './janus-config';
3
- import type { ResolvedConfig } from './resolved-config';
4
- export declare function resolveSignInThrottle(config: SignInConfig | undefined, where: string): ResolvedConfig['signInThrottle'];
5
- //# sourceMappingURL=resolve-sign-in.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"resolve-sign-in.d.ts","sourceRoot":"","sources":["../../../src/auth/config/resolve-sign-in.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAGlG,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAExD,wBAAgB,qBAAqB,CACpC,MAAM,EAAE,YAAY,GAAG,SAAS,EAChC,KAAK,EAAE,MAAM,GACX,cAAc,CAAC,gBAAgB,CAAC,CAoBlC"}