import { createHash, randomBytes } from "crypto"; import { and, eq, isNull } from "drizzle-orm"; import { db } from "@/lib/db"; import { householdApiTokens } from "./schema"; export type HouseholdApiTokenStatus = { hasActiveToken: boolean; lastUsedAt: Date | null; createdAt: Date | null; }; export type CreateHouseholdApiTokenResult = { token: string; }; export function hashApiToken(raw: string): string { return createHash("sha256").update(raw).digest("hex"); } function generateRawToken(): string { return randomBytes(32).toString("base64url"); } export async function createHouseholdApiToken( householdId: string, createdByUserId: string, ): Promise { await db .update(householdApiTokens) .set({ revokedAt: new Date() }) .where( and(eq(householdApiTokens.householdId, householdId), isNull(householdApiTokens.revokedAt)), ); const rawToken = generateRawToken(); const tokenHash = hashApiToken(rawToken); await db.insert(householdApiTokens).values({ householdId, tokenHash, createdBy: createdByUserId, }); return { token: rawToken }; } export async function revokeHouseholdApiToken(householdId: string): Promise { await db .update(householdApiTokens) .set({ revokedAt: new Date() }) .where( and(eq(householdApiTokens.householdId, householdId), isNull(householdApiTokens.revokedAt)), ); } export async function getHouseholdApiTokenStatus( householdId: string, ): Promise { const [row] = await db .select({ lastUsedAt: householdApiTokens.lastUsedAt, createdAt: householdApiTokens.createdAt, }) .from(householdApiTokens) .where( and(eq(householdApiTokens.householdId, householdId), isNull(householdApiTokens.revokedAt)), ) .limit(1); if (!row) { return { hasActiveToken: false, lastUsedAt: null, createdAt: null }; } return { hasActiveToken: true, lastUsedAt: row.lastUsedAt, createdAt: row.createdAt, }; } export async function resolveBearerToken( rawToken: string, ): Promise<{ householdId: string; userId: null } | null> { if (!rawToken) return null; const tokenHash = hashApiToken(rawToken); const [row] = await db .select({ id: householdApiTokens.id, householdId: householdApiTokens.householdId }) .from(householdApiTokens) .where(and(eq(householdApiTokens.tokenHash, tokenHash), isNull(householdApiTokens.revokedAt))) .limit(1); if (!row) return null; void db .update(householdApiTokens) .set({ lastUsedAt: new Date() }) .where(eq(householdApiTokens.id, row.id)); return { householdId: row.householdId, userId: null }; }