/** * Dashboard password authentication API. * * GET /api/auth — Check if password protection is active and if the request is authenticated * POST /api/auth — Validate password and set auth cookie * DELETE /api/auth — Clear auth cookie (lock dashboard) * * This is separate from ATProto OAuth (Sign In button in navbar). * ATProto is for identity (comments, likes, claims). * This gate controls who can view the dashboard and access the API. */ import { NextResponse } from "next/server"; import { cookies } from "next/headers"; import { env } from "@/lib/env"; import { COOKIE_NAME, generateGateToken, validateGateToken, comparePassword, } from "@/lib/gate"; export const dynamic = "force-dynamic"; const isProduction = process.env.NODE_ENV === "production"; export async function GET() { const password = env.HEARTBEADS_PASSWORD; if (!password) { return NextResponse.json({ protected: false }); } const cookieStore = await cookies(); const token = cookieStore.get(COOKIE_NAME)?.value; const authenticated = token ? validateGateToken(token, password) : false; return NextResponse.json({ protected: true, authenticated }); } export async function POST(request: Request) { const password = env.HEARTBEADS_PASSWORD; if (!password) { return NextResponse.json( { error: "Password protection is not enabled" }, { status: 400 } ); } let body: { password?: string }; try { body = await request.json(); } catch { return NextResponse.json( { error: "Invalid request body" }, { status: 400 } ); } const provided = body.password; if (!provided || typeof provided !== "string") { return NextResponse.json( { error: "Password is required" }, { status: 400 } ); } if (!comparePassword(provided, password)) { return NextResponse.json( { error: "Invalid password" }, { status: 401 } ); } // Password matches — set the gate cookie const token = generateGateToken(password); const response = NextResponse.json({ success: true }); response.cookies.set(COOKIE_NAME, token, { httpOnly: true, sameSite: "lax", secure: isProduction, path: "/", // Session cookie — no maxAge, expires when browser closes }); return response; } export async function DELETE() { const response = NextResponse.json({ success: true }); response.cookies.set(COOKIE_NAME, "", { httpOnly: true, sameSite: "lax", secure: isProduction, path: "/", maxAge: 0, // Expire immediately }); return response; }