fix(session): validate SESSION_SECRET lazily, not at module load
Deploy to VPS / deploy (push) Has been cancelled

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) <noreply@anthropic.com>
This commit is contained in:
2026-06-06 17:14:05 -05:00
parent 3e0b286f1a
commit 7c689e034e
+17 -9
View File
@@ -4,24 +4,32 @@ import { cookies } from "next/headers";
// SESSION_SECRET is REQUIRED. No fallback: a public default would let anyone // 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. // 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 // Generate a strong value with: openssl rand -base64 48
const secretKey = process.env.SESSION_SECRET; //
if (!secretKey || secretKey.length < 32) { // Validated LAZILY (inside getEncodedKey, not at module load). `next build`
throw new Error( // imports this module during page-data collection without runtime env vars,
"SESSION_SECRET environment variable is required (min 32 chars). " + // so a top-level throw would break the build. At runtime, any attempt to
"Generate one with: openssl rand -base64 48" // 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) { 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 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) // Creamos el token (El "pase de acceso" encriptado)
const sessionToken = await new SignJWT({ userId, username }) const sessionToken = await new SignJWT({ userId, username })
.setProtectedHeader({ alg: "HS256" }) .setProtectedHeader({ alg: "HS256" })
.setIssuedAt() .setIssuedAt()
.setExpirationTime("7d") .setExpirationTime("7d")
.sign(encodedKey); .sign(getEncodedKey());
// En Next.js 15+, cookies() es una Promesa // En Next.js 15+, cookies() es una Promesa
const cookieStore = await cookies(); const cookieStore = await cookies();