@fonderie/auth 7.3.4 → 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, background } 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,7 +972,7 @@ 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
- await background(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: {},
@@ -999,7 +1001,7 @@ function mfaController(store, config, issuer, bus) {
999
1001
  );
1000
1002
  const matched = checks.find((r) => r.match);
1001
1003
  if (!matched) {
1002
- if (isLoginCompletion) failedMfa();
1004
+ if (isLoginCompletion) await failedMfa();
1003
1005
  return setApiResponse2(HTTP2.UNAUTHORIZED, "INVALID_CODE", "Invalid backup code");
1004
1006
  }
1005
1007
  await backupCodes.consume(matched.id);
@@ -1017,7 +1019,7 @@ function mfaController(store, config, issuer, bus) {
1017
1019
  }
1018
1020
  const secret = mfaCipher.decrypt(storedSecret);
1019
1021
  if (!verifyTotpToken(token, secret)) {
1020
- if (isLoginCompletion) failedMfa();
1022
+ if (isLoginCompletion) await failedMfa();
1021
1023
  return setApiResponse2(HTTP2.UNAUTHORIZED, "INVALID_CODE", "Invalid MFA token");
1022
1024
  }
1023
1025
  if (!ctx.user.mfaEnabled) {
@@ -1030,7 +1032,7 @@ function mfaController(store, config, issuer, bus) {
1030
1032
  });
1031
1033
  await sessions.create(ctx.user.id, refreshToken, refreshTokenExpiry(refreshToken), sid, meta);
1032
1034
  if (isLoginCompletion) {
1033
- loginEvents.recordSafe({
1035
+ await loginEvents.recordSafe({
1034
1036
  userId: ctx.user.id,
1035
1037
  emailAttempted: ctx.user.email,
1036
1038
  method: "mfa",
@@ -1075,7 +1077,7 @@ function mfaController(store, config, issuer, bus) {
1075
1077
  const plainCodes = generateBackupCodes();
1076
1078
  const codeHashes = await Promise.all(plainCodes.map((c) => hashPassword(c)));
1077
1079
  await backupCodes.replace(ctx.user.id, codeHashes);
1078
- await background(bus?.emit(NOTIFICATION_EVENT, {
1080
+ await background2(bus?.emit(NOTIFICATION_EVENT, {
1079
1081
  type: MESSAGE_KEYS.mfaBackupCodesRegenerated,
1080
1082
  locale: ctx.user.locale,
1081
1083
  data: {},
@@ -1102,7 +1104,7 @@ function mfaController(store, config, issuer, bus) {
1102
1104
  return setApiResponse2(HTTP2.UNAUTHORIZED, "INVALID_CODE", "Invalid TOTP code");
1103
1105
  }
1104
1106
  await Promise.all([users.disableMfa(ctx.user.id), backupCodes.deleteByUser(ctx.user.id)]);
1105
- await background(bus?.emit(NOTIFICATION_EVENT, {
1107
+ await background2(bus?.emit(NOTIFICATION_EVENT, {
1106
1108
  type: MESSAGE_KEYS.mfaDisabled,
1107
1109
  locale: user.locale,
1108
1110
  data: {},
@@ -1116,7 +1118,7 @@ function mfaController(store, config, issuer, bus) {
1116
1118
  // src/controllers/auth.controller.ts
1117
1119
  import { randomBytes as randomBytes3, randomInt } from "crypto";
1118
1120
  import { NOTIFICATION_EVENT as NOTIFICATION_EVENT2 } from "@fonderie/events";
1119
- import { setApiResponse as setApiResponse3, HTTP as HTTP3, background as background2 } from "@fonderie/core";
1121
+ import { setApiResponse as setApiResponse3, HTTP as HTTP3, background as background3 } from "@fonderie/core";
1120
1122
 
1121
1123
  // src/services/cooldown.ts
1122
1124
  function checkCooldown(lastSentAt, cooldownMs) {
@@ -1363,7 +1365,7 @@ function authController(store, config, bus) {
1363
1365
  }
1364
1366
  const reqId = ctx.meta["requestId"];
1365
1367
  const reqOpts = reqId !== void 0 ? { requestId: reqId } : void 0;
1366
- await background2(bus?.emit(
1368
+ await background3(bus?.emit(
1367
1369
  NOTIFICATION_EVENT2,
1368
1370
  {
1369
1371
  type: MESSAGE_KEYS.emailRegistration,
@@ -1373,7 +1375,7 @@ function authController(store, config, bus) {
1373
1375
  },
1374
1376
  reqOpts
1375
1377
  ));
1376
- await background2(bus?.emit(
1378
+ await background3(bus?.emit(
1377
1379
  EVENT_KEYS.userRegistered,
1378
1380
  {
1379
1381
  userId: user.id,
@@ -1425,7 +1427,7 @@ function authController(store, config, bus) {
1425
1427
  }
1426
1428
  const reqId2 = ctx.meta["requestId"];
1427
1429
  const reqOpts2 = reqId2 !== void 0 ? { requestId: reqId2 } : void 0;
1428
- await background2(bus?.emit(
1430
+ await background3(bus?.emit(
1429
1431
  NOTIFICATION_EVENT2,
1430
1432
  {
1431
1433
  type: MESSAGE_KEYS.phoneOtp,
@@ -1435,7 +1437,7 @@ function authController(store, config, bus) {
1435
1437
  },
1436
1438
  reqOpts2
1437
1439
  ));
1438
- await background2(bus?.emit(
1440
+ await background3(bus?.emit(
1439
1441
  EVENT_KEYS.userRegistered,
1440
1442
  {
1441
1443
  userId: user.id,
@@ -1476,7 +1478,7 @@ function authController(store, config, bus) {
1476
1478
  }
1477
1479
  const user = await users.findByEmail(email2);
1478
1480
  if (!user || !user.passwordHash) {
1479
- loginEvents.recordSafe({
1481
+ await loginEvents.recordSafe({
1480
1482
  userId: null,
1481
1483
  emailAttempted: email2,
1482
1484
  method: "password",
@@ -1492,7 +1494,7 @@ function authController(store, config, bus) {
1492
1494
  config.legacyVerify
1493
1495
  );
1494
1496
  if (!valid) {
1495
- loginEvents.recordSafe({
1497
+ await loginEvents.recordSafe({
1496
1498
  userId: user.id,
1497
1499
  emailAttempted: email2,
1498
1500
  method: "password",
@@ -1522,7 +1524,7 @@ function authController(store, config, bus) {
1522
1524
  loginMethod: "email"
1523
1525
  });
1524
1526
  await sessions.create(user.id, refreshToken, refreshTokenExpiry(refreshToken), sid, meta);
1525
- loginEvents.recordSafe({
1527
+ await loginEvents.recordSafe({
1526
1528
  userId: user.id,
1527
1529
  emailAttempted: email2,
1528
1530
  method: "password",
@@ -1563,7 +1565,7 @@ function authController(store, config, bus) {
1563
1565
  const otp = randomInt(1e5, 1e6).toString();
1564
1566
  const expiresAt = new Date(Date.now() + OTP_TTL_MS);
1565
1567
  await phoneVerif.upsert(user.id, normalizePhone(phone2), otp, expiresAt);
1566
- await background2(bus?.emit(NOTIFICATION_EVENT2, {
1568
+ await background3(bus?.emit(NOTIFICATION_EVENT2, {
1567
1569
  type: MESSAGE_KEYS.phoneOtp,
1568
1570
  locale: user.locale,
1569
1571
  data: { otp },
@@ -1675,7 +1677,7 @@ function authController(store, config, bus) {
1675
1677
  await passwordReset.create(user.id, pin, token, expiresAt);
1676
1678
  const base = resolved.passwordResetUrl;
1677
1679
  const resetUrl = base ? `${base}${base.includes("?") ? "&" : "?"}token=${token}` : "";
1678
- await background2(bus?.emit(NOTIFICATION_EVENT2, {
1680
+ await background3(bus?.emit(NOTIFICATION_EVENT2, {
1679
1681
  type: MESSAGE_KEYS.passwordReset,
1680
1682
  locale: user.locale,
1681
1683
  recipient: { email: email2, phone: null, deviceToken: null },
@@ -1855,7 +1857,7 @@ function authController(store, config, bus) {
1855
1857
  const otp = randomInt(1e5, 1e6).toString();
1856
1858
  const expiresAt2 = new Date(Date.now() + OTP_TTL_MS);
1857
1859
  await phoneVerif.upsert(ctx.user.id, phone2, otp, expiresAt2);
1858
- await background2(bus?.emit(NOTIFICATION_EVENT2, {
1860
+ await background3(bus?.emit(NOTIFICATION_EVENT2, {
1859
1861
  type: MESSAGE_KEYS.phoneOtp,
1860
1862
  locale: ctx.user.locale,
1861
1863
  data: { otp },
@@ -1894,7 +1896,7 @@ function authController(store, config, bus) {
1894
1896
  const pin = randomInt(1e5, 1e6).toString();
1895
1897
  const expiresAt = new Date(Date.now() + 1e3 * 60 * 60 * 24);
1896
1898
  await emailVerif.replace(ctx.user.id, pin, expiresAt);
1897
- await background2(bus?.emit(NOTIFICATION_EVENT2, {
1899
+ await background3(bus?.emit(NOTIFICATION_EVENT2, {
1898
1900
  type: MESSAGE_KEYS.emailVerification,
1899
1901
  locale: ctx.user.locale,
1900
1902
  recipient: { email: ctx.user.email, phone: null, deviceToken: null },
@@ -1909,7 +1911,7 @@ function authController(store, config, bus) {
1909
1911
 
1910
1912
  // src/controllers/user.controller.ts
1911
1913
  import { randomInt as randomInt2 } from "crypto";
1912
- import { setApiResponse as setApiResponse4, HTTP as HTTP4, dateOrEmpty as dateOrEmpty2, background as background3 } from "@fonderie/core";
1914
+ import { setApiResponse as setApiResponse4, HTTP as HTTP4, dateOrEmpty as dateOrEmpty2, background as background4 } from "@fonderie/core";
1913
1915
  import { NOTIFICATION_EVENT as NOTIFICATION_EVENT3 } from "@fonderie/events";
1914
1916
 
1915
1917
  // src/dtos/login-activity.ts
@@ -2102,14 +2104,14 @@ function userController(store, config, bus) {
2102
2104
  const expiresAt = new Date(Date.now() + 1e3 * 60 * 60 * 24);
2103
2105
  await emailVerif.replace(ctx.user.id, pin, expiresAt);
2104
2106
  await users.updateEmail(ctx.user.id, normalised);
2105
- await background3(bus?.emit(NOTIFICATION_EVENT3, {
2107
+ await background4(bus?.emit(NOTIFICATION_EVENT3, {
2106
2108
  type: MESSAGE_KEYS.emailVerification,
2107
2109
  locale: ctx.user.locale,
2108
2110
  data: { pin },
2109
2111
  recipient: { email: normalised, phone: null, deviceToken: null }
2110
2112
  }));
2111
2113
  if (oldEmail) {
2112
- await background3(bus?.emit(NOTIFICATION_EVENT3, {
2114
+ await background4(bus?.emit(NOTIFICATION_EVENT3, {
2113
2115
  type: MESSAGE_KEYS.emailChanged,
2114
2116
  locale: ctx.user.locale,
2115
2117
  data: { newEmail: normalised },
@@ -2144,14 +2146,14 @@ function userController(store, config, bus) {
2144
2146
  const expiresAt = new Date(Date.now() + 10 * 60 * 1e3);
2145
2147
  await phoneVerif.upsert(ctx.user.id, normalised, otp, expiresAt);
2146
2148
  await users.updatePhone(ctx.user.id, normalised);
2147
- await background3(bus?.emit(NOTIFICATION_EVENT3, {
2149
+ await background4(bus?.emit(NOTIFICATION_EVENT3, {
2148
2150
  type: MESSAGE_KEYS.phoneOtp,
2149
2151
  locale: ctx.user.locale,
2150
2152
  data: { otp },
2151
2153
  recipient: { email: null, phone: normalised, deviceToken: null }
2152
2154
  }));
2153
2155
  if (ctx.user.email) {
2154
- await background3(bus?.emit(NOTIFICATION_EVENT3, {
2156
+ await background4(bus?.emit(NOTIFICATION_EVENT3, {
2155
2157
  type: MESSAGE_KEYS.phoneChanged,
2156
2158
  locale: ctx.user.locale,
2157
2159
  data: {},
@@ -2193,7 +2195,7 @@ function userController(store, config, bus) {
2193
2195
  const userId = ctx.user.id;
2194
2196
  await users.softDelete(userId);
2195
2197
  const reqId = ctx.meta["requestId"];
2196
- await background3(bus?.emit(
2198
+ await background4(bus?.emit(
2197
2199
  EVENT_KEYS.userDeleted,
2198
2200
  { userId },
2199
2201
  reqId !== void 0 ? { requestId: reqId } : void 0
@@ -2368,7 +2370,7 @@ function oauthController(store, config) {
2368
2370
  }
2369
2371
  const upserted = await users.upsertByProvider(normalizedEmail, "apple", claims.sub ?? "");
2370
2372
  if (!upserted) {
2371
- loginEvents.recordSafe({
2373
+ await loginEvents.recordSafe({
2372
2374
  userId: null,
2373
2375
  emailAttempted: normalizedEmail,
2374
2376
  method: "oauth-apple",
@@ -2384,7 +2386,7 @@ function oauthController(store, config) {
2384
2386
  }
2385
2387
  const { accessToken, refreshToken, sid } = issueTokenPair(upserted.id, config, { loginMethod: "apple" });
2386
2388
  await sessions.create(upserted.id, refreshToken, refreshTokenExpiry(refreshToken), sid, meta);
2387
- loginEvents.recordSafe({
2389
+ await loginEvents.recordSafe({
2388
2390
  userId: upserted.id,
2389
2391
  emailAttempted: normalizedEmail,
2390
2392
  method: "oauth-apple",
@@ -2518,7 +2520,7 @@ function oauthController(store, config) {
2518
2520
  const meta = requestMeta(ctx);
2519
2521
  const upserted = await users.upsertByProvider(normalizedEmail, "google", payload.sub ?? "");
2520
2522
  if (!upserted) {
2521
- loginEvents.recordSafe({
2523
+ await loginEvents.recordSafe({
2522
2524
  userId: null,
2523
2525
  emailAttempted: normalizedEmail,
2524
2526
  method: "oauth-google",
@@ -2536,7 +2538,7 @@ function oauthController(store, config) {
2536
2538
  loginMethod: "google"
2537
2539
  });
2538
2540
  await sessions.create(upserted.id, refreshToken, refreshTokenExpiry(refreshToken), sid, meta);
2539
- loginEvents.recordSafe({
2541
+ await loginEvents.recordSafe({
2540
2542
  userId: upserted.id,
2541
2543
  emailAttempted: normalizedEmail,
2542
2544
  method: "oauth-google",
@@ -2653,7 +2655,7 @@ function oauthController(store, config) {
2653
2655
  const claims = await verifyAppleIdToken(identityToken, { audiences, nonce: body?.nonce });
2654
2656
  if (!claims) {
2655
2657
  const meta = requestMeta(ctx);
2656
- loginEvents.recordSafe({
2658
+ await loginEvents.recordSafe({
2657
2659
  userId: null,
2658
2660
  emailAttempted: null,
2659
2661
  method: "oauth-apple",
@@ -2668,7 +2670,7 @@ function oauthController(store, config) {
2668
2670
  const firstUse = await consumedTokens.consumeOnce(tokenHash, new Date(expMs));
2669
2671
  if (!firstUse) {
2670
2672
  const meta = requestMeta(ctx);
2671
- loginEvents.recordSafe({
2673
+ await loginEvents.recordSafe({
2672
2674
  userId: null,
2673
2675
  emailAttempted: claims.email ?? null,
2674
2676
  method: "oauth-apple",