fix: session cookie over plain http, console onboarding gate, webkit qa coverage

- the session cookie was Secure whenever NODE_ENV=production, so a production build on http://localhost got no cookie back in Safari: every page after signup looked signed-out. It is now Secure only when X-Forwarded-Proto says https, or when COOKIE_SECURE=1.
- /app and /app/[id] now require an analyst and a Jev key, the same rule /api/analyze enforces with a 412. Closing the guide mid-way used to leave a console that could not run anything.
- Playwright runs the production server (pree2e builds first) and gains a webkit project for session survival; chromium-only QA could never see a Secure-cookie rejection.
- tests-e2e/session.spec.ts pins both; .env.example gains COOKIE_SECURE and stops shipping a blank APP_SECRET.
- e2e: 21 passed (setup, chromium, webkit, mobile).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
ahmed mohammed
2026-09-22 10:45:12 +03:00
co-authored by Claude Opus 4.7
parent 5160a00db5
commit 2ebfcfb7a6
13 changed files with 124 additions and 12 deletions
+9 -2
View File
@@ -33,6 +33,7 @@ export function signUpNewUser(email: string, password: string): { ok: true; id:
// next/headers is imported lazily: `node --test` loads this module to test the crypto above, and the
// request-scoped binding must not be touched at import time.
const jar = async () => (await import("next/headers")).cookies();
const fwdProto = async () => ((await (await import("next/headers")).headers())).get("x-forwarded-proto") ?? "";
export function changeEmail(userId: string, email: string): { ok: true } | { ok: false; error: string } {
const e = email.trim().toLowerCase();
@@ -47,9 +48,15 @@ export function changeEmail(userId: string, email: string): { ok: true } | { ok:
export async function startSession(userId: string) {
const token = randomBytes(32).toString("hex");
putSession(hashToken(token), userId, Date.now() + SESSION_DAYS * 864e5);
const secure = process.env.COOKIE_SECURE === "1" || (await fwdProto()).includes("https");
(await jar()).set(SESSION_COOKIE, token, {
httpOnly: true, sameSite: "lax", secure: process.env.NODE_ENV === "production",
path: "/", maxAge: SESSION_DAYS * 86400 });
httpOnly: true, sameSite: "lax", secure,
path: "/", maxAge: SESSION_DAYS * 86400,
// NODE_ENV is the wrong signal here. A production build served over plain http://localhost would set
// Secure, Safari then refuses to store the cookie at all, and every page after signup looks signed-out.
// (Chromium accepts Secure on localhost, which is why a chromium-only suite could never see it.)
// Only a TLS proxy reports https; COOKIE_SECURE=1 forces it on for a direct https deploy.
});
}
export async function endSession() {
const store = await jar();