@fonderie/auth 1.1.1 → 1.3.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
@@ -57,6 +57,8 @@ __export(index_exports, {
57
57
  AUTH_CONFIG_KEYS: () => AUTH_CONFIG_KEYS,
58
58
  AuthModule: () => AuthModule,
59
59
  MESSAGE_KEYS: () => MESSAGE_KEYS,
60
+ buildAuthAccountLimiter: () => buildAuthAccountLimiter,
61
+ buildAuthIpLimiter: () => buildAuthIpLimiter,
60
62
  normalizeEmail: () => normalizeEmail,
61
63
  normalizeEmailSafe: () => normalizeEmailSafe,
62
64
  requireAuth: () => import_middlewares3.requireAuth,
@@ -86,6 +88,55 @@ var requireEmailLogin = async (ctx, next) => {
86
88
  // src/middlewares/validate.ts
87
89
  var import_middlewares = require("@fonderie/core/middlewares");
88
90
 
91
+ // src/services/rate-limit.ts
92
+ var import_rate_limit = require("@fonderie/rate-limit");
93
+ var min = (n) => n * 60;
94
+ var DEFAULTS = {
95
+ // login: 10/15min per IP AND 5/15min per account (credential stuffing
96
+ // rotates IPs, so the per-account bucket is the one that bites).
97
+ login: {
98
+ ip: { capacity: 10, refillPerSec: 10 / min(15) },
99
+ account: { capacity: 5, refillPerSec: 5 / min(15) },
100
+ accountField: "email"
101
+ },
102
+ // register: 5/hour per IP — signup abuse is IP-shaped.
103
+ register: { ip: { capacity: 5, refillPerSec: 5 / min(60) } },
104
+ // forgot: 5/hour per IP AND 3/hour per account (email-bombing protection).
105
+ forgot: {
106
+ ip: { capacity: 5, refillPerSec: 5 / min(60) },
107
+ account: { capacity: 3, refillPerSec: 3 / min(60) },
108
+ accountField: "email"
109
+ },
110
+ // mfaVerify: 10/15min per IP — TOTP brute-force.
111
+ mfaVerify: { ip: { capacity: 10, refillPerSec: 10 / min(15) } }
112
+ };
113
+ function resolve(route, store, config) {
114
+ if (config === false) return null;
115
+ const override = config?.rules?.[route];
116
+ if (override === false) return null;
117
+ const backing = config?.store ?? new import_rate_limit.StoreAdapterStore(store);
118
+ const def = DEFAULTS[route];
119
+ return {
120
+ store: backing,
121
+ ipRule: override ?? def.ip,
122
+ ...def.account && def.accountField ? { account: { rule: def.account, field: def.accountField } } : {}
123
+ };
124
+ }
125
+ function buildAuthIpLimiter(route, store, config) {
126
+ const r = resolve(route, store, config);
127
+ if (!r) return null;
128
+ return (0, import_rate_limit.rateLimit)({ store: r.store, rule: r.ipRule, key: (0, import_rate_limit.byIp)(`auth:${route}`) });
129
+ }
130
+ function buildAuthAccountLimiter(route, store, config) {
131
+ const r = resolve(route, store, config);
132
+ if (!r || !r.account) return null;
133
+ return (0, import_rate_limit.rateLimit)({
134
+ store: r.store,
135
+ rule: r.account.rule,
136
+ key: (0, import_rate_limit.byBodyField)(`auth:${route}`, r.account.field)
137
+ });
138
+ }
139
+
89
140
  // src/schemas.ts
90
141
  var schemas_exports = {};
91
142
  __export(schemas_exports, {
@@ -203,6 +254,7 @@ var EVENT_KEYS = {
203
254
  };
204
255
 
205
256
  // src/services/jwt.ts
257
+ var import_node_crypto = require("crypto");
206
258
  var import_jsonwebtoken = __toESM(require("jsonwebtoken"), 1);
207
259
  function issueMfaPendingToken(userId, config, loginMethod) {
208
260
  return import_jsonwebtoken.default.sign(
@@ -219,19 +271,21 @@ function issueMfaPendingToken(userId, config, loginMethod) {
219
271
  }
220
272
  function issueTokenPair(userId, config, options) {
221
273
  const duration = config.sessionDuration ?? "7d";
274
+ const accessDuration = config.accessTokenDuration ?? "24h";
222
275
  const loginMethod = options.loginMethod;
223
276
  const phoneVerified = options.phoneVerified ?? false;
277
+ const sid = (0, import_node_crypto.randomUUID)();
224
278
  const accessToken = import_jsonwebtoken.default.sign(
225
- { sub: userId, type: "access", loginMethod, phoneVerified },
279
+ { sub: userId, type: "access", loginMethod, phoneVerified, sid },
226
280
  config.jwtSecret,
227
- { expiresIn: "24h" }
281
+ { expiresIn: accessDuration }
228
282
  );
229
283
  const refreshToken = import_jsonwebtoken.default.sign(
230
- { sub: userId, type: "refresh", loginMethod, phoneVerified },
284
+ { sub: userId, type: "refresh", loginMethod, phoneVerified, sid },
231
285
  config.jwtSecret,
232
286
  { expiresIn: duration }
233
287
  );
234
- return { accessToken, refreshToken };
288
+ return { accessToken, refreshToken, sid };
235
289
  }
236
290
  function refreshTokenExpiry(token) {
237
291
  const decoded = import_jsonwebtoken.default.decode(token);
@@ -246,7 +300,7 @@ function verifyToken(token, config) {
246
300
  }
247
301
 
248
302
  // src/services/mfa.ts
249
- var import_node_crypto = require("crypto");
303
+ var import_node_crypto2 = require("crypto");
250
304
  var STEP = 30;
251
305
  var DRIFT = 1;
252
306
  var DIGITS = 6;
@@ -258,7 +312,7 @@ function hotp(secret, counter) {
258
312
  c >>= 8;
259
313
  }
260
314
  const key = Buffer.from(base32Decode(secret));
261
- const hmac = (0, import_node_crypto.createHmac)("sha1", key).update(buf).digest();
315
+ const hmac = (0, import_node_crypto2.createHmac)("sha1", key).update(buf).digest();
262
316
  const offset = (hmac[19] ?? 0) & 15;
263
317
  const code = (((hmac[offset] ?? 0) & 127) << 24 | ((hmac[offset + 1] ?? 0) & 255) << 16 | ((hmac[offset + 2] ?? 0) & 255) << 8 | (hmac[offset + 3] ?? 0) & 255) % Math.pow(10, DIGITS);
264
318
  return code.toString().padStart(DIGITS, "0");
@@ -305,7 +359,7 @@ function base32Encode(buf) {
305
359
  return output;
306
360
  }
307
361
  function generateTotpSecret() {
308
- return base32Encode((0, import_node_crypto.randomBytes)(20));
362
+ return base32Encode((0, import_node_crypto2.randomBytes)(20));
309
363
  }
310
364
  function generateTotpUri(email2, secret, issuer) {
311
365
  const params = new URLSearchParams({
@@ -327,7 +381,7 @@ function verifyTotpToken(token, secret) {
327
381
  return false;
328
382
  }
329
383
  function generateBackupCodes(count = 8) {
330
- return Array.from({ length: count }, () => (0, import_node_crypto.randomBytes)(4).toString("hex").toUpperCase());
384
+ return Array.from({ length: count }, () => (0, import_node_crypto2.randomBytes)(4).toString("hex").toUpperCase());
331
385
  }
332
386
 
333
387
  // src/controllers/mfa.controller.ts
@@ -587,12 +641,12 @@ var SessionModel = class {
587
641
  this.store = store;
588
642
  }
589
643
  store;
590
- async create(userId, token, expiresAt) {
644
+ async create(userId, token, expiresAt, sid) {
591
645
  await this.store.query(
592
- `INSERT INTO fonderie_sessions (user_id, token, expires_at)
593
- VALUES ($1, $2, $3)
646
+ `INSERT INTO fonderie_sessions (user_id, token, expires_at, sid)
647
+ VALUES ($1, $2, $3, $4)
594
648
  ON CONFLICT (token) DO NOTHING`,
595
- [userId, token, expiresAt]
649
+ [userId, token, expiresAt, sid ?? null]
596
650
  );
597
651
  }
598
652
  async delete(token) {
@@ -605,6 +659,14 @@ var SessionModel = class {
605
659
  );
606
660
  return rows.length > 0;
607
661
  }
662
+ // Liveness check for session-bound access tokens (by the sid claim).
663
+ async aliveBySid(sid) {
664
+ const rows = await this.store.query(
665
+ `SELECT id FROM fonderie_sessions WHERE sid = $1 AND expires_at > now()`,
666
+ [sid]
667
+ );
668
+ return rows.length > 0;
669
+ }
608
670
  };
609
671
 
610
672
  // src/models/backup-code.model.ts
@@ -735,10 +797,10 @@ function mfaController(store, config, issuer, bus) {
735
797
  await users.enableMfa(ctx.user.id);
736
798
  }
737
799
  }
738
- const { accessToken, refreshToken } = issueTokenPair(ctx.user.id, config, {
800
+ const { accessToken, refreshToken, sid } = issueTokenPair(ctx.user.id, config, {
739
801
  loginMethod: ctx.user.loginMethod
740
802
  });
741
- await sessions.create(ctx.user.id, refreshToken, refreshTokenExpiry(refreshToken));
803
+ await sessions.create(ctx.user.id, refreshToken, refreshTokenExpiry(refreshToken), sid);
742
804
  const fullUser = await users.findById(ctx.user.id);
743
805
  if (!fullUser) {
744
806
  return (0, import_core3.setApiResponse)(import_core3.HTTP.SERVER_ERROR, "SERVER_ERROR", "User not found after MFA verify");
@@ -815,7 +877,7 @@ function mfaController(store, config, issuer, bus) {
815
877
  }
816
878
 
817
879
  // src/controllers/auth.controller.ts
818
- var import_node_crypto2 = require("crypto");
880
+ var import_node_crypto3 = require("crypto");
819
881
  var import_events2 = require("@fonderie/events");
820
882
  var import_core4 = require("@fonderie/core");
821
883
 
@@ -1039,7 +1101,7 @@ function authController(store, config, bus) {
1039
1101
  if (!row) {
1040
1102
  return (0, import_core4.setApiResponse)(import_core4.HTTP.SERVER_ERROR, "SERVER_ERROR", "Registration failed");
1041
1103
  }
1042
- const pin = (0, import_node_crypto2.randomInt)(1e5, 1e6).toString();
1104
+ const pin = (0, import_node_crypto3.randomInt)(1e5, 1e6).toString();
1043
1105
  const expiresAt = new Date(Date.now() + 1e3 * 60 * 60 * 24);
1044
1106
  await emailVerif.create(row.id, pin, expiresAt);
1045
1107
  const user = await users.findById(row.id);
@@ -1070,10 +1132,10 @@ function authController(store, config, bus) {
1070
1132
  reqOpts
1071
1133
  ).catch(() => {
1072
1134
  });
1073
- const { accessToken, refreshToken } = issueTokenPair(user.id, config, {
1135
+ const { accessToken, refreshToken, sid } = issueTokenPair(user.id, config, {
1074
1136
  loginMethod: "email"
1075
1137
  });
1076
- await sessions.create(user.id, refreshToken, refreshTokenExpiry(refreshToken));
1138
+ await sessions.create(user.id, refreshToken, refreshTokenExpiry(refreshToken), sid);
1077
1139
  const resolvedRegister = { ...config, ...config.resolve?.(ctx) };
1078
1140
  const requiresVerification = !!resolvedRegister.requireVerification && !user.emailVerifiedAt;
1079
1141
  return Response.json(
@@ -1104,7 +1166,7 @@ function authController(store, config, bus) {
1104
1166
  firstName ?? null,
1105
1167
  lastName ?? null
1106
1168
  );
1107
- const otp = (0, import_node_crypto2.randomInt)(1e5, 1e6).toString();
1169
+ const otp = (0, import_node_crypto3.randomInt)(1e5, 1e6).toString();
1108
1170
  const expiresAt = new Date(Date.now() + OTP_TTL_MS);
1109
1171
  await phoneVerif.upsert(id, normalizePhone(phone2), otp, expiresAt);
1110
1172
  const user = await users.findById(id);
@@ -1135,10 +1197,10 @@ function authController(store, config, bus) {
1135
1197
  reqOpts2
1136
1198
  ).catch(() => {
1137
1199
  });
1138
- const { accessToken, refreshToken } = issueTokenPair(user.id, config, {
1200
+ const { accessToken, refreshToken, sid } = issueTokenPair(user.id, config, {
1139
1201
  loginMethod: "phone"
1140
1202
  });
1141
- await sessions.create(user.id, refreshToken, refreshTokenExpiry(refreshToken));
1203
+ await sessions.create(user.id, refreshToken, refreshTokenExpiry(refreshToken), sid);
1142
1204
  return Response.json(
1143
1205
  {
1144
1206
  reason: "USER_PHONE_REGISTERED",
@@ -1191,10 +1253,10 @@ function authController(store, config, bus) {
1191
1253
  mfaToken
1192
1254
  });
1193
1255
  }
1194
- const { accessToken, refreshToken } = issueTokenPair(user.id, config, {
1256
+ const { accessToken, refreshToken, sid } = issueTokenPair(user.id, config, {
1195
1257
  loginMethod: "email"
1196
1258
  });
1197
- await sessions.create(user.id, refreshToken, refreshTokenExpiry(refreshToken));
1259
+ await sessions.create(user.id, refreshToken, refreshTokenExpiry(refreshToken), sid);
1198
1260
  const resolvedLogin = { ...config, ...config.resolve?.(ctx) };
1199
1261
  const requiresVerification = !!resolvedLogin.requireVerification && !user.emailVerifiedAt;
1200
1262
  return Response.json(
@@ -1228,7 +1290,7 @@ function authController(store, config, bus) {
1228
1290
  "Account suspended. Please contact support."
1229
1291
  );
1230
1292
  }
1231
- const otp = (0, import_node_crypto2.randomInt)(1e5, 1e6).toString();
1293
+ const otp = (0, import_node_crypto3.randomInt)(1e5, 1e6).toString();
1232
1294
  const expiresAt = new Date(Date.now() + OTP_TTL_MS);
1233
1295
  await phoneVerif.upsert(user.id, normalizePhone(phone2), otp, expiresAt);
1234
1296
  bus?.emit(import_events2.NOTIFICATION_EVENT, {
@@ -1237,10 +1299,10 @@ function authController(store, config, bus) {
1237
1299
  recipient: { email: null, phone: normalizePhone(phone2), deviceToken: null }
1238
1300
  }).catch(() => {
1239
1301
  });
1240
- const { accessToken, refreshToken } = issueTokenPair(user.id, config, {
1302
+ const { accessToken, refreshToken, sid } = issueTokenPair(user.id, config, {
1241
1303
  loginMethod: "phone"
1242
1304
  });
1243
- await sessions.create(user.id, refreshToken, refreshTokenExpiry(refreshToken));
1305
+ await sessions.create(user.id, refreshToken, refreshTokenExpiry(refreshToken), sid);
1244
1306
  return Response.json(
1245
1307
  {
1246
1308
  reason: "USER_PHONE_OTP_SENT",
@@ -1301,11 +1363,11 @@ function authController(store, config, bus) {
1301
1363
  return (0, import_core4.setApiResponse)(import_core4.HTTP.UNAUTHORIZED, "UNAUTHORIZED", "Unauthorized");
1302
1364
  }
1303
1365
  await sessions.delete(token);
1304
- const { accessToken, refreshToken } = issueTokenPair(user.id, config, {
1366
+ const { accessToken, refreshToken, sid } = issueTokenPair(user.id, config, {
1305
1367
  loginMethod: payload.loginMethod ?? "email",
1306
1368
  phoneVerified: payload.phoneVerified ?? false
1307
1369
  });
1308
- await sessions.create(user.id, refreshToken, refreshTokenExpiry(refreshToken));
1370
+ await sessions.create(user.id, refreshToken, refreshTokenExpiry(refreshToken), sid);
1309
1371
  return Response.json(
1310
1372
  {
1311
1373
  reason: "TOKENS_REFRESHED",
@@ -1348,7 +1410,7 @@ function authController(store, config, bus) {
1348
1410
  "Password reset email sent (if account exists)."
1349
1411
  );
1350
1412
  }
1351
- const pin = (0, import_node_crypto2.randomInt)(1e5, 1e6).toString();
1413
+ const pin = (0, import_node_crypto3.randomInt)(1e5, 1e6).toString();
1352
1414
  const expiresAt = new Date(Date.now() + 1e3 * 60 * 60);
1353
1415
  await passwordReset.create(user.id, pin, expiresAt);
1354
1416
  bus?.emit(import_events2.NOTIFICATION_EVENT, {
@@ -1436,11 +1498,11 @@ function authController(store, config, bus) {
1436
1498
  );
1437
1499
  }
1438
1500
  await phoneVerif.deleteByUser(ctx.user.id);
1439
- const { accessToken, refreshToken } = issueTokenPair(ctx.user.id, config, {
1501
+ const { accessToken, refreshToken, sid } = issueTokenPair(ctx.user.id, config, {
1440
1502
  loginMethod: "phone",
1441
1503
  phoneVerified: true
1442
1504
  });
1443
- await sessions.create(ctx.user.id, refreshToken, refreshTokenExpiry(refreshToken));
1505
+ await sessions.create(ctx.user.id, refreshToken, refreshTokenExpiry(refreshToken), sid);
1444
1506
  const verifiedUser = await users.findById(ctx.user.id);
1445
1507
  if (!verifiedUser) {
1446
1508
  return (0, import_core4.setApiResponse)(import_core4.HTTP.NOT_FOUND, "NOT_FOUND", "User not found");
@@ -1516,7 +1578,7 @@ function authController(store, config, bus) {
1516
1578
  }
1517
1579
  );
1518
1580
  }
1519
- const otp = (0, import_node_crypto2.randomInt)(1e5, 1e6).toString();
1581
+ const otp = (0, import_node_crypto3.randomInt)(1e5, 1e6).toString();
1520
1582
  const expiresAt2 = new Date(Date.now() + OTP_TTL_MS);
1521
1583
  await phoneVerif.upsert(ctx.user.id, phone2, otp, expiresAt2);
1522
1584
  bus?.emit(import_events2.NOTIFICATION_EVENT, {
@@ -1555,7 +1617,7 @@ function authController(store, config, bus) {
1555
1617
  }
1556
1618
  );
1557
1619
  }
1558
- const pin = (0, import_node_crypto2.randomInt)(1e5, 1e6).toString();
1620
+ const pin = (0, import_node_crypto3.randomInt)(1e5, 1e6).toString();
1559
1621
  const expiresAt = new Date(Date.now() + 1e3 * 60 * 60 * 24);
1560
1622
  await emailVerif.replace(ctx.user.id, pin, expiresAt);
1561
1623
  bus?.emit(import_events2.NOTIFICATION_EVENT, {
@@ -1572,7 +1634,7 @@ function authController(store, config, bus) {
1572
1634
  }
1573
1635
 
1574
1636
  // src/controllers/user.controller.ts
1575
- var import_node_crypto3 = require("crypto");
1637
+ var import_node_crypto4 = require("crypto");
1576
1638
  var import_core5 = require("@fonderie/core");
1577
1639
  var import_events3 = require("@fonderie/events");
1578
1640
  function normalizePhone2(phone2) {
@@ -1665,7 +1727,7 @@ function userController(store, config, bus) {
1665
1727
  if (existing) {
1666
1728
  return (0, import_core5.setApiResponse)(import_core5.HTTP.CONFLICT, "EMAIL_IN_USE", "Email already in use");
1667
1729
  }
1668
- const pin = (0, import_node_crypto3.randomInt)(1e5, 1e6).toString();
1730
+ const pin = (0, import_node_crypto4.randomInt)(1e5, 1e6).toString();
1669
1731
  const expiresAt = new Date(Date.now() + 1e3 * 60 * 60 * 24);
1670
1732
  await emailVerif.replace(ctx.user.id, pin, expiresAt);
1671
1733
  await users.updateEmail(ctx.user.id, normalised);
@@ -1707,7 +1769,7 @@ function userController(store, config, bus) {
1707
1769
  if (existing) {
1708
1770
  return (0, import_core5.setApiResponse)(import_core5.HTTP.CONFLICT, "PHONE_IN_USE", "Phone number already in use");
1709
1771
  }
1710
- const otp = (0, import_node_crypto3.randomInt)(1e5, 1e6).toString();
1772
+ const otp = (0, import_node_crypto4.randomInt)(1e5, 1e6).toString();
1711
1773
  const expiresAt = new Date(Date.now() + 10 * 60 * 1e3);
1712
1774
  await phoneVerif.upsert(ctx.user.id, normalised, otp, expiresAt);
1713
1775
  await users.updatePhone(ctx.user.id, normalised);
@@ -1858,10 +1920,10 @@ function oauthController(store, config) {
1858
1920
  if (!fullUser) {
1859
1921
  return (0, import_core6.setApiResponse)(import_core6.HTTP.SERVER_ERROR, "SERVER_ERROR", "OAuth login failed");
1860
1922
  }
1861
- const { accessToken, refreshToken } = issueTokenPair(upserted.id, config, {
1923
+ const { accessToken, refreshToken, sid } = issueTokenPair(upserted.id, config, {
1862
1924
  loginMethod: "google"
1863
1925
  });
1864
- await sessions.create(upserted.id, refreshToken, refreshTokenExpiry(refreshToken));
1926
+ await sessions.create(upserted.id, refreshToken, refreshTokenExpiry(refreshToken), sid);
1865
1927
  return Response.json(
1866
1928
  {
1867
1929
  reason: "GOOGLE_AUTH_SUCCESS",
@@ -1889,14 +1951,17 @@ function buildAuthRoutes(store, config, bus) {
1889
1951
  const oauth = oauthController(store, config);
1890
1952
  const mfa = mfaController(store, config, config.appName ?? "Fonderie", bus);
1891
1953
  const verifyGate = config.requireVerification ? import_middlewares2.requireVerified : (_ctx, next) => next();
1954
+ const passthrough = (_ctx, next) => next();
1955
+ const ipLimit = (route) => buildAuthIpLimiter(route, store, config.rateLimit) ?? passthrough;
1956
+ const acctLimit = (route) => buildAuthAccountLimiter(route, store, config.rateLimit) ?? passthrough;
1892
1957
  const routes = [
1893
1958
  // Registration & Login (Public)
1894
- ["POST", "/auth/register", (0, import_middlewares.validate)(registerSchema), auth.register],
1895
- ["POST", "/auth/login", (0, import_middlewares.validate)(loginSchema), auth.login],
1959
+ ["POST", "/auth/register", ipLimit("register"), (0, import_middlewares.validate)(registerSchema), auth.register],
1960
+ ["POST", "/auth/login", ipLimit("login"), (0, import_middlewares.validate)(loginSchema), acctLimit("login"), auth.login],
1896
1961
  // Token Management (Public)
1897
1962
  ["POST", "/auth/refresh", (0, import_middlewares.validate)(refreshSchema), auth.refresh],
1898
1963
  // Email — Password Recovery (Public)
1899
- ["POST", "/auth/email/forgot", (0, import_middlewares.validate)(forgotPasswordSchema), auth.forgotPassword],
1964
+ ["POST", "/auth/email/forgot", ipLimit("forgot"), (0, import_middlewares.validate)(forgotPasswordSchema), acctLimit("forgot"), auth.forgotPassword],
1900
1965
  ["POST", "/auth/email/reset", (0, import_middlewares.validate)(resetPasswordSchema), auth.resetPassword],
1901
1966
  // Verification (Protected — email or phone, determined by loginMethod)
1902
1967
  ["POST", "/auth/verify", import_middlewares2.requireAuth, (0, import_middlewares.validate)(verifySchema), auth.verify],
@@ -1916,7 +1981,7 @@ function buildAuthRoutes(store, config, bus) {
1916
1981
  ["POST", "/auth/mfa/setup", import_middlewares2.requireAuth, requireEmailLogin, import_middlewares2.requireVerified, mfa.setup],
1917
1982
  // /auth/mfa/verify accepts both mfaPending tokens (TOTP/backup-code login)
1918
1983
  // and full tokens (setup confirmation), so requireAnyAuth is used here.
1919
- ["POST", "/auth/mfa/verify", import_middlewares2.requireAnyAuth, requireEmailLogin, import_middlewares2.requireVerified, (0, import_middlewares.validate)(mfaTokenSchema), mfa.verify],
1984
+ ["POST", "/auth/mfa/verify", ipLimit("mfaVerify"), import_middlewares2.requireAnyAuth, requireEmailLogin, import_middlewares2.requireVerified, (0, import_middlewares.validate)(mfaTokenSchema), mfa.verify],
1920
1985
  ["POST", "/auth/mfa/disable", import_middlewares2.requireAuth, requireEmailLogin, import_middlewares2.requireVerified, (0, import_middlewares.validate)(mfaTokenSchema), mfa.disable],
1921
1986
  [
1922
1987
  "POST",
@@ -1940,6 +2005,7 @@ function buildAuthRoutes(store, config, bus) {
1940
2005
  // src/middlewares/session.ts
1941
2006
  function withSession(store, config) {
1942
2007
  const users = new UserModel(store);
2008
+ const sessions = new SessionModel(store);
1943
2009
  return async (ctx, next) => {
1944
2010
  const token = extractToken(ctx.request);
1945
2011
  if (!token) {
@@ -1949,6 +2015,9 @@ function withSession(store, config) {
1949
2015
  if (!payload || payload.type !== "access") {
1950
2016
  return next();
1951
2017
  }
2018
+ if (payload.sid && !await sessions.aliveBySid(payload.sid)) {
2019
+ return next();
2020
+ }
1952
2021
  const user = await users.findById(payload.sub);
1953
2022
  if (!user || user.suspended || user.deletedAt) {
1954
2023
  return next();
@@ -2001,6 +2070,8 @@ var import_middlewares3 = require("@fonderie/core/middlewares");
2001
2070
  AUTH_CONFIG_KEYS,
2002
2071
  AuthModule,
2003
2072
  MESSAGE_KEYS,
2073
+ buildAuthAccountLimiter,
2074
+ buildAuthIpLimiter,
2004
2075
  normalizeEmail,
2005
2076
  normalizeEmailSafe,
2006
2077
  requireAuth,