import { randomBytes } from 'crypto'; import { getDataSource } from '../db/database'; import { SessionTokenEntity } from '../entities/SessionTokenEntity'; import { getUserById } from '../cqrs'; import type { AuthUserPayload } from '../cqrs/types'; const DEFAULT_TOKEN_TTL_MS = 10 * 365 * 24 * 60 * 60 * 1000; export interface IssuedSessionToken { token: string; userId: string; issuedAt: number; expiresAt: number; } export interface AuthenticatedSession { token: string; user: AuthUserPayload; issuedAt: number; expiresAt: number; } function getTokenRepository() { return getDataSource().getRepository(SessionTokenEntity); } export function getSessionTokenTtlMs(): number { const configured = Number(process.env.SESSION_TOKEN_TTL_MS); return Number.isFinite(configured) && configured > 0 ? configured : DEFAULT_TOKEN_TTL_MS; } export async function issueSessionToken(userId: string): Promise { const token = randomBytes(32).toString('hex'); const issuedAt = Date.now(); const expiresAt = issuedAt + getSessionTokenTtlMs(); const repo = getTokenRepository(); await repo.save(repo.create({ token, userId, issuedAt, expiresAt })); return { token, userId, issuedAt, expiresAt }; } export async function consumeSessionToken(token: string): Promise { const normalized = token.trim(); if (!normalized) { return null; } const repo = getTokenRepository(); const record = await repo.findOne({ where: { token: normalized } }); if (!record || record.expiresAt < Date.now()) { if (record) { await repo.delete({ token: normalized }); } return null; } const user = await getUserById(record.userId); if (!user) { await repo.delete({ token: normalized }); return null; } return { token: record.token, user, issuedAt: record.issuedAt, expiresAt: record.expiresAt }; } export async function revokeSessionToken(token: string): Promise { const normalized = token.trim(); if (!normalized) { return; } await getTokenRepository().delete({ token: normalized }); } export async function revokeAllSessionTokensForUser(userId: string): Promise { await getTokenRepository().delete({ userId }); } export async function pruneExpiredSessionTokens(): Promise { const repo = getTokenRepository(); const now = Date.now(); const expired = await repo.createQueryBuilder('token') .where('token.expiresAt < :now', { now }) .getMany(); if (expired.length === 0) { return; } await repo.remove(expired); }