feat(auth): add session history retention and lifecycle updates
- Add daily session history cleanup with a 180-day retention period - Run cleanup on startup and every 24 hours in batches of up to 1,000 sessions - Add tests for session retention and derived session status - Allow viewers to manage their own sessions while preserving global access for super admins - Prevent the current browser session from being revoked through session management - Derive active, expired, and revoked status without writing during list requests - Count only active sessions toward the 20-session per-user limit - Remove unused session event queries and payloads from the session listing endpoint - Update the session management UI for the revised access and revocation policies
This commit is contained in:
1 parent
6ce3dd590a
commit
0bd696d37f
12 files changed
+271
-181
No files matched your search
@@ -7,6 +7,7 @@ import {
|
||||
} from "../../src/server/middleware/auth";
|
||||
import {
|
||||
canRevokeManagedSession,
|
||||
getManagedSessionStatus,
|
||||
getSessionCookieName,
|
||||
hashSessionToken,
|
||||
normalizeSessionUserAgent,
|
||||
@@ -188,6 +189,8 @@ test("only owners and super admins can revoke a managed login session", () => {
|
||||
actorUserId: "operator-1",
|
||||
actorRole: "OPERATOR",
|
||||
targetUserId: "user-2",
|
||||
currentSessionId: "session-1",
|
||||
targetSessionId: "session-2",
|
||||
}),
|
||||
false,
|
||||
);
|
||||
@@ -196,6 +199,8 @@ test("only owners and super admins can revoke a managed login session", () => {
|
||||
actorUserId: "operator-1",
|
||||
actorRole: "OPERATOR",
|
||||
targetUserId: "operator-1",
|
||||
currentSessionId: "session-1",
|
||||
targetSessionId: "session-2",
|
||||
}),
|
||||
true,
|
||||
);
|
||||
@@ -204,9 +209,51 @@ test("only owners and super admins can revoke a managed login session", () => {
|
||||
actorUserId: "admin-1",
|
||||
actorRole: "SUPER_ADMIN",
|
||||
targetUserId: "user-2",
|
||||
currentSessionId: "session-1",
|
||||
targetSessionId: "session-2",
|
||||
}),
|
||||
true,
|
||||
);
|
||||
assert.equal(
|
||||
canRevokeManagedSession({
|
||||
actorUserId: "admin-1",
|
||||
actorRole: "SUPER_ADMIN",
|
||||
targetUserId: "admin-1",
|
||||
currentSessionId: "session-1",
|
||||
targetSessionId: "session-1",
|
||||
}),
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
test("session status is derived without persisting expiry from list requests", () => {
|
||||
const now = new Date("2026-09-12T12:00:00.000Z");
|
||||
const activeSession = {
|
||||
revokedAt: null,
|
||||
revokeReason: null,
|
||||
idleExpiresAt: new Date("2026-09-12T13:00:00.000Z"),
|
||||
absoluteExpiresAt: new Date("2026-09-13T12:00:00.000Z"),
|
||||
};
|
||||
|
||||
assert.equal(getManagedSessionStatus(activeSession, now), "active");
|
||||
assert.equal(
|
||||
getManagedSessionStatus(
|
||||
{ ...activeSession, idleExpiresAt: new Date("2026-09-12T11:59:59.000Z") },
|
||||
now,
|
||||
),
|
||||
"expired",
|
||||
);
|
||||
assert.equal(
|
||||
getManagedSessionStatus(
|
||||
{
|
||||
...activeSession,
|
||||
revokedAt: new Date("2026-09-12T10:00:00.000Z"),
|
||||
revokeReason: "USER_REVOKED",
|
||||
},
|
||||
now,
|
||||
),
|
||||
"revoked",
|
||||
);
|
||||
});
|
||||
|
||||
test("session management is a dedicated protected page before Users and RBAC", () => {
|
||||
@@ -217,12 +264,16 @@ test("session management is a dedicated protected page before Users and RBAC", (
|
||||
const routes = readFileSync("src/server/routes/auth.ts", "utf8");
|
||||
|
||||
assert.ok(navigation.indexOf('href: "/sessions"') < navigation.indexOf('href: "/users"'));
|
||||
assert.match(permissions, /"\/sessions": "OPERATOR"/);
|
||||
assert.match(permissions, /"\/sessions": "VIEWER"/);
|
||||
assert.doesNotMatch(dashboard, /LoginSessionsPanel/);
|
||||
assert.match(page, /route="\/sessions"/);
|
||||
assert.match(page, /All statuses/);
|
||||
assert.match(page, /All users/);
|
||||
assert.match(routes, /app\.get\("\/sessions", \{ preHandler: \[requireRole\("OPERATOR"\)\]/);
|
||||
assert.match(routes, /"\/sessions\/:sessionId",\s+\{ preHandler: \[requireRole\("OPERATOR"\)\] \}/);
|
||||
assert.match(routes, /app\.get\("\/sessions", \{ preHandler: \[requireRole\("VIEWER"\)\]/);
|
||||
assert.match(routes, /"\/sessions\/:sessionId",\s+\{ preHandler: \[requireRole\("VIEWER"\)\] \}/);
|
||||
assert.match(routes, /A browser login session is required/);
|
||||
assert.doesNotMatch(routes, /expiredSessions/);
|
||||
assert.doesNotMatch(routes, /events:\s*\{/);
|
||||
assert.match(routes, /The current session cannot be revoked/);
|
||||
assert.match(page, /session\.current \? null/);
|
||||
});
|
||||
Reference in new issue
Block a user