@nxgt/janus 0.14.0 → 0.16.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 +109 -20
- package/dist/auth/config/janus-config.d.ts +2 -0
- package/dist/auth/config/janus-config.d.ts.map +1 -1
- package/dist/auth/config/resolved-config.d.ts +1 -0
- package/dist/auth/config/resolved-config.d.ts.map +1 -1
- package/dist/auth/email-sign-in.d.ts +45 -0
- package/dist/auth/email-sign-in.d.ts.map +1 -0
- package/dist/auth/events.d.ts +10 -4
- package/dist/auth/events.d.ts.map +1 -1
- package/dist/auth/first-proof.d.ts +37 -0
- package/dist/auth/first-proof.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/magic-link.d.ts +22 -0
- package/dist/auth/magic-link.d.ts.map +1 -0
- package/dist/auth/port/types/tokens.d.ts +6 -3
- package/dist/auth/port/types/tokens.d.ts.map +1 -1
- package/dist/auth/sign-in-code.d.ts +1 -3
- package/dist/auth/sign-in-code.d.ts.map +1 -1
- package/dist/auth/types/email-flows.d.ts +7 -4
- package/dist/auth/types/email-flows.d.ts.map +1 -1
- package/dist/auth/types/index.d.ts +1 -0
- package/dist/auth/types/index.d.ts.map +1 -1
- package/dist/auth/types/janus.d.ts +8 -1
- package/dist/auth/types/janus.d.ts.map +1 -1
- package/dist/auth/types/magic-link.d.ts +50 -0
- package/dist/auth/types/magic-link.d.ts.map +1 -0
- package/dist/auth/users/any-type-api.d.ts +2 -2
- package/dist/auth/users/any-type-api.d.ts.map +1 -1
- package/dist/auth/users/type-api.d.ts +2 -1
- package/dist/auth/users/type-api.d.ts.map +1 -1
- package/dist/conformance/cases/tokens/kinds.d.ts +4 -1
- package/dist/conformance/cases/tokens/kinds.d.ts.map +1 -1
- package/dist/conformance/index.js +23 -1
- package/dist/conformance/index.js.map +3 -3
- package/dist/index.js +173 -88
- package/dist/index.js.map +12 -9
- package/docs/README.md +2 -1
- package/docs/guide/adapters.md +11 -7
- package/docs/guide/email-flows.md +14 -8
- package/docs/guide/errors.md +2 -2
- package/docs/guide/events.md +5 -5
- package/docs/guide/magic-link.md +442 -0
- package/docs/guide/passwords.md +5 -4
- package/docs/guide/second-factor.md +28 -2
- package/docs/guide/sessions.md +4 -1
- package/docs/guide/sign-in-code.md +20 -7
- package/docs/guide/users.md +10 -6
- package/docs/guide/vocabulary.md +6 -5
- package/docs/roadmap.md +18 -12
- package/docs/troubleshooting.md +178 -12
- 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, codes sent by e-mail, a TOTP second factor, sessions, one-time tokens. **Permissions** answers *may
|
|
49
|
+
logins and passwords, codes and links 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.
|
|
@@ -324,6 +324,8 @@ await auth.resetPassword.request(email); // … | null
|
|
|
324
324
|
await auth.resetPassword.confirm(token, newPassword);
|
|
325
325
|
await auth.signInCode.request(email); // { code, challenge, email, expiresAt, user } | null
|
|
326
326
|
await auth.signInCode.confirm(challenge, code); // { status: 'signedIn', user, session, token }
|
|
327
|
+
await auth.magicLink.request(email); // { token, email, expiresAt, user } | null
|
|
328
|
+
await auth.magicLink.confirm(token); // { status: 'signedIn', user, session, token }
|
|
327
329
|
await auth.stepUp.request(user); // { via: 'email', code, challenge, email, expiresAt, user }
|
|
328
330
|
await auth.stepUp.confirm(request, challenge, code); // the request's session, authenticatedAt: now
|
|
329
331
|
|
|
@@ -366,8 +368,8 @@ else reaches the store and is asynchronous.
|
|
|
366
368
|
compile error on `login`, and the message lists the fields you could have
|
|
367
369
|
meant. It is normalised with `'lowercaseTrim'` unless you say otherwise.
|
|
368
370
|
- **`email`** defaults to the field named `email`. A type without one has no
|
|
369
|
-
`verifyEmail`, no `resetPassword` and no `
|
|
370
|
-
its type, not failing at run time. Changing the e-mail sets `emailVerified`
|
|
371
|
+
`verifyEmail`, no `resetPassword`, no `signInCode` and no `magicLink` — they
|
|
372
|
+
are absent from its type, not failing at run time. Changing the e-mail sets `emailVerified`
|
|
371
373
|
back to `false`.
|
|
372
374
|
- **Per type**: `create`, `find` (or `null`), `get` (or `NOT_FOUND`), `list`,
|
|
373
375
|
`update(user, patch)` — merged over the stored fields, then validated whole —
|
|
@@ -502,7 +504,11 @@ await sessions.reauthenticateSession(session.id, new Date()); // { …, authenti
|
|
|
502
504
|
```
|
|
503
505
|
|
|
504
506
|
A token of kind `stepUp` is that confirmation's challenge: kept apart from
|
|
505
|
-
`signInCode`, so neither is ever redeemed as the other.
|
|
507
|
+
`signInCode`, so neither is ever redeemed as the other. A token of kind
|
|
508
|
+
`magicLink`, new in 0.15, is a sign-in link's, kept apart from `signInCode`
|
|
509
|
+
the same way: a sign-in code's challenge is held by whoever asked for it,
|
|
510
|
+
and must never sign anyone in as a link. A store that lists the kinds — a
|
|
511
|
+
`CHECK`, a validator's enum — adds `magicLink`.
|
|
506
512
|
|
|
507
513
|
An adapter written against `@nxgt/janus` 0.3 does not compile against this
|
|
508
514
|
port until it implements `countAttempt`, nor one written against 0.6 until it
|
|
@@ -583,6 +589,10 @@ every user type with a password.
|
|
|
583
589
|
and key the recovery codes' hashes. The first seals and every key opens, so
|
|
584
590
|
keys rotate: put the new one first, keep the old one until no secret is
|
|
585
591
|
sealed and no recovery code hashed with it.
|
|
592
|
+
- **The first proof of an e-mail by a sign-in code or link removes the
|
|
593
|
+
factor too**, active or waiting, with its recovery codes: whoever enrolled
|
|
594
|
+
it had not proved the address. Send `verifyEmail` before offering
|
|
595
|
+
`enroll`, and an owner's factor is never dropped this way.
|
|
586
596
|
- **Asking for a password or a code before `enroll`, `disable` or
|
|
587
597
|
`regenerateRecoveryCodes` is your policy**, not the library's — a recent
|
|
588
598
|
`session.authenticatedAt` is one rule. `regenerateRecoveryCodes` asks for
|
|
@@ -626,14 +636,62 @@ A six-digit code sent to the user's e-mail signs them in, with no password.
|
|
|
626
636
|
attempts: a wrong code is `CODE_INVALID` with `attemptsLeft`, and the fifth
|
|
627
637
|
spends it. An e-mail changed since is `TOKEN_STALE`; a user deactivated
|
|
628
638
|
since is `USER_INACTIVE`.
|
|
629
|
-
- **An
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
639
|
+
- **An e-mail proved for the first time drops the password and the second
|
|
640
|
+
factor, and signs out every session** before the new one opens, as
|
|
641
|
+
`resetPassword.confirm` does for the password: whoever registered the
|
|
642
|
+
address without holding its inbox keeps nothing, and the owner is asked
|
|
643
|
+
for no factor they never set. `user.passwordChanged` follows
|
|
644
|
+
`user.emailVerified` when a password was dropped, then
|
|
645
|
+
`user.secondFactorDisabled` when an active factor was. An e-mail already
|
|
646
|
+
verified changes nothing.
|
|
647
|
+
- **An active second factor is still asked for** on an e-mail already
|
|
648
|
+
verified: with `secondFactor` configured, `confirm` answers `SignInResult`
|
|
649
|
+
on a type with a password, and a user whose factor is active gets a
|
|
650
|
+
challenge for `secondFactor.confirm` — switch on `status`, as after
|
|
651
|
+
`signIn`.
|
|
633
652
|
|
|
634
653
|
[The sign-in code guide](docs/guide/sign-in-code.md) has the request that
|
|
635
654
|
tells nobody who exists, the challenge in a cookie, every error, and a test.
|
|
636
655
|
|
|
656
|
+
### Sign-in links — `magicLink`
|
|
657
|
+
|
|
658
|
+
```ts
|
|
659
|
+
const issued = await auth.magicLink.request(email); // null for nobody, or an inactive user
|
|
660
|
+
if (issued !== null) {
|
|
661
|
+
const link = `https://app.example/sign-in/link?token=${issued.token}`; // a page of yours
|
|
662
|
+
await sendMail(issued.email, `Sign in: ${link}`); // the token in the e-mail, and nowhere else
|
|
663
|
+
}
|
|
664
|
+
// answer the same page either way: nothing reaches the visitor, so there is nothing to forge
|
|
665
|
+
|
|
666
|
+
// the page's button POSTs the token back — never confirm on the link's GET
|
|
667
|
+
const signedIn = await auth.magicLink.confirm(token); // { status: 'signedIn', user, session, token }
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
A link sent to the user's e-mail signs them in: a sign-in code with nothing
|
|
671
|
+
to type.
|
|
672
|
+
|
|
673
|
+
- **On every user type with an e-mail**, with a password or without one, as
|
|
674
|
+
`signInCode`. A type with no e-mail has no `magicLink` at all.
|
|
675
|
+
- **`request(email)` answers `null`** when nobody of this type holds that
|
|
676
|
+
e-mail, or the user is inactive — never say which. It answers a `token` of
|
|
677
|
+
32 random bytes, base64url, for a link; only its hash is stored. One link
|
|
678
|
+
is live per user: a new `request` spends the ones before. A link and a
|
|
679
|
+
code are separate: asking for one leaves the other live.
|
|
680
|
+
- **`confirm(token)`** spends the token in one conditional write — of two
|
|
681
|
+
confirmations at once, one signs in — marks the e-mail verified and opens
|
|
682
|
+
the session. A link lives `'15m'` (`tokens.magicLink`); there are no
|
|
683
|
+
attempts, since there is nothing to guess. `TOKEN_STALE`, `USER_INACTIVE`,
|
|
684
|
+
an active second factor and **an e-mail proved for the first time — the
|
|
685
|
+
password and the second factor dropped, every session signed out** — are
|
|
686
|
+
handled as for a code.
|
|
687
|
+
- **Not ended by a password write, nor limited by the sign-in throttle**:
|
|
688
|
+
the password proves nothing a link does, and the throttle counts
|
|
689
|
+
passwords. Rate-limit `request` per address and per client.
|
|
690
|
+
|
|
691
|
+
[The sign-in link guide](docs/guide/magic-link.md) has the page that
|
|
692
|
+
confirms from a `POST` so mail scanners spend nothing, every error, routes
|
|
693
|
+
and a test.
|
|
694
|
+
|
|
637
695
|
### Step-up — `stepUp` and `assertFresh`
|
|
638
696
|
|
|
639
697
|
```ts
|
|
@@ -699,12 +757,12 @@ await auth.signUp({ email, password }); // the listener has the event before thi
|
|
|
699
757
|
| `type` | Sent by |
|
|
700
758
|
| --- | --- |
|
|
701
759
|
| `user.created` | `create`, `signUp` |
|
|
702
|
-
| `user.emailVerified` | `verifyEmail.confirm`; `resetPassword.confirm` and `signInCode.confirm`, whose link or code proves the e-mail too — never for an e-mail already verified |
|
|
760
|
+
| `user.emailVerified` | `verifyEmail.confirm`; `resetPassword.confirm`, `magicLink.confirm` and `signInCode.confirm`, whose link or code proves the e-mail too — never for an e-mail already verified |
|
|
703
761
|
| `user.passwordReset` | `resetPassword.confirm` |
|
|
704
|
-
| `user.passwordChanged` | `changePassword`, `setPassword` — never a reset, which is `user.passwordReset` alone |
|
|
762
|
+
| `user.passwordChanged` | `changePassword`, `setPassword`; `signInCode.confirm` and `magicLink.confirm` when their first proof of the e-mail dropped a password — never a reset, which is `user.passwordReset` alone |
|
|
705
763
|
| `user.emailChanged` | `update`, when it changed the e-mail — carrying `formerEmail`, the address before (`null` for none) |
|
|
706
764
|
| `user.secondFactorEnabled` | `secondFactor.activate`, once the factor is active — not `enroll`, which leaves it waiting |
|
|
707
|
-
| `user.secondFactorDisabled` | `secondFactor.disable`, when it removed an active factor — never for a user who had none, or one still waiting |
|
|
765
|
+
| `user.secondFactorDisabled` | `secondFactor.disable`, when it removed an active factor; `signInCode.confirm` and `magicLink.confirm` when their first proof of the e-mail removed one, after `user.emailVerified` and any `user.passwordChanged` — never for a user who had none, or one still waiting |
|
|
708
766
|
| `user.recoveryCodesRegenerated` | `secondFactor.regenerateRecoveryCodes` — not `activate`, whose codes come with `user.secondFactorEnabled` |
|
|
709
767
|
| `user.recoveryCodeUsed` | `secondFactor.recover`, once the recovery code is spent: a sign-in without the user's phone |
|
|
710
768
|
| `user.deleted` | `delete`, once — a replay that deletes nobody sends nothing |
|
|
@@ -878,7 +936,7 @@ describeJanusStores({
|
|
|
878
936
|
});
|
|
879
937
|
```
|
|
880
938
|
|
|
881
|
-
There are
|
|
939
|
+
There are 55 cases. They cover:
|
|
882
940
|
- round-trip, byte for byte — including every edge character the core lets
|
|
883
941
|
through (control characters, U+FFFF, a surrogate pair);
|
|
884
942
|
- uniqueness, as a constraint: of twenty concurrent inserts of one login,
|
|
@@ -891,8 +949,9 @@ There are 54 cases. They cover:
|
|
|
891
949
|
and never brings back a revoked session, even one revoked at the same
|
|
892
950
|
moment;
|
|
893
951
|
- one-time tokens: a token of every kind the port names is stored, counted
|
|
894
|
-
and spent — a `CHECK` or an enum that forgot `stepUp`
|
|
895
|
-
|
|
952
|
+
and spent — a `CHECK` or an enum that forgot `stepUp` or `magicLink` fails
|
|
953
|
+
here — and neither a step-up nor a sign-in link is ever counted, spent nor
|
|
954
|
+
answered as a sign-in code; of twenty
|
|
896
955
|
concurrent redemptions, exactly one succeeds; of twenty concurrent
|
|
897
956
|
`countAttempt` calls, each answers a distinct count, and none is counted
|
|
898
957
|
once a racing redemption spent the token;
|
|
@@ -938,9 +997,9 @@ runner-less layer.
|
|
|
938
997
|
## Traps
|
|
939
998
|
|
|
940
999
|
**Once `secondFactor` is configured, switch on `signIn`'s `status`** — and
|
|
941
|
-
on `signInCode.confirm`'s. A user whose factor is active gets
|
|
1000
|
+
on `signInCode.confirm`'s and `magicLink.confirm`'s. A user whose factor is active gets
|
|
942
1001
|
`{ status: 'secondFactor', challenge, expiresAt, userId }`, with no `token`
|
|
943
|
-
and no `session`: an e-mailed code proves the e-mail, not the factor.
|
|
1002
|
+
and no `session`: an e-mailed code or link proves the e-mail, not the factor.
|
|
944
1003
|
`userId` is for your logs and rate limits — answer the visitor the
|
|
945
1004
|
challenge alone.
|
|
946
1005
|
`if (result.status === 'secondFactor') …` before anything reads them.
|
|
@@ -973,6 +1032,35 @@ address**: at most one code is live per user — a new `request` spends the one
|
|
|
973
1032
|
before — so without a limit anyone who knows an address can fill its inbox,
|
|
974
1033
|
or cancel its owner's code before they type it.
|
|
975
1034
|
|
|
1035
|
+
**Confirm a sign-in link from a `POST`, never from its `GET`.** Mail
|
|
1036
|
+
scanners open every link in an e-mail before the user does: a route that
|
|
1037
|
+
calls `magicLink.confirm` on the link's `GET` spends it for the scanner, and
|
|
1038
|
+
the user's click answers `TOKEN_SPENT`. Link to a page that spends nothing,
|
|
1039
|
+
whose button posts the token — and echo into that page only a token of the
|
|
1040
|
+
token's shape. Refuse that `POST` from another site — check its `Origin`,
|
|
1041
|
+
or `Sec-Fetch-Site: same-origin` — or any page can sign a visitor into an
|
|
1042
|
+
account whose link it holds. Send that page with
|
|
1043
|
+
`Referrer-Policy: strict-origin`, not `no-referrer`, which makes its form's
|
|
1044
|
+
`Origin` `null` and the check refuse its own button. Whoever opens the link signs in, on the device
|
|
1045
|
+
that opened it: where the sign-in must complete in the browser that asked,
|
|
1046
|
+
send a code.
|
|
1047
|
+
|
|
1048
|
+
**The first sign-in by link or code drops the password and the second
|
|
1049
|
+
factor.** `signUp` does not wait for the address to be proved, so anyone can
|
|
1050
|
+
register somebody else's e-mail with a password of theirs, and enrol a second
|
|
1051
|
+
factor on their own phone. When `signInCode.confirm` or `magicLink.confirm`
|
|
1052
|
+
proves an e-mail never verified, it drops the password and the second factor
|
|
1053
|
+
— active or waiting, with its recovery codes — and signs out every session
|
|
1054
|
+
before opening its own — so a user who signed
|
|
1055
|
+
up, never verified, and then signs in by code has no password afterwards,
|
|
1056
|
+
and `signIn` answers `CREDENTIALS_INVALID` (`reason: 'noPassword'`). Offer
|
|
1057
|
+
`setPassword` after such a sign-in, or send `verifyEmail` at sign-up. An
|
|
1058
|
+
e-mail changed by `update` is unverified again, and its first proof by link
|
|
1059
|
+
or code drops both too. **So does a user who enrolled a factor before
|
|
1060
|
+
proving their own e-mail**: they sign in with no challenge, and enrol again
|
|
1061
|
+
— offer `secondFactor.enroll` after such a sign-in, or send `verifyEmail`
|
|
1062
|
+
before letting anyone enrol. An e-mail already verified keeps both.
|
|
1063
|
+
|
|
976
1064
|
**Every `janus()` that signs users in needs the same `secondFactor`.** An
|
|
977
1065
|
instance without keys never signs in a user whose factor is active: `signIn`
|
|
978
1066
|
throws a `TypeError` rather than open a session on the password alone. Build
|
|
@@ -1065,7 +1153,7 @@ seconds rather than that the password is wrong. `CredentialRefusal` gained
|
|
|
1065
1153
|
test that tries more than ten wrong passwords at one login over a
|
|
1066
1154
|
`fixedClock` is throttled too: advance the clock past `retryAfter`, or wire
|
|
1067
1155
|
`signIn: { throttle: false }`. **Somebody who knows a login can keep its
|
|
1068
|
-
password sign-in shut**, ten tries a window; a sign-in code, when you wire them, still opens it.
|
|
1156
|
+
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.
|
|
1069
1157
|
On PostgreSQL, delete lapsed tokens on a schedule: every login tried adds a
|
|
1070
1158
|
row per window. One password tried against
|
|
1071
1159
|
many logins is not counted: rate-limit `signIn` per client address yourself
|
|
@@ -1151,7 +1239,7 @@ that sends one, since it is awaited: queue the event and return.
|
|
|
1151
1239
|
|
|
1152
1240
|
## Type safety, counted
|
|
1153
1241
|
|
|
1154
|
-
**One hundred and
|
|
1242
|
+
**One hundred and forty-one plausible mistakes, one hundred and forty-one refused at compile time — and
|
|
1155
1243
|
two gaps, named.**
|
|
1156
1244
|
|
|
1157
1245
|
The lists are typechecked and never run, with one `@ts-expect-error` per
|
|
@@ -1159,9 +1247,10 @@ mistake beside the shapes that must keep compiling. One is a single file:
|
|
|
1159
1247
|
`test/types/refusals.ts` (fifteen, on the shared vocabulary). The other three
|
|
1160
1248
|
are folders with one file per behaviour: `test/types/port/` (twenty-four, on
|
|
1161
1249
|
the identity stores' port, from the point of view of the person implementing
|
|
1162
|
-
it), `test/types/auth/` (fifty, on `janus()`, from the point of view of
|
|
1250
|
+
it), `test/types/auth/` (fifty-four, on `janus()`, from the point of view of
|
|
1163
1251
|
the application — twelve of them on the second factor, three on sign-in codes,
|
|
1164
|
-
five on user events, four on step-ups, six on the sign-in throttle
|
|
1252
|
+
five on user events, four on step-ups, six on the sign-in throttle, four on
|
|
1253
|
+
sign-in links) and `test/types/permissions/` (forty-eight, on the
|
|
1165
1254
|
permission model and the questions asked of it). The rule comes from
|
|
1166
1255
|
`nxgt-data`, and so does the reason to distrust the claim without the files:
|
|
1167
1256
|
when it was last measured on `@nxgt/mongo`, *seven of twelve plausible
|
|
@@ -48,6 +48,8 @@ interface SharedConfig {
|
|
|
48
48
|
readonly resetPassword?: Duration;
|
|
49
49
|
/** How long an e-mailed sign-in code waits. `'10m'` when absent. */
|
|
50
50
|
readonly signInCode?: Duration;
|
|
51
|
+
/** How long an e-mailed sign-in link waits. `'15m'` when absent. */
|
|
52
|
+
readonly magicLink?: Duration;
|
|
51
53
|
/** How long a step-up's challenge waits, e-mailed or not. `'10m'` when absent. */
|
|
52
54
|
readonly stepUp?: Duration;
|
|
53
55
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"janus-config.d.ts","sourceRoot":"","sources":["../../../src/auth/config/janus-config.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"janus-config.d.ts","sourceRoot":"","sources":["../../../src/auth/config/janus-config.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolved-config.d.ts","sourceRoot":"","sources":["../../../src/auth/config/resolved-config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,oFAAoF;AACpF,MAAM,WAAW,YAAY;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE;QAClB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;QAC9C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;KAC3B,GAAG,IAAI,CAAC;IACT,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CACrC;AAED,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAClD,QAAQ,CAAC,UAAU,EAAE;QACpB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;QAC7B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;QAC/B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;QAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;KACxB,CAAC;IACF,gDAAgD;IAChD,QAAQ,CAAC,cAAc,EAAE;QACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;KAC1B,GAAG,IAAI,CAAC;IACT,QAAQ,CAAC,YAAY,EAAE;QACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;KAChC,GAAG,IAAI,CAAC;IACT,QAAQ,CAAC,MAAM,EAAE;QAChB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,QAAQ,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;QAC7C,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;KACzB,CAAC;CACF"}
|
|
1
|
+
{"version":3,"file":"resolved-config.d.ts","sourceRoot":"","sources":["../../../src/auth/config/resolved-config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,oFAAoF;AACpF,MAAM,WAAW,YAAY;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE;QAClB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;QAC9C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;KAC3B,GAAG,IAAI,CAAC;IACT,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CACrC;AAED,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAClD,QAAQ,CAAC,UAAU,EAAE;QACpB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;QAC7B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;QAC/B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;QAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;QAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;KACxB,CAAC;IACF,gDAAgD;IAChD,QAAQ,CAAC,cAAc,EAAE;QACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;KAC1B,GAAG,IAAI,CAAC;IACT,QAAQ,CAAC,YAAY,EAAE;QACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;KAChC,GAAG,IAAI,CAAC;IACT,QAAQ,CAAC,MAAM,EAAE;QAChB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,QAAQ,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;QAC7C,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;KACzB,CAAC;CACF"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the two sign-ins by e-mail share — a code typed back
|
|
3
|
+
* (`sign-in-code.ts`) and a link followed (`magic-link.ts`): who may be sent
|
|
4
|
+
* one, keeping only the last one live, and how a redeemed one ends — the
|
|
5
|
+
* e-mail proved, then the same session or second-factor challenge a
|
|
6
|
+
* password opens.
|
|
7
|
+
*/
|
|
8
|
+
import type { ResolvedType } from './config';
|
|
9
|
+
import { type AnyUser, type Context } from './context';
|
|
10
|
+
import type { TokenKind, TokenRecord, UserRecord } from './port/types';
|
|
11
|
+
import type { SignInResult } from './types';
|
|
12
|
+
/** What a sign-in answers once the user proved who they are: `secondFactor.finish`. */
|
|
13
|
+
export type Finish = (record: UserRecord, where: string) => Promise<SignInResult<AnyUser>>;
|
|
14
|
+
/**
|
|
15
|
+
* The user of this type holding `email`, when they may be sent a sign-in:
|
|
16
|
+
* `null` for nobody, and for an inactive user — the same answer, so the
|
|
17
|
+
* caller cannot tell them apart either.
|
|
18
|
+
*/
|
|
19
|
+
export declare function signInHolder(context: Context, type: ResolvedType, email: string): Promise<UserRecord | null>;
|
|
20
|
+
/**
|
|
21
|
+
* Spends every other unspent token of this kind the user holds, sparing the
|
|
22
|
+
* one just issued. Issued first, spent after, so requests that race leave
|
|
23
|
+
* at most one live — maybe none, and the visitor asks again — never one each.
|
|
24
|
+
*/
|
|
25
|
+
export declare function keepOnlyLatest(context: Context, userId: string, kind: TokenKind, secret: string): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Ends a sign-in by e-mail whose token is spent: refuses an e-mail changed
|
|
28
|
+
* since it was sent and an inactive user, proves the e-mail — it reached the
|
|
29
|
+
* inbox — and finishes as a password would.
|
|
30
|
+
*
|
|
31
|
+
* **An e-mail proved for the first time drops the password and the second
|
|
32
|
+
* factor, and revokes every session** before the new one opens
|
|
33
|
+
* (`./first-proof`): whoever signed up with the address without holding its
|
|
34
|
+
* inbox keeps nothing, and the owner is asked for no factor they never set.
|
|
35
|
+
* An e-mail already proved changes nothing. The proof is written under the
|
|
36
|
+
* version read, so an address changed since is not the one proved: a
|
|
37
|
+
* `VERSION_CONFLICT` then, with the token spent.
|
|
38
|
+
*/
|
|
39
|
+
export declare function finishEmailSignIn(context: Context, type: ResolvedType, finish: Finish, signIn: {
|
|
40
|
+
readonly user: UserRecord;
|
|
41
|
+
readonly token: TokenRecord;
|
|
42
|
+
readonly where: string;
|
|
43
|
+
readonly noun: 'token' | 'code';
|
|
44
|
+
}): Promise<SignInResult<AnyUser>>;
|
|
45
|
+
//# sourceMappingURL=email-sign-in.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"email-sign-in.d.ts","sourceRoot":"","sources":["../../src/auth/email-sign-in.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EAAE,KAAK,OAAO,EAAE,KAAK,OAAO,EAAiB,MAAM,WAAW,CAAC;AAGtE,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAEvE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE5C,uFAAuF;AACvF,MAAM,MAAM,MAAM,GAAG,CACpB,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,MAAM,KACT,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC;AAEpC;;;;GAIG;AACH,wBAAsB,YAAY,CACjC,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,YAAY,EAClB,KAAK,EAAE,MAAM,GACX,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAG5B;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CACnC,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,SAAS,EACf,MAAM,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC,CAOf;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,iBAAiB,CACtC,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,YAAY,EAClB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE;IACP,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC;CAChC,GACC,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAYhC"}
|
package/dist/auth/events.d.ts
CHANGED
|
@@ -5,10 +5,13 @@ import type { Context } from './context';
|
|
|
5
5
|
*
|
|
6
6
|
* - `user.created` — by `create` or `signUp`;
|
|
7
7
|
* - `user.emailVerified` — by `verifyEmail.confirm`, or by the link of
|
|
8
|
-
* `resetPassword.confirm` or
|
|
9
|
-
* the e-mail too; never for an e-mail
|
|
8
|
+
* `resetPassword.confirm` or `magicLink.confirm`, or the code of
|
|
9
|
+
* `signInCode.confirm`, which prove the e-mail too; never for an e-mail
|
|
10
|
+
* already verified;
|
|
10
11
|
* - `user.passwordReset` — by `resetPassword.confirm`;
|
|
11
|
-
* - `user.passwordChanged` — by `changePassword` and `setPassword
|
|
12
|
+
* - `user.passwordChanged` — by `changePassword` and `setPassword`, and by
|
|
13
|
+
* `signInCode.confirm` and `magicLink.confirm` when their first proof of
|
|
14
|
+
* the e-mail dropped a password, after `user.emailVerified`; never by a
|
|
12
15
|
* reset, which is `user.passwordReset` alone;
|
|
13
16
|
* - `user.emailChanged` — by `update`, when it changed the e-mail — compared
|
|
14
17
|
* normalised, the same test that makes the new one unverified. The one
|
|
@@ -16,7 +19,10 @@ import type { Context } from './context';
|
|
|
16
19
|
* - `user.secondFactorEnabled` — by `secondFactor.activate`, once the factor
|
|
17
20
|
* is active: not by `enroll`, which leaves it waiting for its first code;
|
|
18
21
|
* - `user.secondFactorDisabled` — by `secondFactor.disable`, when it removed
|
|
19
|
-
* an active factor
|
|
22
|
+
* an active factor, and by `signInCode.confirm` and `magicLink.confirm`
|
|
23
|
+
* when their first proof of the e-mail removed one, after
|
|
24
|
+
* `user.emailVerified` and any `user.passwordChanged`; never for a user who
|
|
25
|
+
* had none, or one still waiting;
|
|
20
26
|
* - `user.recoveryCodesRegenerated` — by `secondFactor.regenerateRecoveryCodes`:
|
|
21
27
|
* the codes the user held stopped working. Not by `activate`, whose codes
|
|
22
28
|
* come with `user.secondFactorEnabled`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/auth/events.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,EAAU,MAAM,WAAW,CAAC;AAC5C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/auth/events.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,EAAU,MAAM,WAAW,CAAC;AAC5C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,MAAM,aAAa,GACtB,cAAc,GACd,oBAAoB,GACpB,oBAAoB,GACpB,sBAAsB,GACtB,mBAAmB,GACnB,0BAA0B,GAC1B,2BAA2B,GAC3B,+BAA+B,GAC/B,uBAAuB,GACvB,cAAc,CAAC;AAElB;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACzB,kEAAkE;IAClE,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC;IAChB,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B;;;OAGG;IACH,QAAQ,CAAC,UAAU,EAAE,IAAI,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;IACpB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACrC;AAED,wEAAwE;AACxE,MAAM,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;AAEzD;;;;;;GAMG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE3E,oFAAoF;AACpF,wBAAgB,aAAa,CAC5B,MAAM,EAAE,OAAO,EACf,KAAK,EAAE,MAAM,GACX,iBAAiB,GAAG,IAAI,CAQ1B;AAED;;;;;;GAMG;AACH,wBAAsB,IAAI,CACzB,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,aAAa,EACnB,IAAI,EAAE;IAAE,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAChD,UAAU,EAAE,IAAI,EAChB,MAAM,GAAE,WAAgB,GACtB,OAAO,CAAC,IAAI,CAAC,CAqBf"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The first proof of an e-mail by a sign-in code or a sign-in link: the
|
|
3
|
+
* address reached an inbox no one had proved before, so whatever was set on
|
|
4
|
+
* the account before that proof is not the inbox holder's to keep.
|
|
5
|
+
*
|
|
6
|
+
* Anybody can sign up with somebody else's e-mail and a password of their
|
|
7
|
+
* own — and enrol a second factor on their own phone. Without this, the real
|
|
8
|
+
* holder signing in by e-mail later would verify the address and leave that
|
|
9
|
+
* password, that factor and their sessions working: locked out by a factor
|
|
10
|
+
* they never set, or signed in beside someone who still holds the account.
|
|
11
|
+
* So the write that proves the address also drops the password and the
|
|
12
|
+
* second factor with its recovery codes, and every session is revoked before
|
|
13
|
+
* the new one opens: the same ending as `resetPassword.confirm`, which
|
|
14
|
+
* proves the address too, plus the factor it keeps.
|
|
15
|
+
*/
|
|
16
|
+
import type { ResolvedType } from './config';
|
|
17
|
+
import { type Context } from './context';
|
|
18
|
+
import type { UserRecord } from './port/types';
|
|
19
|
+
/**
|
|
20
|
+
* Proves `user`'s e-mail, never proved before, under the version read — an
|
|
21
|
+
* address changed since is not the one proved: a `VERSION_CONFLICT` then.
|
|
22
|
+
* Drops the password and the second factor — active or waiting, and its
|
|
23
|
+
* recovery codes with it — in the same write, and revokes every session and
|
|
24
|
+
* spends what the password opened — reset links and second-factor
|
|
25
|
+
* challenges — **both before the write and after it**.
|
|
26
|
+
*
|
|
27
|
+
* Before, so an outage there leaves the e-mail unproved and the owner's next
|
|
28
|
+
* code or link runs this again; after, so a sign-in landing in between is
|
|
29
|
+
* ended too. An outage after the write is the one that leaves a session live.
|
|
30
|
+
*
|
|
31
|
+
* `user.emailVerified` is sent, then `user.passwordChanged` when a password
|
|
32
|
+
* was dropped and `user.secondFactorDisabled` when an active factor was —
|
|
33
|
+
* the rule of `secondFactor.disable` — from a `finally`, so an outage ending
|
|
34
|
+
* the sessions still reports the write, which a retry would not make again.
|
|
35
|
+
*/
|
|
36
|
+
export declare function proveFirstEmail(context: Context, type: ResolvedType, user: UserRecord, where: string): Promise<UserRecord>;
|
|
37
|
+
//# sourceMappingURL=first-proof.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"first-proof.d.ts","sourceRoot":"","sources":["../../src/auth/first-proof.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EAAE,KAAK,OAAO,EAAa,MAAM,WAAW,CAAC;AAGpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG/C;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,eAAe,CACpC,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,YAAY,EAClB,IAAI,EAAE,UAAU,EAChB,KAAK,EAAE,MAAM,GACX,OAAO,CAAC,UAAU,CAAC,CAwCrB"}
|
package/dist/auth/index.d.ts
CHANGED
|
@@ -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, Checked, EmailOf, HeaderRecord, IssuedCode, IssuedToken, Janus, LoginOf, PasswordApi, RecoveredSignIn, RecoveryCodesIssued, RequestLike, RequiredStringKeys, ResetPasswordApi, SecondFactorApi, SecondFactorEnrolment, SecondFactorRequired, Session, SharedApi, SignedIn, SignInCodeApi, SignInResult, StepUpApi, StepUpByApp, StepUpByEmail, TypeApi, TypesOf, User, UserBase, UserOf, UserRef, UserTypeApi, VerifyEmailApi, WriteOptions, } from './types';
|
|
11
|
+
export type { Authenticated, Checked, EmailOf, HeaderRecord, IssuedCode, IssuedToken, Janus, LoginOf, MagicLinkApi, PasswordApi, RecoveredSignIn, RecoveryCodesIssued, RequestLike, RequiredStringKeys, ResetPasswordApi, SecondFactorApi, SecondFactorEnrolment, SecondFactorRequired, Session, SharedApi, SignedIn, SignInCodeApi, SignInResult, StepUpApi, StepUpByApp, StepUpByEmail, TypeApi, TypesOf, User, UserBase, UserOf, UserRef, UserTypeApi, VerifyEmailApi, WriteOptions, } from './types';
|
|
12
12
|
//# 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,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,OAAO,EACP,OAAO,EACP,YAAY,EACZ,UAAU,EACV,WAAW,EACX,KAAK,EACL,OAAO,EACP,WAAW,EACX,eAAe,EACf,mBAAmB,EACnB,WAAW,EACX,kBAAkB,EAClB,gBAAgB,EAChB,eAAe,EACf,qBAAqB,EACrB,oBAAoB,EACpB,OAAO,EACP,SAAS,EACT,QAAQ,EACR,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,UAAU,EACV,WAAW,EACX,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,OAAO,EACP,OAAO,EACP,YAAY,EACZ,UAAU,EACV,WAAW,EACX,KAAK,EACL,OAAO,EACP,YAAY,EACZ,WAAW,EACX,eAAe,EACf,mBAAmB,EACnB,WAAW,EACX,kBAAkB,EAClB,gBAAgB,EAChB,eAAe,EACf,qBAAqB,EACrB,oBAAoB,EACpB,OAAO,EACP,SAAS,EACT,QAAQ,EACR,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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { At } from './at';
|
|
2
|
+
import type { ResolvedType } from './config';
|
|
3
|
+
import { type AnyUser, type Context } from './context';
|
|
4
|
+
import { type Finish } from './email-sign-in';
|
|
5
|
+
import type { MagicLinkApi, SignInResult } from './types';
|
|
6
|
+
/**
|
|
7
|
+
* Signing in with a link sent to the user's e-mail: no password, and the
|
|
8
|
+
* link proves the address — a sign-in code with nothing to type.
|
|
9
|
+
*
|
|
10
|
+
* The token is 32 random bytes, so there is nothing to guess and no attempt
|
|
11
|
+
* to count: redeeming it is one conditional write, and of two redemptions
|
|
12
|
+
* at once exactly one signs in. **At most one link is live per user**, as
|
|
13
|
+
* for a sign-in code: `request` spends every other once it issued its own.
|
|
14
|
+
* A token of its own kind, `magicLink`, so a sign-in code's challenge — which
|
|
15
|
+
* the visitor holds — is never a link, nor a link a challenge.
|
|
16
|
+
*
|
|
17
|
+
* Whoever opens the link signs in, in the browser that opened it: the
|
|
18
|
+
* application confirms it from a `POST`, never from the `GET` of the link,
|
|
19
|
+
* which a mail scanner follows too.
|
|
20
|
+
*/
|
|
21
|
+
export declare function magicLinkFlows(context: Context, type: ResolvedType, at: At, finish: Finish): MagicLinkApi<AnyUser, SignInResult<AnyUser>>['magicLink'];
|
|
22
|
+
//# sourceMappingURL=magic-link.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"magic-link.d.ts","sourceRoot":"","sources":["../../src/auth/magic-link.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,MAAM,CAAC;AAC/B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EAAE,KAAK,OAAO,EAAE,KAAK,OAAO,EAAU,MAAM,WAAW,CAAC;AAE/D,OAAO,EACN,KAAK,MAAM,EAIX,MAAM,iBAAiB,CAAC;AAEzB,OAAO,KAAK,EAAe,YAAY,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvE;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAC7B,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,YAAY,EAClB,EAAE,EAAE,EAAE,EACN,MAAM,EAAE,MAAM,GACZ,YAAY,CAAC,OAAO,EAAE,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,CAgB3D"}
|
|
@@ -16,12 +16,15 @@ import type { Id } from '../../../ids/id';
|
|
|
16
16
|
* attempts at `regenerateRecoveryCodes`, a token of this kind whose secret
|
|
17
17
|
* nobody is given, never redeemed, only counted.
|
|
18
18
|
* - `signInCode`: a code sent by e-mail to sign in without a password.
|
|
19
|
+
* - `magicLink`: a link sent by e-mail to sign in without a password. A kind
|
|
20
|
+
* of its own, so a sign-in code's challenge — which the visitor holds —
|
|
21
|
+
* never signs anyone in as a link, nor a link's token as a challenge.
|
|
19
22
|
* - `stepUp`: a signed-in user's confirmation of a sensitive action — a code
|
|
20
23
|
* sent by e-mail, or, with an active second factor, a code from their app.
|
|
21
24
|
* A kind of its own, so a sign-in code never confirms an action nor an
|
|
22
25
|
* action's code signs anyone in.
|
|
23
26
|
*/
|
|
24
|
-
export type TokenKind = 'verifyEmail' | 'resetPassword' | 'secondFactor' | 'signInCode' | 'stepUp';
|
|
27
|
+
export type TokenKind = 'verifyEmail' | 'resetPassword' | 'secondFactor' | 'signInCode' | 'magicLink' | 'stepUp';
|
|
25
28
|
/** A one-time token, as a store holds it: its hash, never its secret. */
|
|
26
29
|
export interface TokenRecord {
|
|
27
30
|
/** `sha256` of the token's secret, hex. Unique across the store. */
|
|
@@ -104,8 +107,8 @@ export interface TokenStore {
|
|
|
104
107
|
* Spends every **unspent** token of one user and one `kind` at `at` —
|
|
105
108
|
* but the one whose hash is `except`, when given — and answers how many
|
|
106
109
|
* it spent. What issuing a sign-in code calls, sparing the code it just
|
|
107
|
-
* issued, so only the last code sent works — and so for a
|
|
108
|
-
* a step-up; and what writing a password calls, so no reset link and no
|
|
110
|
+
* issued, so only the last code sent works — and so for a sign-in link,
|
|
111
|
+
* a reset link and a step-up; and what writing a password calls, so no reset link and no
|
|
109
112
|
* second-factor challenge opened with the old one survives.
|
|
110
113
|
*
|
|
111
114
|
* - A spent token keeps its `spentAt`: it never changes once set.
|
|
@@ -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
|
|
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;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,MAAM,SAAS,GAClB,aAAa,GACb,eAAe,GACf,cAAc,GACd,YAAY,GACZ,WAAW,GACX,QAAQ,CAAC;AAEZ,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;;;OAGG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;;;;;;OAMG;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,7 +1,7 @@
|
|
|
1
1
|
import type { At } from './at';
|
|
2
2
|
import type { ResolvedType } from './config';
|
|
3
3
|
import { type AnyUser, type Context } from './context';
|
|
4
|
-
import type
|
|
4
|
+
import { type Finish } from './email-sign-in';
|
|
5
5
|
import type { SignInCodeApi, SignInResult } from './types';
|
|
6
6
|
/**
|
|
7
7
|
* Signing in with a code sent to the user's e-mail: no password, and the
|
|
@@ -18,6 +18,4 @@ import type { SignInCodeApi, SignInResult } from './types';
|
|
|
18
18
|
* tokens alone do not reveal it.
|
|
19
19
|
*/
|
|
20
20
|
export declare function signInCodeFlows(context: Context, type: ResolvedType, at: At, finish: Finish): SignInCodeApi<AnyUser, SignInResult<AnyUser>>['signInCode'];
|
|
21
|
-
type Finish = (record: UserRecord, where: string) => Promise<SignInResult<AnyUser>>;
|
|
22
|
-
export {};
|
|
23
21
|
//# sourceMappingURL=sign-in-code.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sign-in-code.d.ts","sourceRoot":"","sources":["../../src/auth/sign-in-code.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sign-in-code.d.ts","sourceRoot":"","sources":["../../src/auth/sign-in-code.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,MAAM,CAAC;AAC/B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EAAE,KAAK,OAAO,EAAE,KAAK,OAAO,EAAsB,MAAM,WAAW,CAAC;AAC3E,OAAO,EACN,KAAK,MAAM,EAIX,MAAM,iBAAiB,CAAC;AAWzB,OAAO,KAAK,EAAc,aAAa,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvE;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAC9B,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,YAAY,EAClB,EAAE,EAAE,EAAE,EACN,MAAM,EAAE,MAAM,GACZ,aAAa,CAAC,OAAO,EAAE,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC,CAU7D"}
|
|
@@ -44,14 +44,17 @@ export interface SignInCodeApi<U, Answer = SignedIn<U>> {
|
|
|
44
44
|
* time — send the e-mail off the request's path.
|
|
45
45
|
*
|
|
46
46
|
* **Rate-limit it, per e-mail and per client.** Every call issues a new
|
|
47
|
-
* challenge with five attempts of its own, and the earlier ones
|
|
48
|
-
*
|
|
49
|
-
* one account.
|
|
47
|
+
* challenge with five attempts of its own, and spends the earlier ones:
|
|
48
|
+
* only the last code sent works, and a new `request` is five guesses
|
|
49
|
+
* more — the five attempts bound one challenge, not one account.
|
|
50
50
|
*/
|
|
51
51
|
request(email: string): Promise<IssuedCode<U> | null>;
|
|
52
52
|
/**
|
|
53
53
|
* Checks the code against its challenge, marks the e-mail verified —
|
|
54
|
-
* the code reached the inbox — and signs the user in.
|
|
54
|
+
* the code reached the inbox — and signs the user in. **An e-mail
|
|
55
|
+
* proved for the first time drops the password and the second factor,
|
|
56
|
+
* and signs out every session** before the new one opens, as for a
|
|
57
|
+
* link. An e-mail already verified changes nothing.
|
|
55
58
|
*
|
|
56
59
|
* A challenge takes **five attempts**: a code that does not match is
|
|
57
60
|
* `CODE_INVALID` with `attemptsLeft`, and the fifth spends it. An
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"email-flows.d.ts","sourceRoot":"","sources":["../../../src/auth/types/email-flows.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAgB,MAAM,WAAW,CAAC;AACxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEtC,0EAA0E;AAC1E,MAAM,WAAW,WAAW;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,iCAAiC;IACjC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;CACzB;AAED,oFAAoF;AACpF,MAAM,WAAW,UAAU,CAAC,CAAC;IAC5B,mFAAmF;IACnF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,uCAAuC;IACvC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;CACjB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,aAAa,CAAC,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC;IACrD,QAAQ,CAAC,UAAU,EAAE;QACpB;;;;;;;;;;WAUG;QACH,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;QACtD
|
|
1
|
+
{"version":3,"file":"email-flows.d.ts","sourceRoot":"","sources":["../../../src/auth/types/email-flows.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAgB,MAAM,WAAW,CAAC;AACxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEtC,0EAA0E;AAC1E,MAAM,WAAW,WAAW;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,iCAAiC;IACjC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;CACzB;AAED,oFAAoF;AACpF,MAAM,WAAW,UAAU,CAAC,CAAC;IAC5B,mFAAmF;IACnF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,uCAAuC;IACvC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;CACjB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,aAAa,CAAC,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC;IACrD,QAAQ,CAAC,UAAU,EAAE;QACpB;;;;;;;;;;WAUG;QACH,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;QACtD;;;;;;;;;;;;WAYG;QACH,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;KAC1D,CAAC;CACF;AAED,uDAAuD;AACvD,MAAM,WAAW,cAAc,CAAC,CAAC;IAChC,QAAQ,CAAC,WAAW,EAAE;QACrB,yEAAyE;QACzE,IAAI,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;QAC1C;;;WAGG;QACH,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;KACnC,CAAC;CACF;AAED,sEAAsE;AACtE,MAAM,WAAW,gBAAgB,CAAC,CAAC;IAClC,QAAQ,CAAC,aAAa,EAAE;QACvB;;;;;WAKG;QACH,OAAO,CACN,KAAK,EAAE,MAAM,GACX,OAAO,CAAC,CAAC,WAAW,GAAG;YAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAA;SAAE,CAAC,GAAG,IAAI,CAAC,CAAC;QACxD;;;;;;WAMG;QACH,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;KACrD,CAAC;CACF"}
|
|
@@ -11,6 +11,7 @@ export type { Checked } from './checks';
|
|
|
11
11
|
export type { IssuedCode, IssuedToken, ResetPasswordApi, SignInCodeApi, VerifyEmailApi, } from './email-flows';
|
|
12
12
|
export type { EmailOf, LoginOf, RequiredStringKeys, TypesOf, UserOf, } from './inference';
|
|
13
13
|
export type { Janus, SharedApi, TypeApi } from './janus';
|
|
14
|
+
export type { MagicLinkApi } from './magic-link';
|
|
14
15
|
export type { PasswordApi } from './password';
|
|
15
16
|
export type { RecoveredSignIn, RecoveryCodesIssued, SecondFactorApi, SecondFactorEnrolment, } from './second-factor';
|
|
16
17
|
export type { Authenticated, HeaderRecord, RequestLike, Session, } from './session';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/auth/types/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,YAAY,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AACxC,YAAY,EACX,UAAU,EACV,WAAW,EACX,gBAAgB,EAChB,aAAa,EACb,cAAc,GACd,MAAM,eAAe,CAAC;AACvB,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,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,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,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,OAAO,EAAE,MAAM,UAAU,CAAC;AACxC,YAAY,EACX,UAAU,EACV,WAAW,EACX,gBAAgB,EAChB,aAAa,EACb,cAAc,GACd,MAAM,eAAe,CAAC;AACvB,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,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,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"}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import type { JanusConfig, MultiTypeConfig, SingleTypeConfig } from '../config';
|
|
8
8
|
import type { ResetPasswordApi, SignInCodeApi, VerifyEmailApi } from './email-flows';
|
|
9
9
|
import type { EmailOf, FieldsInput, LoginOf, SingleAsType, UserOf, UserOfType } from './inference';
|
|
10
|
+
import type { MagicLinkApi } from './magic-link';
|
|
10
11
|
import type { PasswordApi } from './password';
|
|
11
12
|
import type { SecondFactorApi } from './second-factor';
|
|
12
13
|
import type { Authenticated, RequestLike, Session } from './session';
|
|
@@ -14,11 +15,17 @@ import type { SignedIn, SignInResult } from './sign-in';
|
|
|
14
15
|
import type { StepUpApi, StepUpByApp, StepUpByEmail } from './step-up';
|
|
15
16
|
import type { UserRef } from './user';
|
|
16
17
|
import type { UserTypeApi } from './user-type';
|
|
18
|
+
/**
|
|
19
|
+
* What a sign-in by e-mail — a code or a link — answers: a session, or a
|
|
20
|
+
* challenge when the type has a password in an instance given a
|
|
21
|
+
* `secondFactor`, since the e-mail proves the address and not the factor.
|
|
22
|
+
*/
|
|
23
|
+
type EmailSignInAnswer<Name extends string, Def, TwoFactor extends boolean> = TwoFactor extends true ? [LoginOf<Def>] extends [never] ? SignedIn<UserOfType<Name, Def>> : SignInResult<UserOfType<Name, Def>> : SignedIn<UserOfType<Name, Def>>;
|
|
17
24
|
/**
|
|
18
25
|
* The whole surface of one user type, with only the flows its configuration
|
|
19
26
|
* allows. `TwoFactor` is whether `janus()` was given a `secondFactor`.
|
|
20
27
|
*/
|
|
21
|
-
export type TypeApi<Name extends string, Def, TwoFactor extends boolean = false> = UserTypeApi<UserOfType<Name, Def>, FieldsInput<Def>> & ([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 : VerifyEmailApi<UserOfType<Name, Def>> & SignInCodeApi<UserOfType<Name, Def>,
|
|
28
|
+
export type TypeApi<Name extends string, Def, TwoFactor extends boolean = false> = UserTypeApi<UserOfType<Name, Def>, FieldsInput<Def>> & ([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 : VerifyEmailApi<UserOfType<Name, Def>> & SignInCodeApi<UserOfType<Name, Def>, EmailSignInAnswer<Name, Def, TwoFactor>> & MagicLinkApi<UserOfType<Name, Def>, EmailSignInAnswer<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>>);
|
|
22
29
|
/** What every configuration answers, whatever its user types. */
|
|
23
30
|
export interface SharedApi<U extends {
|
|
24
31
|
readonly type: string;
|