@fonderie/auth 7.16.0 → 7.17.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/brain/signatures.md +1 -0
- package/dist/index.cjs +29 -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 +29 -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 = config.sessionDuration ?? DEFAULT_SESSION_DURATION;
|
|
316
|
+
const accessDuration = config.accessTokenDuration ?? DEFAULT_ACCESS_TOKEN_DURATION;
|
|
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,13 @@ 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
|
+
}
|
|
348
359
|
|
|
349
360
|
// src/services/mfa.ts
|
|
350
361
|
import { createHmac, randomBytes } from "crypto";
|
|
@@ -994,12 +1005,12 @@ var SessionModel = class {
|
|
|
994
1005
|
async match(token) {
|
|
995
1006
|
const hash = hashRefreshToken(token);
|
|
996
1007
|
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`,
|
|
1008
|
+
`SELECT id, user_id, sid, token, created_at FROM fonderie_sessions WHERE token IN ($1, $2) AND expires_at > now() LIMIT 1`,
|
|
998
1009
|
[hash, token]
|
|
999
1010
|
);
|
|
1000
1011
|
if (current) return { kind: "current", row: toRow(current) };
|
|
1001
1012
|
const [previous] = await this.store.query(
|
|
1002
|
-
`SELECT id, user_id, sid, token, (previous_valid_until > now()) AS in_grace
|
|
1013
|
+
`SELECT id, user_id, sid, token, created_at, (previous_valid_until > now()) AS in_grace
|
|
1003
1014
|
FROM fonderie_sessions WHERE previous_token_hash = $1 AND expires_at > now() LIMIT 1`,
|
|
1004
1015
|
[hash]
|
|
1005
1016
|
);
|
|
@@ -1114,7 +1125,7 @@ var SessionModel = class {
|
|
|
1114
1125
|
}
|
|
1115
1126
|
};
|
|
1116
1127
|
function toRow(r) {
|
|
1117
|
-
return { id: r.id, userId: r.user_id, sid: r.sid, stored: r.token };
|
|
1128
|
+
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
1129
|
}
|
|
1119
1130
|
|
|
1120
1131
|
// src/models/backup-code.model.ts
|
|
@@ -1958,6 +1969,8 @@ function authController(store, config, bus, locales = defineLocales()) {
|
|
|
1958
1969
|
if (!payload || payload.type !== "refresh") {
|
|
1959
1970
|
return setApiResponse3(HTTP3.UNAUTHORIZED, "TOKEN_REFRESH_FAILED", "Invalid refresh token");
|
|
1960
1971
|
}
|
|
1972
|
+
const resolvedRefresh = { ...config, ...config.resolve?.(ctx) };
|
|
1973
|
+
const maxAgeMs = durationMs(resolvedRefresh.sessionMaxAge);
|
|
1961
1974
|
let issued = null;
|
|
1962
1975
|
for (let attempt = 0; attempt < 3 && !issued; attempt++) {
|
|
1963
1976
|
const found = await sessions.match(token);
|
|
@@ -1971,14 +1984,20 @@ function authController(store, config, bus, locales = defineLocales()) {
|
|
|
1971
1984
|
);
|
|
1972
1985
|
return setApiResponse3(HTTP3.UNAUTHORIZED, "TOKEN_REFRESH_FAILED", "Session expired or already revoked");
|
|
1973
1986
|
}
|
|
1987
|
+
if (maxAgeMs !== null && Date.now() - found.row.createdAt.getTime() > maxAgeMs) {
|
|
1988
|
+
await sessions.revokeById(found.row.id);
|
|
1989
|
+
return setApiResponse3(HTTP3.UNAUTHORIZED, "TOKEN_REFRESH_FAILED", "Session reached its maximum age \u2014 sign in again");
|
|
1990
|
+
}
|
|
1974
1991
|
const user = await users.findById(found.row.userId);
|
|
1975
1992
|
if (!user || user.suspended || user.deletedAt || user.id !== payload.sub) {
|
|
1976
1993
|
return setApiResponse3(HTTP3.UNAUTHORIZED, "UNAUTHORIZED", "Unauthorized");
|
|
1977
1994
|
}
|
|
1978
|
-
const pair = issueTokenPair(user.id,
|
|
1995
|
+
const pair = issueTokenPair(user.id, resolvedRefresh, {
|
|
1979
1996
|
loginMethod: payload.loginMethod ?? "email",
|
|
1980
1997
|
phoneVerified: payload.phoneVerified ?? false,
|
|
1981
|
-
...found.row.sid ? { sid: found.row.sid } : {}
|
|
1998
|
+
...found.row.sid ? { sid: found.row.sid } : {},
|
|
1999
|
+
// A refresh is not a sign-in: keep when the user last signed in.
|
|
2000
|
+
...typeof payload.auth_time === "number" ? { authTime: payload.auth_time } : {}
|
|
1982
2001
|
});
|
|
1983
2002
|
const next = { token: pair.refreshToken, expiresAt: refreshTokenExpiry(pair.refreshToken) };
|
|
1984
2003
|
if (await sessions.rotate(found.row, token, next, found.kind)) issued = pair;
|
|
@@ -3610,7 +3629,7 @@ var AuthModule = class {
|
|
|
3610
3629
|
name = "@fonderie/auth";
|
|
3611
3630
|
// Baked in at build time by tsup.base, so the operator's Modules page can
|
|
3612
3631
|
// say what is actually deployed rather than 'not reported'.
|
|
3613
|
-
version = "7.
|
|
3632
|
+
version = "7.17.0";
|
|
3614
3633
|
// Users, sessions, login history, suspend — only through @fonderie/admin.
|
|
3615
3634
|
describeAdmin() {
|
|
3616
3635
|
return { routes: describeAuthAdminRoutes(this.store) };
|