@nxgt/janus 0.25.0 → 0.26.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.
@@ -265,6 +265,20 @@ is no longer the one it was asked from although nothing spent it: a write
265
265
  outside janus, or a `send` racing an `update`. `confirm` sends `user.emailChanged` (with
266
266
  `formerEmail`) and `user.emailVerified`.
267
267
 
268
+ **With several user types**, a type's own `confirm` answers `TOKEN_UNKNOWN`
269
+ for a token issued to another type's user, and spends it — a later
270
+ confirmation through the right type is then `TOKEN_SPENT`. A page that does
271
+ not know the type calls the root's `confirm`, which reads the user the token
272
+ was issued to and confirms as their type does: same move, same events, same
273
+ errors, and `type` in the answer says which type it was. `send` stays on each
274
+ type. The root's exists when one type has an e-mail, which is why no type may
275
+ be named `changeEmail`.
276
+
277
+ ```ts
278
+ // one anonymous page for every type
279
+ const changed = await auth.changeEmail.confirm(token); // changed.type: 'admin' | 'member'
280
+ ```
281
+
268
282
  **Gate `send`, and tell the former address.** Whoever changes the e-mail
269
283
  takes over every reset: call `send` behind a fresh sign-in or a step-up.
270
284
 
@@ -168,7 +168,7 @@ and `schemaVersion`, with the defaults above. A login is unique **per type**:
168
168
  the same e-mail may hold a patient user and a staff user. A type name is
169
169
  camelCase, and may not be one of `janus()`'s own methods (`authenticate`,
170
170
  `signOut`, `signOutEverywhere`, `findUser`, `getUser`, `cookie`,
171
- `collectExpired`, `types`) — a compile error, and a `TypeError` for a
171
+ `collectExpired`, `types`, `changeEmail`) — a compile error, and a `TypeError` for a
172
172
  JavaScript caller.
173
173
 
174
174
  ## What each user type answers
package/docs/roadmap.md CHANGED
@@ -69,6 +69,12 @@ Nothing yet.
69
69
  The last ten, newest first, each with the version it came in. Everything
70
70
  before is in the [CHANGELOG](../CHANGELOG.md).
71
71
 
72
+ - **Root `changeEmail.confirm` for instances with several user types, v0.26.0** —
73
+ `auth.changeEmail.confirm(token)` confirms a change of e-mail for a page that
74
+ does not know the user's type: it reads the user the link was issued to and
75
+ confirms as their type does. A type's own `confirm` still answers
76
+ `TOKEN_UNKNOWN`, and spends the token, for another type's link; no user type
77
+ may be named `changeEmail`.
72
78
  - **Collecting lapsed one-time tokens, v0.25.0** — `collectExpired()` deletes
73
79
  lapsed sessions and, through a new optional `TokenStore.deleteExpiredTokens`,
74
80
  lapsed one-time tokens too: the sign-in and mail throttles' counts
@@ -153,15 +159,3 @@ before is in the [CHANGELOG](../CHANGELOG.md).
153
159
  optional `findUsers`, implemented by the reference store,
154
160
  `@nxgt/janus-drizzle` 0.7.0 and `@nxgt/janus-mongo` 0.8.0; a store without it
155
161
  is read with `findUser`, ten at a time, so third-party adapters keep working.
156
- - **Requests that send e-mail are throttled, v0.18.0** —
157
- `janus({ mail: { throttle: { attempts, window } } })`, five per ten
158
- minutes by default and `false` to turn it off. `magicLink.request`,
159
- `signInCode.request` and `resetPassword.request` count per address;
160
- `verifyEmail.send` and an e-mailed `stepUp.request` count per user; each
161
- flow on its own. Past the limit a request throws `MailThrottledError`
162
- (`MAIL_THROTTLED`, 429) with `retryAfter` and issues, spends and rotates
163
- nothing: the last link or code sent still works, so a loop on an address
164
- cannot lock its owner out.
165
- The window is no longer than the shortest default token lifetime. The counts
166
- live in the tokens store, with no port change. `MailConfig` and
167
- `MailThrottleConfig` are exported.
@@ -366,8 +366,8 @@ janus({ user: z.object({ username: z.string() }), password: { login: 'username'
366
366
  Also `janus: "<name>" cannot name a user type — janus() answers a method of that name` and `janus: users declares no user type`.
367
367
 
368
368
  **When:** `janus({ users: {...} })`.
369
- **Why:** each type becomes a property of what `janus()` answers — `auth.staff` — so it must be a name, and not one of the shared methods (`authenticate`, `signOut`, `signOutEverywhere`, `findUser`, `getUser`, `cookie`, `collectExpired`, `types`).
370
- **Fix:** `users: { staffMember: {...} }`, not `'staff-member'` or `cookie`.
369
+ **Why:** each type becomes a property of what `janus()` answers — `auth.staff` — so it must be a name, and not one of the shared methods (`authenticate`, `signOut`, `signOutEverywhere`, `findUser`, `getUser`, `cookie`, `collectExpired`, `types`, `changeEmail`).
370
+ **Fix:** `users: { staffMember: {...} }`, not `'staff-member'`, `cookie` or `changeEmail`.
371
371
 
372
372
  ### `janus: session.lifespan: "<value>" is not a duration; write a number followed by ms, s, m, h or d — for example "15m" or "720h"`
373
373
 
@@ -949,7 +949,7 @@ janus({ ..., hasher: scryptHasher(), verifiers: [bcryptVerifier] }); // a Passwo
949
949
  `TokenError`: `<call>: no such token`, `<call>: the token was already used`, `<call>: the token has expired`.
950
950
 
951
951
  **When:** `verifyEmail.confirm`, `resetPassword.confirm`, `changeEmail.confirm`.
952
- **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, and a change link confirmed through another user type's `changeEmail` or passed to another flow. The defaults are 24 h for `verifyEmail`, 1 h for `resetPassword` and for `changeEmail`. A change link is also spent by a newer `changeEmail.send` for the same user (only the last one works), and by an `update` that changes the user's e-mail.
952
+ **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, and a change link confirmed through another user type's `changeEmail` (which spends it, so the right type then answers `TOKEN_SPENT`) or passed to another flow. The defaults are 24 h for `verifyEmail`, 1 h for `resetPassword` and for `changeEmail`. A change link is also spent by a newer `changeEmail.send` for the same user (only the last one works), and by an `update` that changes the user's e-mail.
953
953
  A reset link answers `resetPassword.confirm: the token was already used` in
954
954
  two more cases: a newer `resetPassword.request` for the same user spent it —
955
955
  only the last link sent works, so a visitor who asked twice and clicked the
@@ -958,7 +958,9 @@ sent, by another link's `confirm`, `changePassword` or `setPassword`. When two
958
958
  requests race, even the last link can be spent: at most one survives,
959
959
  sometimes none.
960
960
  **Fix:** answer 400 and offer to send a new link — and tell the visitor to use
961
- the latest e-mail. To change the lifetimes:
961
+ the latest e-mail. With several user types, confirm a change link from a page
962
+ that does not know the type with the root's `auth.changeEmail.confirm(token)`,
963
+ never through a guessed type. To change the lifetimes:
962
964
 
963
965
  ```ts
964
966
  janus({ ..., tokens: { verifyEmail: '72h', resetPassword: '2h' } });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxgt/janus",
3
- "version": "0.25.0",
3
+ "version": "0.26.0",
4
4
  "description": "Embeddable, type-safe identities and permissions: bring your own database",
5
5
  "license": "MIT",
6
6
  "type": "module",