@nxgt/janus 0.11.0 → 0.13.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 (87) hide show
  1. package/README.md +109 -15
  2. package/dist/auth/config/janus-config.d.ts +2 -0
  3. package/dist/auth/config/janus-config.d.ts.map +1 -1
  4. package/dist/auth/config/resolved-config.d.ts +1 -0
  5. package/dist/auth/config/resolved-config.d.ts.map +1 -1
  6. package/dist/auth/email-flows/reset-password.d.ts.map +1 -1
  7. package/dist/auth/index.d.ts +2 -1
  8. package/dist/auth/index.d.ts.map +1 -1
  9. package/dist/auth/password-written.d.ts +21 -5
  10. package/dist/auth/password-written.d.ts.map +1 -1
  11. package/dist/auth/port/assert-stores.d.ts.map +1 -1
  12. package/dist/auth/port/memory/sessions.d.ts.map +1 -1
  13. package/dist/auth/port/types/sessions.d.ts +16 -1
  14. package/dist/auth/port/types/sessions.d.ts.map +1 -1
  15. package/dist/auth/port/types/tokens.d.ts +10 -4
  16. package/dist/auth/port/types/tokens.d.ts.map +1 -1
  17. package/dist/auth/second-factor/{regenerate-attempts.d.ts → app-code-attempts.d.ts} +13 -10
  18. package/dist/auth/second-factor/app-code-attempts.d.ts.map +1 -0
  19. package/dist/auth/sessions/authenticate.d.ts.map +1 -1
  20. package/dist/auth/sessions/fresh.d.ts +26 -0
  21. package/dist/auth/sessions/fresh.d.ts.map +1 -0
  22. package/dist/auth/sessions/standing.d.ts +20 -0
  23. package/dist/auth/sessions/standing.d.ts.map +1 -0
  24. package/dist/auth/step-up/codes.d.ts +29 -0
  25. package/dist/auth/step-up/codes.d.ts.map +1 -0
  26. package/dist/auth/step-up/flows.d.ts +19 -0
  27. package/dist/auth/step-up/flows.d.ts.map +1 -0
  28. package/dist/auth/step-up/open.d.ts +23 -0
  29. package/dist/auth/step-up/open.d.ts.map +1 -0
  30. package/dist/auth/step-up/request.d.ts +12 -0
  31. package/dist/auth/step-up/request.d.ts.map +1 -0
  32. package/dist/auth/types/email-flows.d.ts +6 -3
  33. package/dist/auth/types/email-flows.d.ts.map +1 -1
  34. package/dist/auth/types/index.d.ts +1 -0
  35. package/dist/auth/types/index.d.ts.map +1 -1
  36. package/dist/auth/types/janus.d.ts +2 -1
  37. package/dist/auth/types/janus.d.ts.map +1 -1
  38. package/dist/auth/types/step-up.d.ts +79 -0
  39. package/dist/auth/types/step-up.d.ts.map +1 -0
  40. package/dist/auth/users/any-type-api.d.ts +2 -2
  41. package/dist/auth/users/any-type-api.d.ts.map +1 -1
  42. package/dist/auth/users/type-api.d.ts +1 -1
  43. package/dist/auth/users/type-api.d.ts.map +1 -1
  44. package/dist/chunks/{index-5e2fw7he.js → index-j2gd8fry.js} +12 -3
  45. package/dist/chunks/{index-5e2fw7he.js.map → index-j2gd8fry.js.map} +4 -4
  46. package/dist/chunks/{index-5tm04t5p.js → index-jp2kka6d.js} +2 -2
  47. package/dist/chunks/{index-6f6wja93.js → index-km7ffctx.js} +2 -2
  48. package/dist/chunks/{index-4m49cye4.js → index-rd2rddvv.js} +15 -12
  49. package/dist/chunks/{index-4m49cye4.js.map → index-rd2rddvv.js.map} +3 -3
  50. package/dist/conformance/cases/outage.d.ts.map +1 -1
  51. package/dist/conformance/cases/sessions/index.d.ts.map +1 -1
  52. package/dist/conformance/cases/sessions/reauthentication.d.ts +4 -0
  53. package/dist/conformance/cases/sessions/reauthentication.d.ts.map +1 -0
  54. package/dist/conformance/cases/tokens/index.d.ts.map +1 -1
  55. package/dist/conformance/cases/tokens/kinds.d.ts +4 -0
  56. package/dist/conformance/cases/tokens/kinds.d.ts.map +1 -0
  57. package/dist/conformance/index.js +147 -60
  58. package/dist/conformance/index.js.map +8 -6
  59. package/dist/errors/janus-error/codes.d.ts +7 -0
  60. package/dist/errors/janus-error/codes.d.ts.map +1 -1
  61. package/dist/errors/janus-error/index.d.ts +1 -1
  62. package/dist/errors/janus-error/index.d.ts.map +1 -1
  63. package/dist/errors/janus-error/refusals.d.ts +8 -0
  64. package/dist/errors/janus-error/refusals.d.ts.map +1 -1
  65. package/dist/errors/janus-error/status.d.ts.map +1 -1
  66. package/dist/index.d.ts +1 -1
  67. package/dist/index.d.ts.map +1 -1
  68. package/dist/index.js +185 -23
  69. package/dist/index.js.map +20 -14
  70. package/dist/permissions/index.js +3 -3
  71. package/docs/README.md +2 -1
  72. package/docs/guide/adapters.md +41 -6
  73. package/docs/guide/email-flows.md +40 -1
  74. package/docs/guide/errors.md +1 -0
  75. package/docs/guide/events.md +1 -1
  76. package/docs/guide/passwords.md +22 -0
  77. package/docs/guide/second-factor.md +22 -16
  78. package/docs/guide/sessions.md +7 -0
  79. package/docs/guide/step-up.md +178 -0
  80. package/docs/guide/users.md +8 -3
  81. package/docs/guide/vocabulary.md +3 -2
  82. package/docs/roadmap.md +19 -24
  83. package/docs/troubleshooting.md +193 -8
  84. package/package.json +1 -1
  85. package/dist/auth/second-factor/regenerate-attempts.d.ts.map +0 -1
  86. /package/dist/chunks/{index-5tm04t5p.js.map → index-jp2kka6d.js.map} +0 -0
  87. /package/dist/chunks/{index-6f6wja93.js.map → index-km7ffctx.js.map} +0 -0
@@ -88,6 +88,17 @@ How the messages are shaped:
88
88
  - [`TS2339: Property 'signInCode' does not exist on type 'TypeApi<…>'.`](#ts2339-property-signincode-does-not-exist-on-type-typeapi)
89
89
  - `CODE_INVALID`, `TOKEN_UNKNOWN`, `TOKEN_SPENT`, `TOKEN_EXPIRED`, `USER_INACTIVE` and `VERSION_CONFLICT` from `signInCode.confirm`, and `TS2339` on its `token`: in their entries above.
90
90
 
91
+ **Step-up**
92
+ - [`STEP_UP_REQUIRED` — `assertFresh: the session proved who it is longer ago than maxAge …`](#step_up_required--assertfresh-the-session-proved-who-it-is-longer-ago-than-maxage--confirm-with-a-step-up)
93
+ - [`TOKEN_UNKNOWN` — `stepUp.confirm: no such challenge`, with a challenge just issued](#token_unknown--stepupconfirm-no-such-challenge-with-a-challenge-just-issued)
94
+ - [`TOKEN_UNKNOWN` — `stepUp.confirm: the session was signed out while the code was checked`](#token_unknown--stepupconfirm-the-session-was-signed-out-while-the-code-was-checked)
95
+ - [`SECOND_FACTOR_ACTIVE` — `stepUp.confirm: the user's second factor became active since the code was sent …`](#second_factor_active--stepupconfirm-the-users-second-factor-became-active-since-the-code-was-sent--request-a-step-up-again)
96
+ - [`SECOND_FACTOR_NOT_ENROLLED` — `stepUp.confirm: the user no longer has a second factor …`](#second_factor_not_enrolled--stepupconfirm-the-user-no-longer-has-a-second-factor--request-a-step-up-again)
97
+ - [`CODE_INVALID` — `stepUp.confirm: too many codes tried …`](#code_invalid--stepupconfirm-too-many-codes-tried--wait-for-the-next-15-minute-window)
98
+ - [`NOT_FOUND` — `stepUp.request: the user has no e-mail`](#not_found--stepuprequest-the-user-has-no-e-mail)
99
+ - [`TS2339: Property 'code' does not exist on type 'StepUpByEmail<…> | StepUpByApp<…>'.`](#ts2339-property-code-does-not-exist-on-type-stepupbyemail--stepupbyapp)
100
+ - [`TS2339: Property 'stepUp' does not exist on type 'TypeApi<…>'.`](#ts2339-property-stepup-does-not-exist-on-type-typeapi)
101
+
91
102
  **User events**
92
103
  - [`[JANUS_EVENT_FAILED] Warning: janus: the events listener failed on <type> <event id> for user <user id>: <name>`](#janus_event_failed-warning-janus-the-events-listener-failed-on-type-event-id-for-user-user-id-name)
93
104
  - [An event you expected never arrived](#an-event-you-expected-never-arrived)
@@ -116,6 +127,8 @@ How the messages are shaped:
116
127
  - [`expected <Class>, got <value>`](#expected-class-got-value)
117
128
  - [`expected null, got undefined — an absence is null; undefined is a store that forgot to answer`](#expected-null-got-undefined--an-absence-is-null-undefined-is-a-store-that-forgot-to-answer)
118
129
  - [`findUser should answer recoveryCodes [] as [] — never null, never undefined`](#finduser-should-answer-recoverycodes--as---never-null-never-undefined)
130
+ - [`reauthenticateSession racing revokeSession: the session should stay revoked — one conditional write, never a read then a write`](#reauthenticatesession-racing-revokesession-the-session-should-stay-revoked--one-conditional-write-never-a-read-then-a-write)
131
+ - [`tokens.insertToken: the store could not answer`, in `tokens.everyKind`](#tokensinserttoken-the-store-could-not-answer-in-tokenseverykind)
119
132
 
120
133
  ---
121
134
 
@@ -249,7 +262,8 @@ An adapter written for an earlier `@nxgt/janus` reports the method a later
249
262
  release added to the port: `store.tokens has no method countAttempt` for one
250
263
  written against 0.3 (the method came in 0.4), and
251
264
  `store.tokens has no method spendUserTokens` for one written against 0.6 (the
252
- method came in 0.7).
265
+ method came in 0.7), and `store.sessions has no method reauthenticateSession`
266
+ for one written against 0.11 (the method came in 0.12).
253
267
 
254
268
  **Fix:** pass the three stores, whole:
255
269
 
@@ -259,16 +273,17 @@ import { createMemoryStores, janus } from '@nxgt/janus';
259
273
  janus({ ..., store: createMemoryStores() });
260
274
  ```
261
275
 
262
- Upgrade the published adapter to the release that implements both —
263
- `@nxgt/janus-drizzle` 0.3, `@nxgt/janus-mongo` 0.4, `@nxgt/janus-redis` 0.3:
276
+ Upgrade the published adapter to the release that implements all three —
277
+ `@nxgt/janus-drizzle` 0.5, `@nxgt/janus-mongo` 0.6, `@nxgt/janus-redis` 0.4:
264
278
 
265
279
  ```bash
266
- bun add @nxgt/janus@^0.7 @nxgt/janus-drizzle@^0.3 # or @nxgt/janus-mongo@^0.4, @nxgt/janus-redis@^0.3
280
+ bun add @nxgt/janus@^0.12 @nxgt/janus-drizzle@^0.5 # or @nxgt/janus-mongo@^0.6, @nxgt/janus-redis@^0.4
267
281
  ```
268
282
 
269
283
  Your own adapter implements them as
270
- [`TokenStore.countAttempt`](guide/adapters.md#tokenstorecountattempt) and
271
- [`TokenStore.spendUserTokens`](guide/adapters.md#tokenstorespendusertokens)
284
+ [`TokenStore.countAttempt`](guide/adapters.md#tokenstorecountattempt),
285
+ [`TokenStore.spendUserTokens`](guide/adapters.md#tokenstorespendusertokens) and
286
+ [`SessionStore.reauthenticateSession`](guide/adapters.md#sessionstorereauthenticatesession)
272
287
  set out, then runs the conformance suite.
273
288
 
274
289
  ### `janus: relations must be a relation store — relations.deleteEntity is missing`
@@ -313,7 +328,7 @@ Also `janus: "<name>" cannot name a user type — janus() answers a method of th
313
328
 
314
329
  ### `janus: session.lifespan: "<value>" is not a duration; write a number followed by ms, s, m, h or d — for example "15m" or "720h"`
315
330
 
316
- The same for `session.renewAfter`, `tokens.verifyEmail`, `tokens.resetPassword`, `tokens.signInCode` and `secondFactor.challenge`. Also `<option>: a duration must be above zero` and `<option>: a duration in milliseconds must be a finite number above zero`.
331
+ The same for `session.renewAfter`, `tokens.verifyEmail`, `tokens.resetPassword`, `tokens.signInCode`, `tokens.stepUp` and `secondFactor.challenge` — 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`.
317
332
 
318
333
  **When:** `janus({...})`.
319
334
  **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.
@@ -408,6 +423,8 @@ janus({
408
423
  **Why:** a store that cannot answer throws; it never answers `null`. The driver's own error is on `error.cause` — not in the message, because a driver message can hold a connection string, and a connection string holds a password.
409
424
  **Fix:** answer **503**, and log `cause`. Never map it to 401, 404, `null` or `false`: that turns an outage into a silent lockout, where every user is told they do not exist.
410
425
 
426
+ **After a password write** — `resetPassword.confirm`, `changePassword`, `setPassword` — the failure may come from spending the user's reset links and second-factor challenges, which runs after the password is written: the new password is then in place. A retried `changePassword` with the old `current` answers `CREDENTIALS_INVALID`; the next `resetPassword.request` spends any link left live.
427
+
411
428
  ```ts
412
429
  import { JanusError } from '@nxgt/janus';
413
430
 
@@ -551,7 +568,15 @@ janus({ ..., hasher: scryptHasher(), verifiers: [bcryptVerifier] }); // a Passwo
551
568
 
552
569
  **When:** `verifyEmail.confirm`, `resetPassword.confirm`.
553
570
  **Why:** a token is spent by its first use, and an expired one is spent too. `TOKEN_UNKNOWN` also covers a token whose user was deleted. The defaults are 24 h for `verifyEmail`, 1 h for `resetPassword`.
554
- **Fix:** answer 400 and offer to send a new link. To change the lifetimes:
571
+ A reset link answers `resetPassword.confirm: the token was already used` in
572
+ two more cases: a newer `resetPassword.request` for the same user spent it —
573
+ only the last link sent works, so a visitor who asked twice and clicked the
574
+ first e-mail gets `TOKEN_SPENT` — or the password was written since it was
575
+ sent, by another link's `confirm`, `changePassword` or `setPassword`. When two
576
+ requests race, even the last link can be spent: at most one survives,
577
+ sometimes none.
578
+ **Fix:** answer 400 and offer to send a new link — and tell the visitor to use
579
+ the latest e-mail. To change the lifetimes:
555
580
 
556
581
  ```ts
557
582
  janus({ ..., tokens: { verifyEmail: '72h', resetPassword: '2h' } });
@@ -1073,6 +1098,140 @@ janus({
1073
1098
 
1074
1099
  ---
1075
1100
 
1101
+ ## Step-up
1102
+
1103
+ The messages start with `stepUp.request` or `stepUp.confirm` — prefixed by
1104
+ the type with several user types: `patient.stepUp.confirm: …`.
1105
+ `stepUp.confirm` also rejects with
1106
+ [`CODE_INVALID`](#code_invalid--call-the-code-does-not-match-or-was-already-used),
1107
+ [`TOKEN_UNKNOWN`, `TOKEN_SPENT`, `TOKEN_EXPIRED`](#token_unknown-token_spent-token_expired),
1108
+ [`TOKEN_STALE`](#token_stale--call-the-code-was-sent-to-an-e-mail-the-user-no-longer-has),
1109
+ [`USER_INACTIVE`](#user_inactive--call-the-user-is-inactive) and
1110
+ [`VERSION_CONFLICT`](#version_conflict--call-expected-version-n-found-m),
1111
+ as `signInCode.confirm` does. [The step-up guide](guide/step-up.md) has the
1112
+ whole flow.
1113
+
1114
+ ### `STEP_UP_REQUIRED` — `assertFresh: the session proved who it is longer ago than maxAge — confirm with a step-up`
1115
+
1116
+ `StepUpRequiredError`, carrying the `userId`; `statusOf` answers 403, and
1117
+ so does `@nxgt/janus-hono`'s `janusErrors()`.
1118
+
1119
+ **When:** `assertFresh(session, maxAge)` — or `fresh(maxAge)` in a Hono
1120
+ app, whose message starts `fresh():` instead — on a session that signed
1121
+ in, or was last confirmed by a step-up, `maxAge` ago or more.
1122
+ **Why:** the route asks for a recent proof of who the user is; renewal
1123
+ keeps a session alive but proves nothing, so it never moves
1124
+ `authenticatedAt`.
1125
+ **Fix:** this is not a denial. Answer 403 with the code, and have the
1126
+ client run a step-up, then send the request again:
1127
+
1128
+ ```ts
1129
+ const issued = await auth.stepUp.request(current.user);
1130
+ // … the user types the code …
1131
+ await auth.stepUp.confirm(request, challenge, code); // authenticatedAt: now
1132
+ ```
1133
+
1134
+ ### `TOKEN_UNKNOWN` — `stepUp.confirm: no such challenge`, with a challenge just issued
1135
+
1136
+ **When:** `stepUp.confirm(request, challenge, code)`, with the challenge
1137
+ `stepUp.request` answered a moment ago.
1138
+ **Why**, in the order to check:
1139
+
1140
+ 1. **The request presents no standing session.** `confirm` stamps the
1141
+ session the request carries: pass the request itself — the cookie or the
1142
+ `Authorization: Bearer` header — not a new one built without it.
1143
+ 2. **The session is another user's.** A challenge is confirmed on its
1144
+ user's own session only; one carried to another browser, signed in as
1145
+ someone else, is unknown there.
1146
+ 3. **The session was signed out, revoked or lapsed** since the challenge
1147
+ was issued — a password reset revokes every session.
1148
+ 4. **It is a sign-in code's challenge.** A step-up is a kind of its own: a
1149
+ `signInCode` challenge never confirms one.
1150
+
1151
+ **Fix:** confirm on the same request that asked; after a sign-out, sign in
1152
+ again — which is fresh on its own.
1153
+
1154
+ ### `TOKEN_UNKNOWN` — `stepUp.confirm: the session was signed out while the code was checked`
1155
+
1156
+ **When:** `stepUp.confirm`, when the session was revoked — a sign-out in
1157
+ another tab, a password reset — between the check of the code and the
1158
+ write that stamps the session.
1159
+ **Why:** a confirmation never brings a revoked session back: the store
1160
+ writes `authenticatedAt` only while `revokedAt` is empty. The challenge was
1161
+ spent.
1162
+ **Fix:** answer 401 and sign in again.
1163
+
1164
+ ### `SECOND_FACTOR_ACTIVE` — `stepUp.confirm: the user's second factor became active since the code was sent — request a step-up again`
1165
+
1166
+ `SecondFactorError`, carrying the `userId`.
1167
+
1168
+ **When:** `stepUp.confirm` with an e-mailed code, for a user who activated
1169
+ their second factor after `stepUp.request`.
1170
+ **Why:** a step-up is never weaker than the sign-in the account asks for —
1171
+ once the factor is active, only the app's code confirms. The challenge is
1172
+ spent.
1173
+ **Fix:** call `stepUp.request` again: it answers `via: 'secondFactor'`, and
1174
+ the user types the code from their app.
1175
+
1176
+ ### `SECOND_FACTOR_NOT_ENROLLED` — `stepUp.confirm: the user no longer has a second factor — request a step-up again`
1177
+
1178
+ `SecondFactorError`, carrying the `userId`.
1179
+
1180
+ **When:** `stepUp.confirm` on a challenge issued `via: 'secondFactor'`, for
1181
+ a user whose factor was disabled since.
1182
+ **Why:** there is no secret left to check the app's code against. The
1183
+ challenge is spent.
1184
+ **Fix:** call `stepUp.request` again: it answers `via: 'email'`, with a code
1185
+ to send.
1186
+
1187
+ ### `CODE_INVALID` — `stepUp.confirm: too many codes tried — wait for the next 15-minute window`
1188
+
1189
+ `TokenError`, `attemptsLeft: 0`.
1190
+
1191
+ **When:** `stepUp.confirm` with an app's code, after five app codes for
1192
+ the same user in the current 15-minute window — across step-ups and
1193
+ `secondFactor.regenerateRecoveryCodes`, which share the count.
1194
+ **Why:** a challenge takes five attempts, but a stolen session could ask
1195
+ for challenge after challenge; the window caps its guesses per user.
1196
+ **Fix:** answer with `attemptsLeft: 0`, and let the user try again in the
1197
+ next window. It is the
1198
+ [same count as `regenerateRecoveryCodes`](#code_invalid--secondfactorregeneraterecoverycodes-too-many-codes-tried--wait-for-the-next-15-minute-window).
1199
+
1200
+ ### `NOT_FOUND` — `stepUp.request: the user has no e-mail`
1201
+
1202
+ `NotFoundError`, carrying the `userId`.
1203
+
1204
+ **When:** `stepUp.request(user)` for a user whose e-mail field is empty —
1205
+ an optional field left unset — and whose second factor is not active.
1206
+ **Why:** the code has nowhere to be sent.
1207
+ **Fix:** ask for the e-mail first. A user with an active second factor
1208
+ never meets this: the step-up asks for their app's code.
1209
+
1210
+ ### `TS2339: Property 'code' does not exist on type 'StepUpByEmail<…> | StepUpByApp<…>'.`
1211
+
1212
+ Followed by `Property 'code' does not exist on type 'StepUpByApp<…>'.`
1213
+
1214
+ **When:** `tsc`, on `issued.code` after `stepUp.request`, with
1215
+ `secondFactor` configured on a type with a password.
1216
+ **Why:** a user whose factor is active is sent no code — the answer is
1217
+ `via: 'secondFactor'`, with a challenge only.
1218
+ **Fix:** narrow on `via` first:
1219
+
1220
+ ```ts
1221
+ const issued = await auth.stepUp.request(user);
1222
+ if (issued.via === 'email') await sendMail(issued.email, issued.code);
1223
+ ```
1224
+
1225
+ ### `TS2339: Property 'stepUp' does not exist on type 'TypeApi<…>'.`
1226
+
1227
+ **When:** `tsc`, on `clinic.<type>.stepUp` or `auth.stepUp`.
1228
+ **Why:** the user type has no e-mail, as for
1229
+ [`signInCode`](#ts2339-property-signincode-does-not-exist-on-type-typeapi):
1230
+ a code has nowhere to be sent.
1231
+ **Fix:** name the field that holds the e-mail with the `email` option.
1232
+
1233
+ ---
1234
+
1076
1235
  ## User events
1077
1236
 
1078
1237
  ### `[JANUS_EVENT_FAILED] Warning: janus: the events listener failed on <type> <event id> for user <user id>: <name>`
@@ -1364,3 +1523,29 @@ secondFactor:
1364
1523
  recoveryCodes: [...(document.secondFactor.recoveryCodes ?? [])],
1365
1524
  },
1366
1525
  ```
1526
+
1527
+ ### `reauthenticateSession racing revokeSession: the session should stay revoked — one conditional write, never a read then a write`
1528
+
1529
+ Also `reauthenticateSession on a revoked session — a confirmation racing a revocation must never bring it back`, followed by `expected null, got {…}`.
1530
+
1531
+ **When:** the cases `sessions.reauthenticateRace` and `sessions.reauthenticate`, on an adapter written before `@nxgt/janus` 0.12, or one that implemented `reauthenticateSession` as a read followed by a write.
1532
+ **Why:** a step-up moves `authenticatedAt` with `reauthenticateSession`. An adapter that reads the session, then writes it back whole, writes `revokedAt: null` over a revocation that landed between the two: a signed-out session stands again.
1533
+ **Fix:** one conditional write, whose condition holds `revokedAt` null — the one `extendSession` already has:
1534
+
1535
+ ```ts
1536
+ // MongoDB
1537
+ const written = await sessions.findOneAndUpdate(
1538
+ { _id: id, revokedAt: null },
1539
+ { $set: { authenticatedAt: at } },
1540
+ { returnDocument: 'after' },
1541
+ );
1542
+ return written === null ? null : toSession(written);
1543
+ ```
1544
+
1545
+ In SQL, `update … set authenticated_at = $2 where id = $1 and revoked_at is null returning *`; in Redis, a Lua script that checks `revokedAt == ''` before its `HSET`. See [`SessionStore.reauthenticateSession`](guide/adapters.md#sessionstorereauthenticatesession).
1546
+
1547
+ ### `tokens.insertToken: the store could not answer`, in `tokens.everyKind`
1548
+
1549
+ **When:** the case `tokens.everyKind`, on an adapter whose database lists the token kinds — a `CHECK`, a validator's enum.
1550
+ **Why:** the list does not hold `stepUp`, which came in `@nxgt/janus` 0.12, so the database refuses the insert, and the adapter reports it as the outage it cannot tell apart.
1551
+ **Fix:** add `stepUp` to the list, and ship the migration or the validator with it: `'verifyEmail', 'resetPassword', 'secondFactor', 'signInCode', 'stepUp'`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxgt/janus",
3
- "version": "0.11.0",
3
+ "version": "0.13.0",
4
4
  "description": "Embeddable, type-safe identities and permissions: bring your own database",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1 +0,0 @@
1
- {"version":3,"file":"regenerate-attempts.d.ts","sourceRoot":"","sources":["../../../src/auth/second-factor/regenerate-attempts.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C,OAAO,KAAK,EAAe,UAAU,EAAE,MAAM,eAAe,CAAC;AAC7D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAGzC;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,iEAAiE;AACjE,eAAO,MAAM,oBAAoB,QAAc,CAAC;AA0ChD;;;;GAIG;AACH,wBAAsB,sBAAsB,CAC3C,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,CAAC,CAgBjB"}