import type { User } from "$lib/server/database"; import Crypto from "node:crypto"; interface UserSession { user: User; last_active: Date; } export interface TokenInfo { user_id: number; creation_time: Date; expiry_time: Date; } class LoginError extends Error { constructor(message: string) { super(message); } } class UserLoginError extends LoginError { user_id: number; constructor(user_id: number, message: string) { super(message); this.user_id = user_id; } } const TOKEN_LENGTH = 256; const active_users = new Map(); const active_session_tokens = new Map(); function issue_access_token_for_user(user: User, expiry_date: Date): string { let user_session = active_users.get(user.id); if (user_session === undefined) { user_session = { user: user, last_active: new Date() } as UserSession; active_users.set(user.id, user_session); } /* | Token (256) | Expiry date as getTime | */ const token_info: TokenInfo = { user_id: user.id, creation_time: new Date(), expiry_time: expiry_date } const session_token = Buffer.concat( [ Crypto.randomBytes(TOKEN_LENGTH / 4 * 3), Buffer.from(token_info.creation_time.getTime().toFixed(0)) ] ).toString("base64"); if (active_session_tokens.has(session_token)) { throw new UserLoginError(user.id, "Tried issuing a duplicate session token."); } active_session_tokens.set(session_token, token_info); return session_token; } function get_user_by_access_token(token: string): User | null { const token_info = active_session_tokens.get(token); if (token_info === undefined || token_info.expiry_time.getTime() < Date.now()) { return null; } const user = active_users.get(token_info.user_id); if (user === undefined) { return null; } user.last_active = new Date(); return user.user; } function logout_user_session(token: string): boolean { const token_info = active_session_tokens.get(token); if (!token_info) { console.log("this shouldn't happen."); return false; } token_info.expiry_time = new Date(0); return true; } async function __clean_session_store() { console.log("cleaning sessions"); const active_users_session = new Set(); active_session_tokens.forEach((token_info, token, token_map) => { if (token_info.expiry_time.getTime() < Date.now()) { token_map.delete(token); } else { active_users_session.add(token_info.user_id); } }); active_users.forEach((user_info, user_id, user_map) => { if (user_info.last_active.getTime() + 15*60*1000 < Date.now() && !active_users.has(user_id)) { user_map.delete(user_id) } }); } export default class SessionStore { static issue_access_token_for_user = issue_access_token_for_user; static get_user_by_access_token = get_user_by_access_token; static logout_user_session = logout_user_session; } setInterval(__clean_session_store, 15*60*1000);