import pg from "pg"; import env from "$lib/server/loadenv"; import { IntegrityError } from "./error"; import db from "./kysely"; export const createSession = async ( userId: number, clientId: number | null, sessionId: string, ip: string | null, agent: string | null, ) => { try { const now = new Date(); await db .insertInto("session") .values({ id: sessionId, user_id: userId, client_id: clientId, created_at: now, last_used_at: now, last_used_by_ip: ip || null, last_used_by_agent: agent || null, }) .execute(); } catch (e) { if (e instanceof pg.DatabaseError && e.code === "23505") { throw new IntegrityError("Session already exists"); } throw e; } }; export const refreshSession = async ( sessionId: string, ip: string | null, agent: string | null, ) => { const now = new Date(); const session = await db .updateTable("session") .set({ last_used_at: now, last_used_by_ip: ip !== "" ? ip : undefined, // Don't update if empty last_used_by_agent: agent !== "" ? agent : undefined, // Don't update if empty }) .where("id", "=", sessionId) .where("last_used_at", ">", new Date(now.getTime() - env.session.exp)) .returning(["user_id", "client_id"]) .executeTakeFirst(); if (!session) { throw new IntegrityError("Session not found"); } return { userId: session.user_id, clientId: session.client_id }; }; export const upgradeSession = async (sessionId: string, clientId: number) => { const res = await db .updateTable("session") .set({ client_id: clientId }) .where("id", "=", sessionId) .where("client_id", "is", null) .executeTakeFirst(); if (res.numUpdatedRows === 0n) { throw new IntegrityError("Session not found"); } }; export const deleteSession = async (sessionId: string) => { await db.deleteFrom("session").where("id", "=", sessionId).execute(); }; export const deleteAllOtherSessions = async (userId: number, sessionId: string) => { await db .deleteFrom("session") .where("id", "!=", sessionId) .where("user_id", "=", userId) .execute(); }; export const cleanupExpiredSessions = async () => { await db .deleteFrom("session") .where("last_used_at", "<=", new Date(Date.now() - env.session.exp)) .execute(); }; export const registerSessionUpgradeChallenge = async ( sessionId: string, clientId: number, answer: string, allowedIp: string, expiresAt: Date, ) => { try { await db .insertInto("session_upgrade_challenge") .values({ session_id: sessionId, client_id: clientId, answer, allowed_ip: allowedIp, expires_at: expiresAt, }) .execute(); } catch (e) { if (e instanceof pg.DatabaseError && e.code === "23505") { throw new IntegrityError("Challenge already registered"); } throw e; } }; export const consumeSessionUpgradeChallenge = async ( sessionId: string, answer: string, ip: string, ) => { const challenge = await db .deleteFrom("session_upgrade_challenge") .where("session_id", "=", sessionId) .where("answer", "=", answer) .where("allowed_ip", "=", ip) .where("expires_at", ">", new Date()) .returning("client_id") .executeTakeFirst(); return challenge ? { clientId: challenge.client_id } : null; }; export const cleanupExpiredSessionUpgradeChallenges = async () => { await db.deleteFrom("session_upgrade_challenge").where("expires_at", "<=", new Date()).execute(); };