Sign-out answered GET behind a <Link>, and Next prefetches links that are in the viewport. The button sits in every page header, so the browser called it as soon as a page rendered and cleared the session cookie before the reader had clicked anything. The first page loaded; the next navigation landed back on the password screen, which read as the session not persisting. It is a POST form now, and the route answers POST only — a prefetch of it gets 405 and changes nothing. The rule this broke is the general one: an endpoint with a side effect must be unreachable by a prefetch, a crawler, or a browser preloading what it thinks you will click. Both session cookies are now set on the returned response rather than through the cookies() store, which a freshly constructed redirect does not reliably carry. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
25 lines
1.1 KiB
TypeScript
25 lines
1.1 KiB
TypeScript
import { NextResponse } from "next/server";
|
|
import { SESSION_COOKIE, checkDashboardPassword, issueSessionValue, sessionCookieOptions } from "@/lib/session";
|
|
|
|
export const runtime = "nodejs";
|
|
export const dynamic = "force-dynamic";
|
|
|
|
/**
|
|
* Deliberately slow to fail. The password is the only thing between the open internet and
|
|
* the history, and a quarter second is unnoticeable once but ruinous for anyone working
|
|
* through a list.
|
|
*/
|
|
export async function POST(request: Request) {
|
|
const form = await request.formData();
|
|
const password = String(form.get("password") ?? "");
|
|
if (!checkDashboardPassword(password)) {
|
|
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
return NextResponse.redirect(new URL("/login?error=1", request.url), { status: 303 });
|
|
}
|
|
const response = NextResponse.redirect(new URL("/", request.url), { status: 303 });
|
|
// Set on the response rather than through the cookies() store: a freshly constructed
|
|
// redirect does not reliably carry what that store recorded.
|
|
response.cookies.set(SESSION_COOKIE, issueSessionValue(), sessionCookieOptions);
|
|
return response;
|
|
}
|