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:
KodekaTeam committed 2026-09-13 10:24:49 +07:00
1 parent 6ce3dd590a
commit 0bd696d37f
12 files changed
+271 -181

No files matched your search

+28 -1
View File
@@ -14,13 +14,35 @@ export function canRevokeManagedSession(input: {
actorUserId: string;
actorRole: string;
targetUserId: string;
currentSessionId: string;
targetSessionId: string;
}): boolean {
if (input.currentSessionId === input.targetSessionId) return false;
return (
input.actorUserId === input.targetUserId ||
input.actorRole === "SUPER_ADMIN"
);
}
export type ManagedSessionStatus = "active" | "expired" | "revoked";
export function getManagedSessionStatus(
session: {
revokedAt: Date | null;
revokeReason: string | null;
idleExpiresAt: Date;
absoluteExpiresAt: Date;
},
now = new Date(),
): ManagedSessionStatus {
if (session.revokedAt) {
return session.revokeReason === "EXPIRED" ? "expired" : "revoked";
}
return session.idleExpiresAt <= now || session.absoluteExpiresAt <= now
? "expired"
: "active";
}
export function getSessionCookieName(env = process.env): string {
return env.NODE_ENV === "production"
? PRODUCTION_SESSION_COOKIE
@@ -120,7 +142,12 @@ export async function createUserSession(input: {
});
const overflowSessions = await prisma.userSession.findMany({
where: { userId: input.userId, revokedAt: null },
where: {
userId: input.userId,
revokedAt: null,
idleExpiresAt: { gt: now },
absoluteExpiresAt: { gt: now },
},
orderBy: { createdAt: "desc" },
skip: MAX_ACTIVE_SESSIONS_PER_USER,
select: { id: true },