@fonderie/auth 7.3.3 → 7.3.5

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/dist/index.js CHANGED
@@ -235,7 +235,7 @@ function cookieHeaders(cookies) {
235
235
 
236
236
  // src/controllers/mfa.controller.ts
237
237
  import QRCode from "qrcode";
238
- import { setApiResponse as setApiResponse2, HTTP as HTTP2 } from "@fonderie/core";
238
+ import { setApiResponse as setApiResponse2, HTTP as HTTP2, background as background2 } from "@fonderie/core";
239
239
  import { NOTIFICATION_EVENT } from "@fonderie/events";
240
240
 
241
241
  // src/config.ts
@@ -836,6 +836,7 @@ var BackupCodeModel = class {
836
836
  };
837
837
 
838
838
  // src/models/login-event.model.ts
839
+ import { background } from "@fonderie/core";
839
840
  var LoginEventModel = class {
840
841
  constructor(store) {
841
842
  this.store = store;
@@ -857,12 +858,13 @@ var LoginEventModel = class {
857
858
  ]
858
859
  );
859
860
  }
860
- // Fire-and-forget variant for the login paths: recording history must never
861
- // block or fail a login (same posture as the bus.emit(...).catch(() => {})
862
- // notification sends).
863
- recordSafe(e) {
864
- void this.record(e).catch(() => {
865
- });
861
+ // Never throws — recording history must not fail a login. It IS awaited,
862
+ // though: this is a security audit trail (who signed in, from where), and a
863
+ // detached write is abandoned when a serverless instance freezes after the
864
+ // response, losing the row entirely. background() decides whether waiting is
865
+ // actually necessary for the current runtime.
866
+ async recordSafe(e) {
867
+ await background(this.record(e));
866
868
  }
867
869
  // One page of a user's own login history, newest first. Keyset-paginated on
868
870
  // (created_at, id) — mirrors audit's event list so the two log UIs share a
@@ -952,7 +954,7 @@ function mfaController(store, config, issuer, bus) {
952
954
  const token = body?.["token"];
953
955
  const isLoginCompletion = ctx.user.mfaPending === true;
954
956
  const meta = requestMeta(ctx);
955
- const failedMfa = () => loginEvents.recordSafe({
957
+ const failedMfa = async () => await loginEvents.recordSafe({
956
958
  userId: ctx.user.id,
957
959
  emailAttempted: ctx.user.email,
958
960
  method: "mfa",
@@ -970,13 +972,12 @@ function mfaController(store, config, issuer, bus) {
970
972
  return setApiResponse2(HTTP2.UNAUTHORIZED, "INVALID_CODE", "Invalid MFA token");
971
973
  }
972
974
  await users.confirmMfaSecret(ctx.user.id);
973
- bus?.emit(NOTIFICATION_EVENT, {
975
+ await background2(bus?.emit(NOTIFICATION_EVENT, {
974
976
  type: MESSAGE_KEYS.mfaEnabled,
975
977
  locale: ctx.user.locale,
976
978
  data: {},
977
979
  recipient: { email: ctx.user.email, phone: null, deviceToken: null }
978
- }).catch(() => {
979
- });
980
+ }));
980
981
  return setApiResponse2(HTTP2.OK, "MFA_VERIFIED", "MFA verified successfully.", {
981
982
  mfaEnabled: true
982
983
  });
@@ -1000,7 +1001,7 @@ function mfaController(store, config, issuer, bus) {
1000
1001
  );
1001
1002
  const matched = checks.find((r) => r.match);
1002
1003
  if (!matched) {
1003
- if (isLoginCompletion) failedMfa();
1004
+ if (isLoginCompletion) await failedMfa();
1004
1005
  return setApiResponse2(HTTP2.UNAUTHORIZED, "INVALID_CODE", "Invalid backup code");
1005
1006
  }
1006
1007
  await backupCodes.consume(matched.id);
@@ -1018,7 +1019,7 @@ function mfaController(store, config, issuer, bus) {
1018
1019
  }
1019
1020
  const secret = mfaCipher.decrypt(storedSecret);
1020
1021
  if (!verifyTotpToken(token, secret)) {
1021
- if (isLoginCompletion) failedMfa();
1022
+ if (isLoginCompletion) await failedMfa();
1022
1023
  return setApiResponse2(HTTP2.UNAUTHORIZED, "INVALID_CODE", "Invalid MFA token");
1023
1024
  }
1024
1025
  if (!ctx.user.mfaEnabled) {
@@ -1031,7 +1032,7 @@ function mfaController(store, config, issuer, bus) {
1031
1032
  });
1032
1033
  await sessions.create(ctx.user.id, refreshToken, refreshTokenExpiry(refreshToken), sid, meta);
1033
1034
  if (isLoginCompletion) {
1034
- loginEvents.recordSafe({
1035
+ await loginEvents.recordSafe({
1035
1036
  userId: ctx.user.id,
1036
1037
  emailAttempted: ctx.user.email,
1037
1038
  method: "mfa",
@@ -1076,13 +1077,12 @@ function mfaController(store, config, issuer, bus) {
1076
1077
  const plainCodes = generateBackupCodes();
1077
1078
  const codeHashes = await Promise.all(plainCodes.map((c) => hashPassword(c)));
1078
1079
  await backupCodes.replace(ctx.user.id, codeHashes);
1079
- bus?.emit(NOTIFICATION_EVENT, {
1080
+ await background2(bus?.emit(NOTIFICATION_EVENT, {
1080
1081
  type: MESSAGE_KEYS.mfaBackupCodesRegenerated,
1081
1082
  locale: ctx.user.locale,
1082
1083
  data: {},
1083
1084
  recipient: { email: ctx.user.email, phone: null, deviceToken: null }
1084
- }).catch(() => {
1085
- });
1085
+ }));
1086
1086
  return setApiResponse2(HTTP2.OK, "BACKUP_CODES_REGENERATED", "Backup codes regenerated.", {
1087
1087
  backupCodes: plainCodes
1088
1088
  });
@@ -1104,13 +1104,12 @@ function mfaController(store, config, issuer, bus) {
1104
1104
  return setApiResponse2(HTTP2.UNAUTHORIZED, "INVALID_CODE", "Invalid TOTP code");
1105
1105
  }
1106
1106
  await Promise.all([users.disableMfa(ctx.user.id), backupCodes.deleteByUser(ctx.user.id)]);
1107
- bus?.emit(NOTIFICATION_EVENT, {
1107
+ await background2(bus?.emit(NOTIFICATION_EVENT, {
1108
1108
  type: MESSAGE_KEYS.mfaDisabled,
1109
1109
  locale: user.locale,
1110
1110
  data: {},
1111
1111
  recipient: { email: user.email, phone: null, deviceToken: null }
1112
- }).catch(() => {
1113
- });
1112
+ }));
1114
1113
  return setApiResponse2(HTTP2.OK, "MFA_DISABLED", "MFA disabled successfully.");
1115
1114
  }
1116
1115
  };
@@ -1119,7 +1118,7 @@ function mfaController(store, config, issuer, bus) {
1119
1118
  // src/controllers/auth.controller.ts
1120
1119
  import { randomBytes as randomBytes3, randomInt } from "crypto";
1121
1120
  import { NOTIFICATION_EVENT as NOTIFICATION_EVENT2 } from "@fonderie/events";
1122
- import { setApiResponse as setApiResponse3, HTTP as HTTP3 } from "@fonderie/core";
1121
+ import { setApiResponse as setApiResponse3, HTTP as HTTP3, background as background3 } from "@fonderie/core";
1123
1122
 
1124
1123
  // src/services/cooldown.ts
1125
1124
  function checkCooldown(lastSentAt, cooldownMs) {
@@ -1366,7 +1365,7 @@ function authController(store, config, bus) {
1366
1365
  }
1367
1366
  const reqId = ctx.meta["requestId"];
1368
1367
  const reqOpts = reqId !== void 0 ? { requestId: reqId } : void 0;
1369
- bus?.emit(
1368
+ await background3(bus?.emit(
1370
1369
  NOTIFICATION_EVENT2,
1371
1370
  {
1372
1371
  type: MESSAGE_KEYS.emailRegistration,
@@ -1375,9 +1374,8 @@ function authController(store, config, bus) {
1375
1374
  recipient: { email: normalizedEmail, phone: null, deviceToken: null }
1376
1375
  },
1377
1376
  reqOpts
1378
- ).catch(() => {
1379
- });
1380
- bus?.emit(
1377
+ ));
1378
+ await background3(bus?.emit(
1381
1379
  EVENT_KEYS.userRegistered,
1382
1380
  {
1383
1381
  userId: user.id,
@@ -1387,8 +1385,7 @@ function authController(store, config, bus) {
1387
1385
  loginMethod: "email"
1388
1386
  },
1389
1387
  reqOpts
1390
- ).catch(() => {
1391
- });
1388
+ ));
1392
1389
  const { accessToken, refreshToken, sid } = issueTokenPair(user.id, config, {
1393
1390
  loginMethod: "email"
1394
1391
  });
@@ -1430,7 +1427,7 @@ function authController(store, config, bus) {
1430
1427
  }
1431
1428
  const reqId2 = ctx.meta["requestId"];
1432
1429
  const reqOpts2 = reqId2 !== void 0 ? { requestId: reqId2 } : void 0;
1433
- bus?.emit(
1430
+ await background3(bus?.emit(
1434
1431
  NOTIFICATION_EVENT2,
1435
1432
  {
1436
1433
  type: MESSAGE_KEYS.phoneOtp,
@@ -1439,9 +1436,8 @@ function authController(store, config, bus) {
1439
1436
  recipient: { email: null, phone: normalizePhone(phone2), deviceToken: null }
1440
1437
  },
1441
1438
  reqOpts2
1442
- ).catch(() => {
1443
- });
1444
- bus?.emit(
1439
+ ));
1440
+ await background3(bus?.emit(
1445
1441
  EVENT_KEYS.userRegistered,
1446
1442
  {
1447
1443
  userId: user.id,
@@ -1451,8 +1447,7 @@ function authController(store, config, bus) {
1451
1447
  loginMethod: "phone"
1452
1448
  },
1453
1449
  reqOpts2
1454
- ).catch(() => {
1455
- });
1450
+ ));
1456
1451
  const otpToken = issueMfaPendingToken(user.id, config, "phone");
1457
1452
  return Response.json(
1458
1453
  {
@@ -1483,7 +1478,7 @@ function authController(store, config, bus) {
1483
1478
  }
1484
1479
  const user = await users.findByEmail(email2);
1485
1480
  if (!user || !user.passwordHash) {
1486
- loginEvents.recordSafe({
1481
+ await loginEvents.recordSafe({
1487
1482
  userId: null,
1488
1483
  emailAttempted: email2,
1489
1484
  method: "password",
@@ -1499,7 +1494,7 @@ function authController(store, config, bus) {
1499
1494
  config.legacyVerify
1500
1495
  );
1501
1496
  if (!valid) {
1502
- loginEvents.recordSafe({
1497
+ await loginEvents.recordSafe({
1503
1498
  userId: user.id,
1504
1499
  emailAttempted: email2,
1505
1500
  method: "password",
@@ -1529,7 +1524,7 @@ function authController(store, config, bus) {
1529
1524
  loginMethod: "email"
1530
1525
  });
1531
1526
  await sessions.create(user.id, refreshToken, refreshTokenExpiry(refreshToken), sid, meta);
1532
- loginEvents.recordSafe({
1527
+ await loginEvents.recordSafe({
1533
1528
  userId: user.id,
1534
1529
  emailAttempted: email2,
1535
1530
  method: "password",
@@ -1570,13 +1565,12 @@ function authController(store, config, bus) {
1570
1565
  const otp = randomInt(1e5, 1e6).toString();
1571
1566
  const expiresAt = new Date(Date.now() + OTP_TTL_MS);
1572
1567
  await phoneVerif.upsert(user.id, normalizePhone(phone2), otp, expiresAt);
1573
- bus?.emit(NOTIFICATION_EVENT2, {
1568
+ await background3(bus?.emit(NOTIFICATION_EVENT2, {
1574
1569
  type: MESSAGE_KEYS.phoneOtp,
1575
1570
  locale: user.locale,
1576
1571
  data: { otp },
1577
1572
  recipient: { email: null, phone: normalizePhone(phone2), deviceToken: null }
1578
- }).catch(() => {
1579
- });
1573
+ }));
1580
1574
  const otpToken = issueMfaPendingToken(user.id, config, "phone");
1581
1575
  return Response.json(
1582
1576
  {
@@ -1683,13 +1677,12 @@ function authController(store, config, bus) {
1683
1677
  await passwordReset.create(user.id, pin, token, expiresAt);
1684
1678
  const base = resolved.passwordResetUrl;
1685
1679
  const resetUrl = base ? `${base}${base.includes("?") ? "&" : "?"}token=${token}` : "";
1686
- bus?.emit(NOTIFICATION_EVENT2, {
1680
+ await background3(bus?.emit(NOTIFICATION_EVENT2, {
1687
1681
  type: MESSAGE_KEYS.passwordReset,
1688
1682
  locale: user.locale,
1689
1683
  recipient: { email: email2, phone: null, deviceToken: null },
1690
1684
  data: { pin, token, resetUrl }
1691
- }).catch(() => {
1692
- });
1685
+ }));
1693
1686
  return setApiResponse3(
1694
1687
  HTTP3.OK,
1695
1688
  "PASSWORD_RESET_EMAIL_SENT",
@@ -1864,13 +1857,12 @@ function authController(store, config, bus) {
1864
1857
  const otp = randomInt(1e5, 1e6).toString();
1865
1858
  const expiresAt2 = new Date(Date.now() + OTP_TTL_MS);
1866
1859
  await phoneVerif.upsert(ctx.user.id, phone2, otp, expiresAt2);
1867
- bus?.emit(NOTIFICATION_EVENT2, {
1860
+ await background3(bus?.emit(NOTIFICATION_EVENT2, {
1868
1861
  type: MESSAGE_KEYS.phoneOtp,
1869
1862
  locale: ctx.user.locale,
1870
1863
  data: { otp },
1871
1864
  recipient: { email: null, phone: phone2, deviceToken: null }
1872
- }).catch(() => {
1873
- });
1865
+ }));
1874
1866
  return setApiResponse3(
1875
1867
  HTTP3.OK,
1876
1868
  "VERIFICATION_SENT",
@@ -1904,13 +1896,12 @@ function authController(store, config, bus) {
1904
1896
  const pin = randomInt(1e5, 1e6).toString();
1905
1897
  const expiresAt = new Date(Date.now() + 1e3 * 60 * 60 * 24);
1906
1898
  await emailVerif.replace(ctx.user.id, pin, expiresAt);
1907
- bus?.emit(NOTIFICATION_EVENT2, {
1899
+ await background3(bus?.emit(NOTIFICATION_EVENT2, {
1908
1900
  type: MESSAGE_KEYS.emailVerification,
1909
1901
  locale: ctx.user.locale,
1910
1902
  recipient: { email: ctx.user.email, phone: null, deviceToken: null },
1911
1903
  data: { pin }
1912
- }).catch(() => {
1913
- });
1904
+ }));
1914
1905
  return setApiResponse3(HTTP3.OK, "VERIFICATION_SENT", "Verification email sent.", {
1915
1906
  email: ctx.user.email
1916
1907
  });
@@ -1920,7 +1911,7 @@ function authController(store, config, bus) {
1920
1911
 
1921
1912
  // src/controllers/user.controller.ts
1922
1913
  import { randomInt as randomInt2 } from "crypto";
1923
- import { setApiResponse as setApiResponse4, HTTP as HTTP4, dateOrEmpty as dateOrEmpty2 } from "@fonderie/core";
1914
+ import { setApiResponse as setApiResponse4, HTTP as HTTP4, dateOrEmpty as dateOrEmpty2, background as background4 } from "@fonderie/core";
1924
1915
  import { NOTIFICATION_EVENT as NOTIFICATION_EVENT3 } from "@fonderie/events";
1925
1916
 
1926
1917
  // src/dtos/login-activity.ts
@@ -2113,21 +2104,19 @@ function userController(store, config, bus) {
2113
2104
  const expiresAt = new Date(Date.now() + 1e3 * 60 * 60 * 24);
2114
2105
  await emailVerif.replace(ctx.user.id, pin, expiresAt);
2115
2106
  await users.updateEmail(ctx.user.id, normalised);
2116
- bus?.emit(NOTIFICATION_EVENT3, {
2107
+ await background4(bus?.emit(NOTIFICATION_EVENT3, {
2117
2108
  type: MESSAGE_KEYS.emailVerification,
2118
2109
  locale: ctx.user.locale,
2119
2110
  data: { pin },
2120
2111
  recipient: { email: normalised, phone: null, deviceToken: null }
2121
- }).catch(() => {
2122
- });
2112
+ }));
2123
2113
  if (oldEmail) {
2124
- bus?.emit(NOTIFICATION_EVENT3, {
2114
+ await background4(bus?.emit(NOTIFICATION_EVENT3, {
2125
2115
  type: MESSAGE_KEYS.emailChanged,
2126
2116
  locale: ctx.user.locale,
2127
2117
  data: { newEmail: normalised },
2128
2118
  recipient: { email: oldEmail, phone: null, deviceToken: null }
2129
- }).catch(() => {
2130
- });
2119
+ }));
2131
2120
  }
2132
2121
  return setApiResponse4(
2133
2122
  HTTP4.OK,
@@ -2157,21 +2146,19 @@ function userController(store, config, bus) {
2157
2146
  const expiresAt = new Date(Date.now() + 10 * 60 * 1e3);
2158
2147
  await phoneVerif.upsert(ctx.user.id, normalised, otp, expiresAt);
2159
2148
  await users.updatePhone(ctx.user.id, normalised);
2160
- bus?.emit(NOTIFICATION_EVENT3, {
2149
+ await background4(bus?.emit(NOTIFICATION_EVENT3, {
2161
2150
  type: MESSAGE_KEYS.phoneOtp,
2162
2151
  locale: ctx.user.locale,
2163
2152
  data: { otp },
2164
2153
  recipient: { email: null, phone: normalised, deviceToken: null }
2165
- }).catch(() => {
2166
- });
2154
+ }));
2167
2155
  if (ctx.user.email) {
2168
- bus?.emit(NOTIFICATION_EVENT3, {
2156
+ await background4(bus?.emit(NOTIFICATION_EVENT3, {
2169
2157
  type: MESSAGE_KEYS.phoneChanged,
2170
2158
  locale: ctx.user.locale,
2171
2159
  data: {},
2172
2160
  recipient: { email: ctx.user.email, phone: null, deviceToken: null }
2173
- }).catch(() => {
2174
- });
2161
+ }));
2175
2162
  }
2176
2163
  return setApiResponse4(
2177
2164
  HTTP4.OK,
@@ -2208,12 +2195,11 @@ function userController(store, config, bus) {
2208
2195
  const userId = ctx.user.id;
2209
2196
  await users.softDelete(userId);
2210
2197
  const reqId = ctx.meta["requestId"];
2211
- bus?.emit(
2198
+ await background4(bus?.emit(
2212
2199
  EVENT_KEYS.userDeleted,
2213
2200
  { userId },
2214
2201
  reqId !== void 0 ? { requestId: reqId } : void 0
2215
- ).catch(() => {
2216
- });
2202
+ ));
2217
2203
  return Response.json(
2218
2204
  { reason: "ACCOUNT_DELETED", explanation: "Account successfully deleted." },
2219
2205
  {
@@ -2384,7 +2370,7 @@ function oauthController(store, config) {
2384
2370
  }
2385
2371
  const upserted = await users.upsertByProvider(normalizedEmail, "apple", claims.sub ?? "");
2386
2372
  if (!upserted) {
2387
- loginEvents.recordSafe({
2373
+ await loginEvents.recordSafe({
2388
2374
  userId: null,
2389
2375
  emailAttempted: normalizedEmail,
2390
2376
  method: "oauth-apple",
@@ -2400,7 +2386,7 @@ function oauthController(store, config) {
2400
2386
  }
2401
2387
  const { accessToken, refreshToken, sid } = issueTokenPair(upserted.id, config, { loginMethod: "apple" });
2402
2388
  await sessions.create(upserted.id, refreshToken, refreshTokenExpiry(refreshToken), sid, meta);
2403
- loginEvents.recordSafe({
2389
+ await loginEvents.recordSafe({
2404
2390
  userId: upserted.id,
2405
2391
  emailAttempted: normalizedEmail,
2406
2392
  method: "oauth-apple",
@@ -2534,7 +2520,7 @@ function oauthController(store, config) {
2534
2520
  const meta = requestMeta(ctx);
2535
2521
  const upserted = await users.upsertByProvider(normalizedEmail, "google", payload.sub ?? "");
2536
2522
  if (!upserted) {
2537
- loginEvents.recordSafe({
2523
+ await loginEvents.recordSafe({
2538
2524
  userId: null,
2539
2525
  emailAttempted: normalizedEmail,
2540
2526
  method: "oauth-google",
@@ -2552,7 +2538,7 @@ function oauthController(store, config) {
2552
2538
  loginMethod: "google"
2553
2539
  });
2554
2540
  await sessions.create(upserted.id, refreshToken, refreshTokenExpiry(refreshToken), sid, meta);
2555
- loginEvents.recordSafe({
2541
+ await loginEvents.recordSafe({
2556
2542
  userId: upserted.id,
2557
2543
  emailAttempted: normalizedEmail,
2558
2544
  method: "oauth-google",
@@ -2669,7 +2655,7 @@ function oauthController(store, config) {
2669
2655
  const claims = await verifyAppleIdToken(identityToken, { audiences, nonce: body?.nonce });
2670
2656
  if (!claims) {
2671
2657
  const meta = requestMeta(ctx);
2672
- loginEvents.recordSafe({
2658
+ await loginEvents.recordSafe({
2673
2659
  userId: null,
2674
2660
  emailAttempted: null,
2675
2661
  method: "oauth-apple",
@@ -2684,7 +2670,7 @@ function oauthController(store, config) {
2684
2670
  const firstUse = await consumedTokens.consumeOnce(tokenHash, new Date(expMs));
2685
2671
  if (!firstUse) {
2686
2672
  const meta = requestMeta(ctx);
2687
- loginEvents.recordSafe({
2673
+ await loginEvents.recordSafe({
2688
2674
  userId: null,
2689
2675
  emailAttempted: claims.email ?? null,
2690
2676
  method: "oauth-apple",