@fonderie/auth 1.2.0 → 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.d.cts CHANGED
@@ -2,8 +2,8 @@ export { IMfaChallenge, ISession, IUser } from './types.cjs';
2
2
  import { IFonderieModule, IFonderieApp } from '@fonderie/core';
3
3
  import { IStoreAdapter } from '@fonderie/store';
4
4
  import { EventBus } from '@fonderie/events';
5
- import { I as IAuthConfig } from './session-C54ksYE8.cjs';
6
- export { A as AUTH_CONFIG_KEYS, a as AuthLimitedRoute, b as AuthMessageKey, c as IAuthRateLimitConfig, d as IAuthRuntimeConfig, e as IAuthSecrets, M as MESSAGE_KEYS, f as buildAuthAccountLimiter, g as buildAuthIpLimiter, w as withSession } from './session-C54ksYE8.cjs';
5
+ import { I as IAuthConfig } from './session-DbtCnbiG.cjs';
6
+ export { A as AUTH_CONFIG_KEYS, a as AuthLimitedRoute, b as AuthMessageKey, c as IAuthRateLimitConfig, d as IAuthRuntimeConfig, e as IAuthSecrets, M as MESSAGE_KEYS, f as buildAuthAccountLimiter, g as buildAuthIpLimiter, w as withSession } from './session-DbtCnbiG.cjs';
7
7
  export { IUserDTO, toUserDTO } from './dtos/user.cjs';
8
8
  export { requireAuth, validate } from '@fonderie/core/middlewares';
9
9
  import { z } from 'zod';
package/dist/index.d.ts CHANGED
@@ -2,8 +2,8 @@ export { IMfaChallenge, ISession, IUser } from './types.js';
2
2
  import { IFonderieModule, IFonderieApp } from '@fonderie/core';
3
3
  import { IStoreAdapter } from '@fonderie/store';
4
4
  import { EventBus } from '@fonderie/events';
5
- import { I as IAuthConfig } from './session-C54ksYE8.js';
6
- export { A as AUTH_CONFIG_KEYS, a as AuthLimitedRoute, b as AuthMessageKey, c as IAuthRateLimitConfig, d as IAuthRuntimeConfig, e as IAuthSecrets, M as MESSAGE_KEYS, f as buildAuthAccountLimiter, g as buildAuthIpLimiter, w as withSession } from './session-C54ksYE8.js';
5
+ import { I as IAuthConfig } from './session-DbtCnbiG.js';
6
+ export { A as AUTH_CONFIG_KEYS, a as AuthLimitedRoute, b as AuthMessageKey, c as IAuthRateLimitConfig, d as IAuthRuntimeConfig, e as IAuthSecrets, M as MESSAGE_KEYS, f as buildAuthAccountLimiter, g as buildAuthIpLimiter, w as withSession } from './session-DbtCnbiG.js';
7
7
  export { IUserDTO, toUserDTO } from './dtos/user.js';
8
8
  export { requireAuth, validate } from '@fonderie/core/middlewares';
9
9
  import { z } from 'zod';
package/dist/index.js CHANGED
@@ -219,6 +219,7 @@ var EVENT_KEYS = {
219
219
  };
220
220
 
221
221
  // src/services/jwt.ts
222
+ import { randomUUID } from "crypto";
222
223
  import jwt from "jsonwebtoken";
223
224
  function issueMfaPendingToken(userId, config, loginMethod) {
224
225
  return jwt.sign(
@@ -235,19 +236,21 @@ function issueMfaPendingToken(userId, config, loginMethod) {
235
236
  }
236
237
  function issueTokenPair(userId, config, options) {
237
238
  const duration = config.sessionDuration ?? "7d";
239
+ const accessDuration = config.accessTokenDuration ?? "24h";
238
240
  const loginMethod = options.loginMethod;
239
241
  const phoneVerified = options.phoneVerified ?? false;
242
+ const sid = randomUUID();
240
243
  const accessToken = jwt.sign(
241
- { sub: userId, type: "access", loginMethod, phoneVerified },
244
+ { sub: userId, type: "access", loginMethod, phoneVerified, sid },
242
245
  config.jwtSecret,
243
- { expiresIn: "24h" }
246
+ { expiresIn: accessDuration }
244
247
  );
245
248
  const refreshToken = jwt.sign(
246
- { sub: userId, type: "refresh", loginMethod, phoneVerified },
249
+ { sub: userId, type: "refresh", loginMethod, phoneVerified, sid },
247
250
  config.jwtSecret,
248
251
  { expiresIn: duration }
249
252
  );
250
- return { accessToken, refreshToken };
253
+ return { accessToken, refreshToken, sid };
251
254
  }
252
255
  function refreshTokenExpiry(token) {
253
256
  const decoded = jwt.decode(token);
@@ -603,12 +606,12 @@ var SessionModel = class {
603
606
  this.store = store;
604
607
  }
605
608
  store;
606
- async create(userId, token, expiresAt) {
609
+ async create(userId, token, expiresAt, sid) {
607
610
  await this.store.query(
608
- `INSERT INTO fonderie_sessions (user_id, token, expires_at)
609
- VALUES ($1, $2, $3)
611
+ `INSERT INTO fonderie_sessions (user_id, token, expires_at, sid)
612
+ VALUES ($1, $2, $3, $4)
610
613
  ON CONFLICT (token) DO NOTHING`,
611
- [userId, token, expiresAt]
614
+ [userId, token, expiresAt, sid ?? null]
612
615
  );
613
616
  }
614
617
  async delete(token) {
@@ -621,6 +624,14 @@ var SessionModel = class {
621
624
  );
622
625
  return rows.length > 0;
623
626
  }
627
+ // Liveness check for session-bound access tokens (by the sid claim).
628
+ async aliveBySid(sid) {
629
+ const rows = await this.store.query(
630
+ `SELECT id FROM fonderie_sessions WHERE sid = $1 AND expires_at > now()`,
631
+ [sid]
632
+ );
633
+ return rows.length > 0;
634
+ }
624
635
  };
625
636
 
626
637
  // src/models/backup-code.model.ts
@@ -751,10 +762,10 @@ function mfaController(store, config, issuer, bus) {
751
762
  await users.enableMfa(ctx.user.id);
752
763
  }
753
764
  }
754
- const { accessToken, refreshToken } = issueTokenPair(ctx.user.id, config, {
765
+ const { accessToken, refreshToken, sid } = issueTokenPair(ctx.user.id, config, {
755
766
  loginMethod: ctx.user.loginMethod
756
767
  });
757
- await sessions.create(ctx.user.id, refreshToken, refreshTokenExpiry(refreshToken));
768
+ await sessions.create(ctx.user.id, refreshToken, refreshTokenExpiry(refreshToken), sid);
758
769
  const fullUser = await users.findById(ctx.user.id);
759
770
  if (!fullUser) {
760
771
  return setApiResponse2(HTTP2.SERVER_ERROR, "SERVER_ERROR", "User not found after MFA verify");
@@ -1086,10 +1097,10 @@ function authController(store, config, bus) {
1086
1097
  reqOpts
1087
1098
  ).catch(() => {
1088
1099
  });
1089
- const { accessToken, refreshToken } = issueTokenPair(user.id, config, {
1100
+ const { accessToken, refreshToken, sid } = issueTokenPair(user.id, config, {
1090
1101
  loginMethod: "email"
1091
1102
  });
1092
- await sessions.create(user.id, refreshToken, refreshTokenExpiry(refreshToken));
1103
+ await sessions.create(user.id, refreshToken, refreshTokenExpiry(refreshToken), sid);
1093
1104
  const resolvedRegister = { ...config, ...config.resolve?.(ctx) };
1094
1105
  const requiresVerification = !!resolvedRegister.requireVerification && !user.emailVerifiedAt;
1095
1106
  return Response.json(
@@ -1151,10 +1162,10 @@ function authController(store, config, bus) {
1151
1162
  reqOpts2
1152
1163
  ).catch(() => {
1153
1164
  });
1154
- const { accessToken, refreshToken } = issueTokenPair(user.id, config, {
1165
+ const { accessToken, refreshToken, sid } = issueTokenPair(user.id, config, {
1155
1166
  loginMethod: "phone"
1156
1167
  });
1157
- await sessions.create(user.id, refreshToken, refreshTokenExpiry(refreshToken));
1168
+ await sessions.create(user.id, refreshToken, refreshTokenExpiry(refreshToken), sid);
1158
1169
  return Response.json(
1159
1170
  {
1160
1171
  reason: "USER_PHONE_REGISTERED",
@@ -1207,10 +1218,10 @@ function authController(store, config, bus) {
1207
1218
  mfaToken
1208
1219
  });
1209
1220
  }
1210
- const { accessToken, refreshToken } = issueTokenPair(user.id, config, {
1221
+ const { accessToken, refreshToken, sid } = issueTokenPair(user.id, config, {
1211
1222
  loginMethod: "email"
1212
1223
  });
1213
- await sessions.create(user.id, refreshToken, refreshTokenExpiry(refreshToken));
1224
+ await sessions.create(user.id, refreshToken, refreshTokenExpiry(refreshToken), sid);
1214
1225
  const resolvedLogin = { ...config, ...config.resolve?.(ctx) };
1215
1226
  const requiresVerification = !!resolvedLogin.requireVerification && !user.emailVerifiedAt;
1216
1227
  return Response.json(
@@ -1253,10 +1264,10 @@ function authController(store, config, bus) {
1253
1264
  recipient: { email: null, phone: normalizePhone(phone2), deviceToken: null }
1254
1265
  }).catch(() => {
1255
1266
  });
1256
- const { accessToken, refreshToken } = issueTokenPair(user.id, config, {
1267
+ const { accessToken, refreshToken, sid } = issueTokenPair(user.id, config, {
1257
1268
  loginMethod: "phone"
1258
1269
  });
1259
- await sessions.create(user.id, refreshToken, refreshTokenExpiry(refreshToken));
1270
+ await sessions.create(user.id, refreshToken, refreshTokenExpiry(refreshToken), sid);
1260
1271
  return Response.json(
1261
1272
  {
1262
1273
  reason: "USER_PHONE_OTP_SENT",
@@ -1317,11 +1328,11 @@ function authController(store, config, bus) {
1317
1328
  return setApiResponse3(HTTP3.UNAUTHORIZED, "UNAUTHORIZED", "Unauthorized");
1318
1329
  }
1319
1330
  await sessions.delete(token);
1320
- const { accessToken, refreshToken } = issueTokenPair(user.id, config, {
1331
+ const { accessToken, refreshToken, sid } = issueTokenPair(user.id, config, {
1321
1332
  loginMethod: payload.loginMethod ?? "email",
1322
1333
  phoneVerified: payload.phoneVerified ?? false
1323
1334
  });
1324
- await sessions.create(user.id, refreshToken, refreshTokenExpiry(refreshToken));
1335
+ await sessions.create(user.id, refreshToken, refreshTokenExpiry(refreshToken), sid);
1325
1336
  return Response.json(
1326
1337
  {
1327
1338
  reason: "TOKENS_REFRESHED",
@@ -1452,11 +1463,11 @@ function authController(store, config, bus) {
1452
1463
  );
1453
1464
  }
1454
1465
  await phoneVerif.deleteByUser(ctx.user.id);
1455
- const { accessToken, refreshToken } = issueTokenPair(ctx.user.id, config, {
1466
+ const { accessToken, refreshToken, sid } = issueTokenPair(ctx.user.id, config, {
1456
1467
  loginMethod: "phone",
1457
1468
  phoneVerified: true
1458
1469
  });
1459
- await sessions.create(ctx.user.id, refreshToken, refreshTokenExpiry(refreshToken));
1470
+ await sessions.create(ctx.user.id, refreshToken, refreshTokenExpiry(refreshToken), sid);
1460
1471
  const verifiedUser = await users.findById(ctx.user.id);
1461
1472
  if (!verifiedUser) {
1462
1473
  return setApiResponse3(HTTP3.NOT_FOUND, "NOT_FOUND", "User not found");
@@ -1874,10 +1885,10 @@ function oauthController(store, config) {
1874
1885
  if (!fullUser) {
1875
1886
  return setApiResponse5(HTTP5.SERVER_ERROR, "SERVER_ERROR", "OAuth login failed");
1876
1887
  }
1877
- const { accessToken, refreshToken } = issueTokenPair(upserted.id, config, {
1888
+ const { accessToken, refreshToken, sid } = issueTokenPair(upserted.id, config, {
1878
1889
  loginMethod: "google"
1879
1890
  });
1880
- await sessions.create(upserted.id, refreshToken, refreshTokenExpiry(refreshToken));
1891
+ await sessions.create(upserted.id, refreshToken, refreshTokenExpiry(refreshToken), sid);
1881
1892
  return Response.json(
1882
1893
  {
1883
1894
  reason: "GOOGLE_AUTH_SUCCESS",
@@ -1959,6 +1970,7 @@ function buildAuthRoutes(store, config, bus) {
1959
1970
  // src/middlewares/session.ts
1960
1971
  function withSession(store, config) {
1961
1972
  const users = new UserModel(store);
1973
+ const sessions = new SessionModel(store);
1962
1974
  return async (ctx, next) => {
1963
1975
  const token = extractToken(ctx.request);
1964
1976
  if (!token) {
@@ -1968,6 +1980,9 @@ function withSession(store, config) {
1968
1980
  if (!payload || payload.type !== "access") {
1969
1981
  return next();
1970
1982
  }
1983
+ if (payload.sid && !await sessions.aliveBySid(payload.sid)) {
1984
+ return next();
1985
+ }
1971
1986
  const user = await users.findById(payload.sub);
1972
1987
  if (!user || user.suspended || user.deletedAt) {
1973
1988
  return next();