백엔드에서 JWT가 아닌 세션 ID 기반으로 인증하도록 변경

This commit is contained in:
static
2025-01-12 07:28:38 +09:00
parent 0bdf990dae
commit 1a86c8d9e0
42 changed files with 487 additions and 624 deletions

View File

@@ -0,0 +1,124 @@
import { SqliteError } from "better-sqlite3";
import { and, eq, gt, lte, isNull } from "drizzle-orm";
import env from "$lib/server/loadenv";
import db from "./drizzle";
import { IntegrityError } from "./error";
import { session, sessionUpgradeChallenge } from "./schema";
export const createSession = async (
userId: number,
clientId: number | null,
sessionId: string,
ip: string | null,
userAgent: string | null,
) => {
try {
const now = new Date();
await db.insert(session).values({
id: sessionId,
userId,
clientId,
createdAt: now,
lastUsedAt: now,
lastUsedByIp: ip,
lastUsedByUserAgent: userAgent,
});
} catch (e) {
if (e instanceof SqliteError && e.code === "SQLITE_CONSTRAINT_UNIQUE") {
throw new IntegrityError("Session already exists");
}
throw e;
}
};
export const refreshSession = async (
sessionId: string,
ip: string | null,
userAgent: string | null,
) => {
const now = new Date();
const sessions = await db
.update(session)
.set({
lastUsedAt: now,
lastUsedByIp: ip,
lastUsedByUserAgent: userAgent,
})
.where(
and(
eq(session.id, sessionId),
gt(session.lastUsedAt, new Date(now.getTime() - env.session.exp)),
),
)
.returning({ userId: session.userId, clientId: session.clientId });
if (!sessions[0]) {
throw new IntegrityError("Session not found");
}
return sessions[0];
};
export const upgradeSession = async (sessionId: string, clientId: number) => {
const res = await db
.update(session)
.set({ clientId })
.where(and(eq(session.id, sessionId), isNull(session.clientId)));
if (res.changes === 0) {
throw new IntegrityError("Session not found");
}
};
export const deleteSession = async (sessionId: string) => {
await db.delete(session).where(eq(session.id, sessionId));
};
export const cleanupExpiredSessions = async () => {
await db.delete(session).where(lte(session.lastUsedAt, new Date(Date.now() - env.session.exp)));
};
export const registerSessionUpgradeChallenge = async (
sessionId: string,
clientId: number,
answer: string,
allowedIp: string,
expiresAt: Date,
) => {
try {
await db.insert(sessionUpgradeChallenge).values({
sessionId,
clientId,
answer,
allowedIp,
expiresAt,
});
} catch (e) {
if (e instanceof SqliteError && e.code === "SQLITE_CONSTRAINT_UNIQUE") {
throw new IntegrityError("Challenge already registered");
}
throw e;
}
};
export const consumeSessionUpgradeChallenge = async (
sessionId: string,
answer: string,
ip: string,
) => {
const challenges = await db
.delete(sessionUpgradeChallenge)
.where(
and(
eq(sessionUpgradeChallenge.sessionId, sessionId),
eq(sessionUpgradeChallenge.answer, answer),
eq(sessionUpgradeChallenge.allowedIp, ip),
gt(sessionUpgradeChallenge.expiresAt, new Date()),
),
)
.returning({ clientId: sessionUpgradeChallenge.clientId });
return challenges[0] ?? null;
};
export const cleanupExpiredSessionUpgradeChallenges = async () => {
await db
.delete(sessionUpgradeChallenge)
.where(lte(sessionUpgradeChallenge.expiresAt, new Date()));
};