import { SignJWT, jwtVerify, type JWTPayload } from "jose"; import type { NextRequest } from "next/server"; const SESSION_COOKIE = "admin_session"; const SESSION_TTL = 28_800; // 8 hours in seconds function getJwtSecret(): Uint8Array { const secret = process.env.ADMIN_JWT_SECRET; if (!secret || secret.length < 32) { throw new Error( "ADMIN_JWT_SECRET env var must be set to a string of at least 32 characters", ); } return new TextEncoder().encode(secret); } export interface AdminSession extends JWTPayload { ip: string; } /** Creates a signed JWT and returns it as the cookie value. */ export async function createSession(ip: string): Promise { const token = await new SignJWT({ ip } satisfies Partial) .setProtectedHeader({ alg: "HS256" }) .setIssuedAt() .setExpirationTime(`${SESSION_TTL}s`) .sign(getJwtSecret()); return token; } /** * Verifies the session JWT from the request cookie. * Works in both Edge and Node.js runtimes (uses Web Crypto). */ export async function getSession( req: NextRequest, ): Promise { const token = req.cookies.get(SESSION_COOKIE)?.value; if (!token) return null; try { const { payload } = await jwtVerify(token, getJwtSecret()); return payload; } catch { return null; } } /** * Destroys a session — with JWT sessions the cookie is cleared client-side. * No server-side state to remove. */ export async function destroySession(_req: NextRequest): Promise { // JWT sessions are stateless — clearing the cookie in the response is sufficient. } export const SESSION_COOKIE_NAME = SESSION_COOKIE; export function makeSessionCookie(token: string, secure: boolean): string { const parts = [ `${SESSION_COOKIE}=${token}`, "HttpOnly", "SameSite=Strict", `Max-Age=${SESSION_TTL}`, "Path=/", ]; if (secure) parts.push("Secure"); return parts.join("; "); } export function clearSessionCookie(): string { return `${SESSION_COOKIE}=; HttpOnly; SameSite=Strict; Max-Age=0; Path=/`; }