Files
Toju/server/src/services/session-auth.service.ts

111 lines
2.6 KiB
TypeScript

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<IssuedSessionToken> {
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<AuthenticatedSession | null> {
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<void> {
const normalized = token.trim();
if (!normalized) {
return;
}
await getTokenRepository().delete({ token: normalized });
}
export async function revokeAllSessionTokensForUser(userId: string): Promise<void> {
await getTokenRepository().delete({ userId });
}
export async function pruneExpiredSessionTokens(): Promise<void> {
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);
}