Files
arkvault/src/lib/server/db/session.ts

134 lines
3.5 KiB
TypeScript

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();
};