import assert from "node:assert/strict"; import { chromium } from "playwright-core"; const baseUrl = 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 response = await fetch( `${baseUrl}/realms/master/protocol/openid-connect/token`, { method: "POST", body: new URLSearchParams({ client_id: "admin-cli", grant_type: "password", username: adminUsername, password: adminPassword, }), }, ); assert.equal(response.status, 200); return (await response.json()).access_token; } async function adminJson(token, path, init = {}) { const response = await fetch(`${baseUrl}/admin/realms/${path}`, { ...init, headers: { Authorization: `Bearer ${token}`, ...(init.body ? { "Content-Type": "application/json" } : {}), }, }); assert.ok(response.ok, `${init.method ?? "GET"} ${path}: ${response.status}`); return response.status === 204 ? undefined : response.json(); } async function users(token, realm, query) { return adminJson(token, `${realm}/users?${new URLSearchParams(query)}`); } async function brokerLogin(browser) { const page = await browser.newPage(); const url = new URL( `${baseUrl}/realms/keycloak-patterns/protocol/openid-connect/auth`, ); url.search = new URLSearchParams({ client_id: "spa-public", redirect_uri: "http://localhost:8088/", response_type: "code", scope: "openid profile email", state: crypto.randomUUID(), nonce: crypto.randomUUID(), code_challenge: "K2qUEfBl-nQvF2gB4dNxC2zYVwZc1CVnZb5CsX2L7fI", code_challenge_method: "S256", kc_idp_hint: "mock-google", prompt: "login", }); await page.goto(url.toString()); await page.waitForURL(/\/realms\/mock-google\//u); await page.locator("#username").fill("mock-new-user"); await page.locator("#password").fill(mockPassword); await page.locator("#kc-login").click(); await page.waitForURL(/localhost:8088\/\?.*code=/u); await page.close(); } const token = await adminToken(); const mockUsers = await users(token, "mock-google", { username: "mock-new-user", exact: "true", }); assert.equal(mockUsers.length, 1); const mockUser = await adminJson( token, `mock-google/users/${mockUsers[0].id}`, ); const originalEmail = mockUser.email; const changedEmail = "broker-renamed-user@example.test"; for (const existing of await users(token, "keycloak-patterns", { username: `mock-google.${mockUser.id}`, exact: "true", })) { await adminJson(token, `keycloak-patterns/users/${existing.id}`, { method: "DELETE", }); } const browser = await chromium.launch({ executablePath: process.env.CHROME_BIN ?? "/usr/bin/google-chrome", headless: true, args: ["--no-sandbox"], }); try { await brokerLogin(browser); const before = await users(token, "keycloak-patterns", { username: `mock-google.${mockUser.id}`, exact: "true", }); assert.equal(before.length, 1); const localUserId = before[0].id; const identities = await adminJson( token, `keycloak-patterns/users/${localUserId}/federated-identity`, ); assert.equal(identities[0].userId, mockUser.id); await adminJson(token, `mock-google/users/${mockUser.id}`, { method: "PUT", body: JSON.stringify({ ...mockUser, email: changedEmail }), }); await brokerLogin(browser); const after = await users(token, "keycloak-patterns", { username: `mock-google.${mockUser.id}`, exact: "true", }); assert.equal(after.length, 1); assert.equal(after[0].id, localUserId); console.log( "Federated identity verified: provider sub stayed linked while upstream email changed", ); } finally { await adminJson(token, `mock-google/users/${mockUser.id}`, { method: "PUT", body: JSON.stringify({ ...mockUser, email: originalEmail }), }); await browser.close(); }