@fonderie/auth 7.16.0 → 7.17.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/brain/signatures.md +1 -0
- package/dist/index.cjs +40 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +40 -10
- package/dist/index.js.map +1 -1
- package/dist/middlewares/index.cjs +8 -3
- package/dist/middlewares/index.cjs.map +1 -1
- package/dist/middlewares/index.d.cts +1 -1
- package/dist/middlewares/index.d.ts +1 -1
- package/dist/middlewares/index.js +8 -3
- package/dist/middlewares/index.js.map +1 -1
- package/dist/{session-auNb2L8m.d.cts → session-C-ptRylj.d.cts} +1 -0
- package/dist/{session-auNb2L8m.d.ts → session-C-ptRylj.d.ts} +1 -0
- package/package.json +1 -1
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-
|
|
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-
|
|
6
|
+
import { I as IAuthConfig, a as IRequestLocation } from './session-C-ptRylj.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-C-ptRylj.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-
|
|
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-
|
|
6
|
+
import { I as IAuthConfig, a as IRequestLocation } from './session-C-ptRylj.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-C-ptRylj.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
|
@@ -243,8 +243,11 @@ import { NOTIFICATION_EVENT } from "@fonderie/events";
|
|
|
243
243
|
|
|
244
244
|
// src/config.ts
|
|
245
245
|
var DEFAULT_VERIFICATION_COOLDOWN = 5 * 60 * 1e3;
|
|
246
|
+
var DEFAULT_SESSION_DURATION = "90d";
|
|
247
|
+
var DEFAULT_ACCESS_TOKEN_DURATION = "1h";
|
|
246
248
|
var AUTH_CONFIG_KEYS = {
|
|
247
249
|
sessionDuration: "auth.session.duration",
|
|
250
|
+
sessionMaxAge: "auth.session.max_age",
|
|
248
251
|
verificationCooldown: "auth.verification.cooldown",
|
|
249
252
|
mfa: "auth.mfa.enabled",
|
|
250
253
|
requireVerification: "auth.verification.required"
|
|
@@ -309,13 +312,14 @@ function issueMfaPendingToken(userId, config, loginMethod) {
|
|
|
309
312
|
);
|
|
310
313
|
}
|
|
311
314
|
function issueTokenPair(userId, config, options) {
|
|
312
|
-
const duration = config.sessionDuration
|
|
313
|
-
const accessDuration = config.accessTokenDuration
|
|
315
|
+
const duration = validDuration(config.sessionDuration, DEFAULT_SESSION_DURATION, "sessionDuration");
|
|
316
|
+
const accessDuration = validDuration(config.accessTokenDuration, DEFAULT_ACCESS_TOKEN_DURATION, "accessTokenDuration");
|
|
317
|
+
const authTime = options.authTime ?? Math.floor(Date.now() / 1e3);
|
|
314
318
|
const loginMethod = options.loginMethod;
|
|
315
319
|
const phoneVerified = options.phoneVerified ?? false;
|
|
316
320
|
const sid = options.sid ?? randomUUID();
|
|
317
321
|
const accessToken = jwt.sign(
|
|
318
|
-
{ sub: userId, type: "access", loginMethod, phoneVerified, sid },
|
|
322
|
+
{ sub: userId, type: "access", loginMethod, phoneVerified, sid, auth_time: authTime },
|
|
319
323
|
config.jwtSecret,
|
|
320
324
|
// jwtid: every token is unique. With the session id kept across rotations,
|
|
321
325
|
// two tokens issued in the same second would otherwise be byte-identical —
|
|
@@ -323,7 +327,7 @@ function issueTokenPair(userId, config, options) {
|
|
|
323
327
|
{ expiresIn: accessDuration, keyid: keyIdOf(config.jwtSecret), jwtid: randomUUID() }
|
|
324
328
|
);
|
|
325
329
|
const refreshToken = jwt.sign(
|
|
326
|
-
{ sub: userId, type: "refresh", loginMethod, phoneVerified, sid },
|
|
330
|
+
{ sub: userId, type: "refresh", loginMethod, phoneVerified, sid, auth_time: authTime },
|
|
327
331
|
config.jwtSecret,
|
|
328
332
|
{ expiresIn: duration, keyid: keyIdOf(config.jwtSecret), jwtid: randomUUID() }
|
|
329
333
|
);
|
|
@@ -345,6 +349,24 @@ function verifyToken(token, config) {
|
|
|
345
349
|
}
|
|
346
350
|
return null;
|
|
347
351
|
}
|
|
352
|
+
function durationMs(value) {
|
|
353
|
+
const m = /^\s*(\d+)\s*(ms|s|m|h|d|w|y)?\s*$/i.exec(value ?? "");
|
|
354
|
+
if (!m) return null;
|
|
355
|
+
const unit = (m[2] ?? "s").toLowerCase();
|
|
356
|
+
const scale = { ms: 1, s: 1e3, m: 6e4, h: 36e5, d: 864e5, w: 6048e5, y: 315576e5 };
|
|
357
|
+
return Number(m[1]) * (scale[unit] ?? 1e3);
|
|
358
|
+
}
|
|
359
|
+
var warnedDurations = /* @__PURE__ */ new Set();
|
|
360
|
+
function validDuration(value, fallback, name) {
|
|
361
|
+
if (value === void 0 || value === null || value === "") return fallback;
|
|
362
|
+
if (durationMs(String(value)) !== null) return String(value);
|
|
363
|
+
const key = `${name}=${String(value)}`;
|
|
364
|
+
if (!warnedDurations.has(key)) {
|
|
365
|
+
warnedDurations.add(key);
|
|
366
|
+
console.warn(`[auth] ${name} ${JSON.stringify(value)} is not a duration (e.g. '90d', '1h') \u2014 using ${fallback}`);
|
|
367
|
+
}
|
|
368
|
+
return fallback;
|
|
369
|
+
}
|
|
348
370
|
|
|
349
371
|
// src/services/mfa.ts
|
|
350
372
|
import { createHmac, randomBytes } from "crypto";
|
|
@@ -994,12 +1016,12 @@ var SessionModel = class {
|
|
|
994
1016
|
async match(token) {
|
|
995
1017
|
const hash = hashRefreshToken(token);
|
|
996
1018
|
const [current] = await this.store.query(
|
|
997
|
-
`SELECT id, user_id, sid, token FROM fonderie_sessions WHERE token IN ($1, $2) AND expires_at > now() LIMIT 1`,
|
|
1019
|
+
`SELECT id, user_id, sid, token, created_at FROM fonderie_sessions WHERE token IN ($1, $2) AND expires_at > now() LIMIT 1`,
|
|
998
1020
|
[hash, token]
|
|
999
1021
|
);
|
|
1000
1022
|
if (current) return { kind: "current", row: toRow(current) };
|
|
1001
1023
|
const [previous] = await this.store.query(
|
|
1002
|
-
`SELECT id, user_id, sid, token, (previous_valid_until > now()) AS in_grace
|
|
1024
|
+
`SELECT id, user_id, sid, token, created_at, (previous_valid_until > now()) AS in_grace
|
|
1003
1025
|
FROM fonderie_sessions WHERE previous_token_hash = $1 AND expires_at > now() LIMIT 1`,
|
|
1004
1026
|
[hash]
|
|
1005
1027
|
);
|
|
@@ -1114,7 +1136,7 @@ var SessionModel = class {
|
|
|
1114
1136
|
}
|
|
1115
1137
|
};
|
|
1116
1138
|
function toRow(r) {
|
|
1117
|
-
return { id: r.id, userId: r.user_id, sid: r.sid, stored: r.token };
|
|
1139
|
+
return { id: r.id, userId: r.user_id, sid: r.sid, stored: r.token, createdAt: r.created_at ? new Date(r.created_at) : /* @__PURE__ */ new Date() };
|
|
1118
1140
|
}
|
|
1119
1141
|
|
|
1120
1142
|
// src/models/backup-code.model.ts
|
|
@@ -1958,6 +1980,8 @@ function authController(store, config, bus, locales = defineLocales()) {
|
|
|
1958
1980
|
if (!payload || payload.type !== "refresh") {
|
|
1959
1981
|
return setApiResponse3(HTTP3.UNAUTHORIZED, "TOKEN_REFRESH_FAILED", "Invalid refresh token");
|
|
1960
1982
|
}
|
|
1983
|
+
const resolvedRefresh = { ...config, ...config.resolve?.(ctx) };
|
|
1984
|
+
const maxAgeMs = resolvedRefresh.sessionMaxAge ? durationMs(String(resolvedRefresh.sessionMaxAge)) : null;
|
|
1961
1985
|
let issued = null;
|
|
1962
1986
|
for (let attempt = 0; attempt < 3 && !issued; attempt++) {
|
|
1963
1987
|
const found = await sessions.match(token);
|
|
@@ -1971,14 +1995,20 @@ function authController(store, config, bus, locales = defineLocales()) {
|
|
|
1971
1995
|
);
|
|
1972
1996
|
return setApiResponse3(HTTP3.UNAUTHORIZED, "TOKEN_REFRESH_FAILED", "Session expired or already revoked");
|
|
1973
1997
|
}
|
|
1998
|
+
if (maxAgeMs !== null && Date.now() - found.row.createdAt.getTime() > maxAgeMs) {
|
|
1999
|
+
await sessions.revokeById(found.row.id);
|
|
2000
|
+
return setApiResponse3(HTTP3.UNAUTHORIZED, "TOKEN_REFRESH_FAILED", "Session reached its maximum age \u2014 sign in again");
|
|
2001
|
+
}
|
|
1974
2002
|
const user = await users.findById(found.row.userId);
|
|
1975
2003
|
if (!user || user.suspended || user.deletedAt || user.id !== payload.sub) {
|
|
1976
2004
|
return setApiResponse3(HTTP3.UNAUTHORIZED, "UNAUTHORIZED", "Unauthorized");
|
|
1977
2005
|
}
|
|
1978
|
-
const pair = issueTokenPair(user.id,
|
|
2006
|
+
const pair = issueTokenPair(user.id, resolvedRefresh, {
|
|
1979
2007
|
loginMethod: payload.loginMethod ?? "email",
|
|
1980
2008
|
phoneVerified: payload.phoneVerified ?? false,
|
|
1981
|
-
...found.row.sid ? { sid: found.row.sid } : {}
|
|
2009
|
+
...found.row.sid ? { sid: found.row.sid } : {},
|
|
2010
|
+
// A refresh is not a sign-in: keep when the user last signed in.
|
|
2011
|
+
...typeof payload.auth_time === "number" ? { authTime: payload.auth_time } : {}
|
|
1982
2012
|
});
|
|
1983
2013
|
const next = { token: pair.refreshToken, expiresAt: refreshTokenExpiry(pair.refreshToken) };
|
|
1984
2014
|
if (await sessions.rotate(found.row, token, next, found.kind)) issued = pair;
|
|
@@ -3610,7 +3640,7 @@ var AuthModule = class {
|
|
|
3610
3640
|
name = "@fonderie/auth";
|
|
3611
3641
|
// Baked in at build time by tsup.base, so the operator's Modules page can
|
|
3612
3642
|
// say what is actually deployed rather than 'not reported'.
|
|
3613
|
-
version = "7.
|
|
3643
|
+
version = "7.17.1";
|
|
3614
3644
|
// Users, sessions, login history, suspend — only through @fonderie/admin.
|
|
3615
3645
|
describeAdmin() {
|
|
3616
3646
|
return { routes: describeAuthAdminRoutes(this.store) };
|