leistungsbilanz-ts/src/server/auth/sessions.ts
Grovy311 c56cfb30ad Add login-required user management (admin/user roles, Config->Benutzer)
No self-signup: admins create every account in Config -> Benutzer with a
typed start password; a forced password change gates first use. Session-
cookie auth protects all /api/* routes; /api/users additionally requires
the admin role. See docs/authentication.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-28 19:51:47 +02:00

55 lines
1.6 KiB
TypeScript

import { randomUUID } from "node:crypto";
interface Session {
id: string;
userId: string;
expiresAt: number;
}
/**
* In-memory session store. The API runs as a single container/process, so a
* restart clearing every session is an acceptable trade-off for the
* simplicity of not needing a persisted session table.
*/
export class SessionStore {
private readonly sessions = new Map<string, Session>();
constructor(private readonly ttlMs: number) {}
create(userId: string): Session {
const session: Session = {
id: randomUUID(),
userId,
expiresAt: Date.now() + this.ttlMs,
};
this.sessions.set(session.id, session);
return session;
}
/** Returns the session and slides its expiry forward, or undefined if gone/expired. */
get(sessionId: string | undefined, slideExpiry = true): Session | undefined {
if (!sessionId) return undefined;
const session = this.sessions.get(sessionId);
if (!session) return undefined;
if (session.expiresAt < Date.now()) {
this.sessions.delete(sessionId);
return undefined;
}
if (slideExpiry) session.expiresAt = Date.now() + this.ttlMs;
return session;
}
destroy(sessionId: string | undefined): void {
if (!sessionId) return;
this.sessions.delete(sessionId);
}
/** Ends every other session for a user, e.g. after a password change. */
destroyAllForUser(userId: string, exceptSessionId?: string): void {
for (const [id, session] of this.sessions) {
if (session.userId === userId && id !== exceptSessionId) {
this.sessions.delete(id);
}
}
}
}