From 5153f303091fdecd4bc7ed5cc52696b75889c09b Mon Sep 17 00:00:00 2001 From: Kevin Veen-Birkenbach Date: Mon, 10 Aug 2026 20:54:14 +0200 Subject: [PATCH] fix(dashboard): load keycloak-js 26 as an ES module keycloak-js dropped the UMD build in 26.0.0. The package now ships only lib/keycloak.js with "type": "module" and a default export, so the CDN path dist/keycloak.js resolves to a file that no longer exists, and a classic script tag could not parse the file that does. The adapter never loaded, window.__oidcLoginReady was never set, and every app job that co-deploys the dashboard failed its Playwright run on the resulting console error. The loader now uses a dynamic import and takes the constructor from the module's default export. That also retires the window.Keycloak check, which had nothing left to check once the global stopped existing. Module fetches always run in CORS mode, unlike classic script loads. The internal CDN emitted no Access-Control-Allow-Origin at all, so it could not have served an ES module to the dashboard's origin. jsdelivr sends "*", which is why the external flavor would have hidden the gap. Every init option the template passes carries the same default in 24.0.5 and 26.2.4, and the silent-SSO postMessage contract is unchanged, so the adapter's behaviour is untouched beyond the packaging. Co-Authored-By: Claude Opus 5 (1M context) --- .../templates/javascript/oidc.js.j2 | 24 +++++++------------ roles/web-svc-cdn/tasks/00_core.yml | 2 ++ tests/unit/plugins/lookup/test_asset.py | 2 +- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/roles/web-app-dashboard/templates/javascript/oidc.js.j2 b/roles/web-app-dashboard/templates/javascript/oidc.js.j2 index f720f7627d..1c3be75bb5 100644 --- a/roles/web-app-dashboard/templates/javascript/oidc.js.j2 +++ b/roles/web-app-dashboard/templates/javascript/oidc.js.j2 @@ -63,14 +63,12 @@ function setItemVisible(el, visible) { /* ============================================== 3) Dynamically load the Keycloak.js adapter ============================================== */ -function loadKeycloakAdapter(src) { - return new Promise((resolve, reject) => { - const s = document.createElement("script"); - s.src = src; - s.onload = resolve; - s.onerror = () => reject(new Error("Failed to load keycloak.js from " + src)); - document.head.appendChild(s); - }); +async function loadKeycloakAdapter(src) { + const mod = await import(src); + if (typeof mod.default !== "function") { + throw new Error("keycloak.js exposed no default export at " + src); + } + return mod.default; } /* ============================================== @@ -137,19 +135,15 @@ async function initAuthUI() { syncAuthMenuVisibility(false); // Load keycloak.js - const kcJsUrl = "{{ lookup('asset', application_id, 'keycloak-js', 'dist/keycloak.js') }}"; + const kcJsUrl = "{{ lookup('asset', application_id, 'keycloak-js', 'lib/keycloak.js') }}"; + let Keycloak; try { - await loadKeycloakAdapter(kcJsUrl); + Keycloak = await loadKeycloakAdapter(kcJsUrl); } catch (e) { console.error("[oidc] Failed to load adapter:", e); return; // nothing else to do } - if (typeof window.Keycloak !== "function") { - console.error("[oidc] window.Keycloak is not available after loading:", kcJsUrl); - return; - } - // Initialize the Keycloak instance try { keycloak = new Keycloak({ url: KC_CONFIG.url, realm: KC_CONFIG.realm, clientId: KC_CONFIG.clientId }); diff --git a/roles/web-svc-cdn/tasks/00_core.yml b/roles/web-svc-cdn/tasks/00_core.yml index 09b0f66cff..b0fda6da31 100644 --- a/roles/web-svc-cdn/tasks/00_core.yml +++ b/roles/web-svc-cdn/tasks/00_core.yml @@ -11,6 +11,8 @@ - name: "🧩 Deploy the CDN stack (one-shot npm-mirror build) + front proxy" include_role: name: sys-stk-full + vars: + aca_origin: "* always" - name: "🧩 Front proxy only (external CDN flavor)" include_role: diff --git a/tests/unit/plugins/lookup/test_asset.py b/tests/unit/plugins/lookup/test_asset.py index 1220c9464a..b3e426afc9 100644 --- a/tests/unit/plugins/lookup/test_asset.py +++ b/tests/unit/plugins/lookup/test_asset.py @@ -202,7 +202,7 @@ class TestCommittedRoleLockfiles(unittest.TestCase): return lookup.run([app, package, path], variables={"group_names": []})[0] def test_dashboard_keycloak_js(self): - url = self._url("web-app-dashboard", "keycloak-js", "dist/keycloak.js") + url = self._url("web-app-dashboard", "keycloak-js", "lib/keycloak.js") self.assertRegex(url, self.URL_RE) def test_littlejs_bootstrap(self):