From db6feb6fad4137b288e462cb23b4d26944fe9eb1 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Fri, 11 Sep 2026 22:29:12 -0300 Subject: [PATCH] =?UTF-8?q?fix(auth):=20dashboard=20session=20requires=20t?= =?UTF-8?q?he=20authenticated=20claim=20=E2=80=94=20Cursor=20CLI=20tokens?= =?UTF-8?q?=20are=20not=20sessions=20(#13375)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merged as part of the owner batch of 2026-09-11. This PR had a live worktree in another session, so it sat outside the main 39. Merged on your explicit call, validated first rather than taken on trust: boarded with the other 10 worktree-held PRs into a consolidated worktree off `release/v3.8.51`. - ESLint over every changed file: no errors - `typecheck:core` clean; `check:dashboard-typecheck` OK; `check:changelog-integrity` OK - complexity 2821 / baseline 3218 and cognitive-complexity 1272 / baseline 1437 - 203 of 208 assertions green. The 5 remaining (`guide-settings-route` ×4, `hard-session-lease-bypass-inventory` ×1) reproduce on the pure tip with nothing from this batch applied. - `imageGeneration.ts` rebaselined 3259 → 3293 for #12945's image-only-model guard, landed separately in #13392 so nothing was pushed onto a live branch. ⚠️ base-red inherited: #12732 — provider count 356 vs 358 and `open-sse/utils/stream.ts` 3115 > frozen 3098, both reproducing on the pure tip. --- ...8-dashboard-session-authenticated-claim.md | 1 + docs/architecture/AUTHZ_GUIDE.md | 8 ++ src/app/api/auth/status/route.ts | 16 ++- src/app/api/settings/require-login/route.ts | 15 +-- src/lib/ws/handshake.ts | 9 +- src/server/authz/pipeline.ts | 11 +- src/server/ws/liveServer.ts | 10 +- src/shared/utils/apiAuth.ts | 10 +- src/shared/utils/dashboardSessionToken.ts | 42 ++++++++ .../cli-tools-apply-opencode-jsonc.test.ts | 2 +- tests/unit/cli-tools-keys-route.test.ts | 2 +- .../codex-settings-wire-api-default.test.ts | 2 +- .../dashboard-session-token-13298.test.ts | 100 ++++++++++++++++++ ...oard-session-verifier-source-guard.test.ts | 37 +++++++ .../embeddings-route-apikeymeta-6929.test.ts | 2 +- .../feature-flags-route-virtual-lanes.test.ts | 2 +- tests/unit/guide-settings-route.test.ts | 2 +- ...s-agent-settings-route-keyid-10711.test.ts | 2 +- .../unit/image-generation-route-auth.test.ts | 2 +- tests/unit/playground-key-policy-3503.test.ts | 2 +- tests/unit/qwen-settings-route.test.ts | 2 +- 21 files changed, 225 insertions(+), 54 deletions(-) create mode 100644 changelog.d/fixes/13298-dashboard-session-authenticated-claim.md create mode 100644 src/shared/utils/dashboardSessionToken.ts create mode 100644 tests/unit/dashboard-session-token-13298.test.ts create mode 100644 tests/unit/dashboard-session-verifier-source-guard.test.ts diff --git a/changelog.d/fixes/13298-dashboard-session-authenticated-claim.md b/changelog.d/fixes/13298-dashboard-session-authenticated-claim.md new file mode 100644 index 0000000000..c84635053a --- /dev/null +++ b/changelog.d/fixes/13298-dashboard-session-authenticated-claim.md @@ -0,0 +1 @@ +- **fix(auth):** a dashboard session now requires the `authenticated: true` claim that login, OIDC and the session refresh already emit — a JWT merely signed with `JWT_SECRET` (for example the Cursor CLI passthrough token, which any API-key holder can obtain) no longer verifies as the `auth_token` cookie on any route, the WebSocket handshake or the live server; existing sessions keep working ([#13298](https://github.com/diegosouzapw/OmniRoute/issues/13298)) diff --git a/docs/architecture/AUTHZ_GUIDE.md b/docs/architecture/AUTHZ_GUIDE.md index 911b1bd72c..4b478876b8 100644 --- a/docs/architecture/AUTHZ_GUIDE.md +++ b/docs/architecture/AUTHZ_GUIDE.md @@ -35,6 +35,14 @@ For dashboard pages and admin operations. Cookie: auth_token= ``` +A cookie is a session only when the JWT verifies **and** carries `authenticated: true` +(`src/shared/utils/dashboardSessionToken.ts` → `verifyDashboardSessionToken`). Every +consumer of the cookie (route guard, authz pipeline refresh, WebSocket handshake, live +server, `/api/settings/require-login`, `/api/auth/status`) goes through that helper. +Other JWTs signed with `JWT_SECRET` exist — the Cursor CLI passthrough mints +`iss "omniroute" / aud "cursor-cli"` tokens for key holders — and are never sessions +(#13298). + Verified by `isDashboardSessionAuthenticated()` in `src/shared/utils/apiAuth.ts`. The pipeline auto-refreshes the JWT when it has fewer than 7 days left in its 30-day lifetime. Some management routes accept **either** mode: cookie OR `Bearer ` when the API key has the `manage` (or `admin`) scope. This is what enables the "configurable via API calls" workflow added in v3.8. diff --git a/src/app/api/auth/status/route.ts b/src/app/api/auth/status/route.ts index 62cb1a7191..48f04bd552 100644 --- a/src/app/api/auth/status/route.ts +++ b/src/app/api/auth/status/route.ts @@ -1,25 +1,23 @@ import { NextResponse } from "next/server"; export const dynamic = "force-dynamic"; import { cookies } from "next/headers"; -import { jwtVerify } from "jose"; - -function getJwtSecret(): Uint8Array | null { - const secret = process.env.JWT_SECRET?.trim(); - return secret ? new TextEncoder().encode(secret) : null; -} +import { + getDashboardJwtSecret, + verifyDashboardSessionToken, +} from "@/shared/utils/dashboardSessionToken"; export async function GET() { try { const cookieStore = await cookies(); const token = cookieStore.get("auth_token")?.value; - const secret = getJwtSecret(); + const secret = getDashboardJwtSecret(); if (!token || !secret) { return NextResponse.json({ authenticated: false }); } - await jwtVerify(token, secret); - return NextResponse.json({ authenticated: true }); + const payload = await verifyDashboardSessionToken(token, secret); + return NextResponse.json({ authenticated: payload !== null }); } catch { return NextResponse.json({ authenticated: false }); } diff --git a/src/app/api/settings/require-login/route.ts b/src/app/api/settings/require-login/route.ts index 570221409d..a5d988a43b 100644 --- a/src/app/api/settings/require-login/route.ts +++ b/src/app/api/settings/require-login/route.ts @@ -1,6 +1,5 @@ import { NextResponse } from "next/server"; import { cookies } from "next/headers"; -import { jwtVerify } from "jose"; import { isFeatureFlagEnabled } from "@/shared/utils/featureFlags"; import { getSettings, updateSettings } from "@/lib/db/settings"; import { @@ -8,23 +7,19 @@ import { hashManagementPassword, } from "@/lib/auth/managementPassword"; import { isAuthenticated } from "@/shared/utils/apiAuth"; +import { + getDashboardJwtSecret, + verifyDashboardSessionToken, +} from "@/shared/utils/dashboardSessionToken"; import { getNodeRuntimeSupport } from "@/shared/utils/nodeRuntimeSupport.ts"; import { updateRequireLoginSchema } from "@/shared/validation/schemas"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; -function getJwtSecret(): Uint8Array | null { - const secret = process.env.JWT_SECRET?.trim(); - return secret ? new TextEncoder().encode(secret) : null; -} - async function checkSessionAuthenticated(): Promise { try { const cookieStore = await cookies(); const token = cookieStore.get("auth_token")?.value; - const secret = getJwtSecret(); - if (!token || !secret) return false; - await jwtVerify(token, secret); - return true; + return (await verifyDashboardSessionToken(token, getDashboardJwtSecret())) !== null; } catch { return false; } diff --git a/src/lib/ws/handshake.ts b/src/lib/ws/handshake.ts index 454fbe9457..a730171b7b 100644 --- a/src/lib/ws/handshake.ts +++ b/src/lib/ws/handshake.ts @@ -1,4 +1,4 @@ -import { jwtVerify } from "jose"; +import { verifyDashboardSessionToken } from "@/shared/utils/dashboardSessionToken"; import { getSettings } from "@/lib/db/settings"; import { validateApiKey } from "@/lib/db/apiKeys"; @@ -44,12 +44,7 @@ async function hasValidSessionCookie(request: Request): Promise { const token = getCookieValue(request.headers.get("cookie"), "auth_token"); if (!token) return false; - try { - await jwtVerify(token, new TextEncoder().encode(secretValue)); - return true; - } catch { - return false; - } + return (await verifyDashboardSessionToken(token, new TextEncoder().encode(secretValue))) !== null; } export function extractWsTokenFromUrl(input: string | URL): string | null { diff --git a/src/server/authz/pipeline.ts b/src/server/authz/pipeline.ts index 9ef7fed2fd..bc1dbb98cc 100644 --- a/src/server/authz/pipeline.ts +++ b/src/server/authz/pipeline.ts @@ -1,8 +1,9 @@ -import { jwtVerify, SignJWT } from "jose"; +import { SignJWT } from "jose"; import { NextResponse, type NextRequest } from "next/server"; import { getCachedSettings } from "../../lib/db/readCache"; import { isDraining } from "../../lib/gracefulShutdown"; import { checkBodySize, getBodySizeLimit } from "../../shared/middleware/bodySizeGuard"; +import { verifyDashboardSessionToken } from "@/shared/utils/dashboardSessionToken"; import { generateRequestId } from "../../shared/utils/requestId"; import { applyCorsHeaders } from "../cors/origins"; import { validateBrowserMutationOrigin } from "../origin/publicOrigin"; @@ -153,7 +154,13 @@ async function refreshDashboardSessionIfNeeded( if (!token) return; try { - const { payload } = await jwtVerify(token, secret); + const payload = await verifyDashboardSessionToken(token, secret); + if (!payload) { + // Not a dashboard session (foreign/expired/claim-less token): drop it so a + // Cursor CLI token can never ride along as the cookie (#13298). + response.cookies.delete("auth_token"); + return; + } const exp = typeof payload.exp === "number" ? payload.exp : null; if (!exp) return; diff --git a/src/server/ws/liveServer.ts b/src/server/ws/liveServer.ts index bd870ba604..3ad9874728 100644 --- a/src/server/ws/liveServer.ts +++ b/src/server/ws/liveServer.ts @@ -18,9 +18,9 @@ */ import { WebSocketServer, WebSocket } from "ws"; -import { jwtVerify } from "jose"; import { createServer, type IncomingMessage, type ServerResponse } from "http"; import { randomUUID } from "crypto"; +import { verifyDashboardSessionToken } from "@/shared/utils/dashboardSessionToken"; // ── Types ───────────────────────────────────────────────────────────────── @@ -191,13 +191,7 @@ async function isDashboardCookieAuthenticated( ): Promise { const token = getCookieValueFromHeader(request.headers, "auth_token"); if (!token || !process.env.JWT_SECRET) return false; - try { - const secret = new TextEncoder().encode(process.env.JWT_SECRET); - await jwtVerify(token, secret); - return true; - } catch { - return false; - } + return (await verifyDashboardSessionToken(token)) !== null; } function extractBearerToken(request: import("http").IncomingMessage): string | null { diff --git a/src/shared/utils/apiAuth.ts b/src/shared/utils/apiAuth.ts index d53c72ea9e..3c7afe96eb 100644 --- a/src/shared/utils/apiAuth.ts +++ b/src/shared/utils/apiAuth.ts @@ -7,10 +7,10 @@ * @module shared/utils/apiAuth */ -import { jwtVerify } from "jose"; import { cookies } from "next/headers"; import { getSettings } from "@/lib/db/settings"; import { isPublicApiRoute } from "@/shared/constants/publicApiRoutes"; +import { verifyDashboardSessionToken } from "@/shared/utils/dashboardSessionToken"; import { extractApiKey } from "@/sse/services/auth"; type RequestLike = { @@ -247,13 +247,7 @@ export async function isDashboardSessionAuthenticated( if (!token) return false; - try { - const secret = new TextEncoder().encode(process.env.JWT_SECRET); - await jwtVerify(token, secret); - return true; - } catch { - return false; - } + return (await verifyDashboardSessionToken(token)) !== null; } // ──────────────── Auth Verification ──────────────── diff --git a/src/shared/utils/dashboardSessionToken.ts b/src/shared/utils/dashboardSessionToken.ts new file mode 100644 index 0000000000..227b272695 --- /dev/null +++ b/src/shared/utils/dashboardSessionToken.ts @@ -0,0 +1,42 @@ +/** + * Dashboard session token — the ONE verifier for the `auth_token` cookie. + * + * A dashboard session is a JWT that verifies against JWT_SECRET AND carries + * `authenticated: true` — the claim every session minter emits + * (`api/auth/login`, `api/auth/oidc/callback`, the authz pipeline refresh). + * Other tokens signed with the same secret exist (the Cursor CLI passthrough + * mints `iss "omniroute" / aud "cursor-cli"` tokens for any key holder) and + * MUST NOT verify as a session: before #13298 any such token forged the + * cookie and reached instance-wide operations. Every place that trusts the + * cookie goes through `verifyDashboardSessionToken`; a bare `jwtVerify` on + * `auth_token` is a regression (guarded by + * tests/unit/dashboard-session-verifier-source-guard.test.ts). + */ +import { jwtVerify, type JWTPayload } from "jose"; + +export const DASHBOARD_SESSION_COOKIE = "auth_token"; +export const DASHBOARD_SESSION_CLAIM = "authenticated"; + +export function getDashboardJwtSecret(): Uint8Array | null { + const secret = process.env.JWT_SECRET?.trim(); + return secret ? new TextEncoder().encode(secret) : null; +} + +/** + * Returns the verified payload when `token` is a dashboard session, else null. + * Never throws: a malformed, expired, foreign-secret or claim-less token is + * simply "not a session". Pass `secret` explicitly when the caller already + * resolved it (the authz pipeline does); `null` means "no secret → no session". + */ +export async function verifyDashboardSessionToken( + token: string | null | undefined, + secret: Uint8Array | null = getDashboardJwtSecret() +): Promise { + if (!token || typeof token !== "string" || !secret) return null; + try { + const { payload } = await jwtVerify(token, secret); + return payload[DASHBOARD_SESSION_CLAIM] === true ? payload : null; + } catch { + return null; + } +} diff --git a/tests/unit/cli-tools-apply-opencode-jsonc.test.ts b/tests/unit/cli-tools-apply-opencode-jsonc.test.ts index 69dde22adb..3eee8ced0b 100644 --- a/tests/unit/cli-tools-apply-opencode-jsonc.test.ts +++ b/tests/unit/cli-tools-apply-opencode-jsonc.test.ts @@ -24,7 +24,7 @@ const testRoots = new Set(); async function createAuthCookie(): Promise { process.env.JWT_SECRET = "test-cli-tools-apply-secret"; const secret = new TextEncoder().encode(process.env.JWT_SECRET); - const token = await new SignJWT({ sub: "test-user" }) + const token = await new SignJWT({ authenticated: true, sub: "test-user" }) .setProtectedHeader({ alg: "HS256" }) .setIssuedAt() .setExpirationTime("1h") diff --git a/tests/unit/cli-tools-keys-route.test.ts b/tests/unit/cli-tools-keys-route.test.ts index 2c69c714c0..240d3810a2 100644 --- a/tests/unit/cli-tools-keys-route.test.ts +++ b/tests/unit/cli-tools-keys-route.test.ts @@ -12,7 +12,7 @@ const originalApiKeySecret = process.env.API_KEY_SECRET; async function createAuthCookie() { process.env.JWT_SECRET = "test-cli-tools-keys-secret"; const secret = new TextEncoder().encode(process.env.JWT_SECRET); - const token = await new SignJWT({ sub: "test-user" }) + const token = await new SignJWT({ authenticated: true, sub: "test-user" }) .setProtectedHeader({ alg: "HS256" }) .setIssuedAt() .setExpirationTime("1h") diff --git a/tests/unit/codex-settings-wire-api-default.test.ts b/tests/unit/codex-settings-wire-api-default.test.ts index 7b7d5fd32d..24bb20276e 100644 --- a/tests/unit/codex-settings-wire-api-default.test.ts +++ b/tests/unit/codex-settings-wire-api-default.test.ts @@ -18,7 +18,7 @@ const route = await import("../../src/app/api/cli-tools/codex-settings/route.ts" const authCookie = async (): Promise => { process.env.JWT_SECRET = "codex-wire-api-default-test-secret"; - const token = await new SignJWT({ sub: "codex-wire-api-default-test" }) + const token = await new SignJWT({ authenticated: true, sub: "codex-wire-api-default-test" }) .setProtectedHeader({ alg: "HS256" }) .setIssuedAt() .setExpirationTime("1h") diff --git a/tests/unit/dashboard-session-token-13298.test.ts b/tests/unit/dashboard-session-token-13298.test.ts new file mode 100644 index 0000000000..be2a16f8bb --- /dev/null +++ b/tests/unit/dashboard-session-token-13298.test.ts @@ -0,0 +1,100 @@ +/** + * #13298 — a dashboard session is a JWT that (a) verifies against JWT_SECRET AND + * (b) carries `authenticated: true`, the claim every dashboard minter emits + * (login, OIDC callback, pipeline refresh). Any other JWT signed with the same + * secret — notably the Cursor CLI passthrough token (iss "omniroute", aud + * "cursor-cli", no claim) — is NOT a session. + */ +import "../_setup/isolateDataDir.ts"; +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { SignJWT } from "jose"; + +process.env.JWT_SECRET = "dashboard-session-token-13298-secret"; + +const { verifyDashboardSessionToken, getDashboardJwtSecret, DASHBOARD_SESSION_COOKIE } = + await import("../../src/shared/utils/dashboardSessionToken.ts"); + +const secret = new TextEncoder().encode(process.env.JWT_SECRET); +const sign = ( + claims: Record, + opts: { exp?: string; iss?: string; aud?: string } = {} +) => { + let j = new SignJWT(claims) + .setProtectedHeader({ alg: "HS256" }) + .setExpirationTime(opts.exp ?? "1h"); + if (opts.iss) j = j.setIssuer(opts.iss); + if (opts.aud) j = j.setAudience(opts.aud); + return j.sign(secret); +}; + +test("cookie name constant", () => { + assert.equal(DASHBOARD_SESSION_COOKIE, "auth_token"); +}); + +test("getDashboardJwtSecret encodes the trimmed env secret, null when unset/blank", () => { + assert.ok(getDashboardJwtSecret() instanceof Uint8Array); + const saved = process.env.JWT_SECRET; + process.env.JWT_SECRET = " "; + assert.equal(getDashboardJwtSecret(), null); + delete process.env.JWT_SECRET; + assert.equal(getDashboardJwtSecret(), null); + process.env.JWT_SECRET = saved; +}); + +test("accepts the login-shaped token { authenticated: true } and returns its payload", async () => { + const payload = await verifyDashboardSessionToken(await sign({ authenticated: true })); + assert.ok(payload, "login-shaped token is a session"); + assert.equal(payload!.authenticated, true); + assert.equal(typeof payload!.exp, "number"); +}); + +test("REJECTS the Cursor CLI passthrough token (same secret, iss omniroute / aud cursor-cli, no claim)", async () => { + const cursorToken = await sign({ name: "some-key" }, { iss: "omniroute", aud: "cursor-cli" }); + assert.equal(await verifyDashboardSessionToken(cursorToken), null); +}); + +test("rejects a verified token whose claim is missing or not strictly true", async () => { + assert.equal(await verifyDashboardSessionToken(await sign({ sub: "admin" })), null); + assert.equal(await verifyDashboardSessionToken(await sign({ authenticated: "true" })), null); + assert.equal(await verifyDashboardSessionToken(await sign({ authenticated: 1 })), null); +}); + +test("rejects expired, wrong-secret, garbage, empty and missing tokens without throwing", async () => { + assert.equal( + await verifyDashboardSessionToken(await sign({ authenticated: true }, { exp: "-1s" })), + null + ); + const other = new TextEncoder().encode("another-secret"); + const foreign = await new SignJWT({ authenticated: true }) + .setProtectedHeader({ alg: "HS256" }) + .setExpirationTime("1h") + .sign(other); + assert.equal(await verifyDashboardSessionToken(foreign), null); + assert.equal(await verifyDashboardSessionToken("not.a.jwt"), null); + assert.equal(await verifyDashboardSessionToken(""), null); + assert.equal(await verifyDashboardSessionToken(null), null); + assert.equal(await verifyDashboardSessionToken(undefined), null); +}); + +test("an explicit secret argument wins over the env; a null secret means no session", async () => { + const other = new TextEncoder().encode("explicit-secret"); + const tok = await new SignJWT({ authenticated: true }) + .setProtectedHeader({ alg: "HS256" }) + .setExpirationTime("1h") + .sign(other); + assert.ok(await verifyDashboardSessionToken(tok, other)); + assert.equal(await verifyDashboardSessionToken(tok), null); + assert.equal(await verifyDashboardSessionToken(await sign({ authenticated: true }), null), null); +}); + +test("isDashboardSessionAuthenticated(): login token → true, Cursor CLI token → false (route-level)", async () => { + const { isDashboardSessionAuthenticated } = await import("../../src/shared/utils/apiAuth.ts"); + const login = await sign({ authenticated: true }); + const cursor = await sign({ name: "k" }, { iss: "omniroute", aud: "cursor-cli" }); + const req = (cookie: string) => + new Request("http://localhost/api/settings", { headers: { cookie: `auth_token=${cookie}` } }); + assert.equal(await isDashboardSessionAuthenticated(req(login)), true); + assert.equal(await isDashboardSessionAuthenticated(req(cursor)), false); + assert.equal(await isDashboardSessionAuthenticated(req("garbage")), false); +}); diff --git a/tests/unit/dashboard-session-verifier-source-guard.test.ts b/tests/unit/dashboard-session-verifier-source-guard.test.ts new file mode 100644 index 0000000000..d91d444748 --- /dev/null +++ b/tests/unit/dashboard-session-verifier-source-guard.test.ts @@ -0,0 +1,37 @@ +/** + * #13298 source guard: every consumer of the `auth_token` cookie must verify it + * through verifyDashboardSessionToken (which requires `authenticated: true`). + * A bare jose `jwtVerify` (called or aliased) in one of these files re-opens the + * forgeable-session hole (Cursor CLI tokens share JWT_SECRET). + */ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import path from "node:path"; + +const ROOT = path.resolve(import.meta.dirname, "../.."); +const VERIFIERS = [ + "src/shared/utils/apiAuth.ts", + "src/server/authz/pipeline.ts", + "src/lib/ws/handshake.ts", + "src/server/ws/liveServer.ts", + "src/app/api/settings/require-login/route.ts", + "src/app/api/auth/status/route.ts", +]; + +for (const rel of VERIFIERS) { + test(`${rel} verifies auth_token only through verifyDashboardSessionToken`, () => { + const src = fs.readFileSync(path.join(ROOT, rel), "utf8"); + assert.match(src, /verifyDashboardSessionToken\s*\(/, "must call the shared verifier"); + assert.doesNotMatch(src, /\bjwtVerify\b/, "any bare jwtVerify is the #13298 regression"); + }); +} + +test("the helper itself is the only src file that calls jwtVerify on the dashboard cookie", () => { + const helper = fs.readFileSync( + path.join(ROOT, "src/shared/utils/dashboardSessionToken.ts"), + "utf8" + ); + assert.match(helper, /jwtVerify\(token, secret\)/); + assert.match(helper, /=== true/); +}); diff --git a/tests/unit/embeddings-route-apikeymeta-6929.test.ts b/tests/unit/embeddings-route-apikeymeta-6929.test.ts index d3e6862979..b6898311af 100644 --- a/tests/unit/embeddings-route-apikeymeta-6929.test.ts +++ b/tests/unit/embeddings-route-apikeymeta-6929.test.ts @@ -49,7 +49,7 @@ test.after(() => { async function sessionCookie(): Promise { const secret = new TextEncoder().encode(process.env.JWT_SECRET); - const jwt = await new SignJWT({ sub: "admin" }) + const jwt = await new SignJWT({ authenticated: true, sub: "admin" }) .setProtectedHeader({ alg: "HS256" }) .setExpirationTime("1h") .sign(secret); diff --git a/tests/unit/feature-flags-route-virtual-lanes.test.ts b/tests/unit/feature-flags-route-virtual-lanes.test.ts index 1332a9cc28..4428c3e203 100644 --- a/tests/unit/feature-flags-route-virtual-lanes.test.ts +++ b/tests/unit/feature-flags-route-virtual-lanes.test.ts @@ -37,7 +37,7 @@ type FlagPayload = { async function authCookie(): Promise { process.env.JWT_SECRET = "test-feature-flags-route-secret"; const secret = new TextEncoder().encode(process.env.JWT_SECRET); - const token = await new SignJWT({ sub: "test-user" }) + const token = await new SignJWT({ authenticated: true, sub: "test-user" }) .setProtectedHeader({ alg: "HS256" }) .setIssuedAt() .setExpirationTime("1h") diff --git a/tests/unit/guide-settings-route.test.ts b/tests/unit/guide-settings-route.test.ts index 8fcb89615f..a4c668bac4 100644 --- a/tests/unit/guide-settings-route.test.ts +++ b/tests/unit/guide-settings-route.test.ts @@ -21,7 +21,7 @@ const originalJwtSecret = process.env.JWT_SECRET; async function createAuthCookie() { process.env.JWT_SECRET = "test-cli-tools-secret"; const secret = new TextEncoder().encode(process.env.JWT_SECRET); - const token = await new SignJWT({ sub: "test-user" }) + const token = await new SignJWT({ authenticated: true, sub: "test-user" }) .setProtectedHeader({ alg: "HS256" }) .setIssuedAt() .setExpirationTime("1h") diff --git a/tests/unit/hermes-agent-settings-route-keyid-10711.test.ts b/tests/unit/hermes-agent-settings-route-keyid-10711.test.ts index 206fbde245..3071627e5f 100644 --- a/tests/unit/hermes-agent-settings-route-keyid-10711.test.ts +++ b/tests/unit/hermes-agent-settings-route-keyid-10711.test.ts @@ -45,7 +45,7 @@ test.after(() => { async function authCookie(): Promise { const secret = new TextEncoder().encode(process.env.JWT_SECRET); - const jwt = await new SignJWT({ sub: "admin" }) + const jwt = await new SignJWT({ authenticated: true, sub: "admin" }) .setProtectedHeader({ alg: "HS256" }) .setExpirationTime("1h") .sign(secret); diff --git a/tests/unit/image-generation-route-auth.test.ts b/tests/unit/image-generation-route-auth.test.ts index 03a2912832..1618c1c534 100644 --- a/tests/unit/image-generation-route-auth.test.ts +++ b/tests/unit/image-generation-route-auth.test.ts @@ -203,7 +203,7 @@ test("v1 image generation POST accepts a dashboard session when REQUIRE_API_KEY try { const { SignJWT } = await import("jose"); - const token = await new SignJWT({ sub: "dashboard" }) + const token = await new SignJWT({ authenticated: true, sub: "dashboard" }) .setProtectedHeader({ alg: "HS256" }) .setExpirationTime("1h") .sign(new TextEncoder().encode(process.env.JWT_SECRET)); diff --git a/tests/unit/playground-key-policy-3503.test.ts b/tests/unit/playground-key-policy-3503.test.ts index 67263d8444..fce4bb6446 100644 --- a/tests/unit/playground-key-policy-3503.test.ts +++ b/tests/unit/playground-key-policy-3503.test.ts @@ -32,7 +32,7 @@ const KEY_SECRET = created.key; async function sessionCookie(): Promise { const secret = new TextEncoder().encode(process.env.JWT_SECRET); - const jwt = await new SignJWT({ sub: "admin" }) + const jwt = await new SignJWT({ authenticated: true, sub: "admin" }) .setProtectedHeader({ alg: "HS256" }) .setExpirationTime("1h") .sign(secret); diff --git a/tests/unit/qwen-settings-route.test.ts b/tests/unit/qwen-settings-route.test.ts index 60f998e92d..62e1ccc856 100644 --- a/tests/unit/qwen-settings-route.test.ts +++ b/tests/unit/qwen-settings-route.test.ts @@ -19,7 +19,7 @@ const route = await import("../../src/app/api/cli-tools/qwen-settings/route.ts") const authCookie = async (): Promise => { process.env.JWT_SECRET = "qwen-settings-route-test-secret"; - const token = await new SignJWT({ sub: "qwen-route-test" }) + const token = await new SignJWT({ authenticated: true, sub: "qwen-route-test" }) .setProtectedHeader({ alg: "HS256" }) .setIssuedAt() .setExpirationTime("1h")