@nxgt/janus 0.19.0 → 0.21.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 +164 -10
- package/dist/auth/email-flows/reset-password.d.ts.map +1 -1
- package/dist/auth/events.d.ts +3 -3
- package/dist/auth/index.d.ts +1 -1
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/magic-link.d.ts +3 -0
- package/dist/auth/magic-link.d.ts.map +1 -1
- package/dist/auth/one-time/issue.d.ts +6 -0
- package/dist/auth/one-time/issue.d.ts.map +1 -1
- package/dist/auth/prepared.d.ts +21 -0
- package/dist/auth/prepared.d.ts.map +1 -0
- package/dist/auth/proven-sign-in.d.ts +22 -0
- package/dist/auth/proven-sign-in.d.ts.map +1 -0
- package/dist/auth/sign-in-code.d.ts +4 -0
- package/dist/auth/sign-in-code.d.ts.map +1 -1
- package/dist/auth/types/email-flows.d.ts +18 -0
- package/dist/auth/types/email-flows.d.ts.map +1 -1
- package/dist/auth/types/find-by-email.d.ts +31 -0
- package/dist/auth/types/find-by-email.d.ts.map +1 -0
- package/dist/auth/types/index.d.ts +3 -0
- package/dist/auth/types/index.d.ts.map +1 -1
- package/dist/auth/types/janus.d.ts +8 -5
- package/dist/auth/types/janus.d.ts.map +1 -1
- package/dist/auth/types/magic-link.d.ts +11 -0
- package/dist/auth/types/magic-link.d.ts.map +1 -1
- package/dist/auth/types/prepared.d.ts +41 -0
- package/dist/auth/types/prepared.d.ts.map +1 -0
- package/dist/auth/types/proven-sign-in.d.ts +43 -0
- package/dist/auth/types/proven-sign-in.d.ts.map +1 -0
- package/dist/auth/types/sign-in.d.ts +2 -2
- 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/find-by-email.d.ts +11 -0
- package/dist/auth/users/find-by-email.d.ts.map +1 -0
- package/dist/auth/users/type-api.d.ts +2 -2
- package/dist/auth/users/type-api.d.ts.map +1 -1
- package/dist/index.js +105 -17
- package/dist/index.js.map +12 -9
- package/docs/README.md +2 -2
- package/docs/guide/devices.md +4 -0
- package/docs/guide/email-flows.md +105 -0
- package/docs/guide/errors.md +2 -2
- package/docs/guide/events.md +2 -2
- package/docs/guide/magic-link.md +49 -2
- package/docs/guide/passwords.md +5 -2
- package/docs/guide/second-factor.md +1 -1
- package/docs/guide/sessions.md +105 -1
- package/docs/guide/sign-in-code.md +54 -2
- package/docs/guide/users.md +70 -0
- package/docs/roadmap.md +24 -33
- package/docs/troubleshooting.md +100 -6
- package/package.json +1 -1
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What `prepare` answers — `magicLink.prepare`, `signInCode.prepare`,
|
|
3
|
+
* `resetPassword.prepare`: a request for an e-mail, counted, not yet looked
|
|
4
|
+
* up.
|
|
5
|
+
*
|
|
6
|
+
* Part of what `janus()` hands back; `./index` gathers it.
|
|
7
|
+
*/
|
|
8
|
+
import type { IssuedCode } from './email-flows';
|
|
9
|
+
/**
|
|
10
|
+
* A request counted by the mail throttle and not yet looked up: `send()`
|
|
11
|
+
* does the rest of what `request` does — looks the address up, issues, and
|
|
12
|
+
* spends the earlier tokens — and answers what `request` answers, `null`
|
|
13
|
+
* for an address nobody (active) holds.
|
|
14
|
+
*
|
|
15
|
+
* **`send()` runs once.** A second call is a `TypeError`, and so is a call
|
|
16
|
+
* after a first one failed: `prepare` again, which counts again.
|
|
17
|
+
*/
|
|
18
|
+
export interface PreparedRequest<Issued> {
|
|
19
|
+
/**
|
|
20
|
+
* Looks the address up and issues, without counting again — the count
|
|
21
|
+
* was `prepare`'s. Send it off the visitor's request: its time tells
|
|
22
|
+
* whether the address has an account.
|
|
23
|
+
*/
|
|
24
|
+
send(): Promise<Issued | null>;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* A sign-in code's prepared request: the `challenge` is minted before
|
|
28
|
+
* anybody is looked up, so it goes to the visitor now, whoever holds the
|
|
29
|
+
* address — and replaces the decoy an unknown address needs. For an address
|
|
30
|
+
* nobody holds, `send()` answers `null` and the challenge is
|
|
31
|
+
* `TOKEN_UNKNOWN`; otherwise it is the `challenge` of what `send()` issues.
|
|
32
|
+
*/
|
|
33
|
+
export interface PreparedCode<U> extends PreparedRequest<IssuedCode<U>> {
|
|
34
|
+
/**
|
|
35
|
+
* The secret the code will be checked against. **Keep it with the
|
|
36
|
+
* visitor** — a short-lived cookie, or the code form's body — never in
|
|
37
|
+
* the e-mail, a URL or a log.
|
|
38
|
+
*/
|
|
39
|
+
readonly challenge: string;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=prepared.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prepared.d.ts","sourceRoot":"","sources":["../../../src/auth/types/prepared.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEhD;;;;;;;;GAQG;AACH,MAAM,WAAW,eAAe,CAAC,MAAM;IACtC;;;;OAIG;IACH,IAAI,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC/B;AAED;;;;;;GAMG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC,CAAE,SAAQ,eAAe,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IACtE;;;;OAIG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC3B"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What every user type answers besides: signing in a user the application
|
|
3
|
+
* proved by its own means, with no credential janus checks.
|
|
4
|
+
*
|
|
5
|
+
* Part of what `janus()` hands back; `./index` gathers it.
|
|
6
|
+
*/
|
|
7
|
+
import type { SignedIn, SignInOptions } from './sign-in';
|
|
8
|
+
import type { UserRef } from './user';
|
|
9
|
+
/**
|
|
10
|
+
* Signing in a user **the application has already proved** — by an
|
|
11
|
+
* invitation token it issued and the person followed, or a channel of its
|
|
12
|
+
* own it verified — without a password, a code or a link.
|
|
13
|
+
*
|
|
14
|
+
* `Answer` is `SignInResult` when the type may have a second factor: the
|
|
15
|
+
* application's proof is a first factor, and an active factor is still asked
|
|
16
|
+
* for.
|
|
17
|
+
*/
|
|
18
|
+
export interface ProvenSignInApi<U, Answer = SignedIn<U>> {
|
|
19
|
+
/**
|
|
20
|
+
* Signs in the user of this type with this id, and answers what every
|
|
21
|
+
* other sign-in answers: a session, or a second-factor challenge when
|
|
22
|
+
* their factor is active — switch on `status`.
|
|
23
|
+
*
|
|
24
|
+
* **Privileged: janus checks no credential here, the application carries
|
|
25
|
+
* the proof.** Call it only after your server verified one — an
|
|
26
|
+
* invitation token it issued, a channel of its own — and never with an
|
|
27
|
+
* id a request supplied: whoever can choose the id signs in as anybody.
|
|
28
|
+
*
|
|
29
|
+
* Sends no e-mail, issues no token, counts nothing in the mail or the
|
|
30
|
+
* sign-in throttle, and **spends nothing**: links and codes already sent
|
|
31
|
+
* still work. Proves no e-mail either: `emailVerified` stays as it was.
|
|
32
|
+
*
|
|
33
|
+
* An inactive user is `USER_INACTIVE`, as for every sign-in. A malformed
|
|
34
|
+
* id, an unknown one or one of another type is `NOT_FOUND`, as for `get`:
|
|
35
|
+
* a sign-in that found nobody is never an answer to read past.
|
|
36
|
+
*
|
|
37
|
+
* Given a `device`, answers `newDevice` and `deviceToken`, and sends
|
|
38
|
+
* `user.newDeviceSignedIn` for a device new to the user. A challenge
|
|
39
|
+
* carries no device: give it again to `secondFactor.confirm`.
|
|
40
|
+
*/
|
|
41
|
+
signInProven(user: UserRef, options?: SignInOptions): Promise<Answer>;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=proven-sign-in.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proven-sign-in.d.ts","sourceRoot":"","sources":["../../../src/auth/types/proven-sign-in.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AACzD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEtC;;;;;;;;GAQG;AACH,MAAM,WAAW,eAAe,CAAC,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC;IACvD;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,YAAY,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACtE"}
|
|
@@ -33,8 +33,8 @@ export interface SignedIn<U> {
|
|
|
33
33
|
}
|
|
34
34
|
/**
|
|
35
35
|
* What a sign-in takes besides its credentials: `signUp`, `signIn`,
|
|
36
|
-
* `secondFactor.confirm`, `secondFactor.recover`, `signInCode.confirm
|
|
37
|
-
* `magicLink.confirm`.
|
|
36
|
+
* `secondFactor.confirm`, `secondFactor.recover`, `signInCode.confirm`,
|
|
37
|
+
* `magicLink.confirm` and `signInProven`.
|
|
38
38
|
*/
|
|
39
39
|
export interface SignInOptions {
|
|
40
40
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AnyUser } from '../context';
|
|
2
|
-
import type { MagicLinkApi, PasswordApi, ResetPasswordApi, SecondFactorApi, SignInCodeApi, SignInResult, StepUpApi, StepUpByApp, StepUpByEmail, UserTypeApi, VerifyEmailApi } from '../types';
|
|
2
|
+
import type { FindByEmailApi, MagicLinkApi, PasswordApi, ProvenSignInApi, ResetPasswordApi, SecondFactorApi, SignInCodeApi, SignInResult, StepUpApi, StepUpByApp, StepUpByEmail, UserTypeApi, VerifyEmailApi } from '../types';
|
|
3
3
|
import type { Input } from './flow-types';
|
|
4
4
|
/** Everything one user type answers. Which flows it has is decided by its types; all are built. */
|
|
5
|
-
export type AnyTypeApi = UserTypeApi<AnyUser, Input> & PasswordApi<AnyUser, Input, string, SignInResult<AnyUser>> & SecondFactorApi<AnyUser> & SignInCodeApi<AnyUser, SignInResult<AnyUser>> & MagicLinkApi<AnyUser, SignInResult<AnyUser>> & StepUpApi<AnyUser, StepUpByEmail<AnyUser> | StepUpByApp<AnyUser>> & VerifyEmailApi<AnyUser> & ResetPasswordApi<AnyUser>;
|
|
5
|
+
export type AnyTypeApi = UserTypeApi<AnyUser, Input> & PasswordApi<AnyUser, Input, string, SignInResult<AnyUser>> & SecondFactorApi<AnyUser> & ProvenSignInApi<AnyUser, SignInResult<AnyUser>> & SignInCodeApi<AnyUser, SignInResult<AnyUser>> & MagicLinkApi<AnyUser, SignInResult<AnyUser>> & StepUpApi<AnyUser, StepUpByEmail<AnyUser> | StepUpByApp<AnyUser>> & VerifyEmailApi<AnyUser> & FindByEmailApi<AnyUser> & ResetPasswordApi<AnyUser>;
|
|
6
6
|
//# sourceMappingURL=any-type-api.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"any-type-api.d.ts","sourceRoot":"","sources":["../../../src/auth/users/any-type-api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,KAAK,EACX,YAAY,EACZ,WAAW,EACX,gBAAgB,EAChB,eAAe,EACf,aAAa,EACb,YAAY,EACZ,SAAS,EACT,WAAW,EACX,aAAa,EACb,WAAW,EACX,cAAc,EACd,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAE1C,mGAAmG;AACnG,MAAM,MAAM,UAAU,GAAG,WAAW,CAAC,OAAO,EAAE,KAAK,CAAC,GACnD,WAAW,CAAC,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,CAAC,OAAO,CAAC,CAAC,GAC1D,eAAe,CAAC,OAAO,CAAC,GACxB,aAAa,CAAC,OAAO,EAAE,YAAY,CAAC,OAAO,CAAC,CAAC,GAC7C,YAAY,CAAC,OAAO,EAAE,YAAY,CAAC,OAAO,CAAC,CAAC,GAC5C,SAAS,CAAC,OAAO,EAAE,aAAa,CAAC,OAAO,CAAC,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC,GACjE,cAAc,CAAC,OAAO,CAAC,GACvB,gBAAgB,CAAC,OAAO,CAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"any-type-api.d.ts","sourceRoot":"","sources":["../../../src/auth/users/any-type-api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,KAAK,EACX,cAAc,EACd,YAAY,EACZ,WAAW,EACX,eAAe,EACf,gBAAgB,EAChB,eAAe,EACf,aAAa,EACb,YAAY,EACZ,SAAS,EACT,WAAW,EACX,aAAa,EACb,WAAW,EACX,cAAc,EACd,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAE1C,mGAAmG;AACnG,MAAM,MAAM,UAAU,GAAG,WAAW,CAAC,OAAO,EAAE,KAAK,CAAC,GACnD,WAAW,CAAC,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,CAAC,OAAO,CAAC,CAAC,GAC1D,eAAe,CAAC,OAAO,CAAC,GACxB,eAAe,CAAC,OAAO,EAAE,YAAY,CAAC,OAAO,CAAC,CAAC,GAC/C,aAAa,CAAC,OAAO,EAAE,YAAY,CAAC,OAAO,CAAC,CAAC,GAC7C,YAAY,CAAC,OAAO,EAAE,YAAY,CAAC,OAAO,CAAC,CAAC,GAC5C,SAAS,CAAC,OAAO,EAAE,aAAa,CAAC,OAAO,CAAC,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC,GACjE,cAAc,CAAC,OAAO,CAAC,GACvB,cAAc,CAAC,OAAO,CAAC,GACvB,gBAAgB,CAAC,OAAO,CAAC,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ResolvedType } from '../config';
|
|
2
|
+
import { type AnyUser, type Context } from '../context';
|
|
3
|
+
import type { FindByEmailApi } from '../types';
|
|
4
|
+
/**
|
|
5
|
+
* `findByEmail` for one user type: the holder the e-mail flows would find —
|
|
6
|
+
* the same normalisation, the same comparison with the stored address — with
|
|
7
|
+
* nothing counted, issued or written. A value that is no address is
|
|
8
|
+
* nobody's, as a malformed id is for `find`, and the store is not asked.
|
|
9
|
+
*/
|
|
10
|
+
export declare function findByEmailFlows(context: Context, type: ResolvedType): FindByEmailApi<AnyUser>;
|
|
11
|
+
//# sourceMappingURL=find-by-email.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"find-by-email.d.ts","sourceRoot":"","sources":["../../../src/auth/users/find-by-email.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,KAAK,OAAO,EAAE,KAAK,OAAO,EAAyB,MAAM,YAAY,CAAC;AAC/E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAE/C;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC/B,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,YAAY,GAChB,cAAc,CAAC,OAAO,CAAC,CASzB"}
|
|
@@ -3,8 +3,8 @@ import type { Context } from '../context';
|
|
|
3
3
|
import type { AnyTypeApi } from './any-type-api';
|
|
4
4
|
/**
|
|
5
5
|
* Everything one user type answers, gathered from its flows: its records,
|
|
6
|
-
* its password, its
|
|
7
|
-
* and its e-mails.
|
|
6
|
+
* its password, its sign-in on the application's proof, its second factor,
|
|
7
|
+
* its sign-in code and link, its step-up, its e-mails and the lookup by e-mail.
|
|
8
8
|
*/
|
|
9
9
|
export declare function typeApi(context: Context, type: ResolvedType): AnyTypeApi;
|
|
10
10
|
//# sourceMappingURL=type-api.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"type-api.d.ts","sourceRoot":"","sources":["../../../src/auth/users/type-api.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"type-api.d.ts","sourceRoot":"","sources":["../../../src/auth/users/type-api.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAO1C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAKjD;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,GAAG,UAAU,CAmBxE"}
|
package/dist/index.js
CHANGED
|
@@ -1153,7 +1153,7 @@ async function issueCode(context, token) {
|
|
|
1153
1153
|
}
|
|
1154
1154
|
async function insertOneTime(context, token, code) {
|
|
1155
1155
|
const now = context.clock.now();
|
|
1156
|
-
const secret = mintSecret();
|
|
1156
|
+
const secret = token.secret ?? mintSecret();
|
|
1157
1157
|
const expiresAt = new Date(now.getTime() + token.ttlMs);
|
|
1158
1158
|
await context.store.tokens.insertToken({
|
|
1159
1159
|
tokenHash: hashSecret(secret),
|
|
@@ -1295,6 +1295,18 @@ async function heldByPassword(context, type, verified, password, result, where)
|
|
|
1295
1295
|
return false;
|
|
1296
1296
|
}
|
|
1297
1297
|
|
|
1298
|
+
// src/auth/prepared.ts
|
|
1299
|
+
function sendOnce(where, issue) {
|
|
1300
|
+
let sent = false;
|
|
1301
|
+
return async () => {
|
|
1302
|
+
if (sent) {
|
|
1303
|
+
throw new TypeError(`${where}(…).send: already called — a prepared request sends once; call ${where} again for another`);
|
|
1304
|
+
}
|
|
1305
|
+
sent = true;
|
|
1306
|
+
return issue();
|
|
1307
|
+
};
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1298
1310
|
// src/auth/email-flows/email-token.ts
|
|
1299
1311
|
async function redeemEmailToken(context, type, kind, secret, where) {
|
|
1300
1312
|
const token = await spendOneTime(context, secret, kind, where, "token");
|
|
@@ -1327,20 +1339,32 @@ function resetPasswordFlows(context, type, at) {
|
|
|
1327
1339
|
return {
|
|
1328
1340
|
async request(email) {
|
|
1329
1341
|
const where = at("resetPassword.request");
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
const issued = await issueEmailToken(context, type, "resetPassword", record, where);
|
|
1336
|
-
await context.store.tokens.spendUserTokens(record.id, "resetPassword", context.clock.now(), hashSecret(issued.token));
|
|
1337
|
-
return { ...issued, user: toUser(record) };
|
|
1342
|
+
return (await prepareReset(context, type, String(email), where)).send();
|
|
1343
|
+
},
|
|
1344
|
+
async prepare(email) {
|
|
1345
|
+
const where = at("resetPassword.prepare");
|
|
1346
|
+
return prepareReset(context, type, String(email), where);
|
|
1338
1347
|
},
|
|
1339
1348
|
async confirm(secret, password) {
|
|
1340
1349
|
return confirmReset(context, type, secret, password, at("resetPassword.confirm"));
|
|
1341
1350
|
}
|
|
1342
1351
|
};
|
|
1343
1352
|
}
|
|
1353
|
+
async function prepareReset(context, type, email, where) {
|
|
1354
|
+
passwordRule(type, where);
|
|
1355
|
+
await countMailByAddress(context, type, "resetPassword", email, where);
|
|
1356
|
+
return Object.freeze({
|
|
1357
|
+
send: sendOnce(where, () => issueReset(context, type, email, where))
|
|
1358
|
+
});
|
|
1359
|
+
}
|
|
1360
|
+
async function issueReset(context, type, email, where) {
|
|
1361
|
+
const record = await holderOfEmail(context, type, email);
|
|
1362
|
+
if (record === null)
|
|
1363
|
+
return null;
|
|
1364
|
+
const issued = await issueEmailToken(context, type, "resetPassword", record, where);
|
|
1365
|
+
await context.store.tokens.spendUserTokens(record.id, "resetPassword", context.clock.now(), hashSecret(issued.token));
|
|
1366
|
+
return { ...issued, user: toUser(record) };
|
|
1367
|
+
}
|
|
1344
1368
|
async function confirmReset(context, type, secret, password, where) {
|
|
1345
1369
|
passwordRule(type, where);
|
|
1346
1370
|
checkPassword(type, password, where);
|
|
@@ -1561,7 +1585,12 @@ async function finishEmailSignIn(context, type, finish, signIn) {
|
|
|
1561
1585
|
function magicLinkFlows(context, type, at, finish) {
|
|
1562
1586
|
return {
|
|
1563
1587
|
async request(email) {
|
|
1564
|
-
|
|
1588
|
+
const where = at("magicLink.request");
|
|
1589
|
+
return (await prepareLink(context, type, String(email), where)).send();
|
|
1590
|
+
},
|
|
1591
|
+
async prepare(email) {
|
|
1592
|
+
const where = at("magicLink.prepare");
|
|
1593
|
+
return prepareLink(context, type, String(email), where);
|
|
1565
1594
|
},
|
|
1566
1595
|
async confirm(token, options) {
|
|
1567
1596
|
const where = at("magicLink.confirm");
|
|
@@ -1570,8 +1599,13 @@ function magicLinkFlows(context, type, at, finish) {
|
|
|
1570
1599
|
}
|
|
1571
1600
|
};
|
|
1572
1601
|
}
|
|
1573
|
-
async function
|
|
1602
|
+
async function prepareLink(context, type, email, where) {
|
|
1574
1603
|
await countMailByAddress(context, type, "magicLink", email, where);
|
|
1604
|
+
return Object.freeze({
|
|
1605
|
+
send: sendOnce(where, () => issueLink(context, type, email))
|
|
1606
|
+
});
|
|
1607
|
+
}
|
|
1608
|
+
async function issueLink(context, type, email) {
|
|
1575
1609
|
const record = await signInHolder(context, type, email);
|
|
1576
1610
|
if (record === null)
|
|
1577
1611
|
return null;
|
|
@@ -1596,6 +1630,32 @@ async function confirmLink(context, type, finish, secret, where, device) {
|
|
|
1596
1630
|
});
|
|
1597
1631
|
}
|
|
1598
1632
|
|
|
1633
|
+
// src/auth/proven-sign-in.ts
|
|
1634
|
+
function provenSignInFlows(context, type, at, finish) {
|
|
1635
|
+
return {
|
|
1636
|
+
async signInProven(user, options) {
|
|
1637
|
+
const where = at("signInProven");
|
|
1638
|
+
const device = deviceHint(context, options, where);
|
|
1639
|
+
const record = await getRecord(context, idOf2(user), type.name, where);
|
|
1640
|
+
if (!record.active) {
|
|
1641
|
+
throw new UserInactiveError2(`${where}: the user is inactive`, {
|
|
1642
|
+
userId: record.id,
|
|
1643
|
+
userType: type.name
|
|
1644
|
+
});
|
|
1645
|
+
}
|
|
1646
|
+
const result = await finish(record, where, device);
|
|
1647
|
+
await reportNewDevice(context, result);
|
|
1648
|
+
return result;
|
|
1649
|
+
}
|
|
1650
|
+
};
|
|
1651
|
+
}
|
|
1652
|
+
function idOf2(user) {
|
|
1653
|
+
if (typeof user === "string")
|
|
1654
|
+
return user;
|
|
1655
|
+
const id = typeof user === "object" && user !== null ? user.id : undefined;
|
|
1656
|
+
return typeof id === "string" ? id : "";
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1599
1659
|
// src/auth/sign-in-attempts.ts
|
|
1600
1660
|
var PURPOSE3 = "janus/sign-in/attempts/v1";
|
|
1601
1661
|
async function countLogin(context, type, login, where) {
|
|
@@ -1974,29 +2034,43 @@ function secondFactorFlows(context, type, at) {
|
|
|
1974
2034
|
function signInCodeFlows(context, type, at, finish) {
|
|
1975
2035
|
return {
|
|
1976
2036
|
async request(email) {
|
|
1977
|
-
|
|
2037
|
+
const where = at("signInCode.request");
|
|
2038
|
+
return (await prepareCode(context, type, String(email), where)).send();
|
|
2039
|
+
},
|
|
2040
|
+
async prepare(email) {
|
|
2041
|
+
const where = at("signInCode.prepare");
|
|
2042
|
+
return prepareCode(context, type, String(email), where);
|
|
1978
2043
|
},
|
|
1979
2044
|
async confirm(challenge, code, options) {
|
|
1980
2045
|
return confirmCode(context, type, finish, { challenge, code, options }, at);
|
|
1981
2046
|
}
|
|
1982
2047
|
};
|
|
1983
2048
|
}
|
|
1984
|
-
async function
|
|
2049
|
+
async function prepareCode(context, type, email, where) {
|
|
1985
2050
|
await countMailByAddress(context, type, "signInCode", email, where);
|
|
2051
|
+
const challenge = mintSecret();
|
|
2052
|
+
return Object.freeze({
|
|
2053
|
+
challenge,
|
|
2054
|
+
send: sendOnce(where, () => issueSignInCode(context, type, email, challenge))
|
|
2055
|
+
});
|
|
2056
|
+
}
|
|
2057
|
+
async function issueSignInCode(context, type, email, challenge) {
|
|
1986
2058
|
const record = await signInHolder(context, type, email);
|
|
1987
2059
|
if (record === null)
|
|
1988
2060
|
return null;
|
|
2061
|
+
const address = String(record.fields[type.email]);
|
|
1989
2062
|
const { secret, code, expiresAt } = await issueCode(context, {
|
|
1990
2063
|
kind: "signInCode",
|
|
1991
2064
|
userId: record.id,
|
|
1992
|
-
address
|
|
1993
|
-
ttlMs: context.config.tokenTtlMs.signInCode
|
|
2065
|
+
address,
|
|
2066
|
+
ttlMs: context.config.tokenTtlMs.signInCode,
|
|
2067
|
+
secret: challenge
|
|
1994
2068
|
});
|
|
1995
2069
|
await keepOnlyLatest(context, record.id, "signInCode", secret);
|
|
1996
2070
|
return {
|
|
1997
2071
|
code,
|
|
1998
2072
|
challenge: secret,
|
|
1999
|
-
email:
|
|
2073
|
+
email: address,
|
|
2000
2074
|
expiresAt,
|
|
2001
2075
|
user: toUser(record)
|
|
2002
2076
|
};
|
|
@@ -2164,6 +2238,18 @@ async function confirmStepUp(context, type, request, secret, code, where) {
|
|
|
2164
2238
|
return toSession(confirmed);
|
|
2165
2239
|
}
|
|
2166
2240
|
|
|
2241
|
+
// src/auth/users/find-by-email.ts
|
|
2242
|
+
function findByEmailFlows(context, type) {
|
|
2243
|
+
return {
|
|
2244
|
+
async findByEmail(email) {
|
|
2245
|
+
if (typeof email !== "string" || email.trim() === "")
|
|
2246
|
+
return null;
|
|
2247
|
+
const record = await holderOfEmail(context, type, email);
|
|
2248
|
+
return record === null ? null : toUser(record);
|
|
2249
|
+
}
|
|
2250
|
+
};
|
|
2251
|
+
}
|
|
2252
|
+
|
|
2167
2253
|
// src/auth/users/by-login.ts
|
|
2168
2254
|
var byLogin = async (context, type, login) => isStorable(login) ? context.store.users.findUserByLogin(type.name, login) : null;
|
|
2169
2255
|
|
|
@@ -2448,10 +2534,12 @@ function typeApi(context, type) {
|
|
|
2448
2534
|
return {
|
|
2449
2535
|
...recordFlows(context, type, at),
|
|
2450
2536
|
...passwordFlows(context, type, at, secondFactor.finish),
|
|
2537
|
+
...provenSignInFlows(context, type, at, secondFactor.finish),
|
|
2451
2538
|
secondFactor: secondFactor.api,
|
|
2452
2539
|
signInCode,
|
|
2453
2540
|
magicLink: magicLinkFlows(context, type, at, secondFactor.finish),
|
|
2454
2541
|
stepUp: stepUpFlows(context, type, at),
|
|
2542
|
+
...findByEmailFlows(context, type),
|
|
2455
2543
|
...emailFlows(context, type, at)
|
|
2456
2544
|
};
|
|
2457
2545
|
}
|
|
@@ -2540,5 +2628,5 @@ export {
|
|
|
2540
2628
|
systemClock
|
|
2541
2629
|
};
|
|
2542
2630
|
|
|
2543
|
-
//# debugId=
|
|
2631
|
+
//# debugId=8864B5F285AA35A664756E2164756E21
|
|
2544
2632
|
//# sourceMappingURL=index.js.map
|