@fonderie/auth 7.14.4 → 7.15.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
@@ -3,8 +3,8 @@ export { IMfaChallenge, ISession } from './types.cjs';
3
3
  import { IFonderieModule, IAdminDescription, IReadinessProblem, IFonderieApp, IDefaultTemplate, IAdminRoute } from '@fonderie/core';
4
4
  import { IStoreAdapter } from '@fonderie/store';
5
5
  import { EventBus } from '@fonderie/events';
6
- import { I as IAuthConfig, a as IRequestLocation } from './session-BBF5t-3Y.cjs';
7
- export { A as AUTH_CONFIG_KEYS, b as AuthLimitedRoute, c as AuthMessageKey, d as IAuthRateLimitConfig, e as IAuthRuntimeConfig, f as IAuthSecrets, g as IDataExportContributor, h as ILocationRequest, L as LocationResolver, M as MESSAGE_KEYS, i as buildAuthAccountLimiter, j as buildAuthIpLimiter, r as resolveLocation, s as sanitizeLocation, w as withSession } from './session-BBF5t-3Y.cjs';
6
+ import { I as IAuthConfig, a as IRequestLocation } from './session-auNb2L8m.cjs';
7
+ export { A as AUTH_CONFIG_KEYS, b as AuthLimitedRoute, c as AuthMessageKey, d as IAuthRateLimitConfig, e as IAuthRuntimeConfig, f as IAuthSecrets, g as IDataExportContributor, h as ILocationRequest, L as LocationResolver, M as MESSAGE_KEYS, i as buildAuthAccountLimiter, j as buildAuthIpLimiter, r as resolveLocation, s as sanitizeLocation, w as withSession } from './session-auNb2L8m.cjs';
8
8
  import { IUserDTO } from './dtos/user.cjs';
9
9
  export { toUserDTO } from './dtos/user.cjs';
10
10
  export { requireAuth, validate } from '@fonderie/core/middlewares';
package/dist/index.d.ts CHANGED
@@ -3,8 +3,8 @@ export { IMfaChallenge, ISession } from './types.js';
3
3
  import { IFonderieModule, IAdminDescription, IReadinessProblem, IFonderieApp, IDefaultTemplate, IAdminRoute } from '@fonderie/core';
4
4
  import { IStoreAdapter } from '@fonderie/store';
5
5
  import { EventBus } from '@fonderie/events';
6
- import { I as IAuthConfig, a as IRequestLocation } from './session-BBF5t-3Y.js';
7
- export { A as AUTH_CONFIG_KEYS, b as AuthLimitedRoute, c as AuthMessageKey, d as IAuthRateLimitConfig, e as IAuthRuntimeConfig, f as IAuthSecrets, g as IDataExportContributor, h as ILocationRequest, L as LocationResolver, M as MESSAGE_KEYS, i as buildAuthAccountLimiter, j as buildAuthIpLimiter, r as resolveLocation, s as sanitizeLocation, w as withSession } from './session-BBF5t-3Y.js';
6
+ import { I as IAuthConfig, a as IRequestLocation } from './session-auNb2L8m.js';
7
+ export { A as AUTH_CONFIG_KEYS, b as AuthLimitedRoute, c as AuthMessageKey, d as IAuthRateLimitConfig, e as IAuthRuntimeConfig, f as IAuthSecrets, g as IDataExportContributor, h as ILocationRequest, L as LocationResolver, M as MESSAGE_KEYS, i as buildAuthAccountLimiter, j as buildAuthIpLimiter, r as resolveLocation, s as sanitizeLocation, w as withSession } from './session-auNb2L8m.js';
8
8
  import { IUserDTO } from './dtos/user.js';
9
9
  export { toUserDTO } from './dtos/user.js';
10
10
  export { requireAuth, validate } from '@fonderie/core/middlewares';
package/dist/index.js CHANGED
@@ -284,8 +284,14 @@ var EVENT_KEYS = {
284
284
  };
285
285
 
286
286
  // src/services/jwt.ts
287
- import { randomUUID } from "crypto";
287
+ import { createHash, randomUUID } from "crypto";
288
288
  import jwt from "jsonwebtoken";
289
+ function keyIdOf(secret) {
290
+ return createHash("sha256").update(secret).digest("hex").slice(0, 16);
291
+ }
292
+ function keyRing(config) {
293
+ return [config.jwtSecret, ...config.jwtPreviousSecrets ?? []].filter((s) => typeof s === "string" && s.length > 0);
294
+ }
289
295
  function issueMfaPendingToken(userId, config, loginMethod) {
290
296
  return jwt.sign(
291
297
  {
@@ -296,7 +302,7 @@ function issueMfaPendingToken(userId, config, loginMethod) {
296
302
  mfaPending: true
297
303
  },
298
304
  config.jwtSecret,
299
- { expiresIn: "5m" }
305
+ { expiresIn: "5m", keyid: keyIdOf(config.jwtSecret) }
300
306
  );
301
307
  }
302
308
  function issueTokenPair(userId, config, options) {
@@ -308,12 +314,12 @@ function issueTokenPair(userId, config, options) {
308
314
  const accessToken = jwt.sign(
309
315
  { sub: userId, type: "access", loginMethod, phoneVerified, sid },
310
316
  config.jwtSecret,
311
- { expiresIn: accessDuration }
317
+ { expiresIn: accessDuration, keyid: keyIdOf(config.jwtSecret) }
312
318
  );
313
319
  const refreshToken = jwt.sign(
314
320
  { sub: userId, type: "refresh", loginMethod, phoneVerified, sid },
315
321
  config.jwtSecret,
316
- { expiresIn: duration }
322
+ { expiresIn: duration, keyid: keyIdOf(config.jwtSecret) }
317
323
  );
318
324
  return { accessToken, refreshToken, sid };
319
325
  }
@@ -322,11 +328,16 @@ function refreshTokenExpiry(token) {
322
328
  return decoded?.exp ? new Date(decoded.exp * 1e3) : new Date(Date.now() + 7 * 24 * 60 * 60 * 1e3);
323
329
  }
324
330
  function verifyToken(token, config) {
325
- try {
326
- return jwt.verify(token, config.jwtSecret);
327
- } catch {
328
- return null;
331
+ const ring = keyRing(config);
332
+ const kid = jwt.decode(token, { complete: true })?.header?.kid;
333
+ const candidates = typeof kid === "string" ? ring.filter((s) => keyIdOf(s) === kid) : ring;
334
+ for (const secret of candidates) {
335
+ try {
336
+ return jwt.verify(token, secret);
337
+ } catch {
338
+ }
329
339
  }
340
+ return null;
330
341
  }
331
342
 
332
343
  // src/services/mfa.ts
@@ -1421,8 +1432,8 @@ function normalizeEmailSafe(email2) {
1421
1432
  }
1422
1433
 
1423
1434
  // src/models/password-reset.model.ts
1424
- import { createHash } from "crypto";
1425
- var hashSecret = (value) => createHash("sha256").update(value).digest("hex");
1435
+ import { createHash as createHash2 } from "crypto";
1436
+ var hashSecret = (value) => createHash2("sha256").update(value).digest("hex");
1426
1437
  var PasswordResetModel = class {
1427
1438
  constructor(store) {
1428
1439
  this.store = store;
@@ -2589,7 +2600,7 @@ function userController(store, config, bus) {
2589
2600
  }
2590
2601
 
2591
2602
  // src/controllers/oauth.controller.ts
2592
- import { randomBytes as randomBytes4, createPublicKey, createHash as createHash2 } from "crypto";
2603
+ import { randomBytes as randomBytes4, createPublicKey, createHash as createHash3 } from "crypto";
2593
2604
  import jwt2 from "jsonwebtoken";
2594
2605
  import { setApiResponse as setApiResponse5, HTTP as HTTP5, constantTimeEqual as constantTimeEqual2, background as background5 } from "@fonderie/core";
2595
2606
  import { NOTIFICATION_EVENT as NOTIFICATION_EVENT4 } from "@fonderie/events";
@@ -3066,7 +3077,7 @@ function oauthController(store, config, bus) {
3066
3077
  });
3067
3078
  return setApiResponse5(HTTP5.UNAUTHORIZED, "APPLE_AUTH_FAILED", "Invalid Apple identity token");
3068
3079
  }
3069
- const tokenHash = createHash2("sha256").update(identityToken).digest("hex");
3080
+ const tokenHash = createHash3("sha256").update(identityToken).digest("hex");
3070
3081
  const expMs = typeof claims.exp === "number" ? claims.exp * 1e3 : Date.now() + 5 * 60 * 1e3;
3071
3082
  const firstUse = await consumedTokens.consumeOnce(tokenHash, new Date(expMs));
3072
3083
  if (!firstUse) {
@@ -3226,6 +3237,18 @@ function collectAuthConfigProblems(config) {
3226
3237
  reason: "JWT_SECRET_PLACEHOLDER"
3227
3238
  });
3228
3239
  }
3240
+ (config.jwtPreviousSecrets ?? []).forEach((previous, index) => {
3241
+ if (secretStrengthProblem(previous ?? "")) {
3242
+ problems.push({
3243
+ module: MODULE,
3244
+ severity: "error",
3245
+ message: `jwtPreviousSecrets[${index}] is too short or a placeholder \u2014 it still verifies tokens`,
3246
+ domain: "auth",
3247
+ reason: "JWT_PREVIOUS_SECRET_WEAK",
3248
+ metadata: { index }
3249
+ });
3250
+ }
3251
+ });
3229
3252
  if (config.secureCookies === false) {
3230
3253
  problems.push({
3231
3254
  module: MODULE,
@@ -3511,7 +3534,7 @@ var AuthModule = class {
3511
3534
  name = "@fonderie/auth";
3512
3535
  // Baked in at build time by tsup.base, so the operator's Modules page can
3513
3536
  // say what is actually deployed rather than 'not reported'.
3514
- version = "7.14.4";
3537
+ version = "7.15.0";
3515
3538
  // Users, sessions, login history, suspend — only through @fonderie/admin.
3516
3539
  describeAdmin() {
3517
3540
  return { routes: describeAuthAdminRoutes(this.store) };