From 14fe8c04456f4f6f98acf6021ecd2b209c2a56f6 Mon Sep 17 00:00:00 2001 From: Oleg Date: Wed, 5 Aug 2026 07:47:22 +0000 Subject: [PATCH] Fix silent login failure on infra without HTTPS yet, and build-time SQLITE_BUSY MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Session cookie always set Secure (tied to NODE_ENV=production, hardcoded in the Dockerfile), which the browser silently drops over plain HTTP — looked like login accepted the password but bounced straight back to /login. Secure now only drops when COOKIE_ALLOW_INSECURE=true, for temporary use before the HTTPS reverse proxy is wired up. Also fixed pragma order in db/client.ts: busy_timeout must be set before journal_mode, since switching to WAL itself takes a momentary exclusive lock that isn't covered by a busy_timeout set afterward — caused an intermittent SQLITE_BUSY during `next build`'s parallel page-data collection. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_012o9j9RezxbZVKQMrB7oRLY --- src/lib/auth/session.ts | 7 ++++++- src/lib/db/client.ts | 11 +++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/lib/auth/session.ts b/src/lib/auth/session.ts index 530ccbf..e3d2a41 100644 --- a/src/lib/auth/session.ts +++ b/src/lib/auth/session.ts @@ -46,7 +46,12 @@ export async function setSessionCookie(token: string): Promise { const cookieStore = await cookies(); cookieStore.set(SESSION_COOKIE, token, { httpOnly: true, - secure: process.env.NODE_ENV === "production", + // Secure by default in production. Set COOKIE_ALLOW_INSECURE=true only + // for temporary testing over plain HTTP (e.g. mid-migration before the + // HTTPS reverse proxy is wired up) — a Secure cookie is silently + // dropped by the browser over HTTP, which looks exactly like "login + // accepts the password but you're bounced straight back to /login". + secure: process.env.NODE_ENV === "production" && process.env.COOKIE_ALLOW_INSECURE !== "true", sameSite: "lax", path: "/", maxAge: SESSION_TTL_MS / 1000, diff --git a/src/lib/db/client.ts b/src/lib/db/client.ts index 832c34a..086a32e 100644 --- a/src/lib/db/client.ts +++ b/src/lib/db/client.ts @@ -8,14 +8,13 @@ const dataDir = process.env.DATA_DIR ?? path.join(process.cwd(), "data"); fs.mkdirSync(dataDir, { recursive: true }); const sqlite = new Database(path.join(dataDir, "db.sqlite")); +// busy_timeout MUST be set before journal_mode: switching to WAL itself +// takes a momentary exclusive lock, and if a concurrent worker is mid-switch +// on a fresh db.sqlite, that first statement has no busy_timeout protection +// yet and throws SQLITE_BUSY immediately instead of waiting. +sqlite.pragma("busy_timeout = 30000"); sqlite.pragma("journal_mode = WAL"); sqlite.pragma("foreign_keys = ON"); -// Next's build-time page-data collection evaluates route modules across -// several worker processes concurrently — without this, two workers -// racing to open/initialize a fresh db.sqlite can throw SQLITE_BUSY -// instead of just waiting for the other's lock to clear. 5s wasn't enough -// once the route count grew — bumped to 30s (build-time only cost). -sqlite.pragma("busy_timeout = 30000"); export const db = drizzle(sqlite, { schema }); export type DB = typeof db;