@fonderie/auth 1.0.0 → 1.1.1

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
@@ -45,6 +45,94 @@ var requireEmailLogin = async (ctx, next) => {
45
45
  return next();
46
46
  };
47
47
 
48
+ // src/middlewares/validate.ts
49
+ import { validate } from "@fonderie/core/middlewares";
50
+
51
+ // src/schemas.ts
52
+ var schemas_exports = {};
53
+ __export(schemas_exports, {
54
+ changePasswordSchema: () => changePasswordSchema,
55
+ forgotPasswordSchema: () => forgotPasswordSchema,
56
+ loginSchema: () => loginSchema,
57
+ mfaTokenSchema: () => mfaTokenSchema,
58
+ refreshSchema: () => refreshSchema,
59
+ registerSchema: () => registerSchema,
60
+ resetPasswordSchema: () => resetPasswordSchema,
61
+ updateEmailSchema: () => updateEmailSchema,
62
+ updatePhoneSchema: () => updatePhoneSchema,
63
+ updatePreferencesSchema: () => updatePreferencesSchema,
64
+ updateProfileSchema: () => updateProfileSchema,
65
+ verifySchema: () => verifySchema
66
+ });
67
+ import { z } from "zod";
68
+ var email = z.string().trim().pipe(z.email());
69
+ var password = z.string().min(8, "password must be at least 8 characters").max(128);
70
+ var phone = z.string().refine((v) => /^\+?[1-9]\d{6,14}$/.test(v.replace(/[\s\-()]/g, "")), "Invalid phone number");
71
+ var sixDigitPin = z.string().trim().regex(/^\d{6}$/, "must be a 6-digit code");
72
+ var registerSchema = z.union([
73
+ z.object({
74
+ email,
75
+ password,
76
+ firstName: z.string().max(100).nullish(),
77
+ lastName: z.string().max(100).nullish()
78
+ }),
79
+ z.object({ phone })
80
+ ]);
81
+ var loginSchema = z.union([
82
+ z.object({ email, password: z.string().min(1).max(128) }),
83
+ z.object({ phone })
84
+ ]);
85
+ var refreshSchema = z.object({ refreshToken: z.string().min(1).optional() });
86
+ var forgotPasswordSchema = z.object({ email });
87
+ var resetPasswordSchema = z.object({ pin: sixDigitPin, password });
88
+ var verifySchema = z.object({ token: sixDigitPin });
89
+ var updateProfileSchema = z.object({
90
+ firstName: z.string().max(100).nullable().optional(),
91
+ lastName: z.string().max(100).nullable().optional(),
92
+ avatarUrl: z.string().trim().pipe(z.url()).nullable().optional()
93
+ }).refine(
94
+ (o) => Object.values(o).some((v) => v !== void 0),
95
+ "Provide at least one of: firstName, lastName, avatarUrl"
96
+ );
97
+ var updatePreferencesSchema = z.object({
98
+ locale: z.string().max(35).optional(),
99
+ timezone: z.string().max(64).optional(),
100
+ notifications: z.unknown().optional(),
101
+ emailDigest: z.unknown().optional(),
102
+ dateFormat: z.unknown().optional(),
103
+ timeFormat: z.unknown().optional()
104
+ }).refine(
105
+ (o) => Object.values(o).some((v) => v !== void 0),
106
+ "Provide at least one preference field"
107
+ );
108
+ var updateEmailSchema = z.object({ email });
109
+ var updatePhoneSchema = z.object({ phone });
110
+ var changePasswordSchema = z.object({
111
+ currentPassword: z.string().min(1).max(128),
112
+ newPassword: password
113
+ });
114
+ var mfaTokenSchema = z.object({ token: z.string().trim().min(6).max(64) });
115
+
116
+ // src/services/cookies.ts
117
+ function secureAttr(config) {
118
+ const secure = config.secureCookies ?? process.env["NODE_ENV"] === "production";
119
+ return secure ? "; Secure" : "";
120
+ }
121
+ function tokenPairCookies(accessToken, refreshToken, config) {
122
+ const secure = secureAttr(config);
123
+ return [
124
+ `access_token=${accessToken}; HttpOnly; SameSite=Strict; Path=/${secure}`,
125
+ `refresh_token=${refreshToken}; HttpOnly; SameSite=Strict; Path=/auth/refresh${secure}`
126
+ ].join(", ");
127
+ }
128
+ function clearedTokenCookies(config) {
129
+ const secure = secureAttr(config);
130
+ return [
131
+ `access_token=; HttpOnly; SameSite=Strict; Path=/; Max-Age=0${secure}`,
132
+ `refresh_token=; HttpOnly; SameSite=Strict; Path=/auth/refresh; Max-Age=0${secure}`
133
+ ].join(", ");
134
+ }
135
+
48
136
  // src/controllers/mfa.controller.ts
49
137
  import QRCode from "qrcode";
50
138
  import { setApiResponse as setApiResponse2, HTTP as HTTP2 } from "@fonderie/core";
@@ -181,7 +269,7 @@ function base32Encode(buf) {
181
269
  function generateTotpSecret() {
182
270
  return base32Encode(randomBytes(20));
183
271
  }
184
- function generateTotpUri(email, secret, issuer) {
272
+ function generateTotpUri(email2, secret, issuer) {
185
273
  const params = new URLSearchParams({
186
274
  secret,
187
275
  issuer,
@@ -189,7 +277,7 @@ function generateTotpUri(email, secret, issuer) {
189
277
  digits: String(DIGITS),
190
278
  period: String(STEP)
191
279
  });
192
- return `otpauth://totp/${encodeURIComponent(issuer)}:${encodeURIComponent(email)}?${params}`;
280
+ return `otpauth://totp/${encodeURIComponent(issuer)}:${encodeURIComponent(email2)}?${params}`;
193
281
  }
194
282
  function verifyTotpToken(token, secret) {
195
283
  const t = timeCounter();
@@ -281,21 +369,21 @@ var UserModel = class {
281
369
  );
282
370
  return row ?? null;
283
371
  }
284
- async findByEmail(email) {
372
+ async findByEmail(email2) {
285
373
  const [row] = await this.store.query(
286
374
  `SELECT ${USER_COLUMNS} FROM fonderie_users WHERE email = $1 AND deleted_at IS NULL`,
287
- [email]
375
+ [email2]
288
376
  );
289
377
  return row ?? null;
290
378
  }
291
- async findByPhone(phone) {
379
+ async findByPhone(phone2) {
292
380
  const [row] = await this.store.query(
293
381
  `SELECT ${USER_COLUMNS} FROM fonderie_users WHERE phone = $1 AND deleted_at IS NULL`,
294
- [phone]
382
+ [phone2]
295
383
  );
296
384
  return row ?? null;
297
385
  }
298
- async findOrCreateByPhone(phone, firstName = null, lastName = null) {
386
+ async findOrCreateByPhone(phone2, firstName = null, lastName = null) {
299
387
  const [row] = await this.store.query(
300
388
  `INSERT INTO fonderie_users (phone, first_name, last_name)
301
389
  VALUES ($1, $2, $3)
@@ -304,16 +392,16 @@ var UserModel = class {
304
392
  last_name = COALESCE(EXCLUDED.last_name, fonderie_users.last_name),
305
393
  updated_at = now()
306
394
  RETURNING id`,
307
- [phone, firstName, lastName]
395
+ [phone2, firstName, lastName]
308
396
  );
309
397
  return row;
310
398
  }
311
- async create(email, passwordHash, firstName, lastName) {
399
+ async create(email2, passwordHash, firstName, lastName) {
312
400
  const [row] = await this.store.query(
313
401
  `INSERT INTO fonderie_users (email, password_hash, first_name, last_name)
314
402
  VALUES ($1, $2, $3, $4)
315
403
  RETURNING id`,
316
- [email.toLowerCase().trim(), passwordHash, firstName, lastName]
404
+ [email2.toLowerCase().trim(), passwordHash, firstName, lastName]
317
405
  );
318
406
  return row ?? null;
319
407
  }
@@ -400,16 +488,16 @@ var UserModel = class {
400
488
  [id]
401
489
  );
402
490
  }
403
- async updateEmail(id, email) {
491
+ async updateEmail(id, email2) {
404
492
  await this.store.query(
405
493
  `UPDATE fonderie_users SET email = $1, email_verified_at = NULL, updated_at = now() WHERE id = $2`,
406
- [email.toLowerCase().trim(), id]
494
+ [email2.toLowerCase().trim(), id]
407
495
  );
408
496
  }
409
- async updatePhone(id, phone) {
497
+ async updatePhone(id, phone2) {
410
498
  await this.store.query(
411
499
  `UPDATE fonderie_users SET phone = $1, updated_at = now() WHERE id = $2`,
412
- [phone, id]
500
+ [phone2, id]
413
501
  );
414
502
  }
415
503
  async updatePreferences(id, fields) {
@@ -442,14 +530,14 @@ var UserModel = class {
442
530
  );
443
531
  return row?.mfa_secret ?? null;
444
532
  }
445
- async upsertByProvider(email, provider, providerId) {
533
+ async upsertByProvider(email2, provider, providerId) {
446
534
  const [row] = await this.store.query(
447
535
  `INSERT INTO fonderie_users (email, email_verified_at, provider, provider_id)
448
536
  VALUES ($1, now(), $2, $3)
449
537
  ON CONFLICT (email) DO UPDATE
450
538
  SET provider = $2, provider_id = $3
451
539
  RETURNING id`,
452
- [email, provider, providerId]
540
+ [email2, provider, providerId]
453
541
  );
454
542
  return row ?? null;
455
543
  }
@@ -629,10 +717,7 @@ function mfaController(store, config, issuer, bus) {
629
717
  {
630
718
  status: 200,
631
719
  headers: {
632
- "Set-Cookie": [
633
- `access_token=${accessToken}; HttpOnly; SameSite=Strict; Path=/`,
634
- `refresh_token=${refreshToken}; HttpOnly; SameSite=Strict; Path=/auth/refresh`
635
- ].join(", ")
720
+ "Set-Cookie": tokenPairCookies(accessToken, refreshToken, config)
636
721
  }
637
722
  }
638
723
  );
@@ -706,11 +791,11 @@ function checkCooldown(lastSentAt, cooldownMs) {
706
791
  init_password();
707
792
 
708
793
  // src/services/email.ts
709
- function normalizeEmail(email) {
710
- if (typeof email !== "string" || email.length === 0) {
794
+ function normalizeEmail(email2) {
795
+ if (typeof email2 !== "string" || email2.length === 0) {
711
796
  throw new Error("Invalid email");
712
797
  }
713
- const lower = email.trim().toLowerCase();
798
+ const lower = email2.trim().toLowerCase();
714
799
  if (lower.length === 0) {
715
800
  throw new Error("Email cannot be empty");
716
801
  }
@@ -730,9 +815,9 @@ function normalizeEmail(email) {
730
815
  }
731
816
  return `${normalizedLocal}@${domain}`;
732
817
  }
733
- function normalizeEmailSafe(email) {
818
+ function normalizeEmailSafe(email2) {
734
819
  try {
735
- return normalizeEmail(email);
820
+ return normalizeEmail(email2);
736
821
  } catch {
737
822
  return null;
738
823
  }
@@ -833,13 +918,13 @@ var PhoneVerificationModel = class {
833
918
  this.store = store;
834
919
  }
835
920
  store;
836
- async upsert(userId, phone, otp, expiresAt) {
921
+ async upsert(userId, phone2, otp, expiresAt) {
837
922
  await this.store.query(
838
923
  `INSERT INTO fonderie_phone_verifications (phone, user_id, otp, expires_at)
839
924
  VALUES ($1, $2, $3, $4)
840
925
  ON CONFLICT (phone) DO UPDATE
841
926
  SET user_id = $2, otp = $3, expires_at = $4, created_at = now()`,
842
- [phone, userId, otp, expiresAt]
927
+ [phone2, userId, otp, expiresAt]
843
928
  );
844
929
  }
845
930
  async findByUser(userId, otp) {
@@ -864,11 +949,11 @@ var PhoneVerificationModel = class {
864
949
  };
865
950
 
866
951
  // src/controllers/auth.controller.ts
867
- function normalizePhone(phone) {
868
- return phone.trim().replace(/[\s()\-\.]/g, "");
952
+ function normalizePhone(phone2) {
953
+ return phone2.trim().replace(/[\s()\-\.]/g, "");
869
954
  }
870
- function isValidPhone(phone) {
871
- return typeof phone === "string" && /^\+?[1-9]\d{6,14}$/.test(normalizePhone(phone));
955
+ function isValidPhone(phone2) {
956
+ return typeof phone2 === "string" && /^\+?[1-9]\d{6,14}$/.test(normalizePhone(phone2));
872
957
  }
873
958
  function extractRefreshToken(ctx) {
874
959
  const body = ctx.meta["body"];
@@ -889,13 +974,13 @@ function authController(store, config, bus) {
889
974
  return {
890
975
  register: async (ctx) => {
891
976
  const body = ctx.meta["body"];
892
- const { email, password, phone, firstName = null, lastName = null } = body ?? {};
893
- if (typeof email === "string" && typeof password === "string") {
894
- const normalizedEmail = normalizeEmailSafe(email);
977
+ const { email: email2, password: password2, phone: phone2, firstName = null, lastName = null } = body ?? {};
978
+ if (typeof email2 === "string" && typeof password2 === "string") {
979
+ const normalizedEmail = normalizeEmailSafe(email2);
895
980
  if (!normalizedEmail) {
896
981
  return setApiResponse3(HTTP3.UNPROCESSABLE, "INVALID_PARAMETER", "Invalid email address");
897
982
  }
898
- if (password.length < 8) {
983
+ if (password2.length < 8) {
899
984
  return setApiResponse3(
900
985
  HTTP3.UNPROCESSABLE,
901
986
  "INVALID_PARAMETER",
@@ -906,7 +991,7 @@ function authController(store, config, bus) {
906
991
  if (existing) {
907
992
  return setApiResponse3(HTTP3.CONFLICT, "USER_ALREADY_EXISTS", "Email already registered");
908
993
  }
909
- const passwordHash = await hashPassword(password);
994
+ const passwordHash = await hashPassword(password2);
910
995
  const row = await users.create(
911
996
  normalizedEmail,
912
997
  passwordHash,
@@ -966,27 +1051,24 @@ function authController(store, config, bus) {
966
1051
  {
967
1052
  status: 201,
968
1053
  headers: {
969
- "Set-Cookie": [
970
- `access_token=${accessToken}; HttpOnly; SameSite=Strict; Path=/`,
971
- `refresh_token=${refreshToken}; HttpOnly; SameSite=Strict; Path=/auth/refresh`
972
- ].join(", ")
1054
+ "Set-Cookie": tokenPairCookies(accessToken, refreshToken, config)
973
1055
  }
974
1056
  }
975
1057
  );
976
1058
  }
977
- if (isValidPhone(phone)) {
978
- const existing = await users.findByPhone(normalizePhone(phone));
1059
+ if (isValidPhone(phone2)) {
1060
+ const existing = await users.findByPhone(normalizePhone(phone2));
979
1061
  if (existing) {
980
1062
  return setApiResponse3(HTTP3.CONFLICT, "USER_ALREADY_EXISTS", "Phone already registered");
981
1063
  }
982
1064
  const { id } = await users.findOrCreateByPhone(
983
- normalizePhone(phone),
1065
+ normalizePhone(phone2),
984
1066
  firstName ?? null,
985
1067
  lastName ?? null
986
1068
  );
987
1069
  const otp = randomInt(1e5, 1e6).toString();
988
1070
  const expiresAt = new Date(Date.now() + OTP_TTL_MS);
989
- await phoneVerif.upsert(id, normalizePhone(phone), otp, expiresAt);
1071
+ await phoneVerif.upsert(id, normalizePhone(phone2), otp, expiresAt);
990
1072
  const user = await users.findById(id);
991
1073
  if (!user) {
992
1074
  return setApiResponse3(HTTP3.SERVER_ERROR, "SERVER_ERROR", "Registration failed");
@@ -998,7 +1080,7 @@ function authController(store, config, bus) {
998
1080
  {
999
1081
  type: MESSAGE_KEYS.phoneOtp,
1000
1082
  data: { otp },
1001
- recipient: { email: null, phone: normalizePhone(phone), deviceToken: null }
1083
+ recipient: { email: null, phone: normalizePhone(phone2), deviceToken: null }
1002
1084
  },
1003
1085
  reqOpts2
1004
1086
  ).catch(() => {
@@ -1031,10 +1113,7 @@ function authController(store, config, bus) {
1031
1113
  {
1032
1114
  status: 202,
1033
1115
  headers: {
1034
- "Set-Cookie": [
1035
- `access_token=${accessToken}; HttpOnly; SameSite=Strict; Path=/`,
1036
- `refresh_token=${refreshToken}; HttpOnly; SameSite=Strict; Path=/auth/refresh`
1037
- ].join(", ")
1116
+ "Set-Cookie": tokenPairCookies(accessToken, refreshToken, config)
1038
1117
  }
1039
1118
  }
1040
1119
  );
@@ -1048,16 +1127,16 @@ function authController(store, config, bus) {
1048
1127
  login: async (ctx) => {
1049
1128
  const body = ctx.meta["body"];
1050
1129
  if (typeof body?.["email"] === "string" && typeof body?.["password"] === "string") {
1051
- const { email: rawEmail, password } = body;
1052
- const email = normalizeEmailSafe(rawEmail);
1053
- if (!email) {
1130
+ const { email: rawEmail, password: password2 } = body;
1131
+ const email2 = normalizeEmailSafe(rawEmail);
1132
+ if (!email2) {
1054
1133
  return setApiResponse3(HTTP3.UNPROCESSABLE, "INVALID_PARAMETER", "Invalid email address");
1055
1134
  }
1056
- const user = await users.findByEmail(email);
1135
+ const user = await users.findByEmail(email2);
1057
1136
  if (!user || !user.passwordHash) {
1058
1137
  return setApiResponse3(HTTP3.UNAUTHORIZED, "INVALID_CREDENTIALS", "Invalid credentials");
1059
1138
  }
1060
- const valid = await verifyPassword(password, user.passwordHash);
1139
+ const valid = await verifyPassword(password2, user.passwordHash);
1061
1140
  if (!valid) {
1062
1141
  return setApiResponse3(HTTP3.UNAUTHORIZED, "INVALID_CREDENTIALS", "Invalid credentials");
1063
1142
  }
@@ -1093,17 +1172,14 @@ function authController(store, config, bus) {
1093
1172
  {
1094
1173
  status: 200,
1095
1174
  headers: {
1096
- "Set-Cookie": [
1097
- `access_token=${accessToken}; HttpOnly; SameSite=Strict; Path=/`,
1098
- `refresh_token=${refreshToken}; HttpOnly; SameSite=Strict; Path=/auth/refresh`
1099
- ].join(", ")
1175
+ "Set-Cookie": tokenPairCookies(accessToken, refreshToken, config)
1100
1176
  }
1101
1177
  }
1102
1178
  );
1103
1179
  }
1104
- const phone = body?.["phone"];
1105
- if (isValidPhone(phone)) {
1106
- const user = await users.findByPhone(normalizePhone(phone));
1180
+ const phone2 = body?.["phone"];
1181
+ if (isValidPhone(phone2)) {
1182
+ const user = await users.findByPhone(normalizePhone(phone2));
1107
1183
  if (!user) {
1108
1184
  return setApiResponse3(HTTP3.UNAUTHORIZED, "INVALID_CREDENTIALS", "Invalid credentials");
1109
1185
  }
@@ -1116,11 +1192,11 @@ function authController(store, config, bus) {
1116
1192
  }
1117
1193
  const otp = randomInt(1e5, 1e6).toString();
1118
1194
  const expiresAt = new Date(Date.now() + OTP_TTL_MS);
1119
- await phoneVerif.upsert(user.id, normalizePhone(phone), otp, expiresAt);
1195
+ await phoneVerif.upsert(user.id, normalizePhone(phone2), otp, expiresAt);
1120
1196
  bus?.emit(NOTIFICATION_EVENT2, {
1121
1197
  type: MESSAGE_KEYS.phoneOtp,
1122
1198
  data: { otp },
1123
- recipient: { email: null, phone: normalizePhone(phone), deviceToken: null }
1199
+ recipient: { email: null, phone: normalizePhone(phone2), deviceToken: null }
1124
1200
  }).catch(() => {
1125
1201
  });
1126
1202
  const { accessToken, refreshToken } = issueTokenPair(user.id, config, {
@@ -1139,10 +1215,7 @@ function authController(store, config, bus) {
1139
1215
  {
1140
1216
  status: 202,
1141
1217
  headers: {
1142
- "Set-Cookie": [
1143
- `access_token=${accessToken}; HttpOnly; SameSite=Strict; Path=/`,
1144
- `refresh_token=${refreshToken}; HttpOnly; SameSite=Strict; Path=/auth/refresh`
1145
- ].join(", ")
1218
+ "Set-Cookie": tokenPairCookies(accessToken, refreshToken, config)
1146
1219
  }
1147
1220
  }
1148
1221
  );
@@ -1163,10 +1236,7 @@ function authController(store, config, bus) {
1163
1236
  {
1164
1237
  status: 200,
1165
1238
  headers: {
1166
- "Set-Cookie": [
1167
- "access_token=; HttpOnly; SameSite=Strict; Path=/; Max-Age=0",
1168
- "refresh_token=; HttpOnly; SameSite=Strict; Path=/auth/refresh; Max-Age=0"
1169
- ].join(", ")
1239
+ "Set-Cookie": clearedTokenCookies(config)
1170
1240
  }
1171
1241
  }
1172
1242
  );
@@ -1207,10 +1277,7 @@ function authController(store, config, bus) {
1207
1277
  {
1208
1278
  status: 200,
1209
1279
  headers: {
1210
- "Set-Cookie": [
1211
- `access_token=${accessToken}; HttpOnly; SameSite=Strict; Path=/`,
1212
- `refresh_token=${refreshToken}; HttpOnly; SameSite=Strict; Path=/auth/refresh`
1213
- ].join(", ")
1280
+ "Set-Cookie": tokenPairCookies(accessToken, refreshToken, config)
1214
1281
  }
1215
1282
  }
1216
1283
  );
@@ -1221,11 +1288,11 @@ function authController(store, config, bus) {
1221
1288
  if (typeof rawEmail !== "string") {
1222
1289
  return setApiResponse3(HTTP3.UNPROCESSABLE, "INVALID_PARAMETER", "email is required");
1223
1290
  }
1224
- const email = normalizeEmailSafe(rawEmail);
1225
- if (!email) {
1291
+ const email2 = normalizeEmailSafe(rawEmail);
1292
+ if (!email2) {
1226
1293
  return setApiResponse3(HTTP3.UNPROCESSABLE, "INVALID_PARAMETER", "Invalid email address");
1227
1294
  }
1228
- const user = await users.findByEmail(email);
1295
+ const user = await users.findByEmail(email2);
1229
1296
  if (!user) {
1230
1297
  return setApiResponse3(
1231
1298
  HTTP3.OK,
@@ -1238,12 +1305,9 @@ function authController(store, config, bus) {
1238
1305
  const remaining = checkCooldown(await passwordReset.findLastSentAt(user.id), cooldown);
1239
1306
  if (remaining > 0) {
1240
1307
  return setApiResponse3(
1241
- HTTP3.TOO_MANY_REQUESTS,
1242
- "VERIFICATION_COOLDOWN",
1243
- "Please wait before requesting a new password reset code.",
1244
- {
1245
- retryAfter: Math.ceil(remaining / 1e3)
1246
- }
1308
+ HTTP3.OK,
1309
+ "PASSWORD_RESET_EMAIL_SENT",
1310
+ "Password reset email sent (if account exists)."
1247
1311
  );
1248
1312
  }
1249
1313
  const pin = randomInt(1e5, 1e6).toString();
@@ -1251,7 +1315,7 @@ function authController(store, config, bus) {
1251
1315
  await passwordReset.create(user.id, pin, expiresAt);
1252
1316
  bus?.emit(NOTIFICATION_EVENT2, {
1253
1317
  type: MESSAGE_KEYS.passwordReset,
1254
- recipient: { email, phone: null, deviceToken: null },
1318
+ recipient: { email: email2, phone: null, deviceToken: null },
1255
1319
  data: { pin }
1256
1320
  }).catch(() => {
1257
1321
  });
@@ -1264,8 +1328,8 @@ function authController(store, config, bus) {
1264
1328
  resetPassword: async (ctx) => {
1265
1329
  const body = ctx.meta["body"];
1266
1330
  const raw = body?.["pin"];
1267
- const password = body?.["password"];
1268
- if (typeof raw !== "string" || typeof password !== "string") {
1331
+ const password2 = body?.["password"];
1332
+ if (typeof raw !== "string" || typeof password2 !== "string") {
1269
1333
  return setApiResponse3(
1270
1334
  HTTP3.UNPROCESSABLE,
1271
1335
  "INVALID_PARAMETER",
@@ -1279,7 +1343,7 @@ function authController(store, config, bus) {
1279
1343
  "pin must be a 6-digit code"
1280
1344
  );
1281
1345
  }
1282
- if (password.length < 8) {
1346
+ if (password2.length < 8) {
1283
1347
  return setApiResponse3(
1284
1348
  HTTP3.UNPROCESSABLE,
1285
1349
  "INVALID_PARAMETER",
@@ -1291,7 +1355,7 @@ function authController(store, config, bus) {
1291
1355
  if (!row || /* @__PURE__ */ new Date() > row.expiresAt) {
1292
1356
  return setApiResponse3(HTTP3.BAD_REQUEST, "PASSWORD_RESET_FAILED", "Invalid or expired pin");
1293
1357
  }
1294
- const passwordHash = await hashPassword(password);
1358
+ const passwordHash = await hashPassword(password2);
1295
1359
  await store.transaction(async (tx) => {
1296
1360
  await Promise.all([
1297
1361
  tx.query(`UPDATE fonderie_users SET password_hash = $1 WHERE id = $2`, [
@@ -1362,10 +1426,7 @@ function authController(store, config, bus) {
1362
1426
  {
1363
1427
  status: 200,
1364
1428
  headers: {
1365
- "Set-Cookie": [
1366
- `access_token=${accessToken}; HttpOnly; SameSite=Strict; Path=/`,
1367
- `refresh_token=${refreshToken}; HttpOnly; SameSite=Strict; Path=/auth/refresh`
1368
- ].join(", ")
1429
+ "Set-Cookie": tokenPairCookies(accessToken, refreshToken, config)
1369
1430
  }
1370
1431
  }
1371
1432
  );
@@ -1398,8 +1459,8 @@ function authController(store, config, bus) {
1398
1459
  const resolved = { ...config, ...config.resolve?.(ctx) };
1399
1460
  const cooldown = resolved.verificationCooldown ?? DEFAULT_VERIFICATION_COOLDOWN;
1400
1461
  if (ctx.user.loginMethod === "phone") {
1401
- const phone = ctx.user.phone;
1402
- if (!phone) {
1462
+ const phone2 = ctx.user.phone;
1463
+ if (!phone2) {
1403
1464
  return setApiResponse3(
1404
1465
  HTTP3.BAD_REQUEST,
1405
1466
  "NO_PHONE_ON_ACCOUNT",
@@ -1419,11 +1480,11 @@ function authController(store, config, bus) {
1419
1480
  }
1420
1481
  const otp = randomInt(1e5, 1e6).toString();
1421
1482
  const expiresAt2 = new Date(Date.now() + OTP_TTL_MS);
1422
- await phoneVerif.upsert(ctx.user.id, phone, otp, expiresAt2);
1483
+ await phoneVerif.upsert(ctx.user.id, phone2, otp, expiresAt2);
1423
1484
  bus?.emit(NOTIFICATION_EVENT2, {
1424
1485
  type: MESSAGE_KEYS.phoneOtp,
1425
1486
  data: { otp },
1426
- recipient: { email: null, phone, deviceToken: null }
1487
+ recipient: { email: null, phone: phone2, deviceToken: null }
1427
1488
  }).catch(() => {
1428
1489
  });
1429
1490
  return setApiResponse3(
@@ -1476,13 +1537,13 @@ function authController(store, config, bus) {
1476
1537
  import { randomInt as randomInt2 } from "crypto";
1477
1538
  import { setApiResponse as setApiResponse4, HTTP as HTTP4 } from "@fonderie/core";
1478
1539
  import { NOTIFICATION_EVENT as NOTIFICATION_EVENT3 } from "@fonderie/events";
1479
- function normalizePhone2(phone) {
1480
- return phone.trim().replace(/[\s()\-\.]/g, "");
1540
+ function normalizePhone2(phone2) {
1541
+ return phone2.trim().replace(/[\s()\-\.]/g, "");
1481
1542
  }
1482
- function isValidPhone2(phone) {
1483
- return typeof phone === "string" && /^\+?[1-9]\d{6,14}$/.test(normalizePhone2(phone));
1543
+ function isValidPhone2(phone2) {
1544
+ return typeof phone2 === "string" && /^\+?[1-9]\d{6,14}$/.test(normalizePhone2(phone2));
1484
1545
  }
1485
- function userController(store, bus) {
1546
+ function userController(store, config, bus) {
1486
1547
  const users = new UserModel(store);
1487
1548
  const emailVerif = new EmailVerificationModel(store);
1488
1549
  const phoneVerif = new PhoneVerificationModel(store);
@@ -1671,10 +1732,7 @@ function userController(store, bus) {
1671
1732
  {
1672
1733
  status: 200,
1673
1734
  headers: {
1674
- "Set-Cookie": [
1675
- "access_token=; HttpOnly; SameSite=Strict; Path=/; Max-Age=0",
1676
- "refresh_token=; HttpOnly; SameSite=Strict; Path=/auth/refresh; Max-Age=0"
1677
- ].join(", ")
1735
+ "Set-Cookie": clearedTokenCookies(config)
1678
1736
  }
1679
1737
  }
1680
1738
  );
@@ -1778,10 +1836,7 @@ function oauthController(store, config) {
1778
1836
  {
1779
1837
  status: 200,
1780
1838
  headers: {
1781
- "Set-Cookie": [
1782
- `access_token=${accessToken}; HttpOnly; SameSite=Strict; Path=/`,
1783
- `refresh_token=${refreshToken}; HttpOnly; SameSite=Strict; Path=/auth/refresh`
1784
- ].join(", ")
1839
+ "Set-Cookie": tokenPairCookies(accessToken, refreshToken, config)
1785
1840
  }
1786
1841
  }
1787
1842
  );
@@ -1791,46 +1846,47 @@ function oauthController(store, config) {
1791
1846
 
1792
1847
  // src/routes.ts
1793
1848
  function buildAuthRoutes(store, config, bus) {
1794
- const user = userController(store, bus);
1849
+ const user = userController(store, config, bus);
1795
1850
  const auth = authController(store, config, bus);
1796
1851
  const oauth = oauthController(store, config);
1797
1852
  const mfa = mfaController(store, config, config.appName ?? "Fonderie", bus);
1798
1853
  const verifyGate = config.requireVerification ? requireVerified : (_ctx, next) => next();
1799
1854
  const routes = [
1800
1855
  // Registration & Login (Public)
1801
- ["POST", "/auth/register", auth.register],
1802
- ["POST", "/auth/login", auth.login],
1856
+ ["POST", "/auth/register", validate(registerSchema), auth.register],
1857
+ ["POST", "/auth/login", validate(loginSchema), auth.login],
1803
1858
  // Token Management (Public)
1804
- ["POST", "/auth/refresh", auth.refresh],
1859
+ ["POST", "/auth/refresh", validate(refreshSchema), auth.refresh],
1805
1860
  // Email — Password Recovery (Public)
1806
- ["POST", "/auth/email/forgot", auth.forgotPassword],
1807
- ["POST", "/auth/email/reset", auth.resetPassword],
1861
+ ["POST", "/auth/email/forgot", validate(forgotPasswordSchema), auth.forgotPassword],
1862
+ ["POST", "/auth/email/reset", validate(resetPasswordSchema), auth.resetPassword],
1808
1863
  // Verification (Protected — email or phone, determined by loginMethod)
1809
- ["POST", "/auth/verify", requireAuth, auth.verify],
1864
+ ["POST", "/auth/verify", requireAuth, validate(verifySchema), auth.verify],
1810
1865
  ["GET", "/auth/send-verification", requireAuth, auth.sendVerification],
1811
1866
  // Account Management (Protected)
1812
- ["POST", "/auth/logout", requireAuth, auth.logout],
1867
+ ["POST", "/auth/logout", requireAuth, validate(refreshSchema), auth.logout],
1813
1868
  // User Profile (Protected; writes also gate on requireVerification)
1814
1869
  ["GET", "/users", requireAuth, user.me],
1815
- ["PUT", "/users/profile", requireAuth, verifyGate, user.updateProfile],
1816
- ["PUT", "/users/preferences", requireAuth, verifyGate, user.updatePreferences],
1817
- ["PUT", "/users/email", requireAuth, verifyGate, user.updateEmail],
1818
- ["PUT", "/users/phone", requireAuth, verifyGate, user.updatePhone],
1819
- ["PUT", "/users/password", requireAuth, user.changePassword],
1870
+ ["PUT", "/users/profile", requireAuth, verifyGate, validate(updateProfileSchema), user.updateProfile],
1871
+ ["PUT", "/users/preferences", requireAuth, verifyGate, validate(updatePreferencesSchema), user.updatePreferences],
1872
+ ["PUT", "/users/email", requireAuth, verifyGate, validate(updateEmailSchema), user.updateEmail],
1873
+ ["PUT", "/users/phone", requireAuth, verifyGate, validate(updatePhoneSchema), user.updatePhone],
1874
+ ["PUT", "/users/password", requireAuth, validate(changePasswordSchema), user.changePassword],
1820
1875
  ["DELETE", "/users", requireAuth, verifyGate, user.deleteMe],
1821
1876
  // MFA (email sessions only — requireVerified is always enforced here
1822
1877
  // because MFA is a security feature and email verification is meaningful)
1823
1878
  ["POST", "/auth/mfa/setup", requireAuth, requireEmailLogin, requireVerified, mfa.setup],
1824
1879
  // /auth/mfa/verify accepts both mfaPending tokens (TOTP/backup-code login)
1825
1880
  // and full tokens (setup confirmation), so requireAnyAuth is used here.
1826
- ["POST", "/auth/mfa/verify", requireAnyAuth, requireEmailLogin, requireVerified, mfa.verify],
1827
- ["POST", "/auth/mfa/disable", requireAuth, requireEmailLogin, requireVerified, mfa.disable],
1881
+ ["POST", "/auth/mfa/verify", requireAnyAuth, requireEmailLogin, requireVerified, validate(mfaTokenSchema), mfa.verify],
1882
+ ["POST", "/auth/mfa/disable", requireAuth, requireEmailLogin, requireVerified, validate(mfaTokenSchema), mfa.disable],
1828
1883
  [
1829
1884
  "POST",
1830
1885
  "/auth/mfa/backup-codes",
1831
1886
  requireAuth,
1832
1887
  requireEmailLogin,
1833
1888
  requireVerified,
1889
+ validate(mfaTokenSchema),
1834
1890
  mfa.regenerateBackupCodes
1835
1891
  ]
1836
1892
  ];
@@ -1909,7 +1965,9 @@ export {
1909
1965
  normalizeEmail,
1910
1966
  normalizeEmailSafe,
1911
1967
  requireAuth2 as requireAuth,
1968
+ schemas_exports as schemas,
1912
1969
  toUserDTO,
1970
+ validate,
1913
1971
  withSession
1914
1972
  };
1915
1973
  //# sourceMappingURL=index.js.map