91 lines
3.0 KiB
JavaScript
91 lines
3.0 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",
|
|
);
|
|
}
|
|
|
|
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 positive 200/negative 401, Web Storage token 0, reload clears token",
|
|
);
|
|
} finally {
|
|
await browser.close();
|
|
}
|