133 lines
4.0 KiB
JavaScript
133 lines
4.0 KiB
JavaScript
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();
|
|
}
|