@nxgt/janus 0.23.1 → 0.25.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 (78) hide show
  1. package/README.md +119 -21
  2. package/dist/auth/email-flows/prove-first.d.ts +23 -1
  3. package/dist/auth/email-flows/prove-first.d.ts.map +1 -1
  4. package/dist/auth/index.d.ts +1 -1
  5. package/dist/auth/index.d.ts.map +1 -1
  6. package/dist/auth/outage.d.ts +17 -6
  7. package/dist/auth/outage.d.ts.map +1 -1
  8. package/dist/auth/port/assert-stores.d.ts.map +1 -1
  9. package/dist/auth/port/memory/tokens.d.ts.map +1 -1
  10. package/dist/auth/port/types/stores.d.ts +2 -0
  11. package/dist/auth/port/types/stores.d.ts.map +1 -1
  12. package/dist/auth/port/types/tokens.d.ts +12 -0
  13. package/dist/auth/port/types/tokens.d.ts.map +1 -1
  14. package/dist/auth/sessions/collect-expired.d.ts +14 -0
  15. package/dist/auth/sessions/collect-expired.d.ts.map +1 -0
  16. package/dist/auth/sessions/shared-api.d.ts +1 -1
  17. package/dist/auth/sessions/shared-api.d.ts.map +1 -1
  18. package/dist/auth/types/email-flows.d.ts +13 -2
  19. package/dist/auth/types/email-flows.d.ts.map +1 -1
  20. package/dist/auth/types/find-or-create.d.ts +59 -0
  21. package/dist/auth/types/find-or-create.d.ts.map +1 -0
  22. package/dist/auth/types/index.d.ts +1 -0
  23. package/dist/auth/types/index.d.ts.map +1 -1
  24. package/dist/auth/types/janus.d.ts +6 -4
  25. package/dist/auth/types/janus.d.ts.map +1 -1
  26. package/dist/auth/users/any-type-api.d.ts +2 -2
  27. package/dist/auth/users/any-type-api.d.ts.map +1 -1
  28. package/dist/auth/users/find-or-create.d.ts +12 -0
  29. package/dist/auth/users/find-or-create.d.ts.map +1 -0
  30. package/dist/auth/users/insert.d.ts +8 -3
  31. package/dist/auth/users/insert.d.ts.map +1 -1
  32. package/dist/auth/users/type-api.d.ts +1 -1
  33. package/dist/auth/users/type-api.d.ts.map +1 -1
  34. package/dist/chunks/{index-6bjfv4xv.js → index-q3w3vr69.js} +13 -3
  35. package/dist/chunks/{index-6bjfv4xv.js.map → index-q3w3vr69.js.map} +3 -3
  36. package/dist/chunks/{index-cntabs9d.js → index-rpp0bw6e.js} +2 -2
  37. package/dist/chunks/{index-kppkcbn5.js → index-spwh8b9z.js} +2 -5
  38. package/dist/chunks/{index-kppkcbn5.js.map → index-spwh8b9z.js.map} +2 -2
  39. package/dist/chunks/{index-4khsm7mw.js → index-y9b327j8.js} +18 -18
  40. package/dist/chunks/{index-4khsm7mw.js.map → index-y9b327j8.js.map} +5 -5
  41. package/dist/conformance/cases/outage.d.ts.map +1 -1
  42. package/dist/conformance/cases/tokens/deletion.d.ts.map +1 -1
  43. package/dist/conformance/describe.d.ts +1 -0
  44. package/dist/conformance/describe.d.ts.map +1 -1
  45. package/dist/conformance/index.js +39 -5
  46. package/dist/conformance/index.js.map +5 -5
  47. package/dist/conformance/types.d.ts +1 -1
  48. package/dist/conformance/types.d.ts.map +1 -1
  49. package/dist/index.js +167 -44
  50. package/dist/index.js.map +11 -9
  51. package/dist/permissions/engine.d.ts.map +1 -1
  52. package/dist/permissions/index.d.ts +1 -1
  53. package/dist/permissions/index.d.ts.map +1 -1
  54. package/dist/permissions/index.js +52 -19
  55. package/dist/permissions/index.js.map +6 -5
  56. package/dist/permissions/input.d.ts +13 -1
  57. package/dist/permissions/input.d.ts.map +1 -1
  58. package/dist/permissions/model/grant.d.ts +14 -1
  59. package/dist/permissions/model/grant.d.ts.map +1 -1
  60. package/dist/permissions/model/permissions.d.ts +13 -1
  61. package/dist/permissions/model/permissions.d.ts.map +1 -1
  62. package/dist/permissions/port/types.d.ts +8 -2
  63. package/dist/permissions/port/types.d.ts.map +1 -1
  64. package/dist/permissions/replace.d.ts +25 -0
  65. package/dist/permissions/replace.d.ts.map +1 -0
  66. package/docs/README.md +2 -2
  67. package/docs/guide/adapters.md +40 -3
  68. package/docs/guide/email-flows.md +35 -5
  69. package/docs/guide/errors.md +2 -2
  70. package/docs/guide/passwords.md +7 -10
  71. package/docs/guide/permissions.md +42 -1
  72. package/docs/guide/sessions.md +21 -4
  73. package/docs/guide/users.md +44 -0
  74. package/docs/guide/vocabulary.md +1 -1
  75. package/docs/roadmap.md +33 -38
  76. package/docs/troubleshooting.md +50 -21
  77. package/package.json +1 -1
  78. /package/dist/chunks/{index-cntabs9d.js.map → index-rpp0bw6e.js.map} +0 -0
package/README.md CHANGED
@@ -37,7 +37,7 @@ supported.
37
37
  | Import | What it holds |
38
38
  | --- | --- |
39
39
  | `@nxgt/janus` | **Identities**: `janus()`, the identity stores' port and its in-memory reference (`createMemoryStores`), the hashers, the user events (`UserEvent`, `UserEventListener`, `UserEventType`). And the **shared vocabulary**: errors, subjects and the tuple notation, ids, pagination, time |
40
- | `@nxgt/janus/permissions` | **Permissions**: `defineModel`, `fromField`, `when`, `permissions()` — `can`, `list`, `grant`, `revoke` — the relation store's port and its in-memory reference (`createMemoryRelations`) |
40
+ | `@nxgt/janus/permissions` | **Permissions**: `defineModel`, `fromField`, `when`, `permissions()` — `can`, `list`, `grant`, `revoke`, `replace` — the relation store's port and its in-memory reference (`createMemoryRelations`) |
41
41
  | `@nxgt/janus/conformance` | **For adapters**: the suites a store runs — `describeJanusStores`, `describeRelationStores` — their cases as data, and the reference harnesses |
42
42
 
43
43
  A subpath appears in `exports` only once it exports something you should call:
@@ -333,6 +333,7 @@ await auth.magicLink.request(email); // { token, email, expiresAt,
333
333
  await auth.magicLink.confirm(token); // { status: 'signedIn', user, session, token }
334
334
  await auth.magicLink.prepare(email); // counted, nobody looked up: { send() } — and signInCode, resetPassword
335
335
  await auth.findByEmail(email); // User | null — server-side only, counts nothing; every type with an e-mail
336
+ await auth.findOrCreate(key, () => input); // { user, created } — by login (e-mail without a password); input() only when nobody holds the key
336
337
  await auth.signInProven(user.id); // privileged: after a proof YOUR server verified — { status: 'signedIn', … }
337
338
  await auth.stepUp.request(user); // { via: 'email', code, challenge, email, expiresAt, user }
338
339
  await auth.stepUp.confirm(request, challenge, code); // the request's session, authenticatedAt: now
@@ -409,7 +410,8 @@ else reaches the store and is asynchronous.
409
410
  - **Shared**: `authenticate`, `signOut`, `signOutEverywhere(user, { except })`,
410
411
  `findUser` and `getUser` across types, `cookie.serialize(token, session)` and
411
412
  `cookie.clear()` — `HttpOnly; SameSite=Lax; Secure` unless you say otherwise
412
- — and `collectExpired`.
413
+ — and `collectExpired`, which deletes lapsed sessions and lapsed one-time
414
+ tokens.
413
415
  - **Password guessing is throttled**, on by default: past ten passwords
414
416
  tried at one login in a 15-minute window, `signIn` refuses every one — the
415
417
  right password included — with `CREDENTIALS_INVALID`, `reason:
@@ -920,6 +922,55 @@ password and reset flows on it.
920
922
  `STORE_FAILED`, never `null`.
921
923
  - **Server-side only**: it tells whether an account exists. See *Traps*.
922
924
 
925
+ ### Finding a user, or creating them — `findOrCreate`
926
+
927
+ ```ts
928
+ // Accepting an invitation: the account may exist; if not, it is created only when a password was given.
929
+ const invitation = await invitations.redeem(body.invitationToken); // yours: verified first
930
+ if (invitation === null) return new Response(null, { status: 404 });
931
+
932
+ const { user } = await auth.findOrCreate(invitation.email, () => {
933
+ if (body.password === undefined) throw new Error('a password is required'); // refused: nothing is written
934
+ return { email: invitation.email, name: body.name }; // no password here: proveFirst drops it, setPassword sets it once
935
+ });
936
+ await auth.verifyEmail.proveFirst(user); // drops a squatter's password, factor and sessions: first
937
+ if (typeof body.password === 'string') await auth.setPassword(user, body.password); // then the invitee's own
938
+ const result = await auth.signInProven(user); // last
939
+ ```
940
+
941
+ The user of this type holding `key`, or the one `input()` describes,
942
+ created — in one call, with the lost race handled. Typed by the exported
943
+ `FindOrCreateApi`; the
944
+ [users guide](docs/guide/users.md#finding-a-user-or-creating-them--findorcreate)
945
+ has the details. On **every type with a
946
+ login or an e-mail**: the key is the login, normalised as `findByLogin`
947
+ normalises it, on a type with a password, and the e-mail, normalised as
948
+ `findByEmail` does, on a type without one. On a type whose login is a
949
+ username, an e-mail key finds an existing user (e-mails are stored as
950
+ logins) but can never create one: the input's username is not the key, so
951
+ it is `USER_INVALID`.
952
+
953
+ - **Found: nothing else happens.** The user is answered
954
+ `{ user, created: false }` — an inactive one included — `input` is not
955
+ called and nothing is written.
956
+ - **`input` is the refusal point.** It runs only when nobody holds the key,
957
+ and what it throws propagates with nothing written: refuse there what the
958
+ caller must refuse before a write, such as a missing password.
959
+ - **The key is a string.** Any other value is `USER_INVALID` before `input()`
960
+ runs.
961
+ - **The input must name the key.** Once validated, its login (its e-mail,
962
+ without a password) must equal the normalised `key`, or the call is
963
+ `USER_INVALID` before the write.
964
+ - **A lost race is not an error.** When another call created the user between
965
+ the read and the write, the store's `LOGIN_TAKEN` is followed by one more
966
+ read of the key; the winner is answered `{ user, created: false }`. The
967
+ `LOGIN_TAKEN` is rethrown only when that read finds nobody — another login
968
+ of the new user is taken.
969
+ - **Created as `create` creates**: the same validation, the same hashing,
970
+ `PASSWORD_TOO_SHORT` below the policy, and `user.created` sent only when
971
+ this call created. Signs nobody in.
972
+ - **Server-side only**, an account-existence oracle as `findByEmail` is.
973
+
923
974
  ### Proving an e-mail on your own proof — `verifyEmail.proveFirst`
924
975
 
925
976
  ```ts
@@ -949,8 +1000,15 @@ type with an e-mail; `signInProven` alone proves nothing.
949
1000
  or sign-in-throttle count.
950
1001
  - **Refused as `signInProven` refuses**: an inactive user is
951
1002
  `USER_INACTIVE`; a malformed id, an unknown one or one of another type is
952
- `NOT_FOUND`. A user written between its read and its write is
953
- `VERSION_CONFLICT`, with nothing proved; an outage is `STORE_FAILED`.
1003
+ `NOT_FOUND`. A write landing between the read and the proof is
1004
+ retried once on a fresh read (`proved: false` when that write proved the
1005
+ e-mail); `VERSION_CONFLICT`, with nothing proved, when it changed the
1006
+ address or the retry conflicts too; an outage is `STORE_FAILED`.
1007
+ - **The address you hold is checked.** Given a `User` (an object carrying
1008
+ the type's e-mail field), it compares that address, normalised as e-mails
1009
+ are, with the one it reads, on the first read and on the retry read:
1010
+ another address is `VERSION_CONFLICT` and nothing is proved. Given a bare
1011
+ id, or an object without the e-mail field, it proves the current address.
954
1012
  - **Privileged, and the order matters**: see *Traps*.
955
1013
 
956
1014
  [E-mail flows](docs/guide/email-flows.md#proving-an-e-mail-on-your-own-proof--provefirst)
@@ -1215,6 +1273,28 @@ would have to read another object's field.
1215
1273
  [permissions guide](docs/guide/permissions.md#a-function-of-your-own-around-can)
1216
1274
  has the example.
1217
1275
 
1276
+ **Sync a relation to a wanted list with `replace`.** When the application
1277
+ holds the list a group's members should match, `replace(object, relation,
1278
+ subjects)` adds the missing holders and removes the others in one write, and
1279
+ answers the tuples added and the tuples removed:
1280
+
1281
+ ```ts
1282
+ const wanted = await db.staff.membersOf('cardiology'); // your own source of truth
1283
+ const { added, removed } = await access.replace({ type: 'team', id: 'cardiology' }, 'members', wanted); // RelationTuple[] each
1284
+ await access.replace({ type: 'team', id: 'cardiology' }, 'members', []); // empties the group
1285
+ ```
1286
+
1287
+ The subjects are typed as `grant`'s is, as an array, and every one is checked
1288
+ before anything is read, so a refusal writes nothing. Duplicates count once,
1289
+ holders already right are not written (no write at all when nothing changes),
1290
+ and a subject set is not its entity. Other relations and objects are untouched.
1291
+ **Two concurrent `replace` calls on one object and relation are not
1292
+ serialised**: each reads, then writes its diff, so the result can hold the
1293
+ union of both adds — serialise them yourself when one must win. A relation
1294
+ store must answer every holder of a relation to `findEntities` and
1295
+ `findSubjectSets`, since `replace` reads them all; the
1296
+ [permissions guide](docs/guide/permissions.md#replace) has the details.
1297
+
1218
1298
  **A user type may also be an object type**: declare `staff` under `types`, and
1219
1299
  a staff member is an object too — who may edit them is a relation on them. See
1220
1300
  [permissions on a user](docs/guide/permissions.md#permissions-on-a-user).
@@ -1251,7 +1331,7 @@ describeJanusStores({
1251
1331
  });
1252
1332
  ```
1253
1333
 
1254
- There are 59 cases. They cover:
1334
+ There are 61 cases. They cover:
1255
1335
  - round-trip, byte for byte — including every edge character the core lets
1256
1336
  through (control characters, U+FFFF, a surrogate pair);
1257
1337
  - uniqueness, as a constraint: of twenty concurrent inserts of one login,
@@ -1280,12 +1360,15 @@ There are 59 cases. They cover:
1280
1360
  goes, with a replay answering `false` or `0` rather than failing;
1281
1361
  - the optional `users.findUsers`: each stored user once, whatever its type, an
1282
1362
  id nobody holds left out, and `[]` — never `null` — when nobody holds any;
1283
- - **outages**, one case for each of the fifteen methods whose honest answer can
1363
+ - the optional `tokens.deleteExpiredTokens`: lapsed tokens deleted, spent or
1364
+ not, and a token one millisecond short of lapsing kept, still spent when it
1365
+ was;
1366
+ - **outages**, one case for each of the sixteen methods whose honest answer can
1284
1367
  be "nothing".
1285
1368
 
1286
1369
  **An optional method a store lacks skips its cases, with the reason** — a
1287
- store without `sessions.deleteExpiredSessions` or `users.findUsers` passes the
1288
- suite. A case's `needs` names what it cannot run without, one `CaseNeed` or a list
1370
+ store without `sessions.deleteExpiredSessions`, `tokens.deleteExpiredTokens`
1371
+ or `users.findUsers` passes the suite. A case's `needs` names what it cannot run without, one `CaseNeed` or a list
1289
1372
  (`outage.findUsers` needs `['faults', 'findUsers']`).
1290
1373
 
1291
1374
  The suite imports no test framework and no assertion library. It runs under
@@ -1373,8 +1456,9 @@ acceptance answer 429.
1373
1456
  every session.** On an e-mail never proved, it does what a first sign-in by
1374
1457
  link does, so a password set before it is erased — the invitee's own,
1375
1458
  chosen on your acceptance form, included — and a session opened before it
1376
- is revoked. Accept an invitation in this order: `findByEmail` (or
1377
- `create`), then `verifyEmail.proveFirst`, then `setPassword` on a type with
1459
+ is revoked. Accept an invitation in this order: `findOrCreate` (find or
1460
+ create in one call; `findByEmail` then `create` does the same in two), then
1461
+ `verifyEmail.proveFirst`, then `setPassword` on a type with
1378
1462
  a password, then `signInProven`. Called after `setPassword`, the person
1379
1463
  signs in once and then finds `signIn` answering `CREDENTIALS_INVALID`
1380
1464
  (`reason: 'noPassword'`). **It is privileged, like `signInProven`**: it
@@ -1384,10 +1468,14 @@ address — on the user that proof names, whose e-mail is the address the
1384
1468
  proof reached; never with an id a request supplied, or anybody can wipe
1385
1469
  anybody's password and second factor and sign them out.
1386
1470
 
1387
- **`findByEmail` is an account-existence oracle — never expose it to an
1388
- anonymous client.** It answers whether somebody of the type holds an
1389
- address, at once, with no throttle: a route that calls it on an address a
1390
- visitor typed tells anybody who has an account. Call it on the server, after
1471
+ **`findByEmail` and `findOrCreate` are account-existence oracles — never
1472
+ expose either to an anonymous client.** `findByEmail` answers whether
1473
+ somebody of the type holds an address, at once, with no throttle;
1474
+ `findOrCreate` is worse, since it also creates accounts: an address nobody
1475
+ holds becomes one. Call `findOrCreate` on the server only, after your own
1476
+ proof, with the key read from that proof's record. A route that calls
1477
+ `findByEmail` on an address a visitor typed tells anybody who has an account.
1478
+ Call `findByEmail` on the server, after
1391
1479
  a proof of your own — an invitation token your server issued and just
1392
1480
  verified — with the address read from that proof's record, never from the
1393
1481
  request. A visitor who types an address goes through `magicLink.request` or
@@ -1577,8 +1665,8 @@ test that tries more than ten wrong passwords at one login over a
1577
1665
  `fixedClock` is throttled too: advance the clock past `retryAfter`, or wire
1578
1666
  `signIn: { throttle: false }`. **Somebody who knows a login can keep its
1579
1667
  password sign-in shut**, ten tries a window; a sign-in code or link, when you wire them, still opens it — the throttle counts passwords, never those.
1580
- On PostgreSQL, delete lapsed tokens on a schedule: every login tried adds a
1581
- row per window. One password tried against
1668
+ On a store with no TTL (PostgreSQL through `@nxgt/janus-drizzle`), schedule
1669
+ `collectExpired()`: every login tried adds a row per window, and it deletes them. One password tried against
1582
1670
  many logins is not counted: rate-limit `signIn` per client address yourself
1583
1671
  ([passwords](docs/guide/passwords.md#what-you-still-limit-yourself)). The
1584
1672
  counts live in the tokens store: a flushed or evicting Redis forgets them,
@@ -1609,11 +1697,21 @@ counted: add a per-IP ceiling with `@nxgt/redis` rate limits
1609
1697
  [example](docs/guide/email-flows.md#requests-that-send-e-mail-are-throttled)). A test suite that requests more than five of
1610
1698
  one flow for one address or user over a `fixedClock` is throttled: advance the
1611
1699
  clock past `retryAfter`, or wire `mail: { throttle: false }`. The counts live
1612
- in the tokens store, so on PostgreSQL schedule the delete of lapsed tokens, a
1700
+ in the tokens store, so on a store with no TTL (PostgreSQL through `@nxgt/janus-drizzle`) schedule
1701
+ `collectExpired()`, which deletes them; a
1613
1702
  flushed or evicting Redis forgets them, and a tokens store that cannot count
1614
1703
  throws `STORE_FAILED` with nothing issued
1615
1704
  ([e-mail flows](docs/guide/email-flows.md#requests-that-send-e-mail-are-throttled)).
1616
1705
 
1706
+ **`collectExpired()` also collects lapsed one-time tokens, and a custom
1707
+ tokens store has to implement `deleteExpiredTokens` for it.** Without the
1708
+ method, only the sessions are collected — or `UNSUPPORTED`, when the sessions
1709
+ store has none either — and the throttles' counts pile up on a database with
1710
+ no TTL. Once a lapsed token is collected, its link answers `TOKEN_UNKNOWN`
1711
+ instead of `TOKEN_EXPIRED` or `TOKEN_SPENT`: a page that tells the two apart
1712
+ should treat the third as "this link is no longer valid"
1713
+ ([sessions](docs/guide/sessions.md#collectexpired)).
1714
+
1617
1715
  **A sign-in can move a user's `version`.** Rewriting a stale hash is a write. A
1618
1716
  user object read before that sign-in, and then passed as `ifVersion`, gets
1619
1717
  `VERSION_CONFLICT`. That is the conflict doing its job: read the user again.
@@ -1698,18 +1796,18 @@ that sends one, since it is awaited: queue the event and return.
1698
1796
 
1699
1797
  ## Type safety, counted
1700
1798
 
1701
- **One hundred and seventy-two plausible mistakes, one hundred and seventy-two refused at compile time — and
1799
+ **One hundred and eighty-one plausible mistakes, one hundred and eighty-one refused at compile time — and
1702
1800
  two gaps, named.**
1703
1801
 
1704
1802
  The lists are typechecked and never run, with one `@ts-expect-error` per
1705
1803
  mistake beside the shapes that must keep compiling. One is a single file:
1706
1804
  `test/types/refusals.ts` (fifteen, on the shared vocabulary). The other three
1707
- are folders with one file per behaviour: `test/types/port/` (twenty-five, on
1805
+ are folders with one file per behaviour: `test/types/port/` (twenty-six, on
1708
1806
  the identity stores' port, from the point of view of the person implementing
1709
- it), `test/types/auth/` (eighty-four, on `janus()`, from the point of view of
1807
+ it), `test/types/auth/` (eighty-eight, on `janus()`, from the point of view of
1710
1808
  the application — twelve of them on the second factor, three on sign-in codes,
1711
1809
  five on user events, four on step-ups, six on the sign-in throttle, five on the mail throttle, four on
1712
- sign-in links, six on devices, two on `findMany`, five on requests in two steps, three on signing in on the application's proof, three on finding a user by e-mail, three on proving an e-mail on the application's proof, three on changing an e-mail) and `test/types/permissions/` (forty-eight, on the
1810
+ sign-in links, six on devices, two on `findMany`, five on requests in two steps, three on signing in on the application's proof, three on finding a user by e-mail, three on proving an e-mail on the application's proof, three on changing an e-mail, four on `findOrCreate`) and `test/types/permissions/` (fifty-two, on the
1713
1811
  permission model and the questions asked of it). The rule comes from
1714
1812
  `nxgt-data`, and so does the reason to distrust the claim without the files:
1715
1813
  when it was last measured on `@nxgt/mongo`, *seven of twelve plausible
@@ -4,7 +4,7 @@
4
4
  * sign-in by link or code gives an e-mail never proved (`../first-proof`).
5
5
  */
6
6
  import type { At } from '../at';
7
- import type { ResolvedType } from '../config';
7
+ import { type ResolvedType } from '../config';
8
8
  import { type AnyUser, type Context } from '../context';
9
9
  import type { VerifyEmailApi } from '../types';
10
10
  /**
@@ -15,6 +15,28 @@ import type { VerifyEmailApi } from '../types';
15
15
  * on the record read, `emailVerifiedAt === null`. An e-mail already proved
16
16
  * is answered as it is, with nothing written.
17
17
  *
18
+ * **The address the caller holds is checked.** When the `UserRef` is an
19
+ * object carrying the type's e-mail field — a `User` — that address,
20
+ * normalised as e-mails are, must be the one read, on the first read and on
21
+ * the retry read; otherwise it is a `VERSION_CONFLICT` and nothing is proved.
22
+ * With a bare id, or an object without the e-mail field, the current address
23
+ * is proved.
24
+ *
25
+ * **A write landing between the read and the proof is retried once.**
26
+ * `VERSION_CONFLICT` from `proveFirstEmail` runs the whole flow again on a
27
+ * fresh read: nobody, or an inactive user, is refused as before; an e-mail
28
+ * that is no longer the one first read — compared as e-mails are — is a
29
+ * `VERSION_CONFLICT` naming both versions, since the address changed since the
30
+ * first read (or is not the one the passed `User` carries) and the proof the
31
+ * caller holds is for the address it read; an e-mail proved meanwhile is
32
+ * answered `proved: false`; otherwise the proof runs under the new version.
33
+ * A second `VERSION_CONFLICT` propagates.
34
+ *
35
+ * Running `proveFirstEmail` twice is harmless: a failed attempt wrote
36
+ * nothing and sent no event (events follow the write); all it left is the
37
+ * sessions revoked and the tokens spent before the write, which the second
38
+ * attempt does again to the same end.
39
+ *
18
40
  * No throttle is counted and no count restarted, no e-mail sent and no
19
41
  * token issued; the only tokens spent are the ones `proveFirstEmail`
20
42
  * spends, what the password opened.
@@ -1 +1 @@
1
- {"version":3,"file":"prove-first.d.ts","sourceRoot":"","sources":["../../../src/auth/email-flows/prove-first.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAChC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EACN,KAAK,OAAO,EACZ,KAAK,OAAO,EAIZ,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAE/C;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAC7B,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,YAAY,EAClB,EAAE,EAAE,EAAE,GACJ,cAAc,CAAC,OAAO,CAAC,CAAC,aAAa,CAAC,CAAC,YAAY,CAAC,CAgBtD"}
1
+ {"version":3,"file":"prove-first.d.ts","sourceRoot":"","sources":["../../../src/auth/email-flows/prove-first.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAChC,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9D,OAAO,EACN,KAAK,OAAO,EACZ,KAAK,OAAO,EAKZ,MAAM,YAAY,CAAC;AAIpB,OAAO,KAAK,EAAW,cAAc,EAAE,MAAM,UAAU,CAAC;AAExD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAgB,cAAc,CAC7B,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,YAAY,EAClB,EAAE,EAAE,EAAE,GACJ,cAAc,CAAC,OAAO,CAAC,CAAC,aAAa,CAAC,CAAC,YAAY,CAAC,CA2BtD"}
@@ -8,5 +8,5 @@ export type { JanusStores, Json, JsonObject, PasswordRecord, SecondFactorRecord,
8
8
  export type { SealingKey } from './sealing';
9
9
  export { assertFresh } from './sessions/fresh';
10
10
  export type { StandardSchemaV1 } from './standard-schema';
11
- export type { Authenticated, ChangeEmailApi, Checked, EmailOf, FindByEmailApi, FirstProof, HeaderRecord, IssuedCode, IssuedToken, Janus, LoginOf, MagicLinkApi, PasswordApi, PreparedCode, PreparedRequest, ProvenSignInApi, RecoveredSignIn, RecoveryCodesIssued, RequestLike, RequiredStringKeys, ResetPasswordApi, SecondFactorApi, SecondFactorEnrolment, SecondFactorRequired, Session, SharedApi, SignedIn, SignInCodeApi, SignInOptions, SignInResult, StepUpApi, StepUpByApp, StepUpByEmail, TypeApi, TypesOf, User, UserBase, UserOf, UserRef, UserTypeApi, VerifyEmailApi, WriteOptions, } from './types';
11
+ export type { Authenticated, ChangeEmailApi, Checked, EmailOf, FindByEmailApi, FindOrCreateApi, FirstProof, HeaderRecord, IssuedCode, IssuedToken, Janus, LoginOf, MagicLinkApi, PasswordApi, PreparedCode, PreparedRequest, ProvenSignInApi, RecoveredSignIn, RecoveryCodesIssued, RequestLike, RequiredStringKeys, ResetPasswordApi, SecondFactorApi, SecondFactorEnrolment, SecondFactorRequired, Session, SharedApi, SignedIn, SignInCodeApi, SignInOptions, SignInResult, StepUpApi, StepUpByApp, StepUpByEmail, TypeApi, TypesOf, User, UserBase, UserOf, UserRef, UserTypeApi, VerifyEmailApi, WriteOptions, } from './types';
12
12
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACX,YAAY,EACZ,aAAa,EACb,UAAU,EACV,WAAW,EACX,UAAU,EACV,kBAAkB,EAClB,eAAe,EACf,SAAS,EACT,cAAc,EACd,cAAc,EACd,kBAAkB,EAClB,aAAa,EACb,YAAY,EACZ,oBAAoB,EACpB,gBAAgB,EAChB,UAAU,EACV,cAAc,GACd,MAAM,UAAU,CAAC;AAClB,YAAY,EACX,SAAS,EACT,iBAAiB,EACjB,aAAa,GACb,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACpD,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,YAAY,EACX,WAAW,EACX,IAAI,EACJ,UAAU,EACV,cAAc,EACd,kBAAkB,EAClB,SAAS,EACT,aAAa,EACb,YAAY,EACZ,iBAAiB,EACjB,SAAS,EACT,WAAW,EACX,UAAU,EACV,eAAe,EACf,SAAS,EACT,UAAU,EACV,SAAS,GACT,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,YAAY,EACX,aAAa,EACb,cAAc,EACd,OAAO,EACP,OAAO,EACP,cAAc,EACd,UAAU,EACV,YAAY,EACZ,UAAU,EACV,WAAW,EACX,KAAK,EACL,OAAO,EACP,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,eAAe,EACf,eAAe,EACf,eAAe,EACf,mBAAmB,EACnB,WAAW,EACX,kBAAkB,EAClB,gBAAgB,EAChB,eAAe,EACf,qBAAqB,EACrB,oBAAoB,EACpB,OAAO,EACP,SAAS,EACT,QAAQ,EACR,aAAa,EACb,aAAa,EACb,YAAY,EACZ,SAAS,EACT,WAAW,EACX,aAAa,EACb,OAAO,EACP,OAAO,EACP,IAAI,EACJ,QAAQ,EACR,MAAM,EACN,OAAO,EACP,WAAW,EACX,cAAc,EACd,YAAY,GACZ,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACX,YAAY,EACZ,aAAa,EACb,UAAU,EACV,WAAW,EACX,UAAU,EACV,kBAAkB,EAClB,eAAe,EACf,SAAS,EACT,cAAc,EACd,cAAc,EACd,kBAAkB,EAClB,aAAa,EACb,YAAY,EACZ,oBAAoB,EACpB,gBAAgB,EAChB,UAAU,EACV,cAAc,GACd,MAAM,UAAU,CAAC;AAClB,YAAY,EACX,SAAS,EACT,iBAAiB,EACjB,aAAa,GACb,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACpD,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,YAAY,EACX,WAAW,EACX,IAAI,EACJ,UAAU,EACV,cAAc,EACd,kBAAkB,EAClB,SAAS,EACT,aAAa,EACb,YAAY,EACZ,iBAAiB,EACjB,SAAS,EACT,WAAW,EACX,UAAU,EACV,eAAe,EACf,SAAS,EACT,UAAU,EACV,SAAS,GACT,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,YAAY,EACX,aAAa,EACb,cAAc,EACd,OAAO,EACP,OAAO,EACP,cAAc,EACd,eAAe,EACf,UAAU,EACV,YAAY,EACZ,UAAU,EACV,WAAW,EACX,KAAK,EACL,OAAO,EACP,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,eAAe,EACf,eAAe,EACf,eAAe,EACf,mBAAmB,EACnB,WAAW,EACX,kBAAkB,EAClB,gBAAgB,EAChB,eAAe,EACf,qBAAqB,EACrB,oBAAoB,EACpB,OAAO,EACP,SAAS,EACT,QAAQ,EACR,aAAa,EACb,aAAa,EACb,YAAY,EACZ,SAAS,EACT,WAAW,EACX,aAAa,EACb,OAAO,EACP,OAAO,EACP,IAAI,EACJ,QAAQ,EACR,MAAM,EACN,OAAO,EACP,WAAW,EACX,cAAc,EACd,YAAY,GACZ,MAAM,SAAS,CAAC"}
@@ -7,14 +7,14 @@
7
7
  * {@link guardStores} puts the three identity stores behind the shared guard
8
8
  * (`src/stores/guard.ts`), the one place a store's answer is caught.
9
9
  * {@link required} turns an absence the caller cannot accept into its refusal,
10
- * and {@link unlessVersionConflict} absorbs one named conflict and rethrows
11
- * everything else.
10
+ * {@link unlessVersionConflict} absorbs one named conflict and rethrows
11
+ * everything else, and {@link unlessLoginTaken} hands back the other one.
12
12
  *
13
13
  * `outage.scan.spec.ts` reads every other file of `src/auth/`, `src/permissions/`
14
14
  * and `src/stores/` and fails if a `catch` appears in one, because the failure
15
15
  * this design exists to prevent is a single careless `catch { return null }`.
16
16
  */
17
- import { type JanusError } from '../errors/janus-error';
17
+ import { type JanusError, StoreConflict } from '../errors/janus-error';
18
18
  import type { JanusStores } from './port/types';
19
19
  /**
20
20
  * The stores, with every method guarded.
@@ -29,8 +29,8 @@ import type { JanusStores } from './port/types';
29
29
  * Rule 2 of the port, enforced at run time for the JavaScript adapter the
30
30
  * compiler never saw.
31
31
  *
32
- * The optional `deleteExpiredSessions` is guarded when present and left absent
33
- * when absent, so capability detection still reads the truth.
32
+ * The optional `deleteExpiredSessions` and `deleteExpiredTokens` are guarded
33
+ * when present and left absent when absent, so capability detection still reads the truth.
34
34
  */
35
35
  export declare function guardStores(stores: JanusStores): JanusStores;
36
36
  /**
@@ -49,7 +49,18 @@ export declare function required<T>(value: T | null, absent: () => JanusError):
49
49
  * taken login still throws, `NOT_FOUND` still throws: an outage never reads as
50
50
  * "somebody else wrote first". It exists for writes the caller did not ask for
51
51
  * — rewriting a password hash on sign-in — where losing to a concurrent update
52
- * means only that the next sign-in tries again.
52
+ * means only that the next sign-in tries again — and for `verifyEmail.proveFirst`,
53
+ * which reads again and retries once.
53
54
  */
54
55
  export declare function unlessVersionConflict<T>(write: Promise<T>): Promise<T | null>;
56
+ /**
57
+ * A write that may lose a race on a login, and says so: its answer, or the
58
+ * `StoreConflict('login')` the store refused it with, **as a value**, for the
59
+ * caller to read the login again and then rethrow or answer the winner.
60
+ *
61
+ * **Only `StoreConflict('login')` is handed back.** A failure still throws, a
62
+ * version conflict still throws: an outage never reads as "somebody else
63
+ * created the user first".
64
+ */
65
+ export declare function unlessLoginTaken<T>(write: Promise<T>): Promise<T | StoreConflict>;
55
66
  //# sourceMappingURL=outage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"outage.d.ts","sourceRoot":"","sources":["../../src/auth/outage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,KAAK,UAAU,EAAiB,MAAM,uBAAuB,CAAC;AAEvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,WAAW,GAAG,WAAW,CAM5D;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,UAAU,GAAG,CAAC,CAGxE;AAED;;;;;;;;;GASG;AACH,wBAAsB,qBAAqB,CAAC,CAAC,EAC5C,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,GACf,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAOnB"}
1
+ {"version":3,"file":"outage.d.ts","sourceRoot":"","sources":["../../src/auth/outage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,KAAK,UAAU,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAEvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,WAAW,GAAG,WAAW,CAM5D;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,UAAU,GAAG,CAAC,CAGxE;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,qBAAqB,CAAC,CAAC,EAC5C,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,GACf,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAOnB;AAED;;;;;;;;GAQG;AACH,wBAAsB,gBAAgB,CAAC,CAAC,EACvC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,GACf,OAAO,CAAC,CAAC,GAAG,aAAa,CAAC,CAO5B"}
@@ -1 +1 @@
1
- {"version":3,"file":"assert-stores.d.ts","sourceRoot":"","sources":["../../../src/auth/port/assert-stores.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAe,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAoC9D;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAC3B,MAAM,EAAE,OAAO,EACf,KAAK,EAAE,MAAM,GACX,iBAAiB,CA+BnB"}
1
+ {"version":3,"file":"assert-stores.d.ts","sourceRoot":"","sources":["../../../src/auth/port/assert-stores.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAe,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAoC9D;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAC3B,MAAM,EAAE,OAAO,EACf,KAAK,EAAE,MAAM,GACX,iBAAiB,CAqCnB"}
@@ -1 +1 @@
1
- {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../../../src/auth/port/memory/tokens.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAe,UAAU,EAAE,MAAM,UAAU,CAAC;AAGxD,gDAAgD;AAChD,wBAAgB,gBAAgB,IAAI,UAAU,CAkE7C"}
1
+ {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../../../src/auth/port/memory/tokens.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAe,UAAU,EAAE,MAAM,UAAU,CAAC;AAGxD,gDAAgD;AAChD,wBAAgB,gBAAgB,IAAI,UAAU,CA+E7C"}
@@ -24,6 +24,8 @@ export interface JanusStores {
24
24
  export interface StoreCapabilities {
25
25
  /** Whether `sessions.deleteExpiredSessions` is implemented. */
26
26
  readonly collectExpired: boolean;
27
+ /** Whether `tokens.deleteExpiredTokens` is implemented. */
28
+ readonly collectExpiredTokens: boolean;
27
29
  /**
28
30
  * Whether `users.findUsers` is implemented: `findMany` answers in one query
29
31
  * when it is, and with `findUser`, a few ids at a time, when it is not.
@@ -1 +1 @@
1
- {"version":3,"file":"stores.d.ts","sourceRoot":"","sources":["../../../../src/auth/port/types/stores.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC3B,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;CAC5B;AAED,6DAA6D;AAC7D,MAAM,WAAW,iBAAiB;IACjC,+DAA+D;IAC/D,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC;;;OAGG;IACH,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC5B"}
1
+ {"version":3,"file":"stores.d.ts","sourceRoot":"","sources":["../../../../src/auth/port/types/stores.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC3B,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;CAC5B;AAED,6DAA6D;AAC7D,MAAM,WAAW,iBAAiB;IACjC,+DAA+D;IAC/D,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,2DAA2D;IAC3D,QAAQ,CAAC,oBAAoB,EAAE,OAAO,CAAC;IACvC;;;OAGG;IACH,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC5B"}
@@ -134,5 +134,17 @@ export interface TokenStore {
134
134
  * must not outlive the user until its expiry.
135
135
  */
136
136
  deleteUserTokens(userId: Id): Promise<number>;
137
+ /**
138
+ * **Optional capability.** Deletes every token — spent or not, of any kind
139
+ * and any user — whose `expiresAt` is at or before `before`, and answers how
140
+ * many. Never one whose `expiresAt` is later, spent or not: a spent token is
141
+ * kept until it lapses, or a count would start over.
142
+ *
143
+ * What collects the sign-in and mail throttles' counts, which are ordinary
144
+ * tokens under a keyed-hash user id that `deleteUserTokens` never reaches.
145
+ * A store with its own expiry — a TTL index, a key TTL — does not implement
146
+ * it; `collectExpired()` reads its presence.
147
+ */
148
+ deleteExpiredTokens?(before: Date): Promise<number>;
137
149
  }
138
150
  //# sourceMappingURL=tokens.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../../../src/auth/port/types/tokens.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,iBAAiB,CAAC;AAE1C;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,MAAM,SAAS,GAClB,aAAa,GACb,eAAe,GACf,cAAc,GACd,YAAY,GACZ,WAAW,GACX,QAAQ,GACR,aAAa,CAAC;AAEjB,yEAAyE;AACzE,MAAM,WAAW,WAAW;IAC3B,oEAAoE;IACpE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;IACpB;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;;;;;;;OAOG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;IACzB,mDAAmD;IACnD,QAAQ,CAAC,OAAO,EAAE,IAAI,GAAG,IAAI,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;CACzB;AAED,kDAAkD;AAClD,MAAM,WAAW,UAAU;IAC1B;;;OAGG;IACH,WAAW,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEhD;;;;;;;;;;;;;;;;;;OAkBG;IACH,YAAY,CACX,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,SAAS,EACf,EAAE,EAAE,IAAI,GACN,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAE/B;;;;;;;;;;;;;;;;;;;OAmBG;IACH,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAE9E;;;;;;;;;;;;;;;;;OAiBG;IACH,eAAe,CACd,MAAM,EAAE,EAAE,EACV,IAAI,EAAE,SAAS,EACf,EAAE,EAAE,IAAI,EACR,MAAM,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC,CAAC;IAEnB;;;;OAIG;IACH,gBAAgB,CAAC,MAAM,EAAE,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAC9C"}
1
+ {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../../../src/auth/port/types/tokens.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,iBAAiB,CAAC;AAE1C;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,MAAM,SAAS,GAClB,aAAa,GACb,eAAe,GACf,cAAc,GACd,YAAY,GACZ,WAAW,GACX,QAAQ,GACR,aAAa,CAAC;AAEjB,yEAAyE;AACzE,MAAM,WAAW,WAAW;IAC3B,oEAAoE;IACpE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;IACpB;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;;;;;;;OAOG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;IACzB,mDAAmD;IACnD,QAAQ,CAAC,OAAO,EAAE,IAAI,GAAG,IAAI,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;CACzB;AAED,kDAAkD;AAClD,MAAM,WAAW,UAAU;IAC1B;;;OAGG;IACH,WAAW,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEhD;;;;;;;;;;;;;;;;;;OAkBG;IACH,YAAY,CACX,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,SAAS,EACf,EAAE,EAAE,IAAI,GACN,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAE/B;;;;;;;;;;;;;;;;;;;OAmBG;IACH,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAE9E;;;;;;;;;;;;;;;;;OAiBG;IACH,eAAe,CACd,MAAM,EAAE,EAAE,EACV,IAAI,EAAE,SAAS,EACf,EAAE,EAAE,IAAI,EACR,MAAM,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC,CAAC;IAEnB;;;;OAIG;IACH,gBAAgB,CAAC,MAAM,EAAE,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAE9C;;;;;;;;;;OAUG;IACH,mBAAmB,CAAC,CAAC,MAAM,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACpD"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * `collectExpired()`: the lapsed sessions and the lapsed one-time tokens, from
3
+ * whichever of the two stores implements its collector.
4
+ */
5
+ import type { Context } from '../context';
6
+ /**
7
+ * Deletes what has lapsed at `clock.now()` and answers how many in all.
8
+ *
9
+ * Sessions first, then tokens, one after the other: both deletions are
10
+ * idempotent, so a token failure after the sessions went leaves a call that can
11
+ * simply be repeated. `UNSUPPORTED` only when neither store has a collector.
12
+ */
13
+ export declare function collectExpired(context: Context): Promise<number>;
14
+ //# sourceMappingURL=collect-expired.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collect-expired.d.ts","sourceRoot":"","sources":["../../../src/auth/sessions/collect-expired.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C;;;;;;GAMG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAmBtE"}
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * What `janus()` answers whatever its user types: authenticating a request,
3
3
  * signing out, finding a user by id, the cookie, and collecting expired
4
- * sessions.
4
+ * sessions and one-time tokens.
5
5
  */
6
6
  import { type AnyUser, type Context } from '../context';
7
7
  import type { Authenticated, RequestLike, SharedApi } from '../types';
@@ -1 +1 @@
1
- {"version":3,"file":"shared-api.d.ts","sourceRoot":"","sources":["../../../src/auth/sessions/shared-api.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,EACN,KAAK,OAAO,EACZ,KAAK,OAAO,EAKZ,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAKtE,6EAA6E;AAC7E,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,cAAc,CAAC,GAAG;IAC1E,YAAY,CACX,OAAO,EAAE,WAAW,EACpB,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAClC,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;CAC1C,CAAC;AAEF,4DAA4D;AAC5D,wBAAgB,SAAS,CAAC,OAAO,EAAE,OAAO,GAAG,iBAAiB,CAqD7D"}
1
+ {"version":3,"file":"shared-api.d.ts","sourceRoot":"","sources":["../../../src/auth/sessions/shared-api.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EACN,KAAK,OAAO,EACZ,KAAK,OAAO,EAKZ,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAMtE,6EAA6E;AAC7E,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,cAAc,CAAC,GAAG;IAC1E,YAAY,CACX,OAAO,EAAE,WAAW,EACpB,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAClC,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;CAC1C,CAAC;AAEF,4DAA4D;AAC5D,wBAAgB,SAAS,CAAC,OAAO,EAAE,OAAO,GAAG,iBAAiB,CA8C7D"}
@@ -119,8 +119,19 @@ export interface VerifyEmailApi<U> {
119
119
  * Counts nothing in the mail or the sign-in throttle, sends no e-mail
120
120
  * and issues no token. An inactive user is `USER_INACTIVE`; a
121
121
  * malformed id, an unknown one or one of another type is `NOT_FOUND`;
122
- * a user written since it was read is `VERSION_CONFLICT`, and nothing
123
- * is proved.
122
+ * a user written between the read and the proof is read again and the
123
+ * proof retried **once**: `proved: false` when that write proved the
124
+ * e-mail, `VERSION_CONFLICT` with nothing proved when it changed the
125
+ * address (the proof you hold is for the address you read) or when
126
+ * the retry conflicts too, `USER_INACTIVE` when it deactivated the
127
+ * user. Running the proof twice is harmless.
128
+ *
129
+ * **The address you hold is checked.** Given a `User` — an object
130
+ * carrying the type's e-mail field — it compares that address,
131
+ * normalised as e-mails are, with the one it reads, on the first read
132
+ * and on the retry read: a different one is `VERSION_CONFLICT` and
133
+ * nothing is proved. Given a bare id, or an object without the e-mail
134
+ * field, it proves the current address.
124
135
  */
125
136
  proveFirst(user: UserRef): Promise<FirstProof<U>>;
126
137
  };
@@ -1 +1 @@
1
- {"version":3,"file":"email-flows.d.ts","sourceRoot":"","sources":["../../../src/auth/types/email-flows.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"email-flows.d.ts","sourceRoot":"","sources":["../../../src/auth/types/email-flows.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * What a user type with a login or an e-mail answers besides: finding its
3
+ * user by that key, or creating it when nobody holds it.
4
+ *
5
+ * Part of what `janus()` hands back; `./index` gathers it.
6
+ */
7
+ /**
8
+ * Finding a user by the key an invitation names, or creating them — the
9
+ * accept step of an invitation, without the race between the read and the
10
+ * write.
11
+ */
12
+ export interface FindOrCreateApi<U, In> {
13
+ /**
14
+ * The user of this type holding `key`, or the user `input()` describes,
15
+ * created. Answers `{ user, created }`.
16
+ *
17
+ * The key is looked up as the type's own lookup would: the login, normalised
18
+ * as `findByLogin` normalises it, on a type that signs in with a password;
19
+ * the e-mail, normalised as `findByEmail` normalises it, on a type that does
20
+ * not. A user found, an inactive one included, is answered
21
+ * `{ user, created: false }`: `input` is not called and nothing is written.
22
+ *
23
+ * When nobody holds the key, `input` is called (it may be async) and the
24
+ * user is created exactly as `create` creates one. **What `input` throws
25
+ * propagates and nothing is written** — that is how the caller refuses before
26
+ * any write, such as "a password is required". The input must name the same
27
+ * key: once validated, its login (its e-mail, on a type with no password)
28
+ * must equal the normalised `key`, or the call is refused with
29
+ * `USER_INVALID` before the write.
30
+ *
31
+ * A call that loses a race to another that creates the same user gets the
32
+ * `LOGIN_TAKEN` of the store, reads the key once more and answers the
33
+ * winner `{ user, created: false }`; the `LOGIN_TAKEN` is rethrown only
34
+ * when that read finds nobody — another login of the new user is taken.
35
+ *
36
+ * On a type whose login is a username, an e-mail key finds an existing user
37
+ * (e-mails are stored as logins) but can never create one: the input's
38
+ * username is not the key, so it is `USER_INVALID`.
39
+ *
40
+ * **An account-existence oracle, as `findByEmail` is: never expose it to an
41
+ * anonymous client.** A key that is no storable string is nobody's, and goes
42
+ * through `input` and the creation, which refuses it.
43
+ *
44
+ * Rejects with `USER_INVALID`, `PASSWORD_TOO_SHORT`, `LOGIN_TAKEN`, or
45
+ * `STORE_FAILED`, and with whatever `input` throws. Sends `user.created`
46
+ * only when it created.
47
+ */
48
+ findOrCreate(key: string, input: () => (In & {
49
+ readonly password?: string;
50
+ readonly active?: boolean;
51
+ }) | Promise<In & {
52
+ readonly password?: string;
53
+ readonly active?: boolean;
54
+ }>): Promise<{
55
+ readonly user: U;
56
+ readonly created: boolean;
57
+ }>;
58
+ }
59
+ //# sourceMappingURL=find-or-create.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"find-or-create.d.ts","sourceRoot":"","sources":["../../../src/auth/types/find-or-create.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;GAIG;AACH,MAAM,WAAW,eAAe,CAAC,CAAC,EAAE,EAAE;IACrC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAkCG;IACH,YAAY,CACX,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MACJ,CAAC,EAAE,GAAG;QAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,GAChE,OAAO,CAAC,EAAE,GAAG;QAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,GACxE,OAAO,CAAC;QAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CAC5D"}
@@ -11,6 +11,7 @@ export type { ChangeEmailApi } from './change-email';
11
11
  export type { Checked } from './checks';
12
12
  export type { FirstProof, IssuedCode, IssuedToken, ResetPasswordApi, SignInCodeApi, VerifyEmailApi, } from './email-flows';
13
13
  export type { FindByEmailApi } from './find-by-email';
14
+ export type { FindOrCreateApi } from './find-or-create';
14
15
  export type { EmailOf, LoginOf, RequiredStringKeys, TypesOf, UserOf, } from './inference';
15
16
  export type { Janus, SharedApi, TypeApi } from './janus';
16
17
  export type { MagicLinkApi } from './magic-link';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/auth/types/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACrD,YAAY,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AACxC,YAAY,EACX,UAAU,EACV,UAAU,EACV,WAAW,EACX,gBAAgB,EAChB,aAAa,EACb,cAAc,GACd,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtD,YAAY,EACX,OAAO,EACP,OAAO,EACP,kBAAkB,EAClB,OAAO,EACP,MAAM,GACN,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AACzD,YAAY,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACjD,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAChE,YAAY,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACxD,YAAY,EACX,eAAe,EACf,mBAAmB,EACnB,eAAe,EACf,qBAAqB,GACrB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EACX,aAAa,EACb,YAAY,EACZ,WAAW,EACX,OAAO,GACP,MAAM,WAAW,CAAC;AACnB,YAAY,EACX,oBAAoB,EACpB,QAAQ,EACR,aAAa,EACb,YAAY,GACZ,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AACvE,YAAY,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACpE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/auth/types/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACrD,YAAY,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AACxC,YAAY,EACX,UAAU,EACV,UAAU,EACV,WAAW,EACX,gBAAgB,EAChB,aAAa,EACb,cAAc,GACd,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtD,YAAY,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACxD,YAAY,EACX,OAAO,EACP,OAAO,EACP,kBAAkB,EAClB,OAAO,EACP,MAAM,GACN,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AACzD,YAAY,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACjD,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAChE,YAAY,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACxD,YAAY,EACX,eAAe,EACf,mBAAmB,EACnB,eAAe,EACf,qBAAqB,GACrB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EACX,aAAa,EACb,YAAY,EACZ,WAAW,EACX,OAAO,GACP,MAAM,WAAW,CAAC;AACnB,YAAY,EACX,oBAAoB,EACpB,QAAQ,EACR,aAAa,EACb,YAAY,GACZ,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AACvE,YAAY,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACpE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC"}
@@ -8,6 +8,7 @@ import type { JanusConfig, MultiTypeConfig, SingleTypeConfig } from '../config';
8
8
  import type { ChangeEmailApi } from './change-email';
9
9
  import type { ResetPasswordApi, SignInCodeApi, VerifyEmailApi } from './email-flows';
10
10
  import type { FindByEmailApi } from './find-by-email';
11
+ import type { FindOrCreateApi } from './find-or-create';
11
12
  import type { EmailOf, FieldsInput, LoginOf, SingleAsType, UserOf, UserOfType } from './inference';
12
13
  import type { MagicLinkApi } from './magic-link';
13
14
  import type { PasswordApi } from './password';
@@ -29,7 +30,7 @@ type SignInAnswer<Name extends string, Def, TwoFactor extends boolean> = TwoFact
29
30
  * The whole surface of one user type, with only the flows its configuration
30
31
  * allows. `TwoFactor` is whether `janus()` was given a `secondFactor`.
31
32
  */
32
- export type TypeApi<Name extends string, Def, TwoFactor extends boolean = false> = UserTypeApi<UserOfType<Name, Def>, FieldsInput<Def>> & ProvenSignInApi<UserOfType<Name, Def>, SignInAnswer<Name, Def, TwoFactor>> & ([LoginOf<Def>] extends [never] ? unknown : TwoFactor extends true ? PasswordApi<UserOfType<Name, Def>, FieldsInput<Def>, LoginOf<Def>, SignInResult<UserOfType<Name, Def>>> & SecondFactorApi<UserOfType<Name, Def>> : PasswordApi<UserOfType<Name, Def>, FieldsInput<Def>, LoginOf<Def>>) & ([EmailOf<Def>] extends [never] ? unknown : FindByEmailApi<UserOfType<Name, Def>> & VerifyEmailApi<UserOfType<Name, Def>> & ChangeEmailApi<UserOfType<Name, Def>> & SignInCodeApi<UserOfType<Name, Def>, SignInAnswer<Name, Def, TwoFactor>> & MagicLinkApi<UserOfType<Name, Def>, SignInAnswer<Name, Def, TwoFactor>> & StepUpApi<UserOfType<Name, Def>, TwoFactor extends true ? [LoginOf<Def>] extends [never] ? StepUpByEmail<UserOfType<Name, Def>> : StepUpByEmail<UserOfType<Name, Def>> | StepUpByApp<UserOfType<Name, Def>> : StepUpByEmail<UserOfType<Name, Def>>>) & ([LoginOf<Def>] extends [never] ? unknown : [EmailOf<Def>] extends [never] ? unknown : ResetPasswordApi<UserOfType<Name, Def>>);
33
+ export type TypeApi<Name extends string, Def, TwoFactor extends boolean = false> = UserTypeApi<UserOfType<Name, Def>, FieldsInput<Def>> & ProvenSignInApi<UserOfType<Name, Def>, SignInAnswer<Name, Def, TwoFactor>> & ([LoginOf<Def>] extends [never] ? unknown : TwoFactor extends true ? PasswordApi<UserOfType<Name, Def>, FieldsInput<Def>, LoginOf<Def>, SignInResult<UserOfType<Name, Def>>> & SecondFactorApi<UserOfType<Name, Def>> : PasswordApi<UserOfType<Name, Def>, FieldsInput<Def>, LoginOf<Def>>) & ([LoginOf<Def>] extends [never] ? [EmailOf<Def>] extends [never] ? unknown : FindOrCreateApi<UserOfType<Name, Def>, FieldsInput<Def>> : FindOrCreateApi<UserOfType<Name, Def>, FieldsInput<Def>>) & ([EmailOf<Def>] extends [never] ? unknown : FindByEmailApi<UserOfType<Name, Def>> & VerifyEmailApi<UserOfType<Name, Def>> & ChangeEmailApi<UserOfType<Name, Def>> & SignInCodeApi<UserOfType<Name, Def>, SignInAnswer<Name, Def, TwoFactor>> & MagicLinkApi<UserOfType<Name, Def>, SignInAnswer<Name, Def, TwoFactor>> & StepUpApi<UserOfType<Name, Def>, TwoFactor extends true ? [LoginOf<Def>] extends [never] ? StepUpByEmail<UserOfType<Name, Def>> : StepUpByEmail<UserOfType<Name, Def>> | StepUpByApp<UserOfType<Name, Def>> : StepUpByEmail<UserOfType<Name, Def>>>) & ([LoginOf<Def>] extends [never] ? unknown : [EmailOf<Def>] extends [never] ? unknown : ResetPasswordApi<UserOfType<Name, Def>>);
33
34
  /** What every configuration answers, whatever its user types. */
34
35
  export interface SharedApi<U extends {
35
36
  readonly type: string;
@@ -67,9 +68,10 @@ export interface SharedApi<U extends {
67
68
  clear(): string;
68
69
  };
69
70
  /**
70
- * Deletes lapsed sessions. `UNSUPPORTED` when the sessions store does not
71
- * implement `deleteExpiredSessions` — a store with its own TTL does not need
72
- * to.
71
+ * Deletes lapsed sessions and lapsed one-time tokens — the throttles' counts
72
+ * included — from the stores that implement `deleteExpiredSessions` /
73
+ * `deleteExpiredTokens`, and answers how many in all. `UNSUPPORTED` when
74
+ * neither does — a store with its own TTL does not need to.
73
75
  */
74
76
  collectExpired(): Promise<number>;
75
77
  /** The user type names, in the order they were declared. */