/** * Authentication utilities for token handling */ import type { Database } from "@reviq/db-schema"; import type { Kysely } from "kysely"; import { hashToken } from "./crypto.js"; export interface AuthenticatedUser { id: number; email: string; isSuperuser: boolean; } /** * Authenticate a request using session token or API key * Returns the authenticated user or null if not authenticated */ export const authenticateRequest = async ( db: Kysely, sessionToken?: string, apiKey?: string, ): Promise => { // Try session cookie first, then API key const token = sessionToken ?? apiKey; if (!token) { return null; } const tokenHash = await hashToken(token); // Check sessions table const session = await db .selectFrom("sessions") .innerJoin("users", "users.id", "sessions.user_id") .where("sessions.token_hash", "=", tokenHash) .where("sessions.expires_at", ">", new Date()) .where("sessions.revoked_at", "is", null) .select(["users.id", "users.email", "users.is_superuser"]) .executeTakeFirst(); if (session) { return { id: session.id, email: session.email, isSuperuser: session.is_superuser, }; } // Check API tokens table const apiToken = await db .selectFrom("api_tokens") .innerJoin("users", "users.id", "api_tokens.user_id") .where("api_tokens.token_hash", "=", tokenHash) .where("api_tokens.expires_at", ">", new Date()) .select(["users.id", "users.email", "users.is_superuser"]) .executeTakeFirst(); if (apiToken) { // Update last_used_at await db .updateTable("api_tokens") .set({ last_used_at: new Date() }) .where("token_hash", "=", tokenHash) .execute(); return { id: apiToken.id, email: apiToken.email, isSuperuser: apiToken.is_superuser, }; } return null; };