import assert from "node:assert/strict"; import { chromium } from "playwright-core"; const password = process.env.E2E_PASSWORD; assert.ok(password, "E2E_PASSWORD must be set"); async function completeKeycloakLogin(page) { for (let attempt = 1; attempt <= 2; attempt += 1) { await page.locator("#username").fill( process.env.E2E_USERNAME ?? "regular-user", ); await page.locator("#password").fill(password); await page.locator("#kc-login").click(); await page.waitForLoadState("domcontentloaded"); if (page.url() === "http://localhost:4180/edge/me") { return; } if (attempt === 1) { await page.goto("http://localhost:4180/oauth2/start?rd=%2Fedge%2Fme"); await page.waitForURL(/localhost:8080/u); } } throw new Error(`Keycloak login did not return to AP4: ${page.url()}`); } const browser = await chromium.launch({ executablePath: process.env.CHROME_BIN ?? "/usr/bin/google-chrome", headless: true, args: ["--no-sandbox"], }); try { const context = await browser.newContext(); const page = await context.newPage(); const browserRequests = []; page.on("request", (request) => browserRequests.push({ method: request.method(), url: request.url() }), ); const edgeResponsePromise = page.waitForResponse( (response) => response.url() === "http://localhost:4180/edge/me" && response.status() === 302, ); const authorizationRequestPromise = page.waitForRequest((request) => request.url().includes( "/protocol/openid-connect/auth?approval_prompt=", ), ); await page.goto("http://localhost:4180/edge/me"); const unauthenticatedEdgeResponse = await edgeResponsePromise; assert.equal(unauthenticatedEdgeResponse.status(), 302); const authorizationRequest = await authorizationRequestPromise; const authorizationUrl = new URL(authorizationRequest.url()); assert.equal(authorizationUrl.searchParams.get("client_id"), "edge-proxy"); assert.equal(authorizationUrl.searchParams.get("code_challenge_method"), "S256"); assert.ok(authorizationUrl.searchParams.get("code_challenge")); await page.waitForURL(/localhost:8080/u); await completeKeycloakLogin(page); const edgeIdentity = JSON.parse(await page.locator("body").innerText()); assert.equal(edgeIdentity.pattern, "AP4-edge-forward-auth"); assert.ok(edgeIdentity.user); assert.equal(edgeIdentity.identityHeader, "X-Forwarded-User"); const callbackRequest = browserRequests.find(({ url }) => url.startsWith("http://localhost:4180/oauth2/callback?"), ); assert.ok(callbackRequest); assert.equal(callbackRequest.method, "GET"); assert.equal( browserRequests.some(({ url }) => url.includes("/protocol/openid-connect/token"), ), false, "the confidential token exchange must be server-to-server", ); const cookies = await context.cookies("http://localhost:4180/"); const sessionCookie = cookies.find((cookie) => cookie.name === "AP4_SESSION"); assert.ok(sessionCookie); assert.equal(sessionCookie.httpOnly, true); assert.equal(sessionCookie.sameSite, "Lax"); assert.equal(sessionCookie.secure, false); const storage = await page.evaluate(() => ({ localStorage: Object.values(localStorage), sessionStorage: Object.values(sessionStorage), readableCookies: document.cookie, })); assert.deepEqual(storage.localStorage, []); assert.deepEqual(storage.sessionStorage, []); assert.equal(storage.readableCookies.includes("AP4_SESSION"), false); const missingHeader = await fetch("http://localhost:8081/edge/me"); assert.equal(missingHeader.status, 401); const directSpoof = await fetch("http://localhost:8081/edge/me", { headers: { "X-Forwarded-User": "spoofed-admin" }, }); assert.equal(directSpoof.status, 200); const spoofedIdentity = await directSpoof.json(); assert.equal(spoofedIdentity.user, "spoofed-admin"); console.log( "pattern4 oauth2-proxy verified: redirect, PKCE login, forwarded-user 200, direct spoof precondition", ); } finally { await browser.close(); }