@syncello/auth 3.2.1 → 3.3.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/dist/index.cjs +42 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +42 -11
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -644,6 +644,9 @@ async function hashToken(token) {
|
|
|
644
644
|
// src/core/config.ts
|
|
645
645
|
var AUTH_DEFAULTS = {
|
|
646
646
|
SESSION_TTL_DAYS: 7,
|
|
647
|
+
// Absolute cap: a session dies this long after creation regardless of
|
|
648
|
+
// sliding-window activity (limits the value of a stolen session token).
|
|
649
|
+
SESSION_MAX_LIFETIME_DAYS: 30,
|
|
647
650
|
LOCKOUT_DURATION_MINUTES: 30,
|
|
648
651
|
LOCKOUT_MAX_ATTEMPTS: 5,
|
|
649
652
|
PASSWORD_RESET_TTL_MINUTES: 15,
|
|
@@ -676,7 +679,7 @@ async function createSession(db, tables2, userId, fingerprint, ipAddress, sessio
|
|
|
676
679
|
});
|
|
677
680
|
return sessionId;
|
|
678
681
|
}
|
|
679
|
-
async function validateSession(db, tables2, sessionId, currentFingerprint) {
|
|
682
|
+
async function validateSession(db, tables2, sessionId, currentFingerprint, sessionMaxLifetimeMs = AUTH_DEFAULTS.SESSION_MAX_LIFETIME_DAYS * 24 * 60 * 60 * 1e3) {
|
|
680
683
|
const { sessions, users } = tables2;
|
|
681
684
|
logger_default.debug("Validating session", {
|
|
682
685
|
sessionId: sessionId?.slice(0, 8),
|
|
@@ -703,6 +706,17 @@ async function validateSession(db, tables2, sessionId, currentFingerprint) {
|
|
|
703
706
|
return null;
|
|
704
707
|
}
|
|
705
708
|
const row = result[0];
|
|
709
|
+
if (Date.now() - row.sessionCreatedAt > sessionMaxLifetimeMs) {
|
|
710
|
+
logger_default.info("Session exceeded absolute lifetime - revoked", {
|
|
711
|
+
type: "security",
|
|
712
|
+
event: "session_max_lifetime_exceeded",
|
|
713
|
+
severity: "low",
|
|
714
|
+
sessionId: sessionId.slice(0, 8),
|
|
715
|
+
userId: row.userId
|
|
716
|
+
});
|
|
717
|
+
await deleteSession(db, { sessions }, sessionId);
|
|
718
|
+
return null;
|
|
719
|
+
}
|
|
706
720
|
if (currentFingerprint && row.sessionFingerprint && row.sessionFingerprint !== currentFingerprint) {
|
|
707
721
|
logger_default.info("Session fingerprint mismatch - likely SSR request", {
|
|
708
722
|
type: "security",
|
|
@@ -981,8 +995,7 @@ function getSessionCookieName(env) {
|
|
|
981
995
|
return COOKIE_NAMES[environment] || COOKIE_NAMES.development;
|
|
982
996
|
}
|
|
983
997
|
function isProduction(env) {
|
|
984
|
-
|
|
985
|
-
return environment === "production" || environment === "staging";
|
|
998
|
+
return env?.ENVIRONMENT !== "development";
|
|
986
999
|
}
|
|
987
1000
|
function getCookieSecurityFlags(env) {
|
|
988
1001
|
if (isProduction(env)) {
|
|
@@ -1569,6 +1582,19 @@ var problems = {
|
|
|
1569
1582
|
};
|
|
1570
1583
|
|
|
1571
1584
|
// src/middleware/auth.ts
|
|
1585
|
+
var SESSION_MAX_LIFETIME_MS = AUTH_DEFAULTS.SESSION_MAX_LIFETIME_DAYS * 24 * 60 * 60 * 1e3;
|
|
1586
|
+
async function revokeIfPastMaxLifetime(db, sessions, sessionId, session) {
|
|
1587
|
+
if (Date.now() - session.createdAt <= SESSION_MAX_LIFETIME_MS) return false;
|
|
1588
|
+
logger_default.info("Session exceeded absolute lifetime - revoked", {
|
|
1589
|
+
type: "security",
|
|
1590
|
+
event: "session_max_lifetime_exceeded",
|
|
1591
|
+
severity: "low",
|
|
1592
|
+
sessionId: sessionId.slice(0, 8),
|
|
1593
|
+
userId: session.userId
|
|
1594
|
+
});
|
|
1595
|
+
await deleteSession(db, { sessions }, sessionId);
|
|
1596
|
+
return true;
|
|
1597
|
+
}
|
|
1572
1598
|
async function getSessionFromCookie(db, sessions, cookieHeader, request, env) {
|
|
1573
1599
|
const cookieName = getSessionCookieName(env);
|
|
1574
1600
|
const cookiePattern = new RegExp(`${cookieName}=([^;]+)`);
|
|
@@ -1578,10 +1604,13 @@ async function getSessionFromCookie(db, sessions, cookieHeader, request, env) {
|
|
|
1578
1604
|
const foundSessions = await db.select({
|
|
1579
1605
|
userId: sessions.userId,
|
|
1580
1606
|
expiresAt: sessions.expiresAt,
|
|
1607
|
+
createdAt: sessions.createdAt,
|
|
1581
1608
|
fingerprint: sessions.fingerprint
|
|
1582
1609
|
}).from(sessions).where((0, import_drizzle_orm4.and)((0, import_drizzle_orm4.eq)(sessions.id, sessionId), (0, import_drizzle_orm4.gt)(sessions.expiresAt, Date.now()))).limit(1);
|
|
1583
1610
|
if (foundSessions.length === 0) return null;
|
|
1584
1611
|
const session = foundSessions[0];
|
|
1612
|
+
if (await revokeIfPastMaxLifetime(db, sessions, sessionId, session)) return null;
|
|
1613
|
+
await refreshSession(db, { sessions }, sessionId);
|
|
1585
1614
|
const userAgent = request.headers.get("user-agent") || "";
|
|
1586
1615
|
const cfWorker = request.headers.get("cf-worker") || "";
|
|
1587
1616
|
const clientIp = request.headers.get("cf-connecting-ip") || request.headers.get("x-real-ip") || "";
|
|
@@ -1625,10 +1654,12 @@ async function getSessionFromBearerToken(db, sessions, authHeader) {
|
|
|
1625
1654
|
const sessionId = authHeader.slice(7);
|
|
1626
1655
|
const foundSessions = await db.select({
|
|
1627
1656
|
userId: sessions.userId,
|
|
1628
|
-
expiresAt: sessions.expiresAt
|
|
1657
|
+
expiresAt: sessions.expiresAt,
|
|
1658
|
+
createdAt: sessions.createdAt
|
|
1629
1659
|
}).from(sessions).where((0, import_drizzle_orm4.and)((0, import_drizzle_orm4.eq)(sessions.id, sessionId), (0, import_drizzle_orm4.gt)(sessions.expiresAt, Date.now()))).limit(1);
|
|
1630
1660
|
if (foundSessions.length === 0) return null;
|
|
1631
1661
|
const session = foundSessions[0];
|
|
1662
|
+
if (await revokeIfPastMaxLifetime(db, sessions, sessionId, session)) return null;
|
|
1632
1663
|
await refreshSession(db, { sessions }, sessionId);
|
|
1633
1664
|
return {
|
|
1634
1665
|
userId: session.userId,
|
|
@@ -2627,7 +2658,7 @@ var CloudflareEmailAdapter = class {
|
|
|
2627
2658
|
logger_default.error("Cloudflare send error", {
|
|
2628
2659
|
provider: this.providerName,
|
|
2629
2660
|
error: errorText,
|
|
2630
|
-
|
|
2661
|
+
errorName: error instanceof Error ? error.name : typeof error,
|
|
2631
2662
|
to: options.to,
|
|
2632
2663
|
subject: options.subject
|
|
2633
2664
|
});
|
|
@@ -4791,10 +4822,10 @@ var statusRoute = (0, import_zod_openapi24.createRoute)({
|
|
|
4791
4822
|
content: { "application/json": { schema: statusResponseSchema } },
|
|
4792
4823
|
headers: {
|
|
4793
4824
|
"Cache-Control": {
|
|
4794
|
-
description: "Cache for
|
|
4825
|
+
description: "Cache for 60 seconds",
|
|
4795
4826
|
schema: {
|
|
4796
4827
|
type: "string",
|
|
4797
|
-
example: "private, max-age=
|
|
4828
|
+
example: "private, max-age=60"
|
|
4798
4829
|
}
|
|
4799
4830
|
}
|
|
4800
4831
|
}
|
|
@@ -4811,7 +4842,7 @@ var statusHandler = async (c) => {
|
|
|
4811
4842
|
const { db, schema } = getAuthContext(c);
|
|
4812
4843
|
const methods = await getUserEnabled2faMethods(db, userId, schema.user2faMethods);
|
|
4813
4844
|
const backupCodes = await db.select({ id: schema.userBackupCodes.id }).from(schema.userBackupCodes).where((0, import_drizzle_orm20.and)((0, import_drizzle_orm20.eq)(schema.userBackupCodes.userId, userId), (0, import_drizzle_orm20.isNull)(schema.userBackupCodes.usedAt)));
|
|
4814
|
-
c.header("Cache-Control", "private, max-age=
|
|
4845
|
+
c.header("Cache-Control", "private, max-age=60");
|
|
4815
4846
|
c.header("Vary", "Cookie");
|
|
4816
4847
|
return c.json({
|
|
4817
4848
|
enabled: methods.length > 0,
|
|
@@ -5302,10 +5333,10 @@ var trustedDevicesGetRoute = (0, import_zod_openapi32.createRoute)({
|
|
|
5302
5333
|
content: { "application/json": { schema: trustedDevicesResponseSchema } },
|
|
5303
5334
|
headers: {
|
|
5304
5335
|
"Cache-Control": {
|
|
5305
|
-
description: "Cache for
|
|
5336
|
+
description: "Cache for 60 seconds",
|
|
5306
5337
|
schema: {
|
|
5307
5338
|
type: "string",
|
|
5308
|
-
example: "private, max-age=
|
|
5339
|
+
example: "private, max-age=60"
|
|
5309
5340
|
}
|
|
5310
5341
|
}
|
|
5311
5342
|
}
|
|
@@ -5329,7 +5360,7 @@ var trustedDevicesGetHandler = async (c) => {
|
|
|
5329
5360
|
lastUsedAt: schema.userTrustedDevices.lastUsedAt,
|
|
5330
5361
|
createdAt: schema.userTrustedDevices.createdAt
|
|
5331
5362
|
}).from(schema.userTrustedDevices).where((0, import_drizzle_orm28.and)((0, import_drizzle_orm28.eq)(schema.userTrustedDevices.userId, userId), (0, import_drizzle_orm28.gt)(schema.userTrustedDevices.expiresAt, now)));
|
|
5332
|
-
c.header("Cache-Control", "private, max-age=
|
|
5363
|
+
c.header("Cache-Control", "private, max-age=60");
|
|
5333
5364
|
c.header("Vary", "Cookie");
|
|
5334
5365
|
return c.json({ devices });
|
|
5335
5366
|
};
|