import assert from "node:assert/strict"; import { chromium } from "playwright-core"; const expectation = process.env.FIRST_BROKER_EXPECTATION; assert.ok( expectation === "vulnerable" || expectation === "secure", "FIRST_BROKER_EXPECTATION must be vulnerable or secure", ); const keycloakUrl = process.env.KEYCLOAK_URL ?? "http://localhost:8080"; const adminUsername = process.env.KC_BOOTSTRAP_ADMIN_USERNAME; const adminPassword = process.env.KC_BOOTSTRAP_ADMIN_PASSWORD; const mockPassword = process.env.MOCK_GOOGLE_USER_PASSWORD; assert.ok(adminUsername && adminPassword && mockPassword); async function adminToken() { const body = new URLSearchParams({ client_id: "admin-cli", grant_type: "password", username: adminUsername, password: adminPassword, }); const response = await fetch( `${keycloakUrl}/realms/master/protocol/openid-connect/token`, { method: "POST", body }, ); assert.equal(response.status, 200); return (await response.json()).access_token; } async function regularUser(token) { const response = await fetch( `${keycloakUrl}/admin/realms/keycloak-patterns/users?username=regular-user&exact=true`, { headers: { Authorization: `Bearer ${token}` } }, ); assert.equal(response.status, 200); const users = await response.json(); assert.equal(users.length, 1); return users[0]; } async function federatedIdentities(token, userId) { const response = await fetch( `${keycloakUrl}/admin/realms/keycloak-patterns/users/${userId}/federated-identity`, { headers: { Authorization: `Bearer ${token}` } }, ); assert.equal(response.status, 200); return response.json(); } async function removeMockLink(token, userId) { const identities = await federatedIdentities(token, userId); if (identities.some(({ identityProvider }) => identityProvider === "mock-google")) { const response = await fetch( `${keycloakUrl}/admin/realms/keycloak-patterns/users/${userId}/federated-identity/mock-google`, { method: "DELETE", headers: { Authorization: `Bearer ${token}` }, }, ); assert.equal(response.status, 204); } } const token = await adminToken(); const user = await regularUser(token); await removeMockLink(token, user.id); 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 authorizationUrl = new URL( `${keycloakUrl}/realms/keycloak-patterns/protocol/openid-connect/auth`, ); authorizationUrl.search = new URLSearchParams({ client_id: "spa-public", redirect_uri: "http://localhost:8088/", response_type: "code", scope: "openid profile email", state: `first-broker-${expectation}`, nonce: `nonce-${expectation}`, code_challenge: "K2qUEfBl-nQvF2gB4dNxC2zYVwZc1CVnZb5CsX2L7fI", code_challenge_method: "S256", kc_idp_hint: "mock-google", }); await page.goto(authorizationUrl.toString()); await page.waitForURL(/\/realms\/mock-google\//u); await page.locator("#username").fill("mock-collision-user"); await page.locator("#password").fill(mockPassword); await page.locator("#kc-login").click(); await page.waitForLoadState("domcontentloaded"); if (expectation === "vulnerable") { await page.waitForURL(/localhost:8088\/\?.*code=/u); const identities = await federatedIdentities(token, user.id); assert.equal( identities.some(({ identityProvider }) => identityProvider === "mock-google"), true, "unsafe AutoLink should attach the attacker-controlled identity", ); await removeMockLink(token, user.id); } else { assert.match(page.url(), /\/realms\/keycloak-patterns\//u); const body = (await page.locator("body").innerText()).toLowerCase(); assert.match(body, /account already exists|link existing account|existing account/u); const identities = await federatedIdentities(token, user.id); assert.equal( identities.some(({ identityProvider }) => identityProvider === "mock-google"), false, "Confirm Link must not attach the identity without ownership proof", ); } console.log(`first broker login ${expectation} case verified`); } finally { await browser.close(); }