@nxgt/janus 0.19.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +117 -7
  2. package/dist/auth/email-flows/reset-password.d.ts.map +1 -1
  3. package/dist/auth/events.d.ts +3 -3
  4. package/dist/auth/index.d.ts +1 -1
  5. package/dist/auth/index.d.ts.map +1 -1
  6. package/dist/auth/magic-link.d.ts +3 -0
  7. package/dist/auth/magic-link.d.ts.map +1 -1
  8. package/dist/auth/one-time/issue.d.ts +6 -0
  9. package/dist/auth/one-time/issue.d.ts.map +1 -1
  10. package/dist/auth/prepared.d.ts +21 -0
  11. package/dist/auth/prepared.d.ts.map +1 -0
  12. package/dist/auth/proven-sign-in.d.ts +22 -0
  13. package/dist/auth/proven-sign-in.d.ts.map +1 -0
  14. package/dist/auth/sign-in-code.d.ts +4 -0
  15. package/dist/auth/sign-in-code.d.ts.map +1 -1
  16. package/dist/auth/types/email-flows.d.ts +18 -0
  17. package/dist/auth/types/email-flows.d.ts.map +1 -1
  18. package/dist/auth/types/index.d.ts +2 -0
  19. package/dist/auth/types/index.d.ts.map +1 -1
  20. package/dist/auth/types/janus.d.ts +7 -5
  21. package/dist/auth/types/janus.d.ts.map +1 -1
  22. package/dist/auth/types/magic-link.d.ts +11 -0
  23. package/dist/auth/types/magic-link.d.ts.map +1 -1
  24. package/dist/auth/types/prepared.d.ts +41 -0
  25. package/dist/auth/types/prepared.d.ts.map +1 -0
  26. package/dist/auth/types/proven-sign-in.d.ts +43 -0
  27. package/dist/auth/types/proven-sign-in.d.ts.map +1 -0
  28. package/dist/auth/types/sign-in.d.ts +2 -2
  29. package/dist/auth/users/any-type-api.d.ts +2 -2
  30. package/dist/auth/users/any-type-api.d.ts.map +1 -1
  31. package/dist/auth/users/type-api.d.ts +2 -2
  32. package/dist/auth/users/type-api.d.ts.map +1 -1
  33. package/dist/index.js +92 -17
  34. package/dist/index.js.map +11 -9
  35. package/docs/README.md +1 -1
  36. package/docs/guide/devices.md +4 -0
  37. package/docs/guide/email-flows.md +105 -0
  38. package/docs/guide/errors.md +2 -2
  39. package/docs/guide/events.md +2 -2
  40. package/docs/guide/magic-link.md +49 -2
  41. package/docs/guide/passwords.md +5 -2
  42. package/docs/guide/second-factor.md +1 -1
  43. package/docs/guide/sessions.md +94 -1
  44. package/docs/guide/sign-in-code.md +54 -2
  45. package/docs/guide/users.md +1 -0
  46. package/docs/roadmap.md +17 -26
  47. package/docs/troubleshooting.md +81 -5
  48. package/package.json +1 -1
@@ -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,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,gBAAgB,CAAC,OAAO,CAAC,CAAC"}
@@ -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 second factor, its sign-in code and link, its step-up
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 and its e-mails.
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;AAM1C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAIjD;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,GAAG,UAAU,CAiBxE"}
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;AAIjD;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,GAAG,UAAU,CAkBxE"}
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
- passwordRule(type, where);
1331
- await countMailByAddress(context, type, "resetPassword", String(email), where);
1332
- const record = await holderOfEmail(context, type, String(email));
1333
- if (record === null)
1334
- return null;
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
- return requestLink(context, type, String(email), at("magicLink.request"));
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 requestLink(context, type, email, where) {
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
- return requestCode(context, type, String(email), at("signInCode.request"));
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 requestCode(context, type, email, where) {
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: String(record.fields[type.email]),
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: String(record.fields[type.email]),
2073
+ email: address,
2000
2074
  expiresAt,
2001
2075
  user: toUser(record)
2002
2076
  };
@@ -2448,6 +2522,7 @@ function typeApi(context, type) {
2448
2522
  return {
2449
2523
  ...recordFlows(context, type, at),
2450
2524
  ...passwordFlows(context, type, at, secondFactor.finish),
2525
+ ...provenSignInFlows(context, type, at, secondFactor.finish),
2451
2526
  secondFactor: secondFactor.api,
2452
2527
  signInCode,
2453
2528
  magicLink: magicLinkFlows(context, type, at, secondFactor.finish),
@@ -2540,5 +2615,5 @@ export {
2540
2615
  systemClock
2541
2616
  };
2542
2617
 
2543
- //# debugId=22ACDB89AAF4B56564756E2164756E21
2618
+ //# debugId=58DD1D230B6CE48B64756E2164756E21
2544
2619
  //# sourceMappingURL=index.js.map