From 86d93a153b811e2514b8a531adf3b75c41cfd061 Mon Sep 17 00:00:00 2001
From: Christoph Reiter <reiter.christoph@gmail.com>
Date: Tue, 19 Nov 2019 15:11:25 +0100
Subject: [PATCH] Support redirect-less login in case the user is already
 logged in.

The newest keycloak library supporst a mode for doing the redirect in an iframe
with a separate page that needs to be whitelisted in keycloak.

The newer version also finally fixes the native promise bugs so we might as well
use the one from npm instead of fetching it from the keycloak server at runtime.
---
 packages/auth/assets/silent-check-sso.html |  7 ++++
 packages/auth/package.json                 |  1 +
 packages/auth/rollup.config.js             |  8 ++++
 packages/auth/src/keycloak.js              | 48 +++++++---------------
 packages/auth/src/vpu-auth-demo.js         |  3 +-
 packages/auth/src/vpu-auth.js              |  3 +-
 6 files changed, 35 insertions(+), 35 deletions(-)
 create mode 100644 packages/auth/assets/silent-check-sso.html

diff --git a/packages/auth/assets/silent-check-sso.html b/packages/auth/assets/silent-check-sso.html
new file mode 100644
index 00000000..94fe2268
--- /dev/null
+++ b/packages/auth/assets/silent-check-sso.html
@@ -0,0 +1,7 @@
+<html>
+<body>
+    <script>
+        parent.postMessage(location.href, location.origin)
+    </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/packages/auth/package.json b/packages/auth/package.json
index b0714cce..7ce23467 100644
--- a/packages/auth/package.json
+++ b/packages/auth/package.json
@@ -28,6 +28,7 @@
     "vpu-common": "file:./vendor/common"
   },
   "dependencies": {
+    "keycloak-js": "^8.0.0",
     "lit-element": "^2.1.0"
   },
   "scripts": {
diff --git a/packages/auth/rollup.config.js b/packages/auth/rollup.config.js
index 37a3abb0..549369f1 100644
--- a/packages/auth/rollup.config.js
+++ b/packages/auth/rollup.config.js
@@ -22,6 +22,13 @@ export default {
         format: 'esm',
         sourcemap: true
     },
+    onwarn: function (warning, warn) {
+        // keycloak bundled code uses eval
+        if (warning.code === 'EVAL') {
+            return;
+        }
+        warn(warning);
+    },
     plugins: [
         del({
             targets: 'dist/*'
@@ -43,6 +50,7 @@ export default {
         copy({
             targets: [
                 {src: 'assets/index.html', dest:'dist'},
+                {src: 'assets/silent-check-sso.html', dest:'dist'},
                 {src: 'assets/favicon.ico', dest:'dist'},
                 {src: 'node_modules/vpu-common/assets/icons/*.svg', dest: 'dist/local/vpu-common/icons'},
             ]
diff --git a/packages/auth/src/keycloak.js b/packages/auth/src/keycloak.js
index 616c4ed8..98bc0cf9 100644
--- a/packages/auth/src/keycloak.js
+++ b/packages/auth/src/keycloak.js
@@ -1,29 +1,3 @@
-/**
- * Imports the keycloak JS API as if it was a module.
- *
- * @param baseUrl {string}
- */
-async function importKeycloak(baseUrl) {
-    const keycloakSrc = baseUrl + '/js/keycloak.js';
-    // Importing will write it to window so we take it from there
-    await import(keycloakSrc);
-    if (importKeycloak._keycloakMod !== undefined)
-        return importKeycloak._keycloakMod;
-    importKeycloak._keycloakMod = {Keycloak: window.Keycloak};
-    delete window.Keycloak;
-    return importKeycloak._keycloakMod;
-}
-
-
-async function kcMakeAsync(promise) {
-    // the native keycloak promise implementation is broken, wrap it instead
-    // https://stackoverflow.com/questions/58436689/react-keycloak-typeerror-kc-updatetoken-success-is-not-a-function
-    return new Promise(function(resolve, reject) {
-        promise.success((...args) => { resolve(...args); }).error((...args) => { reject(...args); });
-    });
-}
-
-
 /**
  * Wraps the keycloak API to support async/await, adds auto token refreshing and consolidates all
  * events into one native "changed" event
@@ -32,7 +6,7 @@ async function kcMakeAsync(promise) {
  */
 export class KeycloakWrapper extends EventTarget {
 
-    constructor(baseURL, realm, clientId) {
+    constructor(baseURL, realm, clientId, silentCheckSsoUri) {
         super();
 
         this._baseURL = baseURL;
@@ -40,6 +14,7 @@ export class KeycloakWrapper extends EventTarget {
         this._clientId = clientId;
         this._keycloak = null;
         this._initDone = false;
+        this._silentCheckSsoUri = silentCheckSsoUri;
     }
 
     _onChanged() {
@@ -62,7 +37,7 @@ export class KeycloakWrapper extends EventTarget {
         let refreshed = false;
 
         try {
-            refreshed = await kcMakeAsync(this._keycloak.updateToken(5));
+            refreshed = await this._keycloak.updateToken(5);
         } catch (error) {
             console.log('Failed to refresh the token', error);
             return;
@@ -79,9 +54,9 @@ export class KeycloakWrapper extends EventTarget {
         if (this._keycloak !== null)
             return;
 
-        const module = await importKeycloak(this._baseURL);
+        const Keycloak = await import('keycloak-js').then((mod) => { return mod.default; });
 
-        this._keycloak = module.Keycloak({
+        this._keycloak = Keycloak({
             url: this._baseURL,
             realm: this._realm,
             clientId: this._clientId,
@@ -101,7 +76,14 @@ export class KeycloakWrapper extends EventTarget {
         if (this._initDone)
             return;
         this._initDone = true;
-        await kcMakeAsync(this._keycloak.init());
+
+        const options = {promiseType: 'native'};
+        if (this._silentCheckSsoUri) {
+            options['onLoad'] = 'check-sso';
+            options['silentCheckSsoRedirectUri'] = this._silentCheckSsoUri;
+        }
+
+        await this._keycloak.init(options);
     }
 
     /**
@@ -119,9 +101,9 @@ export class KeycloakWrapper extends EventTarget {
         const language = options['lang'] || 'en';
 
         if (!this._keycloak.authenticated) {
-            await kcMakeAsync(this._keycloak.login({
+            await this._keycloak.login({
                 kcLocale: language,
-            }));
+            });
         }
     }
 
diff --git a/packages/auth/src/vpu-auth-demo.js b/packages/auth/src/vpu-auth-demo.js
index a4ed4482..d6e59e6b 100644
--- a/packages/auth/src/vpu-auth-demo.js
+++ b/packages/auth/src/vpu-auth-demo.js
@@ -27,6 +27,7 @@ class AuthDemo extends LitElement {
 
     render() {
         commonUtils.initAssetBaseURL('vpu-auth-src');
+        const silentCheckSsoUri = commonUtils.getAssetURL('silent-check-sso.html');
         return html`
             <style>
                /* from BULMA.CSS */
@@ -49,7 +50,7 @@ class AuthDemo extends LitElement {
                     <h1 class="title">Auth-Demo</h1>
                 </div>
                 <div class="container">
-                    <vpu-auth lang="${this.lang}" client-id="${commonUtils.setting('keyCloakClientId')}" load-person remember-login></vpu-auth>
+                    <vpu-auth lang="${this.lang}" client-id="${commonUtils.setting('keyCloakClientId')}" silent-check-sso-uri="${silentCheckSsoUri}" load-person remember-login></vpu-auth>
                 </div>
             </section>
         `;
diff --git a/packages/auth/src/vpu-auth.js b/packages/auth/src/vpu-auth.js
index e0e099f9..6479ab57 100644
--- a/packages/auth/src/vpu-auth.js
+++ b/packages/auth/src/vpu-auth.js
@@ -146,6 +146,7 @@ class VPUAuth extends VPULitElement {
             rememberLogin: { type: Boolean, attribute: 'remember-login' },
             loadPerson: { type: Boolean, attribute: 'load-person' },
             clientId: { type: String, attribute: 'client-id' },
+            silentCheckSsoUri: { type: String, attribute: 'silent-check-sso-uri' },
             name: { type: String, attribute: false },
             token: { type: String, attribute: false },
             subject: { type: String, attribute: false },
@@ -160,7 +161,7 @@ class VPUAuth extends VPULitElement {
 
         const baseURL = commonUtils.setting('keyCloakBaseURL');
         const realm = commonUtils.setting('keyCloakRealm');
-        this._kcwrapper = new KeycloakWrapper(baseURL, realm, this.clientId);
+        this._kcwrapper = new KeycloakWrapper(baseURL, realm, this.clientId, this.silentCheckSsoUri);
         this._kcwrapper.addEventListener('changed', this._onKCChanged);
 
 
-- 
GitLab