Files
keycloak-pattern/e2e/pattern1.mjs

102 lines
3.3 KiB
JavaScript

import assert from "node:assert/strict";
import { chromium } from "playwright-core";
const username = process.env.E2E_USERNAME ?? "regular-user";
const password = process.env.E2E_PASSWORD;
assert.ok(password, "E2E_PASSWORD must be set");
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();
let authorizationUrl;
page.on("request", (request) => {
if (request.url().includes("/protocol/openid-connect/auth")) {
authorizationUrl = new URL(request.url());
}
});
await page.goto("http://localhost:8088");
await page.locator("#login").click();
await page.waitForURL(/localhost:8080/u);
await page.locator("#username").fill(username);
await page.locator("#password").fill(password);
await page.locator("#kc-login").click();
await page.waitForURL("http://localhost:8088/");
await page.locator('[data-authenticated="true"]').waitFor();
assert.equal(authorizationUrl?.searchParams.get("response_type"), "code");
assert.equal(authorizationUrl?.searchParams.get("code_challenge_method"), "S256");
assert.ok(authorizationUrl?.searchParams.get("code_challenge"));
const accessToken = await page.evaluate(() => window.__pattern1.getAccessToken());
assert.ok(accessToken, "access token must exist in browser memory");
const payload = JSON.parse(
Buffer.from(accessToken.split(".")[1], "base64url").toString("utf8"),
);
const audiences = Array.isArray(payload.aud) ? payload.aud : [payload.aud];
assert.ok(
audiences.includes("keycloak-pattern-api"),
"access token must target keycloak-pattern-api",
);
const storageSnapshot = await page.evaluate(() => ({
localStorage: Object.values(localStorage),
sessionStorage: Object.values(sessionStorage),
}));
assert.equal(
JSON.stringify(storageSnapshot).includes(accessToken),
false,
"access token must not be persisted in Web Storage",
);
await page.locator("#call-api").click();
await page.waitForFunction(() => {
const text = document.querySelector("#result")?.textContent ?? "";
return text.includes('"httpStatus": 200');
});
if (process.env.WRONG_AUDIENCE_URL) {
const response = await fetch(process.env.WRONG_AUDIENCE_URL, {
headers: { Authorization: `Bearer ${accessToken}` },
});
assert.equal(
response.status,
401,
"the same signed token must fail when the Resource Server expects another audience",
);
}
if (process.env.WRONG_ISSUER_URL) {
const response = await fetch(process.env.WRONG_ISSUER_URL, {
headers: { Authorization: `Bearer ${accessToken}` },
});
assert.equal(
response.status,
401,
"the same signed token must fail when the Resource Server expects another issuer",
);
}
await page.reload();
await page.locator('[data-authenticated="false"]').waitFor();
assert.equal(
await page.evaluate(() => window.__pattern1.getAccessToken()),
null,
"reload must clear the memory-only token",
);
console.log(
"pattern1 browser verified: code+PKCE S256, audience/issuer negatives 401, Web Storage token 0, reload clears token",
);
} finally {
await browser.close();
}