From 7c689e034e80b5d29187fbe42fa9214e1e0cbb62 Mon Sep 17 00:00:00 2001 From: DavidHerran Date: Sat, 6 Jun 2026 17:14:05 -0500 Subject: [PATCH] fix(session): validate SESSION_SECRET lazily, not at module load MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The module-level SESSION_SECRET check threw during `next build` (page-data collection imports session.ts but has no runtime env vars), breaking the Docker build at /hq-command/dashboard. Move the check inside getEncodedKey() so it runs at runtime when a session is actually created. Security is unchanged: the secret is still never defaulted, and any attempt to mint a session without a valid 32+ char secret throws loudly. It just no longer blocks the build. Verified: `next build` now compiles with SESSION_SECRET unset (replicating the Docker builder stage) — 56/56 pages generated. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/lib/session.ts | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/lib/session.ts b/src/lib/session.ts index 689847c..0f36629 100644 --- a/src/lib/session.ts +++ b/src/lib/session.ts @@ -4,24 +4,32 @@ import { cookies } from "next/headers"; // SESSION_SECRET is REQUIRED. No fallback: a public default would let anyone // forge a 7-day admin JWT if the env var ever fails to load in production. // Generate a strong value with: openssl rand -base64 48 -const secretKey = process.env.SESSION_SECRET; -if (!secretKey || secretKey.length < 32) { - throw new Error( - "SESSION_SECRET environment variable is required (min 32 chars). " + - "Generate one with: openssl rand -base64 48" - ); +// +// Validated LAZILY (inside getEncodedKey, not at module load). `next build` +// imports this module during page-data collection without runtime env vars, +// so a top-level throw would break the build. At runtime, any attempt to +// create a session without a valid secret still throws loudly — the secret +// is never defaulted, so admin JWTs can't be forged. +function getEncodedKey(): Uint8Array { + const secretKey = process.env.SESSION_SECRET; + if (!secretKey || secretKey.length < 32) { + throw new Error( + "SESSION_SECRET environment variable is required (min 32 chars). " + + "Generate one with: openssl rand -base64 48" + ); + } + return new TextEncoder().encode(secretKey); } -const encodedKey = new TextEncoder().encode(secretKey); export async function createSession(userId: string, username: string) { const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // 7 días de duración - + // Creamos el token (El "pase de acceso" encriptado) const sessionToken = await new SignJWT({ userId, username }) .setProtectedHeader({ alg: "HS256" }) .setIssuedAt() .setExpirationTime("7d") - .sign(encodedKey); + .sign(getEncodedKey()); // En Next.js 15+, cookies() es una Promesa const cookieStore = await cookies();