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:
co-authored by
Claude Opus 4.7
parent
5160a00db5
commit
2ebfcfb7a6
@@ -13,7 +13,9 @@ function withDb<T>(fn: (d: DatabaseSync) => T): T {
|
||||
|
||||
test("a row left running renders what exists instead of redirecting forever", async ({ page }) => {
|
||||
const id = withDb((d) => {
|
||||
const userId = d.prepare("SELECT id FROM users ORDER BY rowid DESC LIMIT 1").get() as { id: string };
|
||||
// The setup project's account, not "the newest user": the webkit specs sign up their own accounts and
|
||||
// would otherwise put this row under an account the stored session cannot open (404, not the note).
|
||||
const userId = d.prepare("SELECT id FROM users WHERE email LIKE 'qa+%@qai.test' ORDER BY rowid DESC LIMIT 1").get() as { id: string };
|
||||
const aid = randomUUID();
|
||||
// Backdated so it never becomes the rail's first row: history.spec.ts depends on that row being a real run.
|
||||
d.prepare("INSERT INTO analyses (id, user_id, title, xml, status, created_at, updated_at) VALUES (?,?,?,?,?,?,?)")
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
// WebKit-only on purpose. Chromium accepts a `Secure` cookie on http://localhost; Safari does not, and it
|
||||
// silently drops it, so a production build on plain http looked like "the wizard signed me out".
|
||||
// This file must stay self-contained: it signs up its own account and never uses .auth/user.json.
|
||||
|
||||
test("signup, wizard Next links, and /app stay logged in over plain http", async ({ page }) => {
|
||||
const email = `safari${Date.now()}@qai.test`;
|
||||
await page.goto("/signup");
|
||||
await page.fill("#email", email);
|
||||
await page.fill("#password", "0123456789ab");
|
||||
await page.getByRole("button", { name: "Create account" }).click();
|
||||
|
||||
await expect(page).toHaveURL(/\/setup/, { timeout: 20_000 });
|
||||
await expect(page.getByText("Step 1 of 3")).toBeVisible();
|
||||
|
||||
await page.getByRole("link", { name: "Next" }).click();
|
||||
await expect(page).toHaveURL(/step=2/);
|
||||
await expect(page.getByText("Say which models to ask.")).toBeVisible();
|
||||
|
||||
await page.getByRole("link", { name: "Next" }).click();
|
||||
await expect(page).toHaveURL(/step=3/);
|
||||
|
||||
// Session proof: this user has no keys yet, so the console gate sends them to the guide.
|
||||
// Signed-in lands on /setup?step=2; a lost session would land on /login.
|
||||
await page.goto("/app");
|
||||
await expect(page).toHaveURL(/\/setup\?step=2/);
|
||||
});
|
||||
|
||||
test("a reload deep in the app keeps the session", async ({ page }) => {
|
||||
const email = `reload${Date.now()}@qai.test`;
|
||||
await page.goto("/signup");
|
||||
await page.fill("#email", email);
|
||||
await page.fill("#password", "0123456789ab");
|
||||
await page.getByRole("button", { name: "Create account" }).click();
|
||||
// Wait for the signup POST to finish: the cookie is not in effect until its response lands, and an
|
||||
// immediate goto races it (which reads as "the session did not survive a reload").
|
||||
await expect(page).toHaveURL(/\/setup/, { timeout: 20_000 });
|
||||
await page.goto("/settings");
|
||||
await page.reload();
|
||||
await expect(page.getByRole("heading", { name: "Settings" })).toBeVisible();
|
||||
// The form is still here after the reload: this is the account's own settings page, not the login form.
|
||||
await expect(page.getByRole("button", { name: "Save" })).toBeVisible();
|
||||
});
|
||||
Reference in New Issue
Block a user