import { eq } from 'drizzle-orm'; import { sha256 } from '@oslojs/crypto/sha2'; import { encodeBase32LowerCaseNoPadding, encodeHexLowerCase } from '@oslojs/encoding'; import { db } from '$lib/server/db'; import * as table from '$lib/server/db/schema'; import type { RequestEvent } from '@sveltejs/kit'; import { dev } from '$app/environment'; import { env } from '$env/dynamic/private'; const DAY_IN_MS = 1000 * 60 * 60 * 24; export const sessionCookieName = 'auth-session'; function generateSessionToken(): string { const bytes = crypto.getRandomValues(new Uint8Array(20)); return encodeBase32LowerCaseNoPadding(bytes); } export async function createSession(userId: string): Promise { const token = generateSessionToken(); const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token))); const session: table.Session = { id: sessionId, userId, expiresAt: new Date(Date.now() + DAY_IN_MS * 30) }; await db.insert(table.sessions).values(session); return session; } export function setSessionTokenCookie(event: RequestEvent, sessionId: string, expiresAt: Date) { event.cookies.set(sessionCookieName, sessionId, { path: '/', sameSite: 'lax', httpOnly: true, expires: expiresAt, secure: !dev, }); } export async function invalidateSession(sessionId: string): Promise { await db.delete(table.sessions).where(eq(table.sessions.id, sessionId)); } export function deleteSessionTokenCookie(event: RequestEvent) { event.cookies.delete(sessionCookieName, { path: '/' }); } export async function validateSession(sessionId: string) { const [result] = await db .select({ // Adjust user table here to tweak returned data user: { id: table.users.id, authSource: table.users.authSource, username: table.users.username, name: table.users.name }, session: table.sessions }) .from(table.sessions) .innerJoin(table.users, eq(table.sessions.userId, table.users.id)) .where(eq(table.sessions.id, sessionId)); if (!result) { return { session: null, user: null }; } const { session, user } = result; const sessionExpired = Date.now() >= session.expiresAt.getTime(); if (sessionExpired) { await db.delete(table.sessions).where(eq(table.sessions.id, session.id)); return { session: null, user: null }; } const renewSession = Date.now() >= session.expiresAt.getTime() - DAY_IN_MS * 15; if (renewSession) { session.expiresAt = new Date(Date.now() + DAY_IN_MS * 30); await db .update(table.sessions) .set({ expiresAt: session.expiresAt }) .where(eq(table.sessions.id, session.id)); } return { session, user }; } export function isValidInviteToken(inviteToken: string) { return inviteToken === env.INVITE_TOKEN; } export type SessionValidationResult = Awaited>;