@nxgt/janus 0.5.0 → 0.7.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 +116 -23
- package/dist/auth/config.d.ts +3 -0
- package/dist/auth/config.d.ts.map +1 -1
- package/dist/auth/context.d.ts +12 -0
- package/dist/auth/context.d.ts.map +1 -1
- package/dist/auth/email-flows.d.ts +11 -0
- package/dist/auth/email-flows.d.ts.map +1 -0
- package/dist/auth/index.d.ts +1 -1
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/one-time.d.ts +66 -6
- package/dist/auth/one-time.d.ts.map +1 -1
- package/dist/auth/password-written.d.ts +24 -0
- package/dist/auth/password-written.d.ts.map +1 -0
- package/dist/auth/port/assert-stores.d.ts.map +1 -1
- package/dist/auth/port/types.d.ts +23 -1
- package/dist/auth/port/types.d.ts.map +1 -1
- package/dist/auth/second-factor/challenge.d.ts +0 -6
- package/dist/auth/second-factor/challenge.d.ts.map +1 -1
- package/dist/auth/second-factor/factor.d.ts +1 -4
- package/dist/auth/second-factor/factor.d.ts.map +1 -1
- package/dist/auth/second-factor/flows.d.ts +7 -3
- package/dist/auth/second-factor/flows.d.ts.map +1 -1
- package/dist/auth/second-factor/lifecycle.d.ts.map +1 -1
- package/dist/auth/sign-in-code.d.ts +20 -0
- package/dist/auth/sign-in-code.d.ts.map +1 -0
- package/dist/auth/types.d.ts +56 -1
- package/dist/auth/types.d.ts.map +1 -1
- package/dist/auth/users.d.ts +2 -2
- package/dist/auth/users.d.ts.map +1 -1
- package/dist/chunks/{index-gbwn2tts.js → index-c0jajeda.js} +12 -2
- package/dist/chunks/{index-gbwn2tts.js.map → index-c0jajeda.js.map} +3 -3
- package/dist/conformance/cases/outage.d.ts.map +1 -1
- package/dist/conformance/cases/tokens.d.ts.map +1 -1
- package/dist/conformance/index.js +73 -2
- package/dist/conformance/index.js.map +4 -4
- package/dist/index.js +253 -116
- package/dist/index.js.map +15 -12
- package/docs/README.md +2 -1
- package/docs/guide/adapters.md +56 -3
- package/docs/guide/email-flows.md +11 -1
- package/docs/guide/errors.md +3 -3
- package/docs/guide/second-factor.md +31 -4
- package/docs/guide/sign-in-code.md +489 -0
- package/docs/guide/users.md +5 -4
- package/docs/guide/vocabulary.md +7 -6
- package/docs/roadmap.md +28 -13
- package/docs/troubleshooting.md +193 -28
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -46,7 +46,7 @@ a published entry point is a promise.
|
|
|
46
46
|
## Three ways to use it
|
|
47
47
|
|
|
48
48
|
Janus has two sides. **Identities** answers *who is this?* — users, their
|
|
49
|
-
logins and passwords, a TOTP second factor, sessions, one-time tokens. **Permissions** answers *may
|
|
49
|
+
logins and passwords, codes sent by e-mail, a TOTP second factor, sessions, one-time tokens. **Permissions** answers *may
|
|
50
50
|
they?* — a model, the tuples stored against it, and `can`. Each side is usable
|
|
51
51
|
alone, and neither loads the other's code: a spec reads the import graph of
|
|
52
52
|
each entry point and fails if one reaches into the other.
|
|
@@ -177,7 +177,7 @@ compilation of callers that exhaust it:
|
|
|
177
177
|
| `USER_INVALID` | 400, field by field from `issues` |
|
|
178
178
|
| `PASSWORD_TOO_SHORT`, `HASH_UNSUPPORTED` | 400 |
|
|
179
179
|
| `CREDENTIALS_INVALID` | 401 — one code for an unknown login, no password and a wrong one |
|
|
180
|
-
| `CODE_INVALID` | 401 — a
|
|
180
|
+
| `CODE_INVALID` | 401 — a one-time code that does not match: a second factor's, or one sent by e-mail; `attemptsLeft` from either `confirm` belongs in the body |
|
|
181
181
|
| `SECOND_FACTOR_NOT_ENROLLED`, `SECOND_FACTOR_ACTIVE` | 409 — the factor is not in the state the call needs |
|
|
182
182
|
| `USER_INACTIVE` | 403 |
|
|
183
183
|
| `TOKEN_UNKNOWN`, `TOKEN_SPENT`, `TOKEN_EXPIRED`, `TOKEN_STALE` | 400 |
|
|
@@ -311,6 +311,8 @@ await auth.verifyEmail.send(user); // { token, email, expiresAt
|
|
|
311
311
|
await auth.verifyEmail.confirm(token);
|
|
312
312
|
await auth.resetPassword.request(email); // … | null
|
|
313
313
|
await auth.resetPassword.confirm(token, newPassword);
|
|
314
|
+
await auth.signInCode.request(email); // { code, challenge, email, expiresAt, user } | null
|
|
315
|
+
await auth.signInCode.confirm(challenge, code); // { status: 'signedIn', user, session, token }
|
|
314
316
|
|
|
315
317
|
// Several user types
|
|
316
318
|
const clinic = janus({
|
|
@@ -351,8 +353,9 @@ else reaches the store and is asynchronous.
|
|
|
351
353
|
compile error on `login`, and the message lists the fields you could have
|
|
352
354
|
meant. It is normalised with `'lowercaseTrim'` unless you say otherwise.
|
|
353
355
|
- **`email`** defaults to the field named `email`. A type without one has no
|
|
354
|
-
`verifyEmail` and no `
|
|
355
|
-
failing at run time. Changing the e-mail sets `emailVerified`
|
|
356
|
+
`verifyEmail`, no `resetPassword` and no `signInCode` — they are absent from
|
|
357
|
+
its type, not failing at run time. Changing the e-mail sets `emailVerified`
|
|
358
|
+
back to `false`.
|
|
356
359
|
- **Per type**: `create`, `find` (or `null`), `get` (or `NOT_FOUND`), `list`,
|
|
357
360
|
`update(user, patch)` — merged over the stored fields, then validated whole —
|
|
358
361
|
`setActive` and `delete`; with a password, `signUp`, `signIn`, `findByLogin`,
|
|
@@ -426,10 +429,38 @@ await tokens.countAttempt(tokenHash, 'signInCode'); // { …, attempts: 1 }
|
|
|
426
429
|
await tokens.countAttempt(tokenHash, 'verifyEmail'); // null: no token of that kind
|
|
427
430
|
```
|
|
428
431
|
|
|
432
|
+
`spendUserTokens(userId, kind, at, except?)` spends the unspent tokens of one
|
|
433
|
+
user and one kind — but the one whose hash is `except` — and answers how many:
|
|
434
|
+
what issuing a sign-in code and writing a password call:
|
|
435
|
+
|
|
436
|
+
```ts
|
|
437
|
+
const userId = mintId();
|
|
438
|
+
const now = new Date();
|
|
439
|
+
const code = (tokenHash: string) => ({
|
|
440
|
+
tokenHash,
|
|
441
|
+
kind: 'signInCode' as const,
|
|
442
|
+
userId,
|
|
443
|
+
address: 'ada@example.test',
|
|
444
|
+
codeHash: 'c'.repeat(64),
|
|
445
|
+
attempts: 0,
|
|
446
|
+
expiresAt: new Date(now.getTime() + 10 * 60_000),
|
|
447
|
+
spentAt: null,
|
|
448
|
+
createdAt: now,
|
|
449
|
+
});
|
|
450
|
+
const kept = code('d'.repeat(64));
|
|
451
|
+
await tokens.insertToken(kept);
|
|
452
|
+
await tokens.insertToken(code('e'.repeat(64)));
|
|
453
|
+
await tokens.spendUserTokens(userId, 'signInCode', now, kept.tokenHash); // 1: the other one
|
|
454
|
+
await tokens.spendUserTokens(userId, 'signInCode', now); // 1: `kept`, now
|
|
455
|
+
await tokens.spendUserTokens(userId, 'signInCode', now); // 0: none left unspent
|
|
456
|
+
```
|
|
457
|
+
|
|
429
458
|
An adapter written against `@nxgt/janus` 0.3 does not compile against this
|
|
430
|
-
port until it implements `countAttempt`,
|
|
431
|
-
|
|
432
|
-
|
|
459
|
+
port until it implements `countAttempt`, nor one written against 0.6 until it
|
|
460
|
+
implements `spendUserTokens`, and `janus()` refuses either at wiring —
|
|
461
|
+
[Writing an adapter](docs/guide/adapters.md) has the contracts:
|
|
462
|
+
[`countAttempt`](docs/guide/adapters.md#tokenstorecountattempt) and
|
|
463
|
+
[`spendUserTokens`](docs/guide/adapters.md#tokenstorespendusertokens).
|
|
433
464
|
|
|
434
465
|
### Second factor — `secondFactor`
|
|
435
466
|
|
|
@@ -465,7 +496,7 @@ every user type with a password.
|
|
|
465
496
|
|
|
466
497
|
- **`signIn` answers a union once `secondFactor` is configured**:
|
|
467
498
|
`{ status: 'signedIn', user, session, token }`, or
|
|
468
|
-
`{ status: 'secondFactor', challenge, expiresAt }` for a user whose factor
|
|
499
|
+
`{ status: 'secondFactor', challenge, expiresAt, userId }` for a user whose factor
|
|
469
500
|
is active. Switch on `status`: reading `token` before that is a compile
|
|
470
501
|
error. Without `secondFactor`, `signIn` answers a session, as before.
|
|
471
502
|
- **A factor is enrolled, then active.** `enroll` answers the secret and its
|
|
@@ -487,6 +518,48 @@ every user type with a password.
|
|
|
487
518
|
error and state, key rotation, and a sign-in route with the challenge in a
|
|
488
519
|
cookie.
|
|
489
520
|
|
|
521
|
+
### Sign-in codes — `signInCode`
|
|
522
|
+
|
|
523
|
+
```ts
|
|
524
|
+
import { z } from 'zod';
|
|
525
|
+
import { createMemoryStores, janus } from '@nxgt/janus';
|
|
526
|
+
|
|
527
|
+
const auth = janus({
|
|
528
|
+
user: z.object({ email: z.email(), name: z.string() }), // no password: codes only
|
|
529
|
+
store: createMemoryStores(),
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
const issued = await auth.signInCode.request(email); // null for nobody, or an inactive user
|
|
533
|
+
if (issued !== null) {
|
|
534
|
+
await sendMail(issued.email, `Your sign-in code: ${issued.code}`); // the code, and only the code
|
|
535
|
+
}
|
|
536
|
+
// answer the same page either way; keep issued.challenge with the visitor — a cookie, never a URL
|
|
537
|
+
|
|
538
|
+
const signedIn = await auth.signInCode.confirm(challenge, code); // { status: 'signedIn', user, session, token }
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
A six-digit code sent to the user's e-mail signs them in, with no password.
|
|
542
|
+
|
|
543
|
+
- **On every user type with an e-mail**, with a password or without one. A
|
|
544
|
+
type with no password signs in by code alone; a type with no e-mail has no
|
|
545
|
+
`signInCode` at all.
|
|
546
|
+
- **`request(email)` answers `null`** when nobody of this type holds that
|
|
547
|
+
e-mail, or the user is inactive — never say which. It answers the `code`
|
|
548
|
+
for the e-mail and the `challenge` for the visitor; the code's hash is
|
|
549
|
+
stored, keyed by the challenge.
|
|
550
|
+
- **`confirm(challenge, code)`** marks the e-mail verified and opens the
|
|
551
|
+
session. A challenge lives `'10m'` (`tokens.signInCode`) and takes five
|
|
552
|
+
attempts: a wrong code is `CODE_INVALID` with `attemptsLeft`, and the fifth
|
|
553
|
+
spends it. An e-mail changed since is `TOKEN_STALE`; a user deactivated
|
|
554
|
+
since is `USER_INACTIVE`.
|
|
555
|
+
- **An active second factor is still asked for**: with `secondFactor`
|
|
556
|
+
configured, `confirm` answers `SignInResult` on a type with a password, and
|
|
557
|
+
a user whose factor is active gets a challenge for `secondFactor.confirm`
|
|
558
|
+
— switch on `status`, as after `signIn`.
|
|
559
|
+
|
|
560
|
+
[The sign-in code guide](docs/guide/sign-in-code.md) has the request that
|
|
561
|
+
tells nobody who exists, the challenge in a cookie, every error, and a test.
|
|
562
|
+
|
|
490
563
|
### Permissions — `@nxgt/janus/permissions`
|
|
491
564
|
|
|
492
565
|
```ts
|
|
@@ -597,7 +670,7 @@ describeJanusStores({
|
|
|
597
670
|
});
|
|
598
671
|
```
|
|
599
672
|
|
|
600
|
-
There are
|
|
673
|
+
There are 49 cases. They cover:
|
|
601
674
|
- round-trip, byte for byte — including every edge character the core lets
|
|
602
675
|
through (control characters, U+FFFF, a surrogate pair);
|
|
603
676
|
- uniqueness, as a constraint: of twenty concurrent inserts of one login,
|
|
@@ -610,12 +683,15 @@ There are 45 cases. They cover:
|
|
|
610
683
|
- one-time tokens: of twenty concurrent redemptions, exactly one succeeds;
|
|
611
684
|
of twenty concurrent `countAttempt` calls, each answers a distinct count,
|
|
612
685
|
and none is counted once a racing redemption spent the token;
|
|
686
|
+
- spending a user's tokens of one kind — `spendUserTokens` — spends only the
|
|
687
|
+
unspent ones of that user and kind, spares the one named by `except`, and
|
|
688
|
+
never spends the same token as a racing redemption;
|
|
613
689
|
- a second-factor challenge, whose address is `''`, kept, counted and spent;
|
|
614
690
|
- a user's second factor: round-trip, kept by a patch that does not name it,
|
|
615
691
|
removed by one that names `null`;
|
|
616
692
|
- deletion: a user's logins are freed, and every session and token of theirs
|
|
617
693
|
goes, with a replay answering `false` or `0` rather than failing;
|
|
618
|
-
- **outages**, one case for each of the
|
|
694
|
+
- **outages**, one case for each of the thirteen methods whose honest answer can
|
|
619
695
|
be "nothing".
|
|
620
696
|
|
|
621
697
|
The suite imports no test framework and no assertion library. It runs under
|
|
@@ -647,14 +723,29 @@ example; `allRelationCases`, `relationStoreCases`, `relationOutageCases` and
|
|
|
647
723
|
|
|
648
724
|
## Traps
|
|
649
725
|
|
|
650
|
-
**Once `secondFactor` is configured, switch on `signIn`'s `status
|
|
651
|
-
whose factor is active gets
|
|
652
|
-
`
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
`
|
|
657
|
-
|
|
726
|
+
**Once `secondFactor` is configured, switch on `signIn`'s `status`** — and
|
|
727
|
+
on `signInCode.confirm`'s. A user whose factor is active gets
|
|
728
|
+
`{ status: 'secondFactor', challenge, expiresAt, userId }`, with no `token`
|
|
729
|
+
and no `session`: an e-mailed code proves the e-mail, not the factor.
|
|
730
|
+
`userId` is for your logs and rate limits — answer the visitor the
|
|
731
|
+
challenge alone.
|
|
732
|
+
`if (result.status === 'secondFactor') …` before anything reads them.
|
|
733
|
+
|
|
734
|
+
**A challenge is a secret, like a session token** — `signIn`'s and
|
|
735
|
+
`signInCode.request`'s alike. Keep it in a short-lived `HttpOnly` cookie or
|
|
736
|
+
the body of the code form — never in the e-mail, never in a URL, where logs,
|
|
737
|
+
proxies and the `Referer` header see it, and never in a log. The e-mail holds
|
|
738
|
+
the code and nothing else.
|
|
739
|
+
|
|
740
|
+
**Answer `signInCode.request` the same whether it issued a code or not** —
|
|
741
|
+
the same status, body and cookie: set a random challenge when it answered
|
|
742
|
+
`null`. The code route then still tells a decoy (`TOKEN_UNKNOWN`) from a
|
|
743
|
+
real challenge (`CODE_INVALID`, `attemptsLeft`, or `TOKEN_SPENT` once a later
|
|
744
|
+
request spent it): answer its refusals alike
|
|
745
|
+
where addresses must stay secret. And rate-limit the request **per
|
|
746
|
+
address**: at most one code is live per user — a new `request` spends the one
|
|
747
|
+
before — so without a limit anyone who knows an address can fill its inbox,
|
|
748
|
+
or cancel its owner's code before they type it.
|
|
658
749
|
|
|
659
750
|
**Every `janus()` that signs users in needs the same `secondFactor`.** An
|
|
660
751
|
instance without keys never signs in a user whose factor is active: `signIn`
|
|
@@ -701,7 +792,9 @@ denied. `resetPassword.request` answers `null` for an unknown e-mail for the
|
|
|
701
792
|
same reason: answer the visitor the same page either way.
|
|
702
793
|
|
|
703
794
|
**`resetPassword.confirm` signs the user out everywhere, and opens no session.**
|
|
704
|
-
Whoever had the old password loses their sessions
|
|
795
|
+
Whoever had the old password loses their sessions, and a sign-in they left
|
|
796
|
+
waiting on its second factor is spent with them — as it is by `setPassword`
|
|
797
|
+
and `changePassword`; what the visitor does next is
|
|
705
798
|
your policy. A password refused for its length does not spend the token.
|
|
706
799
|
|
|
707
800
|
**A sign-in can move a user's `version`.** Rewriting a stale hash is a write. A
|
|
@@ -774,16 +867,16 @@ could not answer: that is a denial made of an outage.
|
|
|
774
867
|
|
|
775
868
|
## Type safety, counted
|
|
776
869
|
|
|
777
|
-
**One hundred and
|
|
870
|
+
**One hundred and fifteen plausible mistakes, one hundred and fifteen refused at compile time — and
|
|
778
871
|
two gaps, named.**
|
|
779
872
|
|
|
780
873
|
The lists are typechecked and never run, with one `@ts-expect-error` per
|
|
781
874
|
mistake beside the shapes that must keep compiling:
|
|
782
875
|
`test/types/refusals.ts` (fourteen, on the shared vocabulary),
|
|
783
|
-
`test/types/port.ts` (twenty-
|
|
784
|
-
of view of the person implementing it), `test/types/auth.ts` (
|
|
876
|
+
`test/types/port.ts` (twenty-two, on the identity stores' port, from the point
|
|
877
|
+
of view of the person implementing it), `test/types/auth.ts` (thirty-one, on
|
|
785
878
|
`janus()`, from the point of view of the application — eight of them on the
|
|
786
|
-
second factor) and `test/types/permissions.ts` (forty-eight, on the
|
|
879
|
+
second factor, three on sign-in codes) and `test/types/permissions.ts` (forty-eight, on the
|
|
787
880
|
permission model and the questions asked of it). The rule
|
|
788
881
|
comes from `nxgt-data`, and so does the reason to
|
|
789
882
|
distrust the claim without the files: when it was last measured on
|
package/dist/auth/config.d.ts
CHANGED
|
@@ -137,6 +137,8 @@ interface SharedConfig {
|
|
|
137
137
|
readonly verifyEmail?: Duration;
|
|
138
138
|
/** `'1h'` when absent. */
|
|
139
139
|
readonly resetPassword?: Duration;
|
|
140
|
+
/** How long an e-mailed sign-in code waits. `'10m'` when absent. */
|
|
141
|
+
readonly signInCode?: Duration;
|
|
140
142
|
};
|
|
141
143
|
/**
|
|
142
144
|
* A TOTP second factor, for every user type with a password. Absent, no
|
|
@@ -203,6 +205,7 @@ export interface ResolvedConfig {
|
|
|
203
205
|
readonly tokenTtlMs: {
|
|
204
206
|
readonly verifyEmail: number;
|
|
205
207
|
readonly resetPassword: number;
|
|
208
|
+
readonly signInCode: number;
|
|
206
209
|
};
|
|
207
210
|
readonly secondFactor: {
|
|
208
211
|
readonly issuer: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/auth/config.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,KAAK,QAAQ,EAAiB,MAAM,kBAAkB,CAAC;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAiB,KAAK,MAAM,EAAE,KAAK,UAAU,EAAE,MAAM,WAAW,CAAC;AACxE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE1D;;;;;;GAMG;AACH,MAAM,MAAM,SAAS,GAClB,MAAM,GACN,WAAW,GACX,eAAe,GACf,mBAAmB,GACnB,CAAC,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,CAAC;AAE/B;;;;;;;GAOG;AACH,MAAM,MAAM,UAAU,GACnB,MAAM,GACN,MAAM,GACN,OAAO,GACP,IAAI,GACJ,SAAS,UAAU,EAAE,GACrB;IAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAAA;CAAE,CAAC;AAEtD;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG,gBAAgB,CACxC,OAAO,EACP;IAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAAA;CAAE,CAClD,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC9B,iFAAiF;IACjF,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACrC,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACtD;;;;;OAKG;IACH,WAAW,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;CACpC;AAED,kCAAkC;AAClC,MAAM,WAAW,cAAc;IAC9B;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,qCAAqC;IACrC,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;IAC/B,mCAAmC;IACnC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,wDAAwD;AACxD,MAAM,WAAW,aAAa;IAC7B,0BAA0B;IAC1B,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC;IAC7B;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,QAAQ,GAAG,KAAK,CAAC;CACvC;AAED,sDAAsD;AACtD,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,QAAQ,CAAC,EAAE,cAAc,CAAC;IACnC;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,aAAa,CAAC;IACjC;;;;OAIG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;CAChC;AAED,2DAA2D;AAC3D,MAAM,WAAW,YAAY;IAC5B,oFAAoF;IACpF,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,yBAAyB;IACzB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,2BAA2B;IAC3B,QAAQ,CAAC,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IAC9C,0DAA0D;IAC1D,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,UAAU,YAAY;IACrB,iEAAiE;IACjE,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,aAAa,CAAC;IACnC;;;;OAIG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,CAAC;IACjC,0EAA0E;IAC1E,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IAC/C,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,YAAY,CAAC;IAC/B,QAAQ,CAAC,MAAM,CAAC,EAAE;QACjB,2BAA2B;QAC3B,QAAQ,CAAC,WAAW,CAAC,EAAE,QAAQ,CAAC;QAChC,0BAA0B;QAC1B,QAAQ,CAAC,aAAa,CAAC,EAAE,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/auth/config.ts"],"names":[],"mappings":"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"}
|
package/dist/auth/context.d.ts
CHANGED
|
@@ -49,6 +49,12 @@ export declare function emailOf(type: ResolvedType, fields: JsonObject): string
|
|
|
49
49
|
* still be found by the e-mail a reset is requested for.
|
|
50
50
|
*/
|
|
51
51
|
export declare function loginsOf(type: ResolvedType, fields: JsonObject, where: string): string[];
|
|
52
|
+
/** The type's password rule, or a wiring refusal for a JavaScript caller. */
|
|
53
|
+
export declare function passwordRule(type: ResolvedType, where: string): {
|
|
54
|
+
readonly login: string;
|
|
55
|
+
readonly normalize: (value: string) => string;
|
|
56
|
+
readonly minLength: number;
|
|
57
|
+
};
|
|
52
58
|
/** Refuses a password shorter than the policy. Reports the policy, never the password. */
|
|
53
59
|
export declare function checkPassword(type: ResolvedType, password: string, where: string): void;
|
|
54
60
|
/** The hasher. `janus()` refused a password type without one, so this is a wiring net. */
|
|
@@ -84,4 +90,10 @@ export declare function getRecord(context: Context, id: string, type: string | n
|
|
|
84
90
|
* `VERSION_CONFLICT`, and nothing is written.
|
|
85
91
|
*/
|
|
86
92
|
export declare function writeUser(context: Context, user: UserRef, type: ResolvedType, options: WriteOptions | undefined, where: string, patchOf: (record: UserRecord, now: Date) => Omit<UserPatch, 'updatedAt'> | Promise<Omit<UserPatch, 'updatedAt'>>): Promise<UserRecord>;
|
|
93
|
+
/**
|
|
94
|
+
* The user of this type whose e-mail this is, or `null`. Looked up as a
|
|
95
|
+
* login — every e-mail is one — and then compared, so a username that only
|
|
96
|
+
* looks like an e-mail is nobody's.
|
|
97
|
+
*/
|
|
98
|
+
export declare function holderOfEmail(context: Context, type: ResolvedType, email: string): Promise<UserRecord | null>;
|
|
87
99
|
//# sourceMappingURL=context.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/auth/context.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/auth/context.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ResolvedType } from './config';
|
|
2
|
+
import { type AnyUser, type Context } from './context';
|
|
3
|
+
import type { ResetPasswordApi, VerifyEmailApi } from './types';
|
|
4
|
+
/**
|
|
5
|
+
* The e-mail flows: send a one-time token by e-mail, then confirm it —
|
|
6
|
+
* verifying the address, or resetting the password. Both confirm the same
|
|
7
|
+
* way: spend the token, read the user, and check the address again on the
|
|
8
|
+
* very record the write replaces.
|
|
9
|
+
*/
|
|
10
|
+
export declare function emailFlows(context: Context, type: ResolvedType, at: (operation: string) => string): VerifyEmailApi<AnyUser> & ResetPasswordApi<AnyUser>;
|
|
11
|
+
//# sourceMappingURL=email-flows.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"email-flows.d.ts","sourceRoot":"","sources":["../../src/auth/email-flows.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EACN,KAAK,OAAO,EACZ,KAAK,OAAO,EAWZ,MAAM,WAAW,CAAC;AASnB,OAAO,KAAK,EAAe,gBAAgB,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE7E;;;;;GAKG;AACH,wBAAgB,UAAU,CACzB,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,YAAY,EAClB,EAAE,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,GAC/B,cAAc,CAAC,OAAO,CAAC,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAyHrD"}
|
package/dist/auth/index.d.ts
CHANGED
|
@@ -6,5 +6,5 @@ export { createMemoryStores } from './port/memory';
|
|
|
6
6
|
export type { JanusStores, Json, JsonObject, PasswordRecord, SecondFactorRecord, SessionId, SessionRecord, SessionStore, StoreCapabilities, TokenKind, TokenRecord, TokenStore, UserPageRequest, UserPatch, UserRecord, UserStore, } from './port/types';
|
|
7
7
|
export type { SealingKey } from './sealing';
|
|
8
8
|
export type { StandardSchemaV1 } from './standard-schema';
|
|
9
|
-
export type { Authenticated, Checked, EmailOf, HeaderRecord, IssuedToken, Janus, LoginOf, PasswordApi, RequestLike, RequiredStringKeys, ResetPasswordApi, SecondFactorApi, SecondFactorEnrolment, SecondFactorRequired, Session, SharedApi, SignedIn, SignInResult, TypeApi, TypesOf, User, UserBase, UserOf, UserRef, UserTypeApi, VerifyEmailApi, WriteOptions, } from './types';
|
|
9
|
+
export type { Authenticated, Checked, EmailOf, HeaderRecord, IssuedCode, IssuedToken, Janus, LoginOf, PasswordApi, RequestLike, RequiredStringKeys, ResetPasswordApi, SecondFactorApi, SecondFactorEnrolment, SecondFactorRequired, Session, SharedApi, SignedIn, SignInCodeApi, SignInResult, TypeApi, TypesOf, User, UserBase, UserOf, UserRef, UserTypeApi, VerifyEmailApi, WriteOptions, } from './types';
|
|
10
10
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/auth/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACX,YAAY,EACZ,UAAU,EACV,WAAW,EACX,eAAe,EACf,SAAS,EACT,cAAc,EACd,cAAc,EACd,kBAAkB,EAClB,aAAa,EACb,gBAAgB,EAChB,UAAU,EACV,cAAc,GACd,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,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,YAAY,EACX,aAAa,EACb,OAAO,EACP,OAAO,EACP,YAAY,EACZ,WAAW,EACX,KAAK,EACL,OAAO,EACP,WAAW,EACX,WAAW,EACX,kBAAkB,EAClB,gBAAgB,EAChB,eAAe,EACf,qBAAqB,EACrB,oBAAoB,EACpB,OAAO,EACP,SAAS,EACT,QAAQ,EACR,YAAY,EACZ,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,UAAU,EACV,WAAW,EACX,eAAe,EACf,SAAS,EACT,cAAc,EACd,cAAc,EACd,kBAAkB,EAClB,aAAa,EACb,gBAAgB,EAChB,UAAU,EACV,cAAc,GACd,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,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,YAAY,EACX,aAAa,EACb,OAAO,EACP,OAAO,EACP,YAAY,EACZ,UAAU,EACV,WAAW,EACX,KAAK,EACL,OAAO,EACP,WAAW,EACX,WAAW,EACX,kBAAkB,EAClB,gBAAgB,EAChB,eAAe,EACf,qBAAqB,EACrB,oBAAoB,EACpB,OAAO,EACP,SAAS,EACT,QAAQ,EACR,aAAa,EACb,YAAY,EACZ,OAAO,EACP,OAAO,EACP,IAAI,EACJ,QAAQ,EACR,MAAM,EACN,OAAO,EACP,WAAW,EACX,cAAc,EACd,YAAY,GACZ,MAAM,SAAS,CAAC"}
|
package/dist/auth/one-time.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { TokenError } from '../errors/janus-error';
|
|
2
|
-
import type
|
|
3
|
-
import
|
|
2
|
+
import { type ResolvedType } from './config';
|
|
3
|
+
import { type Context } from './context';
|
|
4
|
+
import type { TokenKind, TokenRecord, UserRecord } from './port/types';
|
|
4
5
|
/**
|
|
5
6
|
* The rules every one-time token shares — an e-mail link, a second-factor
|
|
6
7
|
* challenge — kept in one place so the two cannot drift apart.
|
|
@@ -9,6 +10,33 @@ import type { TokenKind, TokenRecord } from './port/types';
|
|
|
9
10
|
* what `signIn` answers. No message names the secret or its hash.
|
|
10
11
|
*/
|
|
11
12
|
export type OneTimeNoun = 'token' | 'challenge';
|
|
13
|
+
/**
|
|
14
|
+
* How many codes one challenge takes — a second factor's or an e-mail
|
|
15
|
+
* code's. A six-digit code has a million values: five attempts is a
|
|
16
|
+
* one-in-200,000 chance **per challenge**, and the attempts are counted by
|
|
17
|
+
* the store in one write, never read then written.
|
|
18
|
+
*/
|
|
19
|
+
export declare const CODE_ATTEMPTS = 5;
|
|
20
|
+
/**
|
|
21
|
+
* Counts one attempt at a challenge's code, before anything is compared: a
|
|
22
|
+
* guess that fails for any reason has still cost one. Refuses a challenge
|
|
23
|
+
* that cannot be used, and one past its last attempt — a call that raced the
|
|
24
|
+
* one that spent it — unread.
|
|
25
|
+
*/
|
|
26
|
+
export declare function countCodeAttempt(context: Context, secret: string, kind: TokenKind, where: string, userType: string): Promise<{
|
|
27
|
+
readonly token: TokenRecord;
|
|
28
|
+
readonly attemptsLeft: number;
|
|
29
|
+
}>;
|
|
30
|
+
/**
|
|
31
|
+
* A code that does not match — a second factor's or an e-mailed one — or a
|
|
32
|
+
* TOTP code already used. `attemptsLeft` only when a challenge counted it.
|
|
33
|
+
*/
|
|
34
|
+
export declare function codeInvalid(where: string, userId: string, userType: string, attemptsLeft?: number): TokenError;
|
|
35
|
+
/**
|
|
36
|
+
* Refuses a token sent to an e-mail the user no longer has: redeeming it
|
|
37
|
+
* would prove an address nobody holds any more.
|
|
38
|
+
*/
|
|
39
|
+
export declare function refuseStale(type: ResolvedType, user: UserRecord, token: TokenRecord, where: string, noun: 'token' | 'code'): void;
|
|
12
40
|
/**
|
|
13
41
|
* Refuses a token that cannot be used: none, spent, or lapsed. What a store
|
|
14
42
|
* answered — before a `consumeToken`, after a `countAttempt` — is read the
|
|
@@ -17,6 +45,13 @@ export type OneTimeNoun = 'token' | 'challenge';
|
|
|
17
45
|
export declare function refuseUnusable(token: TokenRecord | null, now: Date, where: string, noun: OneTimeNoun): asserts token is TokenRecord;
|
|
18
46
|
/** The refusal for a token whose user is gone, or of another type. */
|
|
19
47
|
export declare const unknownOneTime: (where: string, noun: OneTimeNoun) => TokenError;
|
|
48
|
+
/**
|
|
49
|
+
* The refusal for a challenge whose user is gone, or of another type — the
|
|
50
|
+
* API of the wrong type compares no code. Its attempt was counted all the
|
|
51
|
+
* same, before the type was known, so the last one spends the challenge, as
|
|
52
|
+
* a wrong code would: past it, every call answers `TOKEN_SPENT`.
|
|
53
|
+
*/
|
|
54
|
+
export declare function unknownChallenge(context: Context, token: TokenRecord, secret: string, where: string): Promise<TokenError>;
|
|
20
55
|
/**
|
|
21
56
|
* Spends a token, and refuses it when this call did not: the store answers it
|
|
22
57
|
* **as it was before**, so exactly one call ever reads `spentAt: null`. A
|
|
@@ -24,16 +59,41 @@ export declare const unknownOneTime: (where: string, noun: OneTimeNoun) => Token
|
|
|
24
59
|
*/
|
|
25
60
|
export declare function spendOneTime(context: Context, secret: string, kind: TokenKind, where: string, noun: OneTimeNoun): Promise<TokenRecord>;
|
|
26
61
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
62
|
+
* Spends a token whose last attempt failed. Its answer is not read: a racing
|
|
63
|
+
* call that spent it first changes nothing for this one, which is refused
|
|
64
|
+
* for its code either way.
|
|
65
|
+
*/
|
|
66
|
+
export declare function burnOneTime(context: Context, secret: string, kind: TokenKind): Promise<void>;
|
|
67
|
+
/**
|
|
68
|
+
* A code's hash, **keyed by its challenge**: the tokens alone — a dump, a
|
|
69
|
+
* log of the table — do not reveal the code, since a million guesses at it
|
|
70
|
+
* each need the challenge, which only the visitor holds.
|
|
29
71
|
*/
|
|
30
|
-
export declare
|
|
72
|
+
export declare const hashCode: (challenge: string, code: string) => string;
|
|
73
|
+
/** Whether `code` is the one whose hash the token holds, compared in constant time. */
|
|
74
|
+
export declare function codeMatches(token: TokenRecord, challenge: string, code: string): boolean;
|
|
75
|
+
interface OneTimeRequest {
|
|
31
76
|
readonly kind: TokenKind;
|
|
32
77
|
readonly userId: string;
|
|
33
78
|
readonly address: string;
|
|
34
79
|
readonly ttlMs: number;
|
|
35
|
-
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Issues a one-time token: 32 random bytes, given back once, and only their
|
|
83
|
+
* hash stored.
|
|
84
|
+
*/
|
|
85
|
+
export declare function issueOneTime(context: Context, token: OneTimeRequest): Promise<{
|
|
86
|
+
readonly secret: string;
|
|
87
|
+
readonly expiresAt: Date;
|
|
88
|
+
}>;
|
|
89
|
+
/**
|
|
90
|
+
* Issues a one-time token with a six-digit code for a person to type: the
|
|
91
|
+
* token is the challenge, and only the code's hash is stored, keyed by it.
|
|
92
|
+
*/
|
|
93
|
+
export declare function issueCode(context: Context, token: OneTimeRequest): Promise<{
|
|
36
94
|
readonly secret: string;
|
|
37
95
|
readonly expiresAt: Date;
|
|
96
|
+
readonly code: string;
|
|
38
97
|
}>;
|
|
98
|
+
export {};
|
|
39
99
|
//# sourceMappingURL=one-time.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"one-time.d.ts","sourceRoot":"","sources":["../../src/auth/one-time.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"one-time.d.ts","sourceRoot":"","sources":["../../src/auth/one-time.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Id } from '../ids/id';
|
|
2
|
+
import type { ResolvedType } from './config';
|
|
3
|
+
import { type AnyUser, type Context } from './context';
|
|
4
|
+
import type { UserRecord } from './port/types';
|
|
5
|
+
import type { SignInResult } from './types';
|
|
6
|
+
/**
|
|
7
|
+
* What writing a password ends besides: every second-factor challenge of the
|
|
8
|
+
* user still open, so a sign-in started with the old password cannot be
|
|
9
|
+
* finished. Called **after** the write — the other half of
|
|
10
|
+
* {@link heldByPassword}, which reads **after** it issued.
|
|
11
|
+
*/
|
|
12
|
+
export declare function endSignInsWaiting(context: Context, userId: Id): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Whether the password a sign-in verified is still the user's, once the
|
|
15
|
+
* sign-in answered. A password written meanwhile ends the sign-in: its
|
|
16
|
+
* session is revoked, or its challenge spent, and `false` is answered.
|
|
17
|
+
*
|
|
18
|
+
* A password writer writes, then spends the challenges waiting; a sign-in
|
|
19
|
+
* issues, then reads here. Whichever order they interleave in, one of the two
|
|
20
|
+
* sees the other. A hash that changed with the same password — a concurrent
|
|
21
|
+
* sign-in rehashing it — is not a change: the password is compared again.
|
|
22
|
+
*/
|
|
23
|
+
export declare function heldByPassword(context: Context, type: ResolvedType, verified: UserRecord, password: string, result: SignInResult<AnyUser>, where: string): Promise<boolean>;
|
|
24
|
+
//# sourceMappingURL=password-written.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"password-written.d.ts","sourceRoot":"","sources":["../../src/auth/password-written.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EACN,KAAK,OAAO,EACZ,KAAK,OAAO,EAGZ,MAAM,WAAW,CAAC;AAEnB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE5C;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACtC,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,EAAE,GACR,OAAO,CAAC,IAAI,CAAC,CAMf;AAED;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CACnC,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,YAAY,EAClB,QAAQ,EAAE,UAAU,EACpB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,YAAY,CAAC,OAAO,CAAC,EAC7B,KAAK,EAAE,MAAM,GACX,OAAO,CAAC,OAAO,CAAC,CAkBlB"}
|
|
@@ -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;
|
|
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;AAmC9D;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAC3B,MAAM,EAAE,OAAO,EACf,KAAK,EAAE,MAAM,GACX,iBAAiB,CAmCnB"}
|
|
@@ -35,7 +35,11 @@
|
|
|
35
35
|
* 5. **Every method is atomic on its own.** Nothing composes into a
|
|
36
36
|
* transaction, and the core never opens one. An adapter may open one
|
|
37
37
|
* *inside* a method — a normalised SQL schema writes several rows per
|
|
38
|
-
* user — but the port exposes none.
|
|
38
|
+
* user — but the port exposes none. And **a read sees every write that
|
|
39
|
+
* completed before it**: never a secondary or a read replica. A sign-in
|
|
40
|
+
* that re-reads the user after answering, and a new code spending the
|
|
41
|
+
* ones issued before it, both count on it — the one promise here no suite
|
|
42
|
+
* can check.
|
|
39
43
|
* 6. **Schema management is not on this interface.** An adapter exposes its own
|
|
40
44
|
* `sync()`; the core never calls it.
|
|
41
45
|
*
|
|
@@ -441,6 +445,24 @@ export interface TokenStore {
|
|
|
441
445
|
* core's decision, after the call; spending the token is `consumeToken`.
|
|
442
446
|
*/
|
|
443
447
|
countAttempt(tokenHash: string, kind: TokenKind): Promise<TokenRecord | null>;
|
|
448
|
+
/**
|
|
449
|
+
* Spends every **unspent** token of one user and one `kind` at `at` —
|
|
450
|
+
* but the one whose hash is `except`, when given — and answers how many
|
|
451
|
+
* it spent. What issuing a sign-in code calls, sparing the code it just
|
|
452
|
+
* issued, so only the last code sent works; and what writing a password
|
|
453
|
+
* calls, so no second-factor challenge opened with the old one survives.
|
|
454
|
+
*
|
|
455
|
+
* - A spent token keeps its `spentAt`: it never changes once set.
|
|
456
|
+
* - A token of another `kind`, or of another user, is not touched.
|
|
457
|
+
* - An expired token is spent all the same, or not counted by a store that
|
|
458
|
+
* already dropped it.
|
|
459
|
+
* - `0` for a user with none: an absence, not a failure.
|
|
460
|
+
*
|
|
461
|
+
* Each token is spent by a conditional write, as `consumeToken` spends
|
|
462
|
+
* one: a token `consumeToken` spends at the same moment is counted by
|
|
463
|
+
* exactly one of the two calls.
|
|
464
|
+
*/
|
|
465
|
+
spendUserTokens(userId: Id, kind: TokenKind, at: Date, except?: string): Promise<number>;
|
|
444
466
|
/**
|
|
445
467
|
* Deletes every token of one user, spent or not, and answers how many.
|
|
446
468
|
* What deleting a user calls: a token holds the e-mail it was sent to, which
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/auth/port/types.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/auth/port/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,12 +2,6 @@ import type { ResolvedType } from '../config';
|
|
|
2
2
|
import { type AnyUser, type Context } from '../context';
|
|
3
3
|
import type { UserRecord } from '../port/types';
|
|
4
4
|
import type { SecondFactorRequired, SignedIn } from '../types';
|
|
5
|
-
/**
|
|
6
|
-
* How many codes one challenge takes. A six-digit code has a million values:
|
|
7
|
-
* five tries is a one-in-200,000 chance per password guessed right, and the
|
|
8
|
-
* attempts are counted by the store in one write, never read then written.
|
|
9
|
-
*/
|
|
10
|
-
export declare const CHALLENGE_ATTEMPTS = 5;
|
|
11
5
|
/**
|
|
12
6
|
* The challenge `signIn` answers instead of a session, and the code that
|
|
13
7
|
* redeems it. **Only the challenge's hash is stored**, like a session
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"challenge.d.ts","sourceRoot":"","sources":["../../../src/auth/second-factor/challenge.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,KAAK,OAAO,EAAE,KAAK,OAAO,EAAc,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"challenge.d.ts","sourceRoot":"","sources":["../../../src/auth/second-factor/challenge.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,KAAK,OAAO,EAAE,KAAK,OAAO,EAAc,MAAM,YAAY,CAAC;AASpE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEhD,OAAO,KAAK,EAAE,oBAAoB,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAG/D;;;;GAIG;AACH,wBAAgB,cAAc,CAC7B,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,YAAY,EAClB,EAAE,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM;kBAQvB,UAAU,SACX,MAAM,GACX,OAAO,CAAC,oBAAoB,CAAC;uBAqBP,MAAM,QAAQ,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;EAkE3E"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { ResolvedConfig, ResolvedType } from '../config';
|
|
1
|
+
import type { ResolvedConfig } from '../config';
|
|
3
2
|
import type { Context } from '../context';
|
|
4
3
|
import type { SecondFactorRecord, UserRecord } from '../port/types';
|
|
5
4
|
/**
|
|
@@ -24,7 +23,5 @@ export declare function requireSettings(context: Context, where: string, why: st
|
|
|
24
23
|
* or was already used.
|
|
25
24
|
*/
|
|
26
25
|
export declare function acceptCode(configured: Settings, record: UserRecord, factor: SecondFactorRecord, code: string, now: Date, where: string): SecondFactorRecord | null;
|
|
27
|
-
/** A code that does not match. `attemptsLeft` only when a challenge counted it. */
|
|
28
|
-
export declare function codeInvalid(type: ResolvedType, where: string, userId: string, attemptsLeft?: number): TokenError;
|
|
29
26
|
export {};
|
|
30
27
|
//# sourceMappingURL=factor.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"factor.d.ts","sourceRoot":"","sources":["../../../src/auth/second-factor/factor.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"factor.d.ts","sourceRoot":"","sources":["../../../src/auth/second-factor/factor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,KAAK,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAIpE;;;;;;;GAOG;AAEH,MAAM,MAAM,QAAQ,GAAG,WAAW,CAAC,cAAc,CAAC,cAAc,CAAC,CAAC,CAAC;AAEnE,KAAK,YAAY,GAAG,kBAAkB,GAAG;IAAE,QAAQ,CAAC,WAAW,EAAE,IAAI,CAAA;CAAE,CAAC;AAExE,eAAO,MAAM,QAAQ,GACpB,QAAQ,kBAAkB,GAAG,IAAI,KAC/B,MAAM,IAAI,YAA8D,CAAC;AAE5E,yEAAyE;AACzE,wBAAgB,eAAe,CAC9B,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,GACT,QAAQ,CAQV;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CACzB,UAAU,EAAE,QAAQ,EACpB,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,kBAAkB,EAC1B,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,IAAI,EACT,KAAK,EAAE,MAAM,GACX,kBAAkB,GAAG,IAAI,CAiB3B"}
|