@fonderie/auth 5.0.2 → 5.0.3

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.cjs CHANGED
@@ -255,7 +255,7 @@ function cookieHeaders(cookies) {
255
255
 
256
256
  // src/controllers/mfa.controller.ts
257
257
  var import_qrcode = __toESM(require("qrcode"), 1);
258
- var import_core3 = require("@fonderie/core");
258
+ var import_core4 = require("@fonderie/core");
259
259
  var import_events = require("@fonderie/events");
260
260
 
261
261
  // src/config.ts
@@ -332,6 +332,7 @@ function verifyToken(token, config) {
332
332
 
333
333
  // src/services/mfa.ts
334
334
  var import_node_crypto2 = require("crypto");
335
+ var import_core2 = require("@fonderie/core");
335
336
  var STEP = 30;
336
337
  var DRIFT = 1;
337
338
  var DIGITS = 6;
@@ -402,16 +403,10 @@ function generateTotpUri(email2, secret, issuer) {
402
403
  });
403
404
  return `otpauth://totp/${encodeURIComponent(issuer)}:${encodeURIComponent(email2)}?${params}`;
404
405
  }
405
- function safeCodeEqual(a, b) {
406
- const bufA = Buffer.from(a);
407
- const bufB = Buffer.from(b);
408
- if (bufA.length !== bufB.length) return false;
409
- return (0, import_node_crypto2.timingSafeEqual)(bufA, bufB);
410
- }
411
406
  function verifyTotpToken(token, secret) {
412
407
  const t = timeCounter();
413
408
  for (let i = -DRIFT; i <= DRIFT; i++) {
414
- if (safeCodeEqual(hotp(secret, t + i), token)) {
409
+ if ((0, import_core2.constantTimeEqual)(hotp(secret, t + i), token)) {
415
410
  return true;
416
411
  }
417
412
  }
@@ -460,7 +455,7 @@ function makeMfaCipher(keyHex) {
460
455
  }
461
456
 
462
457
  // src/dtos/user.ts
463
- var import_core2 = require("@fonderie/core");
458
+ var import_core3 = require("@fonderie/core");
464
459
  var DEFAULT_PREFERENCES = {
465
460
  locale: "en-US",
466
461
  timezone: "UTC",
@@ -491,14 +486,14 @@ function toUserDTO(user, phoneVerified = false) {
491
486
  const prefs = user.preferences ?? {};
492
487
  const cleaned = cleanPreferences(prefs);
493
488
  return {
494
- id: (0, import_core2.stringOrEmpty)(user.id),
495
- email: (0, import_core2.stringOrEmpty)(user.email),
496
- firstName: (0, import_core2.stringOrEmpty)(user.firstName),
497
- lastName: (0, import_core2.stringOrEmpty)(user.lastName),
498
- phone: (0, import_core2.stringOrEmpty)(user.phone),
499
- profileImageUrl: (0, import_core2.stringOrEmpty)(user.profileImageUrl),
489
+ id: (0, import_core3.stringOrEmpty)(user.id),
490
+ email: (0, import_core3.stringOrEmpty)(user.email),
491
+ firstName: (0, import_core3.stringOrEmpty)(user.firstName),
492
+ lastName: (0, import_core3.stringOrEmpty)(user.lastName),
493
+ phone: (0, import_core3.stringOrEmpty)(user.phone),
494
+ profileImageUrl: (0, import_core3.stringOrEmpty)(user.profileImageUrl),
500
495
  isActive: typeof user.isActive === "boolean" ? user.isActive : true,
501
- lastLogin: user.lastLogin instanceof Date ? user.lastLogin.toISOString() : "",
496
+ lastLogin: (0, import_core3.dateOrEmpty)(user.lastLogin),
502
497
  preferences: {
503
498
  ...DEFAULT_PREFERENCES,
504
499
  ...cleaned,
@@ -511,12 +506,12 @@ function toUserDTO(user, phoneVerified = false) {
511
506
  },
512
507
  isEmailVerified: user.emailVerifiedAt !== null,
513
508
  isPhoneVerified: phoneVerified,
514
- mfaEnabled: (0, import_core2.booleanOrFalse)(user.mfaEnabled),
515
- suspended: (0, import_core2.booleanOrFalse)(user.suspended),
516
- whitelist: (0, import_core2.booleanOrFalse)(user.whitelist),
509
+ mfaEnabled: (0, import_core3.booleanOrFalse)(user.mfaEnabled),
510
+ suspended: (0, import_core3.booleanOrFalse)(user.suspended),
511
+ whitelist: (0, import_core3.booleanOrFalse)(user.whitelist),
517
512
  ipWhitelist: Array.isArray(user.ipWhitelist) ? user.ipWhitelist : [],
518
- createdAt: user.createdAt instanceof Date ? user.createdAt.toISOString() : "",
519
- updatedAt: user.updatedAt instanceof Date ? user.updatedAt.toISOString() : ""
513
+ createdAt: (0, import_core3.dateOrEmpty)(user.createdAt),
514
+ updatedAt: (0, import_core3.dateOrEmpty)(user.updatedAt)
520
515
  };
521
516
  }
522
517
 
@@ -835,8 +830,8 @@ function mfaController(store, config, issuer, bus) {
835
830
  users.saveMfaPendingSecret(ctx.user.id, mfaCipher.encrypt(secret)),
836
831
  backupCodes.replace(ctx.user.id, codeHashes)
837
832
  ]);
838
- return (0, import_core3.setApiResponse)(
839
- import_core3.HTTP.OK,
833
+ return (0, import_core4.setApiResponse)(
834
+ import_core4.HTTP.OK,
840
835
  "MFA_SETUP_INITIATED",
841
836
  "Scan the QR code with your authenticator app.",
842
837
  {
@@ -852,13 +847,13 @@ function mfaController(store, config, issuer, bus) {
852
847
  const body = ctx.meta["body"];
853
848
  const token = body?.["token"];
854
849
  if (typeof token !== "string") {
855
- return (0, import_core3.setApiResponse)(import_core3.HTTP.UNPROCESSABLE, "INVALID_PARAMETER", "token is required");
850
+ return (0, import_core4.setApiResponse)(import_core4.HTTP.UNPROCESSABLE, "INVALID_PARAMETER", "token is required");
856
851
  }
857
852
  const pendingCipher = await users.getMfaPendingSecret(ctx.user.id);
858
853
  const pendingSecret = pendingCipher ? mfaCipher.decrypt(pendingCipher) : null;
859
854
  if (pendingSecret) {
860
855
  if (!verifyTotpToken(token, pendingSecret)) {
861
- return (0, import_core3.setApiResponse)(import_core3.HTTP.UNAUTHORIZED, "INVALID_CODE", "Invalid MFA token");
856
+ return (0, import_core4.setApiResponse)(import_core4.HTTP.UNAUTHORIZED, "INVALID_CODE", "Invalid MFA token");
862
857
  }
863
858
  await users.confirmMfaSecret(ctx.user.id);
864
859
  bus?.emit(import_events.NOTIFICATION_EVENT, {
@@ -868,19 +863,19 @@ function mfaController(store, config, issuer, bus) {
868
863
  recipient: { email: ctx.user.email, phone: null, deviceToken: null }
869
864
  }).catch(() => {
870
865
  });
871
- return (0, import_core3.setApiResponse)(import_core3.HTTP.OK, "MFA_VERIFIED", "MFA verified successfully.", {
866
+ return (0, import_core4.setApiResponse)(import_core4.HTTP.OK, "MFA_VERIFIED", "MFA verified successfully.", {
872
867
  mfaEnabled: true
873
868
  });
874
869
  } else if (/^[A-Z0-9]{8}$/i.test(token)) {
875
870
  if (!ctx.user.mfaPending) {
876
- return (0, import_core3.setApiResponse)(
877
- import_core3.HTTP.FORBIDDEN,
871
+ return (0, import_core4.setApiResponse)(
872
+ import_core4.HTTP.FORBIDDEN,
878
873
  "MFA_NOT_PENDING",
879
874
  "Use the mfaToken from the login response"
880
875
  );
881
876
  }
882
877
  if (!ctx.user.mfaEnabled) {
883
- return (0, import_core3.setApiResponse)(import_core3.HTTP.BAD_REQUEST, "MFA_NOT_CONFIGURED", "MFA not configured");
878
+ return (0, import_core4.setApiResponse)(import_core4.HTTP.BAD_REQUEST, "MFA_NOT_CONFIGURED", "MFA not configured");
884
879
  }
885
880
  const unused = await backupCodes.findUnused(ctx.user.id);
886
881
  const checks = await Promise.all(
@@ -891,24 +886,24 @@ function mfaController(store, config, issuer, bus) {
891
886
  );
892
887
  const matched = checks.find((r) => r.match);
893
888
  if (!matched) {
894
- return (0, import_core3.setApiResponse)(import_core3.HTTP.UNAUTHORIZED, "INVALID_CODE", "Invalid backup code");
889
+ return (0, import_core4.setApiResponse)(import_core4.HTTP.UNAUTHORIZED, "INVALID_CODE", "Invalid backup code");
895
890
  }
896
891
  await backupCodes.consume(matched.id);
897
892
  } else {
898
893
  if (!ctx.user.mfaPending) {
899
- return (0, import_core3.setApiResponse)(
900
- import_core3.HTTP.FORBIDDEN,
894
+ return (0, import_core4.setApiResponse)(
895
+ import_core4.HTTP.FORBIDDEN,
901
896
  "MFA_NOT_PENDING",
902
897
  "Use the mfaToken from the login response"
903
898
  );
904
899
  }
905
900
  const storedSecret = await users.getMfaSecret(ctx.user.id);
906
901
  if (!storedSecret) {
907
- return (0, import_core3.setApiResponse)(import_core3.HTTP.BAD_REQUEST, "MFA_NOT_CONFIGURED", "MFA not configured");
902
+ return (0, import_core4.setApiResponse)(import_core4.HTTP.BAD_REQUEST, "MFA_NOT_CONFIGURED", "MFA not configured");
908
903
  }
909
904
  const secret = mfaCipher.decrypt(storedSecret);
910
905
  if (!verifyTotpToken(token, secret)) {
911
- return (0, import_core3.setApiResponse)(import_core3.HTTP.UNAUTHORIZED, "INVALID_CODE", "Invalid MFA token");
906
+ return (0, import_core4.setApiResponse)(import_core4.HTTP.UNAUTHORIZED, "INVALID_CODE", "Invalid MFA token");
912
907
  }
913
908
  if (!ctx.user.mfaEnabled) {
914
909
  await users.enableMfa(ctx.user.id);
@@ -921,7 +916,7 @@ function mfaController(store, config, issuer, bus) {
921
916
  await sessions.create(ctx.user.id, refreshToken, refreshTokenExpiry(refreshToken), sid);
922
917
  const fullUser = await users.findById(ctx.user.id);
923
918
  if (!fullUser) {
924
- return (0, import_core3.setApiResponse)(import_core3.HTTP.SERVER_ERROR, "SERVER_ERROR", "User not found after MFA verify");
919
+ return (0, import_core4.setApiResponse)(import_core4.HTTP.SERVER_ERROR, "SERVER_ERROR", "User not found after MFA verify");
925
920
  }
926
921
  return Response.json(
927
922
  {
@@ -943,15 +938,15 @@ function mfaController(store, config, issuer, bus) {
943
938
  const body = ctx.meta["body"];
944
939
  const token = body?.["token"];
945
940
  if (typeof token !== "string") {
946
- return (0, import_core3.setApiResponse)(import_core3.HTTP.UNPROCESSABLE, "INVALID_PARAMETER", "TOTP token is required");
941
+ return (0, import_core4.setApiResponse)(import_core4.HTTP.UNPROCESSABLE, "INVALID_PARAMETER", "TOTP token is required");
947
942
  }
948
943
  if (!ctx.user.mfaEnabled) {
949
- return (0, import_core3.setApiResponse)(import_core3.HTTP.BAD_REQUEST, "MFA_NOT_ENABLED", "MFA is not enabled");
944
+ return (0, import_core4.setApiResponse)(import_core4.HTTP.BAD_REQUEST, "MFA_NOT_ENABLED", "MFA is not enabled");
950
945
  }
951
946
  const storedSecret = await users.getMfaSecret(ctx.user.id);
952
947
  const secret = storedSecret ? mfaCipher.decrypt(storedSecret) : null;
953
948
  if (!secret || !verifyTotpToken(token, secret)) {
954
- return (0, import_core3.setApiResponse)(import_core3.HTTP.UNAUTHORIZED, "INVALID_CODE", "Invalid TOTP code");
949
+ return (0, import_core4.setApiResponse)(import_core4.HTTP.UNAUTHORIZED, "INVALID_CODE", "Invalid TOTP code");
955
950
  }
956
951
  const plainCodes = generateBackupCodes();
957
952
  const codeHashes = await Promise.all(plainCodes.map((c) => hashPassword(c)));
@@ -963,7 +958,7 @@ function mfaController(store, config, issuer, bus) {
963
958
  recipient: { email: ctx.user.email, phone: null, deviceToken: null }
964
959
  }).catch(() => {
965
960
  });
966
- return (0, import_core3.setApiResponse)(import_core3.HTTP.OK, "BACKUP_CODES_REGENERATED", "Backup codes regenerated.", {
961
+ return (0, import_core4.setApiResponse)(import_core4.HTTP.OK, "BACKUP_CODES_REGENERATED", "Backup codes regenerated.", {
967
962
  backupCodes: plainCodes
968
963
  });
969
964
  },
@@ -972,16 +967,16 @@ function mfaController(store, config, issuer, bus) {
972
967
  const body = ctx.meta["body"];
973
968
  const token = body?.["token"];
974
969
  if (typeof token !== "string") {
975
- return (0, import_core3.setApiResponse)(import_core3.HTTP.UNPROCESSABLE, "INVALID_PARAMETER", "TOTP token is required");
970
+ return (0, import_core4.setApiResponse)(import_core4.HTTP.UNPROCESSABLE, "INVALID_PARAMETER", "TOTP token is required");
976
971
  }
977
972
  const user = await users.findById(ctx.user.id);
978
973
  if (!user || !user.mfaEnabled) {
979
- return (0, import_core3.setApiResponse)(import_core3.HTTP.BAD_REQUEST, "MFA_NOT_ENABLED", "MFA is not enabled");
974
+ return (0, import_core4.setApiResponse)(import_core4.HTTP.BAD_REQUEST, "MFA_NOT_ENABLED", "MFA is not enabled");
980
975
  }
981
976
  const storedSecret = await users.getMfaSecret(ctx.user.id);
982
977
  const secret = storedSecret ? mfaCipher.decrypt(storedSecret) : null;
983
978
  if (!secret || !verifyTotpToken(token, secret)) {
984
- return (0, import_core3.setApiResponse)(import_core3.HTTP.UNAUTHORIZED, "INVALID_CODE", "Invalid TOTP code");
979
+ return (0, import_core4.setApiResponse)(import_core4.HTTP.UNAUTHORIZED, "INVALID_CODE", "Invalid TOTP code");
985
980
  }
986
981
  await Promise.all([users.disableMfa(ctx.user.id), backupCodes.deleteByUser(ctx.user.id)]);
987
982
  bus?.emit(import_events.NOTIFICATION_EVENT, {
@@ -991,7 +986,7 @@ function mfaController(store, config, issuer, bus) {
991
986
  recipient: { email: user.email, phone: null, deviceToken: null }
992
987
  }).catch(() => {
993
988
  });
994
- return (0, import_core3.setApiResponse)(import_core3.HTTP.OK, "MFA_DISABLED", "MFA disabled successfully.");
989
+ return (0, import_core4.setApiResponse)(import_core4.HTTP.OK, "MFA_DISABLED", "MFA disabled successfully.");
995
990
  }
996
991
  };
997
992
  }
@@ -999,7 +994,7 @@ function mfaController(store, config, issuer, bus) {
999
994
  // src/controllers/auth.controller.ts
1000
995
  var import_node_crypto4 = require("crypto");
1001
996
  var import_events2 = require("@fonderie/events");
1002
- var import_core4 = require("@fonderie/core");
997
+ var import_core5 = require("@fonderie/core");
1003
998
 
1004
999
  // src/services/cooldown.ts
1005
1000
  function checkCooldown(lastSentAt, cooldownMs) {
@@ -1198,18 +1193,18 @@ function authController(store, config, bus) {
1198
1193
  if (typeof email2 === "string" && typeof password2 === "string") {
1199
1194
  const normalizedEmail = normalizeEmailSafe(email2);
1200
1195
  if (!normalizedEmail) {
1201
- return (0, import_core4.setApiResponse)(import_core4.HTTP.UNPROCESSABLE, "INVALID_PARAMETER", "Invalid email address");
1196
+ return (0, import_core5.setApiResponse)(import_core5.HTTP.UNPROCESSABLE, "INVALID_PARAMETER", "Invalid email address");
1202
1197
  }
1203
1198
  if (password2.length < 8) {
1204
- return (0, import_core4.setApiResponse)(
1205
- import_core4.HTTP.UNPROCESSABLE,
1199
+ return (0, import_core5.setApiResponse)(
1200
+ import_core5.HTTP.UNPROCESSABLE,
1206
1201
  "INVALID_PARAMETER",
1207
1202
  "password must be at least 8 characters"
1208
1203
  );
1209
1204
  }
1210
1205
  const existing = await users.findByEmail(normalizedEmail);
1211
1206
  if (existing) {
1212
- return (0, import_core4.setApiResponse)(import_core4.HTTP.CONFLICT, "USER_ALREADY_EXISTS", "Email already registered");
1207
+ return (0, import_core5.setApiResponse)(import_core5.HTTP.CONFLICT, "USER_ALREADY_EXISTS", "Email already registered");
1213
1208
  }
1214
1209
  const passwordHash = await hashPassword(password2);
1215
1210
  const row = await users.create(
@@ -1219,14 +1214,14 @@ function authController(store, config, bus) {
1219
1214
  lastName
1220
1215
  );
1221
1216
  if (!row) {
1222
- return (0, import_core4.setApiResponse)(import_core4.HTTP.SERVER_ERROR, "SERVER_ERROR", "Registration failed");
1217
+ return (0, import_core5.setApiResponse)(import_core5.HTTP.SERVER_ERROR, "SERVER_ERROR", "Registration failed");
1223
1218
  }
1224
1219
  const pin = (0, import_node_crypto4.randomInt)(1e5, 1e6).toString();
1225
1220
  const expiresAt = new Date(Date.now() + 1e3 * 60 * 60 * 24);
1226
1221
  await emailVerif.create(row.id, pin, expiresAt);
1227
1222
  const user = await users.findById(row.id);
1228
1223
  if (!user) {
1229
- return (0, import_core4.setApiResponse)(import_core4.HTTP.SERVER_ERROR, "SERVER_ERROR", "Registration failed");
1224
+ return (0, import_core5.setApiResponse)(import_core5.HTTP.SERVER_ERROR, "SERVER_ERROR", "Registration failed");
1230
1225
  }
1231
1226
  const reqId = ctx.meta["requestId"];
1232
1227
  const reqOpts = reqId !== void 0 ? { requestId: reqId } : void 0;
@@ -1278,7 +1273,7 @@ function authController(store, config, bus) {
1278
1273
  if (isValidPhone(phone2)) {
1279
1274
  const existing = await users.findByPhone(normalizePhone(phone2));
1280
1275
  if (existing) {
1281
- return (0, import_core4.setApiResponse)(import_core4.HTTP.CONFLICT, "USER_ALREADY_EXISTS", "Phone already registered");
1276
+ return (0, import_core5.setApiResponse)(import_core5.HTTP.CONFLICT, "USER_ALREADY_EXISTS", "Phone already registered");
1282
1277
  }
1283
1278
  const { id } = await users.findOrCreateByPhone(
1284
1279
  normalizePhone(phone2),
@@ -1290,7 +1285,7 @@ function authController(store, config, bus) {
1290
1285
  await phoneVerif.upsert(id, normalizePhone(phone2), otp, expiresAt);
1291
1286
  const user = await users.findById(id);
1292
1287
  if (!user) {
1293
- return (0, import_core4.setApiResponse)(import_core4.HTTP.SERVER_ERROR, "SERVER_ERROR", "Registration failed");
1288
+ return (0, import_core5.setApiResponse)(import_core5.HTTP.SERVER_ERROR, "SERVER_ERROR", "Registration failed");
1294
1289
  }
1295
1290
  const reqId2 = ctx.meta["requestId"];
1296
1291
  const reqOpts2 = reqId2 !== void 0 ? { requestId: reqId2 } : void 0;
@@ -1336,8 +1331,8 @@ function authController(store, config, bus) {
1336
1331
  }
1337
1332
  );
1338
1333
  }
1339
- return (0, import_core4.setApiResponse)(
1340
- import_core4.HTTP.UNPROCESSABLE,
1334
+ return (0, import_core5.setApiResponse)(
1335
+ import_core5.HTTP.UNPROCESSABLE,
1341
1336
  "INVALID_PARAMETER",
1342
1337
  "Provide email + password or a valid phone number"
1343
1338
  );
@@ -1348,11 +1343,11 @@ function authController(store, config, bus) {
1348
1343
  const { email: rawEmail, password: password2 } = body;
1349
1344
  const email2 = normalizeEmailSafe(rawEmail);
1350
1345
  if (!email2) {
1351
- return (0, import_core4.setApiResponse)(import_core4.HTTP.UNPROCESSABLE, "INVALID_PARAMETER", "Invalid email address");
1346
+ return (0, import_core5.setApiResponse)(import_core5.HTTP.UNPROCESSABLE, "INVALID_PARAMETER", "Invalid email address");
1352
1347
  }
1353
1348
  const user = await users.findByEmail(email2);
1354
1349
  if (!user || !user.passwordHash) {
1355
- return (0, import_core4.setApiResponse)(import_core4.HTTP.UNAUTHORIZED, "INVALID_CREDENTIALS", "Invalid credentials");
1350
+ return (0, import_core5.setApiResponse)(import_core5.HTTP.UNAUTHORIZED, "INVALID_CREDENTIALS", "Invalid credentials");
1356
1351
  }
1357
1352
  const { valid, needsRehash } = await verifyPasswordForLogin(
1358
1353
  password2,
@@ -1360,21 +1355,21 @@ function authController(store, config, bus) {
1360
1355
  config.legacyVerify
1361
1356
  );
1362
1357
  if (!valid) {
1363
- return (0, import_core4.setApiResponse)(import_core4.HTTP.UNAUTHORIZED, "INVALID_CREDENTIALS", "Invalid credentials");
1358
+ return (0, import_core5.setApiResponse)(import_core5.HTTP.UNAUTHORIZED, "INVALID_CREDENTIALS", "Invalid credentials");
1364
1359
  }
1365
1360
  if (needsRehash) {
1366
1361
  await users.updatePassword(user.id, await hashPassword(password2));
1367
1362
  }
1368
1363
  if (user.suspended) {
1369
- return (0, import_core4.setApiResponse)(
1370
- import_core4.HTTP.FORBIDDEN,
1364
+ return (0, import_core5.setApiResponse)(
1365
+ import_core5.HTTP.FORBIDDEN,
1371
1366
  "ACCOUNT_SUSPENDED",
1372
1367
  "Account suspended. Please contact support."
1373
1368
  );
1374
1369
  }
1375
1370
  if (user.mfaEnabled) {
1376
1371
  const mfaToken = issueMfaPendingToken(user.id, config, "email");
1377
- return (0, import_core4.setApiResponse)(import_core4.HTTP.OK, "MFA_REQUIRED", "Multi-factor authentication required", {
1372
+ return (0, import_core5.setApiResponse)(import_core5.HTTP.OK, "MFA_REQUIRED", "Multi-factor authentication required", {
1378
1373
  mfaToken
1379
1374
  });
1380
1375
  }
@@ -1404,11 +1399,11 @@ function authController(store, config, bus) {
1404
1399
  if (isValidPhone(phone2)) {
1405
1400
  const user = await users.findByPhone(normalizePhone(phone2));
1406
1401
  if (!user) {
1407
- return (0, import_core4.setApiResponse)(import_core4.HTTP.UNAUTHORIZED, "INVALID_CREDENTIALS", "Invalid credentials");
1402
+ return (0, import_core5.setApiResponse)(import_core5.HTTP.UNAUTHORIZED, "INVALID_CREDENTIALS", "Invalid credentials");
1408
1403
  }
1409
1404
  if (user.suspended) {
1410
- return (0, import_core4.setApiResponse)(
1411
- import_core4.HTTP.FORBIDDEN,
1405
+ return (0, import_core5.setApiResponse)(
1406
+ import_core5.HTTP.FORBIDDEN,
1412
1407
  "ACCOUNT_SUSPENDED",
1413
1408
  "Account suspended. Please contact support."
1414
1409
  );
@@ -1442,8 +1437,8 @@ function authController(store, config, bus) {
1442
1437
  }
1443
1438
  );
1444
1439
  }
1445
- return (0, import_core4.setApiResponse)(
1446
- import_core4.HTTP.UNPROCESSABLE,
1440
+ return (0, import_core5.setApiResponse)(
1441
+ import_core5.HTTP.UNPROCESSABLE,
1447
1442
  "INVALID_PARAMETER",
1448
1443
  "Provide email + password or a valid phone number"
1449
1444
  );
@@ -1464,23 +1459,23 @@ function authController(store, config, bus) {
1464
1459
  refresh: async (ctx) => {
1465
1460
  const token = extractRefreshToken(ctx);
1466
1461
  if (!token) {
1467
- return (0, import_core4.setApiResponse)(import_core4.HTTP.UNAUTHORIZED, "INVALID_PARAMETER", "No refresh token provided");
1462
+ return (0, import_core5.setApiResponse)(import_core5.HTTP.UNAUTHORIZED, "INVALID_PARAMETER", "No refresh token provided");
1468
1463
  }
1469
1464
  const payload = verifyToken(token, config);
1470
1465
  if (!payload || payload.type !== "refresh") {
1471
- return (0, import_core4.setApiResponse)(import_core4.HTTP.UNAUTHORIZED, "TOKEN_REFRESH_FAILED", "Invalid refresh token");
1466
+ return (0, import_core5.setApiResponse)(import_core5.HTTP.UNAUTHORIZED, "TOKEN_REFRESH_FAILED", "Invalid refresh token");
1472
1467
  }
1473
1468
  const valid = await sessions.exists(token);
1474
1469
  if (!valid) {
1475
- return (0, import_core4.setApiResponse)(
1476
- import_core4.HTTP.UNAUTHORIZED,
1470
+ return (0, import_core5.setApiResponse)(
1471
+ import_core5.HTTP.UNAUTHORIZED,
1477
1472
  "TOKEN_REFRESH_FAILED",
1478
1473
  "Session expired or already revoked"
1479
1474
  );
1480
1475
  }
1481
1476
  const user = await users.findById(payload.sub);
1482
1477
  if (!user || user.suspended || user.deletedAt) {
1483
- return (0, import_core4.setApiResponse)(import_core4.HTTP.UNAUTHORIZED, "UNAUTHORIZED", "Unauthorized");
1478
+ return (0, import_core5.setApiResponse)(import_core5.HTTP.UNAUTHORIZED, "UNAUTHORIZED", "Unauthorized");
1484
1479
  }
1485
1480
  await sessions.delete(token);
1486
1481
  const { accessToken, refreshToken, sid } = issueTokenPair(user.id, config, {
@@ -1504,16 +1499,16 @@ function authController(store, config, bus) {
1504
1499
  const body = ctx.meta["body"];
1505
1500
  const rawEmail = body?.["email"];
1506
1501
  if (typeof rawEmail !== "string") {
1507
- return (0, import_core4.setApiResponse)(import_core4.HTTP.UNPROCESSABLE, "INVALID_PARAMETER", "email is required");
1502
+ return (0, import_core5.setApiResponse)(import_core5.HTTP.UNPROCESSABLE, "INVALID_PARAMETER", "email is required");
1508
1503
  }
1509
1504
  const email2 = normalizeEmailSafe(rawEmail);
1510
1505
  if (!email2) {
1511
- return (0, import_core4.setApiResponse)(import_core4.HTTP.UNPROCESSABLE, "INVALID_PARAMETER", "Invalid email address");
1506
+ return (0, import_core5.setApiResponse)(import_core5.HTTP.UNPROCESSABLE, "INVALID_PARAMETER", "Invalid email address");
1512
1507
  }
1513
1508
  const user = await users.findByEmail(email2);
1514
1509
  if (!user) {
1515
- return (0, import_core4.setApiResponse)(
1516
- import_core4.HTTP.OK,
1510
+ return (0, import_core5.setApiResponse)(
1511
+ import_core5.HTTP.OK,
1517
1512
  "PASSWORD_RESET_EMAIL_SENT",
1518
1513
  "Password reset email sent (if account exists)."
1519
1514
  );
@@ -1522,8 +1517,8 @@ function authController(store, config, bus) {
1522
1517
  const cooldown = resolved.verificationCooldown ?? DEFAULT_VERIFICATION_COOLDOWN;
1523
1518
  const remaining = checkCooldown(await passwordReset.findLastSentAt(user.id), cooldown);
1524
1519
  if (remaining > 0) {
1525
- return (0, import_core4.setApiResponse)(
1526
- import_core4.HTTP.OK,
1520
+ return (0, import_core5.setApiResponse)(
1521
+ import_core5.HTTP.OK,
1527
1522
  "PASSWORD_RESET_EMAIL_SENT",
1528
1523
  "Password reset email sent (if account exists)."
1529
1524
  );
@@ -1538,8 +1533,8 @@ function authController(store, config, bus) {
1538
1533
  data: { pin }
1539
1534
  }).catch(() => {
1540
1535
  });
1541
- return (0, import_core4.setApiResponse)(
1542
- import_core4.HTTP.OK,
1536
+ return (0, import_core5.setApiResponse)(
1537
+ import_core5.HTTP.OK,
1543
1538
  "PASSWORD_RESET_EMAIL_SENT",
1544
1539
  "Password reset email sent (if account exists)."
1545
1540
  );
@@ -1549,22 +1544,22 @@ function authController(store, config, bus) {
1549
1544
  const raw = body?.["pin"];
1550
1545
  const password2 = body?.["password"];
1551
1546
  if (typeof raw !== "string" || typeof password2 !== "string") {
1552
- return (0, import_core4.setApiResponse)(
1553
- import_core4.HTTP.UNPROCESSABLE,
1547
+ return (0, import_core5.setApiResponse)(
1548
+ import_core5.HTTP.UNPROCESSABLE,
1554
1549
  "INVALID_PARAMETER",
1555
1550
  "pin and password are required"
1556
1551
  );
1557
1552
  }
1558
1553
  if (!/^\d{6}$/.test(raw.trim())) {
1559
- return (0, import_core4.setApiResponse)(
1560
- import_core4.HTTP.UNPROCESSABLE,
1554
+ return (0, import_core5.setApiResponse)(
1555
+ import_core5.HTTP.UNPROCESSABLE,
1561
1556
  "INVALID_PARAMETER",
1562
1557
  "pin must be a 6-digit code"
1563
1558
  );
1564
1559
  }
1565
1560
  if (password2.length < 8) {
1566
- return (0, import_core4.setApiResponse)(
1567
- import_core4.HTTP.UNPROCESSABLE,
1561
+ return (0, import_core5.setApiResponse)(
1562
+ import_core5.HTTP.UNPROCESSABLE,
1568
1563
  "INVALID_PARAMETER",
1569
1564
  "password must be at least 8 characters"
1570
1565
  );
@@ -1572,7 +1567,7 @@ function authController(store, config, bus) {
1572
1567
  const pin = raw.trim();
1573
1568
  const row = await passwordReset.findByPin(pin);
1574
1569
  if (!row || /* @__PURE__ */ new Date() > row.expiresAt) {
1575
- return (0, import_core4.setApiResponse)(import_core4.HTTP.BAD_REQUEST, "PASSWORD_RESET_FAILED", "Invalid or expired pin");
1570
+ return (0, import_core5.setApiResponse)(import_core5.HTTP.BAD_REQUEST, "PASSWORD_RESET_FAILED", "Invalid or expired pin");
1576
1571
  }
1577
1572
  const passwordHash = await hashPassword(password2);
1578
1573
  await store.transaction(async (tx) => {
@@ -1584,11 +1579,11 @@ function authController(store, config, bus) {
1584
1579
  tx.query(`DELETE FROM fonderie_password_resets WHERE user_id = $1`, [row.userId])
1585
1580
  ]);
1586
1581
  });
1587
- return (0, import_core4.setApiResponse)(import_core4.HTTP.OK, "PASSWORD_RESET_SUCCESSFUL", "Password reset successfully.");
1582
+ return (0, import_core5.setApiResponse)(import_core5.HTTP.OK, "PASSWORD_RESET_SUCCESSFUL", "Password reset successfully.");
1588
1583
  },
1589
1584
  verify: async (ctx) => {
1590
1585
  if (ctx.user.loginMethod !== "phone" && ctx.user.emailVerifiedAt) {
1591
- return (0, import_core4.setApiResponse)(import_core4.HTTP.OK, "VERIFIED", "Email verified successfully.", {
1586
+ return (0, import_core5.setApiResponse)(import_core5.HTTP.OK, "VERIFIED", "Email verified successfully.", {
1592
1587
  verified: true,
1593
1588
  email: ctx.user.email
1594
1589
  });
@@ -1596,8 +1591,8 @@ function authController(store, config, bus) {
1596
1591
  const body = ctx.meta["body"];
1597
1592
  const raw = body?.["token"];
1598
1593
  if (typeof raw !== "string" || !/^\d{6}$/.test(raw.trim())) {
1599
- return (0, import_core4.setApiResponse)(
1600
- import_core4.HTTP.UNPROCESSABLE,
1594
+ return (0, import_core5.setApiResponse)(
1595
+ import_core5.HTTP.UNPROCESSABLE,
1601
1596
  "INVALID_PARAMETER",
1602
1597
  "token must be a 6-digit code"
1603
1598
  );
@@ -1606,12 +1601,12 @@ function authController(store, config, bus) {
1606
1601
  if (ctx.user.loginMethod === "phone") {
1607
1602
  const record = await phoneVerif.findByUser(ctx.user.id, pin);
1608
1603
  if (!record) {
1609
- return (0, import_core4.setApiResponse)(import_core4.HTTP.BAD_REQUEST, "VERIFICATION_FAILED", "Invalid or expired pin");
1604
+ return (0, import_core5.setApiResponse)(import_core5.HTTP.BAD_REQUEST, "VERIFICATION_FAILED", "Invalid or expired pin");
1610
1605
  }
1611
1606
  if (/* @__PURE__ */ new Date() > record.expiresAt) {
1612
1607
  await phoneVerif.deleteByUser(ctx.user.id);
1613
- return (0, import_core4.setApiResponse)(
1614
- import_core4.HTTP.BAD_REQUEST,
1608
+ return (0, import_core5.setApiResponse)(
1609
+ import_core5.HTTP.BAD_REQUEST,
1615
1610
  "VERIFICATION_FAILED",
1616
1611
  "Verification code expired"
1617
1612
  );
@@ -1624,11 +1619,11 @@ function authController(store, config, bus) {
1624
1619
  await sessions.create(ctx.user.id, refreshToken, refreshTokenExpiry(refreshToken), sid);
1625
1620
  const verifiedUser = await users.findById(ctx.user.id);
1626
1621
  if (!verifiedUser) {
1627
- return (0, import_core4.setApiResponse)(import_core4.HTTP.NOT_FOUND, "NOT_FOUND", "User not found");
1622
+ return (0, import_core5.setApiResponse)(import_core5.HTTP.NOT_FOUND, "NOT_FOUND", "User not found");
1628
1623
  }
1629
1624
  if (verifiedUser.suspended) {
1630
- return (0, import_core4.setApiResponse)(
1631
- import_core4.HTTP.FORBIDDEN,
1625
+ return (0, import_core5.setApiResponse)(
1626
+ import_core5.HTTP.FORBIDDEN,
1632
1627
  "ACCOUNT_SUSPENDED",
1633
1628
  "Account suspended. Please contact support."
1634
1629
  );
@@ -1650,10 +1645,10 @@ function authController(store, config, bus) {
1650
1645
  }
1651
1646
  const row = await emailVerif.findByUser(ctx.user.id, pin);
1652
1647
  if (!row) {
1653
- return (0, import_core4.setApiResponse)(import_core4.HTTP.BAD_REQUEST, "VERIFICATION_FAILED", "Invalid or expired pin");
1648
+ return (0, import_core5.setApiResponse)(import_core5.HTTP.BAD_REQUEST, "VERIFICATION_FAILED", "Invalid or expired pin");
1654
1649
  }
1655
1650
  if (/* @__PURE__ */ new Date() > row.expiresAt) {
1656
- return (0, import_core4.setApiResponse)(import_core4.HTTP.BAD_REQUEST, "VERIFICATION_FAILED", "Pin expired");
1651
+ return (0, import_core5.setApiResponse)(import_core5.HTTP.BAD_REQUEST, "VERIFICATION_FAILED", "Pin expired");
1657
1652
  }
1658
1653
  await store.transaction(async (tx) => {
1659
1654
  await Promise.all([
@@ -1667,7 +1662,7 @@ function authController(store, config, bus) {
1667
1662
  ])
1668
1663
  ]);
1669
1664
  });
1670
- return (0, import_core4.setApiResponse)(import_core4.HTTP.OK, "VERIFIED", "Email verified successfully.", {
1665
+ return (0, import_core5.setApiResponse)(import_core5.HTTP.OK, "VERIFIED", "Email verified successfully.", {
1671
1666
  verified: true,
1672
1667
  email: ctx.user.email
1673
1668
  });
@@ -1678,16 +1673,16 @@ function authController(store, config, bus) {
1678
1673
  if (ctx.user.loginMethod === "phone") {
1679
1674
  const phone2 = ctx.user.phone;
1680
1675
  if (!phone2) {
1681
- return (0, import_core4.setApiResponse)(
1682
- import_core4.HTTP.BAD_REQUEST,
1676
+ return (0, import_core5.setApiResponse)(
1677
+ import_core5.HTTP.BAD_REQUEST,
1683
1678
  "NO_PHONE_ON_ACCOUNT",
1684
1679
  "No phone number associated with this account"
1685
1680
  );
1686
1681
  }
1687
1682
  const remaining2 = checkCooldown(await phoneVerif.findLastSentAt(ctx.user.id), cooldown);
1688
1683
  if (remaining2 > 0) {
1689
- return (0, import_core4.setApiResponse)(
1690
- import_core4.HTTP.TOO_MANY_REQUESTS,
1684
+ return (0, import_core5.setApiResponse)(
1685
+ import_core5.HTTP.TOO_MANY_REQUESTS,
1691
1686
  "VERIFICATION_COOLDOWN",
1692
1687
  "Please wait before requesting a new code.",
1693
1688
  {
@@ -1705,29 +1700,29 @@ function authController(store, config, bus) {
1705
1700
  recipient: { email: null, phone: phone2, deviceToken: null }
1706
1701
  }).catch(() => {
1707
1702
  });
1708
- return (0, import_core4.setApiResponse)(
1709
- import_core4.HTTP.OK,
1703
+ return (0, import_core5.setApiResponse)(
1704
+ import_core5.HTTP.OK,
1710
1705
  "VERIFICATION_SENT",
1711
1706
  "A verification code has been sent to your phone."
1712
1707
  );
1713
1708
  }
1714
1709
  if (!ctx.user.email) {
1715
- return (0, import_core4.setApiResponse)(
1716
- import_core4.HTTP.BAD_REQUEST,
1710
+ return (0, import_core5.setApiResponse)(
1711
+ import_core5.HTTP.BAD_REQUEST,
1717
1712
  "NO_EMAIL_ON_ACCOUNT",
1718
1713
  "No email address associated with this account"
1719
1714
  );
1720
1715
  }
1721
1716
  if (ctx.user.emailVerifiedAt) {
1722
- return (0, import_core4.setApiResponse)(import_core4.HTTP.OK, "EMAIL_VERIFIED", "Email already verified.", {
1717
+ return (0, import_core5.setApiResponse)(import_core5.HTTP.OK, "EMAIL_VERIFIED", "Email already verified.", {
1723
1718
  verified: true,
1724
1719
  email: ctx.user.email
1725
1720
  });
1726
1721
  }
1727
1722
  const remaining = checkCooldown(await emailVerif.findLastSentAt(ctx.user.id), cooldown);
1728
1723
  if (remaining > 0) {
1729
- return (0, import_core4.setApiResponse)(
1730
- import_core4.HTTP.TOO_MANY_REQUESTS,
1724
+ return (0, import_core5.setApiResponse)(
1725
+ import_core5.HTTP.TOO_MANY_REQUESTS,
1731
1726
  "VERIFICATION_COOLDOWN",
1732
1727
  "Please wait before requesting a new code.",
1733
1728
  {
@@ -1745,7 +1740,7 @@ function authController(store, config, bus) {
1745
1740
  data: { pin }
1746
1741
  }).catch(() => {
1747
1742
  });
1748
- return (0, import_core4.setApiResponse)(import_core4.HTTP.OK, "VERIFICATION_SENT", "Verification email sent.", {
1743
+ return (0, import_core5.setApiResponse)(import_core5.HTTP.OK, "VERIFICATION_SENT", "Verification email sent.", {
1749
1744
  email: ctx.user.email
1750
1745
  });
1751
1746
  }
@@ -1754,7 +1749,7 @@ function authController(store, config, bus) {
1754
1749
 
1755
1750
  // src/controllers/user.controller.ts
1756
1751
  var import_node_crypto5 = require("crypto");
1757
- var import_core5 = require("@fonderie/core");
1752
+ var import_core6 = require("@fonderie/core");
1758
1753
  var import_events3 = require("@fonderie/events");
1759
1754
  function normalizePhone2(phone2) {
1760
1755
  return phone2.trim().replace(/[\s()\-\.]/g, "");
@@ -1771,9 +1766,9 @@ function userController(store, config, bus) {
1771
1766
  me: async (ctx) => {
1772
1767
  const user = await users.findById(ctx.user.id);
1773
1768
  if (!user) {
1774
- return (0, import_core5.setApiResponse)(import_core5.HTTP.NOT_FOUND, "NOT_FOUND", "User not found");
1769
+ return (0, import_core6.setApiResponse)(import_core6.HTTP.NOT_FOUND, "NOT_FOUND", "User not found");
1775
1770
  }
1776
- return (0, import_core5.setApiResponse)(import_core5.HTTP.OK, "USER_ACCOUNT_FETCHED", "User account fetched successful.", {
1771
+ return (0, import_core6.setApiResponse)(import_core6.HTTP.OK, "USER_ACCOUNT_FETCHED", "User account fetched successful.", {
1777
1772
  user: toUserDTO(user, ctx.user.phoneVerified)
1778
1773
  });
1779
1774
  },
@@ -1785,18 +1780,18 @@ function userController(store, config, bus) {
1785
1780
  if (body?.[key] !== void 0) fields[key] = body[key];
1786
1781
  }
1787
1782
  if (Object.keys(fields).length === 0) {
1788
- return (0, import_core5.setApiResponse)(
1789
- import_core5.HTTP.UNPROCESSABLE,
1783
+ return (0, import_core6.setApiResponse)(
1784
+ import_core6.HTTP.UNPROCESSABLE,
1790
1785
  "INVALID_PARAMETER",
1791
1786
  "Provide at least one of: firstName, lastName, avatarUrl"
1792
1787
  );
1793
1788
  }
1794
1789
  const row = await users.update(ctx.user.id, fields);
1795
1790
  if (!row) {
1796
- return (0, import_core5.setApiResponse)(import_core5.HTTP.NOT_FOUND, "NOT_FOUND", "User not found");
1791
+ return (0, import_core6.setApiResponse)(import_core6.HTTP.NOT_FOUND, "NOT_FOUND", "User not found");
1797
1792
  }
1798
1793
  const updated = await users.findById(ctx.user.id);
1799
- return (0, import_core5.setApiResponse)(import_core5.HTTP.OK, "PROFILE_UPDATED", "Profile updated.", {
1794
+ return (0, import_core6.setApiResponse)(import_core6.HTTP.OK, "PROFILE_UPDATED", "Profile updated.", {
1800
1795
  user: toUserDTO(updated, ctx.user.phoneVerified)
1801
1796
  });
1802
1797
  },
@@ -1813,14 +1808,14 @@ function userController(store, config, bus) {
1813
1808
  if (Object.keys(patch).length > 0) fields.patch = patch;
1814
1809
  const row = await users.updatePreferences(ctx.user.id, fields);
1815
1810
  if (!row) {
1816
- return (0, import_core5.setApiResponse)(
1817
- import_core5.HTTP.UNPROCESSABLE,
1811
+ return (0, import_core6.setApiResponse)(
1812
+ import_core6.HTTP.UNPROCESSABLE,
1818
1813
  "INVALID_PARAMETER",
1819
1814
  "Provide at least one preference field"
1820
1815
  );
1821
1816
  }
1822
1817
  const updated = await users.findById(ctx.user.id);
1823
- return (0, import_core5.setApiResponse)(import_core5.HTTP.OK, "PREFERENCES_UPDATED", "Preferences updated.", {
1818
+ return (0, import_core6.setApiResponse)(import_core6.HTTP.OK, "PREFERENCES_UPDATED", "Preferences updated.", {
1824
1819
  user: toUserDTO(updated, ctx.user.phoneVerified)
1825
1820
  });
1826
1821
  },
@@ -1829,23 +1824,23 @@ function userController(store, config, bus) {
1829
1824
  const newEmail = body?.["email"];
1830
1825
  const normalised = typeof newEmail === "string" ? normalizeEmailSafe(newEmail) : null;
1831
1826
  if (!normalised) {
1832
- return (0, import_core5.setApiResponse)(
1833
- import_core5.HTTP.UNPROCESSABLE,
1827
+ return (0, import_core6.setApiResponse)(
1828
+ import_core6.HTTP.UNPROCESSABLE,
1834
1829
  "INVALID_PARAMETER",
1835
1830
  "A valid email address is required"
1836
1831
  );
1837
1832
  }
1838
1833
  const oldEmail = ctx.user.email;
1839
1834
  if (normalised === oldEmail) {
1840
- return (0, import_core5.setApiResponse)(
1841
- import_core5.HTTP.UNPROCESSABLE,
1835
+ return (0, import_core6.setApiResponse)(
1836
+ import_core6.HTTP.UNPROCESSABLE,
1842
1837
  "INVALID_PARAMETER",
1843
1838
  "New email must differ from current email"
1844
1839
  );
1845
1840
  }
1846
1841
  const existing = await users.findByEmail(normalised);
1847
1842
  if (existing) {
1848
- return (0, import_core5.setApiResponse)(import_core5.HTTP.CONFLICT, "EMAIL_IN_USE", "Email already in use");
1843
+ return (0, import_core6.setApiResponse)(import_core6.HTTP.CONFLICT, "EMAIL_IN_USE", "Email already in use");
1849
1844
  }
1850
1845
  const pin = (0, import_node_crypto5.randomInt)(1e5, 1e6).toString();
1851
1846
  const expiresAt = new Date(Date.now() + 1e3 * 60 * 60 * 24);
@@ -1867,8 +1862,8 @@ function userController(store, config, bus) {
1867
1862
  }).catch(() => {
1868
1863
  });
1869
1864
  }
1870
- return (0, import_core5.setApiResponse)(
1871
- import_core5.HTTP.OK,
1865
+ return (0, import_core6.setApiResponse)(
1866
+ import_core6.HTTP.OK,
1872
1867
  "EMAIL_UPDATED",
1873
1868
  "Email updated. A verification code has been sent to your new address.",
1874
1869
  {
@@ -1880,8 +1875,8 @@ function userController(store, config, bus) {
1880
1875
  const body = ctx.meta["body"];
1881
1876
  const newPhone = body?.["phone"];
1882
1877
  if (!isValidPhone2(newPhone)) {
1883
- return (0, import_core5.setApiResponse)(
1884
- import_core5.HTTP.UNPROCESSABLE,
1878
+ return (0, import_core6.setApiResponse)(
1879
+ import_core6.HTTP.UNPROCESSABLE,
1885
1880
  "INVALID_PARAMETER",
1886
1881
  "A valid phone number is required"
1887
1882
  );
@@ -1889,7 +1884,7 @@ function userController(store, config, bus) {
1889
1884
  const normalised = normalizePhone2(newPhone);
1890
1885
  const existing = await users.findByPhone(normalised);
1891
1886
  if (existing) {
1892
- return (0, import_core5.setApiResponse)(import_core5.HTTP.CONFLICT, "PHONE_IN_USE", "Phone number already in use");
1887
+ return (0, import_core6.setApiResponse)(import_core6.HTTP.CONFLICT, "PHONE_IN_USE", "Phone number already in use");
1893
1888
  }
1894
1889
  const otp = (0, import_node_crypto5.randomInt)(1e5, 1e6).toString();
1895
1890
  const expiresAt = new Date(Date.now() + 10 * 60 * 1e3);
@@ -1911,8 +1906,8 @@ function userController(store, config, bus) {
1911
1906
  }).catch(() => {
1912
1907
  });
1913
1908
  }
1914
- return (0, import_core5.setApiResponse)(
1915
- import_core5.HTTP.OK,
1909
+ return (0, import_core6.setApiResponse)(
1910
+ import_core6.HTTP.OK,
1916
1911
  "PHONE_UPDATED",
1917
1912
  "Phone number updated. A verification code has been sent to your new number.",
1918
1913
  {
@@ -1925,22 +1920,22 @@ function userController(store, config, bus) {
1925
1920
  const currentPassword = body?.["currentPassword"];
1926
1921
  const newPassword = body?.["newPassword"];
1927
1922
  if (typeof currentPassword !== "string" || !currentPassword) {
1928
- return (0, import_core5.setApiResponse)(import_core5.HTTP.UNPROCESSABLE, "INVALID_PARAMETER", "currentPassword is required");
1923
+ return (0, import_core6.setApiResponse)(import_core6.HTTP.UNPROCESSABLE, "INVALID_PARAMETER", "currentPassword is required");
1929
1924
  }
1930
1925
  if (typeof newPassword !== "string" || newPassword.length < 8) {
1931
- return (0, import_core5.setApiResponse)(import_core5.HTTP.UNPROCESSABLE, "INVALID_PARAMETER", "newPassword must be at least 8 characters");
1926
+ return (0, import_core6.setApiResponse)(import_core6.HTTP.UNPROCESSABLE, "INVALID_PARAMETER", "newPassword must be at least 8 characters");
1932
1927
  }
1933
1928
  const user = await users.findById(ctx.user.id);
1934
- if (!user) return (0, import_core5.setApiResponse)(import_core5.HTTP.NOT_FOUND, "NOT_FOUND", "User not found");
1929
+ if (!user) return (0, import_core6.setApiResponse)(import_core6.HTTP.NOT_FOUND, "NOT_FOUND", "User not found");
1935
1930
  const { hashPassword: hashPassword2, verifyPassword: verifyPassword2 } = await Promise.resolve().then(() => (init_password(), password_exports));
1936
1931
  const valid = await verifyPassword2(currentPassword, user.passwordHash ?? "");
1937
1932
  if (!valid) {
1938
- return (0, import_core5.setApiResponse)(import_core5.HTTP.UNPROCESSABLE, "INVALID_CREDENTIAL", "Current password is incorrect");
1933
+ return (0, import_core6.setApiResponse)(import_core6.HTTP.UNPROCESSABLE, "INVALID_CREDENTIAL", "Current password is incorrect");
1939
1934
  }
1940
1935
  const hash = await hashPassword2(newPassword);
1941
1936
  await users.updatePassword(ctx.user.id, hash);
1942
1937
  await sessions.deleteByUser(ctx.user.id);
1943
- return (0, import_core5.setApiResponse)(import_core5.HTTP.OK, "PASSWORD_CHANGED", "Password updated successfully.");
1938
+ return (0, import_core6.setApiResponse)(import_core6.HTTP.OK, "PASSWORD_CHANGED", "Password updated successfully.");
1944
1939
  },
1945
1940
  deleteMe: async (ctx) => {
1946
1941
  const userId = ctx.user.id;
@@ -1965,7 +1960,7 @@ function userController(store, config, bus) {
1965
1960
  // no MFA secret, no session tokens (session metadata only).
1966
1961
  exportMe: async (ctx) => {
1967
1962
  const user = await users.findById(ctx.user.id);
1968
- if (!user) return (0, import_core5.setApiResponse)(import_core5.HTTP.NOT_FOUND, "NOT_FOUND", "User not found");
1963
+ if (!user) return (0, import_core6.setApiResponse)(import_core6.HTTP.NOT_FOUND, "NOT_FOUND", "User not found");
1969
1964
  const sessionRows = await sessions.listByUser(ctx.user.id);
1970
1965
  const bundle = {
1971
1966
  exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -1974,8 +1969,8 @@ function userController(store, config, bus) {
1974
1969
  id: s.id,
1975
1970
  userAgent: s.userAgent,
1976
1971
  ipAddress: s.ipAddress,
1977
- createdAt: s.createdAt instanceof Date ? s.createdAt.toISOString() : s.createdAt,
1978
- expiresAt: s.expiresAt instanceof Date ? s.expiresAt.toISOString() : s.expiresAt
1972
+ createdAt: (0, import_core6.dateOrEmpty)(s.createdAt),
1973
+ expiresAt: (0, import_core6.dateOrEmpty)(s.expiresAt)
1979
1974
  })),
1980
1975
  security: {
1981
1976
  mfaEnabled: user.mfaEnabled === true,
@@ -1994,13 +1989,13 @@ function userController(store, config, bus) {
1994
1989
  }
1995
1990
  bundle["modules"] = modules;
1996
1991
  }
1997
- return (0, import_core5.setApiResponse)(import_core5.HTTP.OK, "DATA_EXPORT", "Your account data.", bundle);
1992
+ return (0, import_core6.setApiResponse)(import_core6.HTTP.OK, "DATA_EXPORT", "Your account data.", bundle);
1998
1993
  }
1999
1994
  };
2000
1995
  }
2001
1996
 
2002
1997
  // src/controllers/oauth.controller.ts
2003
- var import_core6 = require("@fonderie/core");
1998
+ var import_core7 = require("@fonderie/core");
2004
1999
  function oauthController(store, config) {
2005
2000
  const users = new UserModel(store);
2006
2001
  const sessions = new SessionModel(store);
@@ -2008,8 +2003,8 @@ function oauthController(store, config) {
2008
2003
  googleInit: async (_ctx) => {
2009
2004
  const google = config.google;
2010
2005
  if (!google) {
2011
- return (0, import_core6.setApiResponse)(
2012
- import_core6.HTTP.NOT_IMPLEMENTED,
2006
+ return (0, import_core7.setApiResponse)(
2007
+ import_core7.HTTP.NOT_IMPLEMENTED,
2013
2008
  "NOT_CONFIGURED",
2014
2009
  "Google OAuth not configured"
2015
2010
  );
@@ -2021,8 +2016,8 @@ function oauthController(store, config) {
2021
2016
  scope: "openid email profile"
2022
2017
  });
2023
2018
  const url = `https://accounts.google.com/o/oauth2/v2/auth?${params}`;
2024
- return (0, import_core6.setApiResponse)(
2025
- import_core6.HTTP.OK,
2019
+ return (0, import_core7.setApiResponse)(
2020
+ import_core7.HTTP.OK,
2026
2021
  "GOOGLE_AUTH_URL",
2027
2022
  "Redirect the user to the returned URL to begin Google OAuth.",
2028
2023
  { url }
@@ -2031,8 +2026,8 @@ function oauthController(store, config) {
2031
2026
  googleCallback: async (ctx) => {
2032
2027
  const google = config.google;
2033
2028
  if (!google) {
2034
- return (0, import_core6.setApiResponse)(
2035
- import_core6.HTTP.NOT_IMPLEMENTED,
2029
+ return (0, import_core7.setApiResponse)(
2030
+ import_core7.HTTP.NOT_IMPLEMENTED,
2036
2031
  "NOT_CONFIGURED",
2037
2032
  "Google OAuth not configured"
2038
2033
  );
@@ -2040,7 +2035,7 @@ function oauthController(store, config) {
2040
2035
  const url = new URL(ctx.request.url);
2041
2036
  const code = url.searchParams.get("code");
2042
2037
  if (!code) {
2043
- return (0, import_core6.setApiResponse)(import_core6.HTTP.BAD_REQUEST, "INVALID_PARAMETER", "Missing code");
2038
+ return (0, import_core7.setApiResponse)(import_core7.HTTP.BAD_REQUEST, "INVALID_PARAMETER", "Missing code");
2044
2039
  }
2045
2040
  const tokenRes = await fetch("https://oauth2.googleapis.com/token", {
2046
2041
  method: "POST",
@@ -2055,8 +2050,8 @@ function oauthController(store, config) {
2055
2050
  });
2056
2051
  const tokenData = await tokenRes.json();
2057
2052
  if (!tokenData.id_token) {
2058
- return (0, import_core6.setApiResponse)(
2059
- import_core6.HTTP.BAD_REQUEST,
2053
+ return (0, import_core7.setApiResponse)(
2054
+ import_core7.HTTP.BAD_REQUEST,
2060
2055
  "GOOGLE_AUTH_FAILED",
2061
2056
  "OAuth token exchange failed"
2062
2057
  );
@@ -2065,19 +2060,19 @@ function oauthController(store, config) {
2065
2060
  Buffer.from(tokenData.id_token.split(".")[1] ?? "", "base64url").toString()
2066
2061
  );
2067
2062
  if (!payload.email) {
2068
- return (0, import_core6.setApiResponse)(import_core6.HTTP.BAD_REQUEST, "GOOGLE_AUTH_FAILED", "No email in OAuth response");
2063
+ return (0, import_core7.setApiResponse)(import_core7.HTTP.BAD_REQUEST, "GOOGLE_AUTH_FAILED", "No email in OAuth response");
2069
2064
  }
2070
2065
  const normalizedEmail = normalizeEmailSafe(payload.email);
2071
2066
  if (!normalizedEmail) {
2072
- return (0, import_core6.setApiResponse)(import_core6.HTTP.BAD_REQUEST, "GOOGLE_AUTH_FAILED", "Invalid email in OAuth response");
2067
+ return (0, import_core7.setApiResponse)(import_core7.HTTP.BAD_REQUEST, "GOOGLE_AUTH_FAILED", "Invalid email in OAuth response");
2073
2068
  }
2074
2069
  const upserted = await users.upsertByProvider(normalizedEmail, "google", payload.sub ?? "");
2075
2070
  if (!upserted) {
2076
- return (0, import_core6.setApiResponse)(import_core6.HTTP.SERVER_ERROR, "SERVER_ERROR", "OAuth login failed");
2071
+ return (0, import_core7.setApiResponse)(import_core7.HTTP.SERVER_ERROR, "SERVER_ERROR", "OAuth login failed");
2077
2072
  }
2078
2073
  const fullUser = await users.findById(upserted.id);
2079
2074
  if (!fullUser) {
2080
- return (0, import_core6.setApiResponse)(import_core6.HTTP.SERVER_ERROR, "SERVER_ERROR", "OAuth login failed");
2075
+ return (0, import_core7.setApiResponse)(import_core7.HTTP.SERVER_ERROR, "SERVER_ERROR", "OAuth login failed");
2081
2076
  }
2082
2077
  const { accessToken, refreshToken, sid } = issueTokenPair(upserted.id, config, {
2083
2078
  loginMethod: "google"
@@ -2159,19 +2154,19 @@ function buildAuthRoutes(store, config, bus) {
2159
2154
  }
2160
2155
 
2161
2156
  // src/services/config-guard.ts
2157
+ var import_core8 = require("@fonderie/core");
2162
2158
  var MODULE = "@fonderie/auth";
2163
- var PLACEHOLDER_SECRET = /dev-secret|test-secret|changeme|change-me|your[-_]secret|placeholder|example|insecure|min-32-chars/i;
2164
- var MIN_SECRET_LENGTH = 32;
2165
2159
  function collectAuthConfigProblems(config) {
2166
2160
  const problems = [];
2167
2161
  const secret = config.jwtSecret ?? "";
2168
- if (secret.length < MIN_SECRET_LENGTH) {
2162
+ const secretProblem = (0, import_core8.secretStrengthProblem)(secret);
2163
+ if (secretProblem === "too-short") {
2169
2164
  problems.push({
2170
2165
  module: MODULE,
2171
2166
  severity: "error",
2172
- message: `jwtSecret must be at least ${MIN_SECRET_LENGTH} characters (got ${secret.length})`
2167
+ message: `jwtSecret must be at least ${import_core8.MIN_SECRET_LENGTH} characters (got ${secret.length})`
2173
2168
  });
2174
- } else if (PLACEHOLDER_SECRET.test(secret)) {
2169
+ } else if (secretProblem === "placeholder") {
2175
2170
  problems.push({
2176
2171
  module: MODULE,
2177
2172
  severity: "error",
@@ -2193,7 +2188,7 @@ function collectAuthConfigProblems(config) {
2193
2188
  severity: "error",
2194
2189
  message: "google OAuth is configured but clientId, clientSecret, or redirectUri is missing"
2195
2190
  });
2196
- } else if (PLACEHOLDER_SECRET.test(clientSecret)) {
2191
+ } else if (import_core8.PLACEHOLDER_SECRET.test(clientSecret)) {
2197
2192
  problems.push({
2198
2193
  module: MODULE,
2199
2194
  severity: "error",