@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/brain/signatures.md
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -288,8 +288,11 @@ var import_events = require("@fonderie/events");
|
|
|
288
288
|
|
|
289
289
|
// src/config.ts
|
|
290
290
|
var DEFAULT_VERIFICATION_COOLDOWN = 5 * 60 * 1e3;
|
|
291
|
+
var DEFAULT_SESSION_DURATION = "90d";
|
|
292
|
+
var DEFAULT_ACCESS_TOKEN_DURATION = "1h";
|
|
291
293
|
var AUTH_CONFIG_KEYS = {
|
|
292
294
|
sessionDuration: "auth.session.duration",
|
|
295
|
+
sessionMaxAge: "auth.session.max_age",
|
|
293
296
|
verificationCooldown: "auth.verification.cooldown",
|
|
294
297
|
mfa: "auth.mfa.enabled",
|
|
295
298
|
requireVerification: "auth.verification.required"
|
|
@@ -354,13 +357,14 @@ function issueMfaPendingToken(userId, config, loginMethod) {
|
|
|
354
357
|
);
|
|
355
358
|
}
|
|
356
359
|
function issueTokenPair(userId, config, options) {
|
|
357
|
-
const duration = config.sessionDuration ??
|
|
358
|
-
const accessDuration = config.accessTokenDuration ??
|
|
360
|
+
const duration = config.sessionDuration ?? DEFAULT_SESSION_DURATION;
|
|
361
|
+
const accessDuration = config.accessTokenDuration ?? DEFAULT_ACCESS_TOKEN_DURATION;
|
|
362
|
+
const authTime = options.authTime ?? Math.floor(Date.now() / 1e3);
|
|
359
363
|
const loginMethod = options.loginMethod;
|
|
360
364
|
const phoneVerified = options.phoneVerified ?? false;
|
|
361
365
|
const sid = options.sid ?? (0, import_node_crypto.randomUUID)();
|
|
362
366
|
const accessToken = import_jsonwebtoken.default.sign(
|
|
363
|
-
{ sub: userId, type: "access", loginMethod, phoneVerified, sid },
|
|
367
|
+
{ sub: userId, type: "access", loginMethod, phoneVerified, sid, auth_time: authTime },
|
|
364
368
|
config.jwtSecret,
|
|
365
369
|
// jwtid: every token is unique. With the session id kept across rotations,
|
|
366
370
|
// two tokens issued in the same second would otherwise be byte-identical —
|
|
@@ -368,7 +372,7 @@ function issueTokenPair(userId, config, options) {
|
|
|
368
372
|
{ expiresIn: accessDuration, keyid: keyIdOf(config.jwtSecret), jwtid: (0, import_node_crypto.randomUUID)() }
|
|
369
373
|
);
|
|
370
374
|
const refreshToken = import_jsonwebtoken.default.sign(
|
|
371
|
-
{ sub: userId, type: "refresh", loginMethod, phoneVerified, sid },
|
|
375
|
+
{ sub: userId, type: "refresh", loginMethod, phoneVerified, sid, auth_time: authTime },
|
|
372
376
|
config.jwtSecret,
|
|
373
377
|
{ expiresIn: duration, keyid: keyIdOf(config.jwtSecret), jwtid: (0, import_node_crypto.randomUUID)() }
|
|
374
378
|
);
|
|
@@ -390,6 +394,13 @@ function verifyToken(token, config) {
|
|
|
390
394
|
}
|
|
391
395
|
return null;
|
|
392
396
|
}
|
|
397
|
+
function durationMs(value) {
|
|
398
|
+
const m = /^\s*(\d+)\s*(ms|s|m|h|d|w|y)?\s*$/i.exec(value ?? "");
|
|
399
|
+
if (!m) return null;
|
|
400
|
+
const unit = (m[2] ?? "s").toLowerCase();
|
|
401
|
+
const scale = { ms: 1, s: 1e3, m: 6e4, h: 36e5, d: 864e5, w: 6048e5, y: 315576e5 };
|
|
402
|
+
return Number(m[1]) * (scale[unit] ?? 1e3);
|
|
403
|
+
}
|
|
393
404
|
|
|
394
405
|
// src/services/mfa.ts
|
|
395
406
|
var import_node_crypto2 = require("crypto");
|
|
@@ -1039,12 +1050,12 @@ var SessionModel = class {
|
|
|
1039
1050
|
async match(token) {
|
|
1040
1051
|
const hash = hashRefreshToken(token);
|
|
1041
1052
|
const [current] = await this.store.query(
|
|
1042
|
-
`SELECT id, user_id, sid, token FROM fonderie_sessions WHERE token IN ($1, $2) AND expires_at > now() LIMIT 1`,
|
|
1053
|
+
`SELECT id, user_id, sid, token, created_at FROM fonderie_sessions WHERE token IN ($1, $2) AND expires_at > now() LIMIT 1`,
|
|
1043
1054
|
[hash, token]
|
|
1044
1055
|
);
|
|
1045
1056
|
if (current) return { kind: "current", row: toRow(current) };
|
|
1046
1057
|
const [previous] = await this.store.query(
|
|
1047
|
-
`SELECT id, user_id, sid, token, (previous_valid_until > now()) AS in_grace
|
|
1058
|
+
`SELECT id, user_id, sid, token, created_at, (previous_valid_until > now()) AS in_grace
|
|
1048
1059
|
FROM fonderie_sessions WHERE previous_token_hash = $1 AND expires_at > now() LIMIT 1`,
|
|
1049
1060
|
[hash]
|
|
1050
1061
|
);
|
|
@@ -1159,7 +1170,7 @@ var SessionModel = class {
|
|
|
1159
1170
|
}
|
|
1160
1171
|
};
|
|
1161
1172
|
function toRow(r) {
|
|
1162
|
-
return { id: r.id, userId: r.user_id, sid: r.sid, stored: r.token };
|
|
1173
|
+
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() };
|
|
1163
1174
|
}
|
|
1164
1175
|
|
|
1165
1176
|
// src/models/backup-code.model.ts
|
|
@@ -2003,6 +2014,8 @@ function authController(store, config, bus, locales = (0, import_core6.defineLoc
|
|
|
2003
2014
|
if (!payload || payload.type !== "refresh") {
|
|
2004
2015
|
return (0, import_core6.setApiResponse)(import_core6.HTTP.UNAUTHORIZED, "TOKEN_REFRESH_FAILED", "Invalid refresh token");
|
|
2005
2016
|
}
|
|
2017
|
+
const resolvedRefresh = { ...config, ...config.resolve?.(ctx) };
|
|
2018
|
+
const maxAgeMs = durationMs(resolvedRefresh.sessionMaxAge);
|
|
2006
2019
|
let issued = null;
|
|
2007
2020
|
for (let attempt = 0; attempt < 3 && !issued; attempt++) {
|
|
2008
2021
|
const found = await sessions.match(token);
|
|
@@ -2016,14 +2029,20 @@ function authController(store, config, bus, locales = (0, import_core6.defineLoc
|
|
|
2016
2029
|
);
|
|
2017
2030
|
return (0, import_core6.setApiResponse)(import_core6.HTTP.UNAUTHORIZED, "TOKEN_REFRESH_FAILED", "Session expired or already revoked");
|
|
2018
2031
|
}
|
|
2032
|
+
if (maxAgeMs !== null && Date.now() - found.row.createdAt.getTime() > maxAgeMs) {
|
|
2033
|
+
await sessions.revokeById(found.row.id);
|
|
2034
|
+
return (0, import_core6.setApiResponse)(import_core6.HTTP.UNAUTHORIZED, "TOKEN_REFRESH_FAILED", "Session reached its maximum age \u2014 sign in again");
|
|
2035
|
+
}
|
|
2019
2036
|
const user = await users.findById(found.row.userId);
|
|
2020
2037
|
if (!user || user.suspended || user.deletedAt || user.id !== payload.sub) {
|
|
2021
2038
|
return (0, import_core6.setApiResponse)(import_core6.HTTP.UNAUTHORIZED, "UNAUTHORIZED", "Unauthorized");
|
|
2022
2039
|
}
|
|
2023
|
-
const pair = issueTokenPair(user.id,
|
|
2040
|
+
const pair = issueTokenPair(user.id, resolvedRefresh, {
|
|
2024
2041
|
loginMethod: payload.loginMethod ?? "email",
|
|
2025
2042
|
phoneVerified: payload.phoneVerified ?? false,
|
|
2026
|
-
...found.row.sid ? { sid: found.row.sid } : {}
|
|
2043
|
+
...found.row.sid ? { sid: found.row.sid } : {},
|
|
2044
|
+
// A refresh is not a sign-in: keep when the user last signed in.
|
|
2045
|
+
...typeof payload.auth_time === "number" ? { authTime: payload.auth_time } : {}
|
|
2027
2046
|
});
|
|
2028
2047
|
const next = { token: pair.refreshToken, expiresAt: refreshTokenExpiry(pair.refreshToken) };
|
|
2029
2048
|
if (await sessions.rotate(found.row, token, next, found.kind)) issued = pair;
|
|
@@ -3655,7 +3674,7 @@ var AuthModule = class {
|
|
|
3655
3674
|
name = "@fonderie/auth";
|
|
3656
3675
|
// Baked in at build time by tsup.base, so the operator's Modules page can
|
|
3657
3676
|
// say what is actually deployed rather than 'not reported'.
|
|
3658
|
-
version = "7.
|
|
3677
|
+
version = "7.17.0";
|
|
3659
3678
|
// Users, sessions, login history, suspend — only through @fonderie/admin.
|
|
3660
3679
|
describeAdmin() {
|
|
3661
3680
|
return { routes: describeAuthAdminRoutes(this.store) };
|