@nxgt/janus 0.10.0 → 0.12.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 +118 -18
- package/dist/auth/config/janus-config.d.ts +2 -0
- package/dist/auth/config/janus-config.d.ts.map +1 -1
- package/dist/auth/config/resolved-config.d.ts +1 -0
- package/dist/auth/config/resolved-config.d.ts.map +1 -1
- package/dist/auth/index.d.ts +2 -1
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/port/assert-stores.d.ts.map +1 -1
- package/dist/auth/port/memory/sessions.d.ts.map +1 -1
- package/dist/auth/port/types/sessions.d.ts +16 -1
- package/dist/auth/port/types/sessions.d.ts.map +1 -1
- package/dist/auth/port/types/tokens.d.ts +7 -2
- package/dist/auth/port/types/tokens.d.ts.map +1 -1
- package/dist/auth/second-factor/{regenerate-attempts.d.ts → app-code-attempts.d.ts} +13 -10
- package/dist/auth/second-factor/app-code-attempts.d.ts.map +1 -0
- package/dist/auth/second-factor/codes-left.d.ts +14 -0
- package/dist/auth/second-factor/codes-left.d.ts.map +1 -0
- package/dist/auth/second-factor/flows.d.ts +1 -0
- package/dist/auth/second-factor/flows.d.ts.map +1 -1
- package/dist/auth/sessions/authenticate.d.ts.map +1 -1
- package/dist/auth/sessions/fresh.d.ts +26 -0
- package/dist/auth/sessions/fresh.d.ts.map +1 -0
- package/dist/auth/sessions/standing.d.ts +20 -0
- package/dist/auth/sessions/standing.d.ts.map +1 -0
- package/dist/auth/step-up/codes.d.ts +29 -0
- package/dist/auth/step-up/codes.d.ts.map +1 -0
- package/dist/auth/step-up/flows.d.ts +19 -0
- package/dist/auth/step-up/flows.d.ts.map +1 -0
- package/dist/auth/step-up/open.d.ts +23 -0
- package/dist/auth/step-up/open.d.ts.map +1 -0
- package/dist/auth/step-up/request.d.ts +12 -0
- package/dist/auth/step-up/request.d.ts.map +1 -0
- package/dist/auth/types/index.d.ts +1 -0
- package/dist/auth/types/index.d.ts.map +1 -1
- package/dist/auth/types/janus.d.ts +2 -1
- package/dist/auth/types/janus.d.ts.map +1 -1
- package/dist/auth/types/second-factor.d.ts +9 -0
- package/dist/auth/types/second-factor.d.ts.map +1 -1
- package/dist/auth/types/step-up.d.ts +79 -0
- package/dist/auth/types/step-up.d.ts.map +1 -0
- package/dist/auth/users/any-type-api.d.ts +2 -2
- package/dist/auth/users/any-type-api.d.ts.map +1 -1
- package/dist/auth/users/type-api.d.ts +1 -1
- package/dist/auth/users/type-api.d.ts.map +1 -1
- package/dist/chunks/{index-5e2fw7he.js → index-j2gd8fry.js} +12 -3
- package/dist/chunks/{index-5e2fw7he.js.map → index-j2gd8fry.js.map} +4 -4
- package/dist/chunks/{index-5tm04t5p.js → index-jp2kka6d.js} +2 -2
- package/dist/chunks/{index-6f6wja93.js → index-km7ffctx.js} +2 -2
- package/dist/chunks/{index-4m49cye4.js → index-rd2rddvv.js} +15 -12
- package/dist/chunks/{index-4m49cye4.js.map → index-rd2rddvv.js.map} +3 -3
- package/dist/conformance/cases/outage.d.ts.map +1 -1
- package/dist/conformance/cases/sessions/index.d.ts.map +1 -1
- package/dist/conformance/cases/sessions/reauthentication.d.ts +4 -0
- package/dist/conformance/cases/sessions/reauthentication.d.ts.map +1 -0
- package/dist/conformance/cases/tokens/index.d.ts.map +1 -1
- package/dist/conformance/cases/tokens/kinds.d.ts +4 -0
- package/dist/conformance/cases/tokens/kinds.d.ts.map +1 -0
- package/dist/conformance/index.js +147 -60
- package/dist/conformance/index.js.map +8 -6
- package/dist/errors/janus-error/codes.d.ts +7 -0
- package/dist/errors/janus-error/codes.d.ts.map +1 -1
- package/dist/errors/janus-error/index.d.ts +1 -1
- package/dist/errors/janus-error/index.d.ts.map +1 -1
- package/dist/errors/janus-error/refusals.d.ts +8 -0
- package/dist/errors/janus-error/refusals.d.ts.map +1 -1
- package/dist/errors/janus-error/status.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +184 -19
- package/dist/index.js.map +18 -11
- package/dist/permissions/index.js +3 -3
- package/docs/README.md +1 -0
- package/docs/guide/adapters.md +38 -4
- package/docs/guide/errors.md +1 -0
- package/docs/guide/events.md +21 -8
- package/docs/guide/second-factor.md +45 -16
- package/docs/guide/sessions.md +7 -0
- package/docs/guide/step-up.md +178 -0
- package/docs/guide/users.md +1 -0
- package/docs/guide/vocabulary.md +3 -2
- package/docs/roadmap.md +18 -23
- package/docs/troubleshooting.md +183 -8
- package/package.json +1 -1
- package/dist/auth/second-factor/regenerate-attempts.d.ts.map +0 -1
- /package/dist/chunks/{index-5tm04t5p.js.map → index-jp2kka6d.js.map} +0 -0
- /package/dist/chunks/{index-6f6wja93.js.map → index-km7ffctx.js.map} +0 -0
package/docs/troubleshooting.md
CHANGED
|
@@ -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
|
|
263
|
-
`@nxgt/janus-drizzle` 0.
|
|
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.
|
|
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)
|
|
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
|
|
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.
|
|
@@ -430,7 +445,7 @@ try {
|
|
|
430
445
|
|
|
431
446
|
### `NOT_FOUND` — `<call>: no <type> has this id`
|
|
432
447
|
|
|
433
|
-
`NotFoundError`, from `get`, `getUser`, `update`, `setActive`, `setPassword`, `verifyEmail.send`. Also `<call>: the user has no e-mail`.
|
|
448
|
+
`NotFoundError`, from `get`, `getUser`, `update`, `setActive`, `setPassword`, `verifyEmail.send`, `secondFactor.recoveryCodesLeft`. Also `<call>: the user has no e-mail`.
|
|
434
449
|
|
|
435
450
|
**When:** the id names nobody, names a user of another type (`auth.staff.get(patientId)`), or is not an id at all.
|
|
436
451
|
**Why:** `get*` calls turn an absence into `NOT_FOUND`; `find*` calls answer `null` instead.
|
|
@@ -1073,6 +1088,140 @@ janus({
|
|
|
1073
1088
|
|
|
1074
1089
|
---
|
|
1075
1090
|
|
|
1091
|
+
## Step-up
|
|
1092
|
+
|
|
1093
|
+
The messages start with `stepUp.request` or `stepUp.confirm` — prefixed by
|
|
1094
|
+
the type with several user types: `patient.stepUp.confirm: …`.
|
|
1095
|
+
`stepUp.confirm` also rejects with
|
|
1096
|
+
[`CODE_INVALID`](#code_invalid--call-the-code-does-not-match-or-was-already-used),
|
|
1097
|
+
[`TOKEN_UNKNOWN`, `TOKEN_SPENT`, `TOKEN_EXPIRED`](#token_unknown-token_spent-token_expired),
|
|
1098
|
+
[`TOKEN_STALE`](#token_stale--call-the-code-was-sent-to-an-e-mail-the-user-no-longer-has),
|
|
1099
|
+
[`USER_INACTIVE`](#user_inactive--call-the-user-is-inactive) and
|
|
1100
|
+
[`VERSION_CONFLICT`](#version_conflict--call-expected-version-n-found-m),
|
|
1101
|
+
as `signInCode.confirm` does. [The step-up guide](guide/step-up.md) has the
|
|
1102
|
+
whole flow.
|
|
1103
|
+
|
|
1104
|
+
### `STEP_UP_REQUIRED` — `assertFresh: the session proved who it is longer ago than maxAge — confirm with a step-up`
|
|
1105
|
+
|
|
1106
|
+
`StepUpRequiredError`, carrying the `userId`; `statusOf` answers 403, and
|
|
1107
|
+
so does `@nxgt/janus-hono`'s `janusErrors()`.
|
|
1108
|
+
|
|
1109
|
+
**When:** `assertFresh(session, maxAge)` — or `fresh(maxAge)` in a Hono
|
|
1110
|
+
app, whose message starts `fresh():` instead — on a session that signed
|
|
1111
|
+
in, or was last confirmed by a step-up, `maxAge` ago or more.
|
|
1112
|
+
**Why:** the route asks for a recent proof of who the user is; renewal
|
|
1113
|
+
keeps a session alive but proves nothing, so it never moves
|
|
1114
|
+
`authenticatedAt`.
|
|
1115
|
+
**Fix:** this is not a denial. Answer 403 with the code, and have the
|
|
1116
|
+
client run a step-up, then send the request again:
|
|
1117
|
+
|
|
1118
|
+
```ts
|
|
1119
|
+
const issued = await auth.stepUp.request(current.user);
|
|
1120
|
+
// … the user types the code …
|
|
1121
|
+
await auth.stepUp.confirm(request, challenge, code); // authenticatedAt: now
|
|
1122
|
+
```
|
|
1123
|
+
|
|
1124
|
+
### `TOKEN_UNKNOWN` — `stepUp.confirm: no such challenge`, with a challenge just issued
|
|
1125
|
+
|
|
1126
|
+
**When:** `stepUp.confirm(request, challenge, code)`, with the challenge
|
|
1127
|
+
`stepUp.request` answered a moment ago.
|
|
1128
|
+
**Why**, in the order to check:
|
|
1129
|
+
|
|
1130
|
+
1. **The request presents no standing session.** `confirm` stamps the
|
|
1131
|
+
session the request carries: pass the request itself — the cookie or the
|
|
1132
|
+
`Authorization: Bearer` header — not a new one built without it.
|
|
1133
|
+
2. **The session is another user's.** A challenge is confirmed on its
|
|
1134
|
+
user's own session only; one carried to another browser, signed in as
|
|
1135
|
+
someone else, is unknown there.
|
|
1136
|
+
3. **The session was signed out, revoked or lapsed** since the challenge
|
|
1137
|
+
was issued — a password reset revokes every session.
|
|
1138
|
+
4. **It is a sign-in code's challenge.** A step-up is a kind of its own: a
|
|
1139
|
+
`signInCode` challenge never confirms one.
|
|
1140
|
+
|
|
1141
|
+
**Fix:** confirm on the same request that asked; after a sign-out, sign in
|
|
1142
|
+
again — which is fresh on its own.
|
|
1143
|
+
|
|
1144
|
+
### `TOKEN_UNKNOWN` — `stepUp.confirm: the session was signed out while the code was checked`
|
|
1145
|
+
|
|
1146
|
+
**When:** `stepUp.confirm`, when the session was revoked — a sign-out in
|
|
1147
|
+
another tab, a password reset — between the check of the code and the
|
|
1148
|
+
write that stamps the session.
|
|
1149
|
+
**Why:** a confirmation never brings a revoked session back: the store
|
|
1150
|
+
writes `authenticatedAt` only while `revokedAt` is empty. The challenge was
|
|
1151
|
+
spent.
|
|
1152
|
+
**Fix:** answer 401 and sign in again.
|
|
1153
|
+
|
|
1154
|
+
### `SECOND_FACTOR_ACTIVE` — `stepUp.confirm: the user's second factor became active since the code was sent — request a step-up again`
|
|
1155
|
+
|
|
1156
|
+
`SecondFactorError`, carrying the `userId`.
|
|
1157
|
+
|
|
1158
|
+
**When:** `stepUp.confirm` with an e-mailed code, for a user who activated
|
|
1159
|
+
their second factor after `stepUp.request`.
|
|
1160
|
+
**Why:** a step-up is never weaker than the sign-in the account asks for —
|
|
1161
|
+
once the factor is active, only the app's code confirms. The challenge is
|
|
1162
|
+
spent.
|
|
1163
|
+
**Fix:** call `stepUp.request` again: it answers `via: 'secondFactor'`, and
|
|
1164
|
+
the user types the code from their app.
|
|
1165
|
+
|
|
1166
|
+
### `SECOND_FACTOR_NOT_ENROLLED` — `stepUp.confirm: the user no longer has a second factor — request a step-up again`
|
|
1167
|
+
|
|
1168
|
+
`SecondFactorError`, carrying the `userId`.
|
|
1169
|
+
|
|
1170
|
+
**When:** `stepUp.confirm` on a challenge issued `via: 'secondFactor'`, for
|
|
1171
|
+
a user whose factor was disabled since.
|
|
1172
|
+
**Why:** there is no secret left to check the app's code against. The
|
|
1173
|
+
challenge is spent.
|
|
1174
|
+
**Fix:** call `stepUp.request` again: it answers `via: 'email'`, with a code
|
|
1175
|
+
to send.
|
|
1176
|
+
|
|
1177
|
+
### `CODE_INVALID` — `stepUp.confirm: too many codes tried — wait for the next 15-minute window`
|
|
1178
|
+
|
|
1179
|
+
`TokenError`, `attemptsLeft: 0`.
|
|
1180
|
+
|
|
1181
|
+
**When:** `stepUp.confirm` with an app's code, after five app codes for
|
|
1182
|
+
the same user in the current 15-minute window — across step-ups and
|
|
1183
|
+
`secondFactor.regenerateRecoveryCodes`, which share the count.
|
|
1184
|
+
**Why:** a challenge takes five attempts, but a stolen session could ask
|
|
1185
|
+
for challenge after challenge; the window caps its guesses per user.
|
|
1186
|
+
**Fix:** answer with `attemptsLeft: 0`, and let the user try again in the
|
|
1187
|
+
next window. It is the
|
|
1188
|
+
[same count as `regenerateRecoveryCodes`](#code_invalid--secondfactorregeneraterecoverycodes-too-many-codes-tried--wait-for-the-next-15-minute-window).
|
|
1189
|
+
|
|
1190
|
+
### `NOT_FOUND` — `stepUp.request: the user has no e-mail`
|
|
1191
|
+
|
|
1192
|
+
`NotFoundError`, carrying the `userId`.
|
|
1193
|
+
|
|
1194
|
+
**When:** `stepUp.request(user)` for a user whose e-mail field is empty —
|
|
1195
|
+
an optional field left unset — and whose second factor is not active.
|
|
1196
|
+
**Why:** the code has nowhere to be sent.
|
|
1197
|
+
**Fix:** ask for the e-mail first. A user with an active second factor
|
|
1198
|
+
never meets this: the step-up asks for their app's code.
|
|
1199
|
+
|
|
1200
|
+
### `TS2339: Property 'code' does not exist on type 'StepUpByEmail<…> | StepUpByApp<…>'.`
|
|
1201
|
+
|
|
1202
|
+
Followed by `Property 'code' does not exist on type 'StepUpByApp<…>'.`
|
|
1203
|
+
|
|
1204
|
+
**When:** `tsc`, on `issued.code` after `stepUp.request`, with
|
|
1205
|
+
`secondFactor` configured on a type with a password.
|
|
1206
|
+
**Why:** a user whose factor is active is sent no code — the answer is
|
|
1207
|
+
`via: 'secondFactor'`, with a challenge only.
|
|
1208
|
+
**Fix:** narrow on `via` first:
|
|
1209
|
+
|
|
1210
|
+
```ts
|
|
1211
|
+
const issued = await auth.stepUp.request(user);
|
|
1212
|
+
if (issued.via === 'email') await sendMail(issued.email, issued.code);
|
|
1213
|
+
```
|
|
1214
|
+
|
|
1215
|
+
### `TS2339: Property 'stepUp' does not exist on type 'TypeApi<…>'.`
|
|
1216
|
+
|
|
1217
|
+
**When:** `tsc`, on `clinic.<type>.stepUp` or `auth.stepUp`.
|
|
1218
|
+
**Why:** the user type has no e-mail, as for
|
|
1219
|
+
[`signInCode`](#ts2339-property-signincode-does-not-exist-on-type-typeapi):
|
|
1220
|
+
a code has nowhere to be sent.
|
|
1221
|
+
**Fix:** name the field that holds the e-mail with the `email` option.
|
|
1222
|
+
|
|
1223
|
+
---
|
|
1224
|
+
|
|
1076
1225
|
## User events
|
|
1077
1226
|
|
|
1078
1227
|
### `[JANUS_EVENT_FAILED] Warning: janus: the events listener failed on <type> <event id> for user <user id>: <name>`
|
|
@@ -1364,3 +1513,29 @@ secondFactor:
|
|
|
1364
1513
|
recoveryCodes: [...(document.secondFactor.recoveryCodes ?? [])],
|
|
1365
1514
|
},
|
|
1366
1515
|
```
|
|
1516
|
+
|
|
1517
|
+
### `reauthenticateSession racing revokeSession: the session should stay revoked — one conditional write, never a read then a write`
|
|
1518
|
+
|
|
1519
|
+
Also `reauthenticateSession on a revoked session — a confirmation racing a revocation must never bring it back`, followed by `expected null, got {…}`.
|
|
1520
|
+
|
|
1521
|
+
**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.
|
|
1522
|
+
**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.
|
|
1523
|
+
**Fix:** one conditional write, whose condition holds `revokedAt` null — the one `extendSession` already has:
|
|
1524
|
+
|
|
1525
|
+
```ts
|
|
1526
|
+
// MongoDB
|
|
1527
|
+
const written = await sessions.findOneAndUpdate(
|
|
1528
|
+
{ _id: id, revokedAt: null },
|
|
1529
|
+
{ $set: { authenticatedAt: at } },
|
|
1530
|
+
{ returnDocument: 'after' },
|
|
1531
|
+
);
|
|
1532
|
+
return written === null ? null : toSession(written);
|
|
1533
|
+
```
|
|
1534
|
+
|
|
1535
|
+
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).
|
|
1536
|
+
|
|
1537
|
+
### `tokens.insertToken: the store could not answer`, in `tokens.everyKind`
|
|
1538
|
+
|
|
1539
|
+
**When:** the case `tokens.everyKind`, on an adapter whose database lists the token kinds — a `CHECK`, a validator's enum.
|
|
1540
|
+
**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.
|
|
1541
|
+
**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 +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"}
|
|
File without changes
|
|
File without changes
|