#!/usr/bin/env node const BASE_URL = [ process.env.OPS_BASE_URL, process.env.APP_URL, process.env.NEXT_PUBLIC_APP_URL ].map((value) => value?.trim()).find(Boolean); const TEST_EMAIL = process.env.OPS_SESSION_CHECK_EMAIL?.trim(); const TEST_PASSWORD = process.env.OPS_SESSION_CHECK_PASSWORD?.trim(); const EXPECTED_TTL_SECONDS = resolveSessionTtl(process.env.SESSION_TTL_SECONDS); const SESSION_COOKIE_NAME = "wa_inbox_session"; if (!BASE_URL) { console.error("[ops-session-check] Missing OPS_BASE_URL / APP_URL / NEXT_PUBLIC_APP_URL"); process.exit(1); } if (!TEST_EMAIL || !TEST_PASSWORD) { console.error("[ops-session-check] Missing OPS_SESSION_CHECK_EMAIL / OPS_SESSION_CHECK_PASSWORD. Test skipped."); process.exit(1); } function resolveSessionTtl(value) { if (!value) { return 60 * 60 * 24 * 7; } const parsed = Number(value.trim()); if (!Number.isFinite(parsed) || parsed <= 0) { return 60 * 60 * 24 * 7; } return Math.floor(parsed); } function readCookieLines(headers) { const direct = []; if (typeof headers.getSetCookie === "function") { const lines = headers.getSetCookie(); if (Array.isArray(lines)) { return lines; } } headers.forEach((value, key) => { if (key.toLowerCase() === "set-cookie") { direct.push(value); } }); return direct; } function getCookieValue(cookieHeader, name) { const prefix = `${name}=`; const found = cookieHeader.find((entry) => entry.startsWith(prefix)); if (!found) { return null; } const [value] = found.split(";")[0].split("=", 2).slice(1); if (typeof value !== "string") { return null; } return `${found.split(";")[0].slice(prefix.length)}`; } function getCookieTtlSeconds(cookieHeader, name) { const prefix = `${name}=`; const found = cookieHeader.find((entry) => entry.startsWith(prefix)); if (!found) { return null; } const parts = found.split(";").map((part) => part.trim()); const attrs = new Map(); for (const part of parts.slice(1)) { const [rawKey, rawValue] = part.split("="); attrs.set(rawKey.toLowerCase(), rawValue ?? ""); } const maxAgeRaw = attrs.get("max-age"); if (maxAgeRaw) { const parsed = Number(maxAgeRaw); if (Number.isFinite(parsed)) { return Math.floor(parsed); } } const expiresRaw = attrs.get("expires"); if (expiresRaw) { const parsedDate = new Date(expiresRaw); if (!Number.isNaN(parsedDate.getTime())) { return Math.floor((parsedDate.getTime() - Date.now()) / 1000); } } return null; } async function requestJson(url, options = {}) { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 12_000); try { const response = await fetch(url, { ...options, signal: controller.signal }); clearTimeout(timeout); return response; } catch (error) { clearTimeout(timeout); throw error; } } async function main() { const loginHeaders = { "Content-Type": "application/x-www-form-urlencoded", "User-Agent": "ops-session-check" }; const loginBody = new URLSearchParams({ email: TEST_EMAIL, password: TEST_PASSWORD, next: "/super-admin" }).toString(); const loginResponse = await requestJson(`${BASE_URL}/auth/login`, { method: "POST", redirect: "manual", headers: loginHeaders, body: loginBody }); if (loginResponse.status !== 307 && loginResponse.status !== 302) { console.error(`[ops-session-check] login status unexpected: ${loginResponse.status}`); process.exit(1); } const setCookieLines = readCookieLines(loginResponse.headers); const sessionCookieValue = getCookieValue(setCookieLines, SESSION_COOKIE_NAME); if (!sessionCookieValue) { console.error("[ops-session-check] session cookie not issued by /auth/login"); process.exit(1); } const ttl = getCookieTtlSeconds(setCookieLines, SESSION_COOKIE_NAME); if (ttl === null) { console.error("[ops-session-check] session cookie ttl missing"); process.exit(1); } if (Math.abs(ttl - EXPECTED_TTL_SECONDS) > 600) { console.warn( `[ops-session-check] session ttl unexpected: ${ttl}s (expected ${EXPECTED_TTL_SECONDS}s ±600s)` ); } else { console.log(`[ops-session-check] session ttl check OK: ${ttl}s`); } const protected = await requestJson(`${BASE_URL}/super-admin`, { method: "GET", redirect: "manual", headers: { Cookie: `${SESSION_COOKIE_NAME}=${sessionCookieValue}` } }); if (protected.status === 200) { console.log("[ops-session-check] protected path access OK (HTTP 200)."); process.exit(0); } if (protected.status >= 300 && protected.status < 400) { const location = protected.headers.get("location") || ""; if (location.includes("/login")) { console.error("[ops-session-check] protected path redirected to login; session not accepted."); process.exit(1); } } console.error(`[ops-session-check] protected path check failed with status ${protected.status}`); process.exit(1); } main().catch((error) => { console.error("[ops-session-check] failed:", error instanceof Error ? error.message : String(error)); process.exit(1); });