@fonderie/auth 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -59,14 +59,16 @@ __export(index_exports, {
59
59
  MESSAGE_KEYS: () => MESSAGE_KEYS,
60
60
  normalizeEmail: () => normalizeEmail,
61
61
  normalizeEmailSafe: () => normalizeEmailSafe,
62
- requireAuth: () => import_middlewares2.requireAuth,
62
+ requireAuth: () => import_middlewares3.requireAuth,
63
+ schemas: () => schemas_exports,
63
64
  toUserDTO: () => toUserDTO,
65
+ validate: () => import_middlewares.validate,
64
66
  withSession: () => withSession
65
67
  });
66
68
  module.exports = __toCommonJS(index_exports);
67
69
 
68
70
  // src/routes.ts
69
- var import_middlewares = require("@fonderie/core/middlewares");
71
+ var import_middlewares2 = require("@fonderie/core/middlewares");
70
72
 
71
73
  // src/middlewares/require-email-login.ts
72
74
  var import_core = require("@fonderie/core");
@@ -81,6 +83,74 @@ var requireEmailLogin = async (ctx, next) => {
81
83
  return next();
82
84
  };
83
85
 
86
+ // src/middlewares/validate.ts
87
+ var import_middlewares = require("@fonderie/core/middlewares");
88
+
89
+ // src/schemas.ts
90
+ var schemas_exports = {};
91
+ __export(schemas_exports, {
92
+ changePasswordSchema: () => changePasswordSchema,
93
+ forgotPasswordSchema: () => forgotPasswordSchema,
94
+ loginSchema: () => loginSchema,
95
+ mfaTokenSchema: () => mfaTokenSchema,
96
+ refreshSchema: () => refreshSchema,
97
+ registerSchema: () => registerSchema,
98
+ resetPasswordSchema: () => resetPasswordSchema,
99
+ updateEmailSchema: () => updateEmailSchema,
100
+ updatePhoneSchema: () => updatePhoneSchema,
101
+ updatePreferencesSchema: () => updatePreferencesSchema,
102
+ updateProfileSchema: () => updateProfileSchema,
103
+ verifySchema: () => verifySchema
104
+ });
105
+ var import_zod = require("zod");
106
+ var email = import_zod.z.string().trim().pipe(import_zod.z.email());
107
+ var password = import_zod.z.string().min(8, "password must be at least 8 characters").max(128);
108
+ var phone = import_zod.z.string().refine((v) => /^\+?[1-9]\d{6,14}$/.test(v.replace(/[\s\-()]/g, "")), "Invalid phone number");
109
+ var sixDigitPin = import_zod.z.string().trim().regex(/^\d{6}$/, "must be a 6-digit code");
110
+ var registerSchema = import_zod.z.union([
111
+ import_zod.z.object({
112
+ email,
113
+ password,
114
+ firstName: import_zod.z.string().max(100).nullish(),
115
+ lastName: import_zod.z.string().max(100).nullish()
116
+ }),
117
+ import_zod.z.object({ phone })
118
+ ]);
119
+ var loginSchema = import_zod.z.union([
120
+ import_zod.z.object({ email, password: import_zod.z.string().min(1).max(128) }),
121
+ import_zod.z.object({ phone })
122
+ ]);
123
+ var refreshSchema = import_zod.z.object({ refreshToken: import_zod.z.string().min(1).optional() });
124
+ var forgotPasswordSchema = import_zod.z.object({ email });
125
+ var resetPasswordSchema = import_zod.z.object({ pin: sixDigitPin, password });
126
+ var verifySchema = import_zod.z.object({ token: sixDigitPin });
127
+ var updateProfileSchema = import_zod.z.object({
128
+ firstName: import_zod.z.string().max(100).nullable().optional(),
129
+ lastName: import_zod.z.string().max(100).nullable().optional(),
130
+ avatarUrl: import_zod.z.string().trim().pipe(import_zod.z.url()).nullable().optional()
131
+ }).refine(
132
+ (o) => Object.values(o).some((v) => v !== void 0),
133
+ "Provide at least one of: firstName, lastName, avatarUrl"
134
+ );
135
+ var updatePreferencesSchema = import_zod.z.object({
136
+ locale: import_zod.z.string().max(35).optional(),
137
+ timezone: import_zod.z.string().max(64).optional(),
138
+ notifications: import_zod.z.unknown().optional(),
139
+ emailDigest: import_zod.z.unknown().optional(),
140
+ dateFormat: import_zod.z.unknown().optional(),
141
+ timeFormat: import_zod.z.unknown().optional()
142
+ }).refine(
143
+ (o) => Object.values(o).some((v) => v !== void 0),
144
+ "Provide at least one preference field"
145
+ );
146
+ var updateEmailSchema = import_zod.z.object({ email });
147
+ var updatePhoneSchema = import_zod.z.object({ phone });
148
+ var changePasswordSchema = import_zod.z.object({
149
+ currentPassword: import_zod.z.string().min(1).max(128),
150
+ newPassword: password
151
+ });
152
+ var mfaTokenSchema = import_zod.z.object({ token: import_zod.z.string().trim().min(6).max(64) });
153
+
84
154
  // src/controllers/mfa.controller.ts
85
155
  var import_qrcode = __toESM(require("qrcode"), 1);
86
156
  var import_core3 = require("@fonderie/core");
@@ -217,7 +287,7 @@ function base32Encode(buf) {
217
287
  function generateTotpSecret() {
218
288
  return base32Encode((0, import_node_crypto.randomBytes)(20));
219
289
  }
220
- function generateTotpUri(email, secret, issuer) {
290
+ function generateTotpUri(email2, secret, issuer) {
221
291
  const params = new URLSearchParams({
222
292
  secret,
223
293
  issuer,
@@ -225,7 +295,7 @@ function generateTotpUri(email, secret, issuer) {
225
295
  digits: String(DIGITS),
226
296
  period: String(STEP)
227
297
  });
228
- return `otpauth://totp/${encodeURIComponent(issuer)}:${encodeURIComponent(email)}?${params}`;
298
+ return `otpauth://totp/${encodeURIComponent(issuer)}:${encodeURIComponent(email2)}?${params}`;
229
299
  }
230
300
  function verifyTotpToken(token, secret) {
231
301
  const t = timeCounter();
@@ -317,21 +387,21 @@ var UserModel = class {
317
387
  );
318
388
  return row ?? null;
319
389
  }
320
- async findByEmail(email) {
390
+ async findByEmail(email2) {
321
391
  const [row] = await this.store.query(
322
392
  `SELECT ${USER_COLUMNS} FROM fonderie_users WHERE email = $1 AND deleted_at IS NULL`,
323
- [email]
393
+ [email2]
324
394
  );
325
395
  return row ?? null;
326
396
  }
327
- async findByPhone(phone) {
397
+ async findByPhone(phone2) {
328
398
  const [row] = await this.store.query(
329
399
  `SELECT ${USER_COLUMNS} FROM fonderie_users WHERE phone = $1 AND deleted_at IS NULL`,
330
- [phone]
400
+ [phone2]
331
401
  );
332
402
  return row ?? null;
333
403
  }
334
- async findOrCreateByPhone(phone, firstName = null, lastName = null) {
404
+ async findOrCreateByPhone(phone2, firstName = null, lastName = null) {
335
405
  const [row] = await this.store.query(
336
406
  `INSERT INTO fonderie_users (phone, first_name, last_name)
337
407
  VALUES ($1, $2, $3)
@@ -340,16 +410,16 @@ var UserModel = class {
340
410
  last_name = COALESCE(EXCLUDED.last_name, fonderie_users.last_name),
341
411
  updated_at = now()
342
412
  RETURNING id`,
343
- [phone, firstName, lastName]
413
+ [phone2, firstName, lastName]
344
414
  );
345
415
  return row;
346
416
  }
347
- async create(email, passwordHash, firstName, lastName) {
417
+ async create(email2, passwordHash, firstName, lastName) {
348
418
  const [row] = await this.store.query(
349
419
  `INSERT INTO fonderie_users (email, password_hash, first_name, last_name)
350
420
  VALUES ($1, $2, $3, $4)
351
421
  RETURNING id`,
352
- [email.toLowerCase().trim(), passwordHash, firstName, lastName]
422
+ [email2.toLowerCase().trim(), passwordHash, firstName, lastName]
353
423
  );
354
424
  return row ?? null;
355
425
  }
@@ -436,16 +506,16 @@ var UserModel = class {
436
506
  [id]
437
507
  );
438
508
  }
439
- async updateEmail(id, email) {
509
+ async updateEmail(id, email2) {
440
510
  await this.store.query(
441
511
  `UPDATE fonderie_users SET email = $1, email_verified_at = NULL, updated_at = now() WHERE id = $2`,
442
- [email.toLowerCase().trim(), id]
512
+ [email2.toLowerCase().trim(), id]
443
513
  );
444
514
  }
445
- async updatePhone(id, phone) {
515
+ async updatePhone(id, phone2) {
446
516
  await this.store.query(
447
517
  `UPDATE fonderie_users SET phone = $1, updated_at = now() WHERE id = $2`,
448
- [phone, id]
518
+ [phone2, id]
449
519
  );
450
520
  }
451
521
  async updatePreferences(id, fields) {
@@ -478,14 +548,14 @@ var UserModel = class {
478
548
  );
479
549
  return row?.mfa_secret ?? null;
480
550
  }
481
- async upsertByProvider(email, provider, providerId) {
551
+ async upsertByProvider(email2, provider, providerId) {
482
552
  const [row] = await this.store.query(
483
553
  `INSERT INTO fonderie_users (email, email_verified_at, provider, provider_id)
484
554
  VALUES ($1, now(), $2, $3)
485
555
  ON CONFLICT (email) DO UPDATE
486
556
  SET provider = $2, provider_id = $3
487
557
  RETURNING id`,
488
- [email, provider, providerId]
558
+ [email2, provider, providerId]
489
559
  );
490
560
  return row ?? null;
491
561
  }
@@ -742,11 +812,11 @@ function checkCooldown(lastSentAt, cooldownMs) {
742
812
  init_password();
743
813
 
744
814
  // src/services/email.ts
745
- function normalizeEmail(email) {
746
- if (typeof email !== "string" || email.length === 0) {
815
+ function normalizeEmail(email2) {
816
+ if (typeof email2 !== "string" || email2.length === 0) {
747
817
  throw new Error("Invalid email");
748
818
  }
749
- const lower = email.trim().toLowerCase();
819
+ const lower = email2.trim().toLowerCase();
750
820
  if (lower.length === 0) {
751
821
  throw new Error("Email cannot be empty");
752
822
  }
@@ -766,9 +836,9 @@ function normalizeEmail(email) {
766
836
  }
767
837
  return `${normalizedLocal}@${domain}`;
768
838
  }
769
- function normalizeEmailSafe(email) {
839
+ function normalizeEmailSafe(email2) {
770
840
  try {
771
- return normalizeEmail(email);
841
+ return normalizeEmail(email2);
772
842
  } catch {
773
843
  return null;
774
844
  }
@@ -869,13 +939,13 @@ var PhoneVerificationModel = class {
869
939
  this.store = store;
870
940
  }
871
941
  store;
872
- async upsert(userId, phone, otp, expiresAt) {
942
+ async upsert(userId, phone2, otp, expiresAt) {
873
943
  await this.store.query(
874
944
  `INSERT INTO fonderie_phone_verifications (phone, user_id, otp, expires_at)
875
945
  VALUES ($1, $2, $3, $4)
876
946
  ON CONFLICT (phone) DO UPDATE
877
947
  SET user_id = $2, otp = $3, expires_at = $4, created_at = now()`,
878
- [phone, userId, otp, expiresAt]
948
+ [phone2, userId, otp, expiresAt]
879
949
  );
880
950
  }
881
951
  async findByUser(userId, otp) {
@@ -900,11 +970,11 @@ var PhoneVerificationModel = class {
900
970
  };
901
971
 
902
972
  // src/controllers/auth.controller.ts
903
- function normalizePhone(phone) {
904
- return phone.trim().replace(/[\s()\-\.]/g, "");
973
+ function normalizePhone(phone2) {
974
+ return phone2.trim().replace(/[\s()\-\.]/g, "");
905
975
  }
906
- function isValidPhone(phone) {
907
- return typeof phone === "string" && /^\+?[1-9]\d{6,14}$/.test(normalizePhone(phone));
976
+ function isValidPhone(phone2) {
977
+ return typeof phone2 === "string" && /^\+?[1-9]\d{6,14}$/.test(normalizePhone(phone2));
908
978
  }
909
979
  function extractRefreshToken(ctx) {
910
980
  const body = ctx.meta["body"];
@@ -925,13 +995,13 @@ function authController(store, config, bus) {
925
995
  return {
926
996
  register: async (ctx) => {
927
997
  const body = ctx.meta["body"];
928
- const { email, password, phone, firstName = null, lastName = null } = body ?? {};
929
- if (typeof email === "string" && typeof password === "string") {
930
- const normalizedEmail = normalizeEmailSafe(email);
998
+ const { email: email2, password: password2, phone: phone2, firstName = null, lastName = null } = body ?? {};
999
+ if (typeof email2 === "string" && typeof password2 === "string") {
1000
+ const normalizedEmail = normalizeEmailSafe(email2);
931
1001
  if (!normalizedEmail) {
932
1002
  return (0, import_core4.setApiResponse)(import_core4.HTTP.UNPROCESSABLE, "INVALID_PARAMETER", "Invalid email address");
933
1003
  }
934
- if (password.length < 8) {
1004
+ if (password2.length < 8) {
935
1005
  return (0, import_core4.setApiResponse)(
936
1006
  import_core4.HTTP.UNPROCESSABLE,
937
1007
  "INVALID_PARAMETER",
@@ -942,7 +1012,7 @@ function authController(store, config, bus) {
942
1012
  if (existing) {
943
1013
  return (0, import_core4.setApiResponse)(import_core4.HTTP.CONFLICT, "USER_ALREADY_EXISTS", "Email already registered");
944
1014
  }
945
- const passwordHash = await hashPassword(password);
1015
+ const passwordHash = await hashPassword(password2);
946
1016
  const row = await users.create(
947
1017
  normalizedEmail,
948
1018
  passwordHash,
@@ -1010,19 +1080,19 @@ function authController(store, config, bus) {
1010
1080
  }
1011
1081
  );
1012
1082
  }
1013
- if (isValidPhone(phone)) {
1014
- const existing = await users.findByPhone(normalizePhone(phone));
1083
+ if (isValidPhone(phone2)) {
1084
+ const existing = await users.findByPhone(normalizePhone(phone2));
1015
1085
  if (existing) {
1016
1086
  return (0, import_core4.setApiResponse)(import_core4.HTTP.CONFLICT, "USER_ALREADY_EXISTS", "Phone already registered");
1017
1087
  }
1018
1088
  const { id } = await users.findOrCreateByPhone(
1019
- normalizePhone(phone),
1089
+ normalizePhone(phone2),
1020
1090
  firstName ?? null,
1021
1091
  lastName ?? null
1022
1092
  );
1023
1093
  const otp = (0, import_node_crypto2.randomInt)(1e5, 1e6).toString();
1024
1094
  const expiresAt = new Date(Date.now() + OTP_TTL_MS);
1025
- await phoneVerif.upsert(id, normalizePhone(phone), otp, expiresAt);
1095
+ await phoneVerif.upsert(id, normalizePhone(phone2), otp, expiresAt);
1026
1096
  const user = await users.findById(id);
1027
1097
  if (!user) {
1028
1098
  return (0, import_core4.setApiResponse)(import_core4.HTTP.SERVER_ERROR, "SERVER_ERROR", "Registration failed");
@@ -1034,7 +1104,7 @@ function authController(store, config, bus) {
1034
1104
  {
1035
1105
  type: MESSAGE_KEYS.phoneOtp,
1036
1106
  data: { otp },
1037
- recipient: { email: null, phone: normalizePhone(phone), deviceToken: null }
1107
+ recipient: { email: null, phone: normalizePhone(phone2), deviceToken: null }
1038
1108
  },
1039
1109
  reqOpts2
1040
1110
  ).catch(() => {
@@ -1084,16 +1154,16 @@ function authController(store, config, bus) {
1084
1154
  login: async (ctx) => {
1085
1155
  const body = ctx.meta["body"];
1086
1156
  if (typeof body?.["email"] === "string" && typeof body?.["password"] === "string") {
1087
- const { email: rawEmail, password } = body;
1088
- const email = normalizeEmailSafe(rawEmail);
1089
- if (!email) {
1157
+ const { email: rawEmail, password: password2 } = body;
1158
+ const email2 = normalizeEmailSafe(rawEmail);
1159
+ if (!email2) {
1090
1160
  return (0, import_core4.setApiResponse)(import_core4.HTTP.UNPROCESSABLE, "INVALID_PARAMETER", "Invalid email address");
1091
1161
  }
1092
- const user = await users.findByEmail(email);
1162
+ const user = await users.findByEmail(email2);
1093
1163
  if (!user || !user.passwordHash) {
1094
1164
  return (0, import_core4.setApiResponse)(import_core4.HTTP.UNAUTHORIZED, "INVALID_CREDENTIALS", "Invalid credentials");
1095
1165
  }
1096
- const valid = await verifyPassword(password, user.passwordHash);
1166
+ const valid = await verifyPassword(password2, user.passwordHash);
1097
1167
  if (!valid) {
1098
1168
  return (0, import_core4.setApiResponse)(import_core4.HTTP.UNAUTHORIZED, "INVALID_CREDENTIALS", "Invalid credentials");
1099
1169
  }
@@ -1137,9 +1207,9 @@ function authController(store, config, bus) {
1137
1207
  }
1138
1208
  );
1139
1209
  }
1140
- const phone = body?.["phone"];
1141
- if (isValidPhone(phone)) {
1142
- const user = await users.findByPhone(normalizePhone(phone));
1210
+ const phone2 = body?.["phone"];
1211
+ if (isValidPhone(phone2)) {
1212
+ const user = await users.findByPhone(normalizePhone(phone2));
1143
1213
  if (!user) {
1144
1214
  return (0, import_core4.setApiResponse)(import_core4.HTTP.UNAUTHORIZED, "INVALID_CREDENTIALS", "Invalid credentials");
1145
1215
  }
@@ -1152,11 +1222,11 @@ function authController(store, config, bus) {
1152
1222
  }
1153
1223
  const otp = (0, import_node_crypto2.randomInt)(1e5, 1e6).toString();
1154
1224
  const expiresAt = new Date(Date.now() + OTP_TTL_MS);
1155
- await phoneVerif.upsert(user.id, normalizePhone(phone), otp, expiresAt);
1225
+ await phoneVerif.upsert(user.id, normalizePhone(phone2), otp, expiresAt);
1156
1226
  bus?.emit(import_events2.NOTIFICATION_EVENT, {
1157
1227
  type: MESSAGE_KEYS.phoneOtp,
1158
1228
  data: { otp },
1159
- recipient: { email: null, phone: normalizePhone(phone), deviceToken: null }
1229
+ recipient: { email: null, phone: normalizePhone(phone2), deviceToken: null }
1160
1230
  }).catch(() => {
1161
1231
  });
1162
1232
  const { accessToken, refreshToken } = issueTokenPair(user.id, config, {
@@ -1257,11 +1327,11 @@ function authController(store, config, bus) {
1257
1327
  if (typeof rawEmail !== "string") {
1258
1328
  return (0, import_core4.setApiResponse)(import_core4.HTTP.UNPROCESSABLE, "INVALID_PARAMETER", "email is required");
1259
1329
  }
1260
- const email = normalizeEmailSafe(rawEmail);
1261
- if (!email) {
1330
+ const email2 = normalizeEmailSafe(rawEmail);
1331
+ if (!email2) {
1262
1332
  return (0, import_core4.setApiResponse)(import_core4.HTTP.UNPROCESSABLE, "INVALID_PARAMETER", "Invalid email address");
1263
1333
  }
1264
- const user = await users.findByEmail(email);
1334
+ const user = await users.findByEmail(email2);
1265
1335
  if (!user) {
1266
1336
  return (0, import_core4.setApiResponse)(
1267
1337
  import_core4.HTTP.OK,
@@ -1287,7 +1357,7 @@ function authController(store, config, bus) {
1287
1357
  await passwordReset.create(user.id, pin, expiresAt);
1288
1358
  bus?.emit(import_events2.NOTIFICATION_EVENT, {
1289
1359
  type: MESSAGE_KEYS.passwordReset,
1290
- recipient: { email, phone: null, deviceToken: null },
1360
+ recipient: { email: email2, phone: null, deviceToken: null },
1291
1361
  data: { pin }
1292
1362
  }).catch(() => {
1293
1363
  });
@@ -1300,8 +1370,8 @@ function authController(store, config, bus) {
1300
1370
  resetPassword: async (ctx) => {
1301
1371
  const body = ctx.meta["body"];
1302
1372
  const raw = body?.["pin"];
1303
- const password = body?.["password"];
1304
- if (typeof raw !== "string" || typeof password !== "string") {
1373
+ const password2 = body?.["password"];
1374
+ if (typeof raw !== "string" || typeof password2 !== "string") {
1305
1375
  return (0, import_core4.setApiResponse)(
1306
1376
  import_core4.HTTP.UNPROCESSABLE,
1307
1377
  "INVALID_PARAMETER",
@@ -1315,7 +1385,7 @@ function authController(store, config, bus) {
1315
1385
  "pin must be a 6-digit code"
1316
1386
  );
1317
1387
  }
1318
- if (password.length < 8) {
1388
+ if (password2.length < 8) {
1319
1389
  return (0, import_core4.setApiResponse)(
1320
1390
  import_core4.HTTP.UNPROCESSABLE,
1321
1391
  "INVALID_PARAMETER",
@@ -1327,7 +1397,7 @@ function authController(store, config, bus) {
1327
1397
  if (!row || /* @__PURE__ */ new Date() > row.expiresAt) {
1328
1398
  return (0, import_core4.setApiResponse)(import_core4.HTTP.BAD_REQUEST, "PASSWORD_RESET_FAILED", "Invalid or expired pin");
1329
1399
  }
1330
- const passwordHash = await hashPassword(password);
1400
+ const passwordHash = await hashPassword(password2);
1331
1401
  await store.transaction(async (tx) => {
1332
1402
  await Promise.all([
1333
1403
  tx.query(`UPDATE fonderie_users SET password_hash = $1 WHERE id = $2`, [
@@ -1434,8 +1504,8 @@ function authController(store, config, bus) {
1434
1504
  const resolved = { ...config, ...config.resolve?.(ctx) };
1435
1505
  const cooldown = resolved.verificationCooldown ?? DEFAULT_VERIFICATION_COOLDOWN;
1436
1506
  if (ctx.user.loginMethod === "phone") {
1437
- const phone = ctx.user.phone;
1438
- if (!phone) {
1507
+ const phone2 = ctx.user.phone;
1508
+ if (!phone2) {
1439
1509
  return (0, import_core4.setApiResponse)(
1440
1510
  import_core4.HTTP.BAD_REQUEST,
1441
1511
  "NO_PHONE_ON_ACCOUNT",
@@ -1455,11 +1525,11 @@ function authController(store, config, bus) {
1455
1525
  }
1456
1526
  const otp = (0, import_node_crypto2.randomInt)(1e5, 1e6).toString();
1457
1527
  const expiresAt2 = new Date(Date.now() + OTP_TTL_MS);
1458
- await phoneVerif.upsert(ctx.user.id, phone, otp, expiresAt2);
1528
+ await phoneVerif.upsert(ctx.user.id, phone2, otp, expiresAt2);
1459
1529
  bus?.emit(import_events2.NOTIFICATION_EVENT, {
1460
1530
  type: MESSAGE_KEYS.phoneOtp,
1461
1531
  data: { otp },
1462
- recipient: { email: null, phone, deviceToken: null }
1532
+ recipient: { email: null, phone: phone2, deviceToken: null }
1463
1533
  }).catch(() => {
1464
1534
  });
1465
1535
  return (0, import_core4.setApiResponse)(
@@ -1512,11 +1582,11 @@ function authController(store, config, bus) {
1512
1582
  var import_node_crypto3 = require("crypto");
1513
1583
  var import_core5 = require("@fonderie/core");
1514
1584
  var import_events3 = require("@fonderie/events");
1515
- function normalizePhone2(phone) {
1516
- return phone.trim().replace(/[\s()\-\.]/g, "");
1585
+ function normalizePhone2(phone2) {
1586
+ return phone2.trim().replace(/[\s()\-\.]/g, "");
1517
1587
  }
1518
- function isValidPhone2(phone) {
1519
- return typeof phone === "string" && /^\+?[1-9]\d{6,14}$/.test(normalizePhone2(phone));
1588
+ function isValidPhone2(phone2) {
1589
+ return typeof phone2 === "string" && /^\+?[1-9]\d{6,14}$/.test(normalizePhone2(phone2));
1520
1590
  }
1521
1591
  function userController(store, bus) {
1522
1592
  const users = new UserModel(store);
@@ -1831,42 +1901,43 @@ function buildAuthRoutes(store, config, bus) {
1831
1901
  const auth = authController(store, config, bus);
1832
1902
  const oauth = oauthController(store, config);
1833
1903
  const mfa = mfaController(store, config, config.appName ?? "Fonderie", bus);
1834
- const verifyGate = config.requireVerification ? import_middlewares.requireVerified : (_ctx, next) => next();
1904
+ const verifyGate = config.requireVerification ? import_middlewares2.requireVerified : (_ctx, next) => next();
1835
1905
  const routes = [
1836
1906
  // Registration & Login (Public)
1837
- ["POST", "/auth/register", auth.register],
1838
- ["POST", "/auth/login", auth.login],
1907
+ ["POST", "/auth/register", (0, import_middlewares.validate)(registerSchema), auth.register],
1908
+ ["POST", "/auth/login", (0, import_middlewares.validate)(loginSchema), auth.login],
1839
1909
  // Token Management (Public)
1840
- ["POST", "/auth/refresh", auth.refresh],
1910
+ ["POST", "/auth/refresh", (0, import_middlewares.validate)(refreshSchema), auth.refresh],
1841
1911
  // Email — Password Recovery (Public)
1842
- ["POST", "/auth/email/forgot", auth.forgotPassword],
1843
- ["POST", "/auth/email/reset", auth.resetPassword],
1912
+ ["POST", "/auth/email/forgot", (0, import_middlewares.validate)(forgotPasswordSchema), auth.forgotPassword],
1913
+ ["POST", "/auth/email/reset", (0, import_middlewares.validate)(resetPasswordSchema), auth.resetPassword],
1844
1914
  // Verification (Protected — email or phone, determined by loginMethod)
1845
- ["POST", "/auth/verify", import_middlewares.requireAuth, auth.verify],
1846
- ["GET", "/auth/send-verification", import_middlewares.requireAuth, auth.sendVerification],
1915
+ ["POST", "/auth/verify", import_middlewares2.requireAuth, (0, import_middlewares.validate)(verifySchema), auth.verify],
1916
+ ["GET", "/auth/send-verification", import_middlewares2.requireAuth, auth.sendVerification],
1847
1917
  // Account Management (Protected)
1848
- ["POST", "/auth/logout", import_middlewares.requireAuth, auth.logout],
1918
+ ["POST", "/auth/logout", import_middlewares2.requireAuth, (0, import_middlewares.validate)(refreshSchema), auth.logout],
1849
1919
  // User Profile (Protected; writes also gate on requireVerification)
1850
- ["GET", "/users", import_middlewares.requireAuth, user.me],
1851
- ["PUT", "/users/profile", import_middlewares.requireAuth, verifyGate, user.updateProfile],
1852
- ["PUT", "/users/preferences", import_middlewares.requireAuth, verifyGate, user.updatePreferences],
1853
- ["PUT", "/users/email", import_middlewares.requireAuth, verifyGate, user.updateEmail],
1854
- ["PUT", "/users/phone", import_middlewares.requireAuth, verifyGate, user.updatePhone],
1855
- ["PUT", "/users/password", import_middlewares.requireAuth, user.changePassword],
1856
- ["DELETE", "/users", import_middlewares.requireAuth, verifyGate, user.deleteMe],
1920
+ ["GET", "/users", import_middlewares2.requireAuth, user.me],
1921
+ ["PUT", "/users/profile", import_middlewares2.requireAuth, verifyGate, (0, import_middlewares.validate)(updateProfileSchema), user.updateProfile],
1922
+ ["PUT", "/users/preferences", import_middlewares2.requireAuth, verifyGate, (0, import_middlewares.validate)(updatePreferencesSchema), user.updatePreferences],
1923
+ ["PUT", "/users/email", import_middlewares2.requireAuth, verifyGate, (0, import_middlewares.validate)(updateEmailSchema), user.updateEmail],
1924
+ ["PUT", "/users/phone", import_middlewares2.requireAuth, verifyGate, (0, import_middlewares.validate)(updatePhoneSchema), user.updatePhone],
1925
+ ["PUT", "/users/password", import_middlewares2.requireAuth, (0, import_middlewares.validate)(changePasswordSchema), user.changePassword],
1926
+ ["DELETE", "/users", import_middlewares2.requireAuth, verifyGate, user.deleteMe],
1857
1927
  // MFA (email sessions only — requireVerified is always enforced here
1858
1928
  // because MFA is a security feature and email verification is meaningful)
1859
- ["POST", "/auth/mfa/setup", import_middlewares.requireAuth, requireEmailLogin, import_middlewares.requireVerified, mfa.setup],
1929
+ ["POST", "/auth/mfa/setup", import_middlewares2.requireAuth, requireEmailLogin, import_middlewares2.requireVerified, mfa.setup],
1860
1930
  // /auth/mfa/verify accepts both mfaPending tokens (TOTP/backup-code login)
1861
1931
  // and full tokens (setup confirmation), so requireAnyAuth is used here.
1862
- ["POST", "/auth/mfa/verify", import_middlewares.requireAnyAuth, requireEmailLogin, import_middlewares.requireVerified, mfa.verify],
1863
- ["POST", "/auth/mfa/disable", import_middlewares.requireAuth, requireEmailLogin, import_middlewares.requireVerified, mfa.disable],
1932
+ ["POST", "/auth/mfa/verify", import_middlewares2.requireAnyAuth, requireEmailLogin, import_middlewares2.requireVerified, (0, import_middlewares.validate)(mfaTokenSchema), mfa.verify],
1933
+ ["POST", "/auth/mfa/disable", import_middlewares2.requireAuth, requireEmailLogin, import_middlewares2.requireVerified, (0, import_middlewares.validate)(mfaTokenSchema), mfa.disable],
1864
1934
  [
1865
1935
  "POST",
1866
1936
  "/auth/mfa/backup-codes",
1867
- import_middlewares.requireAuth,
1937
+ import_middlewares2.requireAuth,
1868
1938
  requireEmailLogin,
1869
- import_middlewares.requireVerified,
1939
+ import_middlewares2.requireVerified,
1940
+ (0, import_middlewares.validate)(mfaTokenSchema),
1870
1941
  mfa.regenerateBackupCodes
1871
1942
  ]
1872
1943
  ];
@@ -1937,7 +2008,7 @@ var AuthModule = class {
1937
2008
  };
1938
2009
 
1939
2010
  // src/middlewares/require-auth.ts
1940
- var import_middlewares2 = require("@fonderie/core/middlewares");
2011
+ var import_middlewares3 = require("@fonderie/core/middlewares");
1941
2012
  // Annotate the CommonJS export names for ESM import in node:
1942
2013
  0 && (module.exports = {
1943
2014
  AUTH_CONFIG_KEYS,
@@ -1946,7 +2017,9 @@ var import_middlewares2 = require("@fonderie/core/middlewares");
1946
2017
  normalizeEmail,
1947
2018
  normalizeEmailSafe,
1948
2019
  requireAuth,
2020
+ schemas,
1949
2021
  toUserDTO,
2022
+ validate,
1950
2023
  withSession
1951
2024
  });
1952
2025
  //# sourceMappingURL=index.cjs.map