From 5fc0078479b08616e6e84f1873717b0dedaf71e9 Mon Sep 17 00:00:00 2001 From: Nelson Carrasquel Date: Mon, 26 Aug 2024 12:23:50 -0400 Subject: [PATCH 1/5] saml and jwt methods separated --- src/slashdbclient.js | 70 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/src/slashdbclient.js b/src/slashdbclient.js index f59d886..0d2453d 100644 --- a/src/slashdbclient.js +++ b/src/slashdbclient.js @@ -194,8 +194,35 @@ class SlashDBClient { */ async loginSSO(popUp) { + const settings = await this.getSettings(); + const jwtSettings = settings.auth_settings.authentication_policies.jwt; + const samlSettings = settings.auth_settings.authentication_policies.saml; + + const idpId = this.sso.idpId; + + if (!jwtSettings.identity_providers.hasOwnProperty(idpId) && !samlSettings.identity_providers.hasOwnProperty(idpId)) { + throw new Error(SDB_SDBC_IDENTITY_PROVIDER_NOT_AVAILABLE); + } + popUp = popUp ? popUp : this.sso.popUp; + if (!jwtSettings.identity_providers.hasOwnProperty(idpId)) { + return this.loginJWT(popUp); + } + + if (!samlSettings.identity_providers.hasOwnProperty(idpId)) { + return this.loginSAML(popUp); + } + + } + + /** + * Logs in to SlashDB instance with JWT token. Only required when using SSO. + * @param {boolean} popUp - optional flag to sign in against the identity provider with a Pop Up window (false by default) + */ + + async loginJWT(popUp) { + const ssoConfig = await this._getSsoConfig(); const pkce = new PKCE(ssoConfig); const additionalParams = await this._buildSession(); @@ -235,6 +262,47 @@ class SlashDBClient { }); } + /** + * Logs in to SlashDB instance with SAML token. Only required when using SSO. + * @param {boolean} popUp - optional flag to sign in against the identity provider with a Pop Up window (false by default) + */ + + async loginSAML(popUp){ + + const sso = this.sso; + const ref = sso.redirectUri; + const idpId = sso.idpId; + + let path = `/sso/saml/${idpId}?return_to=${ref}`; + let url = this.sdbConfig._buildEndpointString(path); + + if (!popUp) { + window.location.replace(url); + } + + const width = 500; + const height = 600; + + const popupWindow = popupCenter(loginUrl, "login", width, height); + + return new Promise((resolve, reject) => { + const checkPopup = setInterval(() => { + let popUpHref = ""; + try { + popUpHref = popupWindow.window.location.href; + } catch (e) { + console.warn(e); + } + if (popUpHref.startsWith(window.location.origin)) { + popupWindow.close(); + } + if (!popupWindow || !popupWindow.closed) return; + clearInterval(checkPopup); + resolve(true); + }, 250); + }); + } + /** * Refreshes the SSO access token. */ @@ -445,7 +513,7 @@ class SlashDBClient { let idpId = this.sso.idpId; let redirectUri = this.sso.redirectUri; - const jwtSettings = response.auth_settings.authentication_policies.jwt + const jwtSettings = response.auth_settings.authentication_policies.jwt; if (!jwtSettings.identity_providers.hasOwnProperty(this.sso.idpId)) { throw new Error(SDB_SDBC_IDENTITY_PROVIDER_NOT_AVAILABLE); From dd79f28403949b9a7a622065321899695e6e4174 Mon Sep 17 00:00:00 2001 From: Nelson Carrasquel Date: Mon, 26 Aug 2024 14:40:50 -0400 Subject: [PATCH 2/5] variables renamed --- src/slashdbclient.js | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/slashdbclient.js b/src/slashdbclient.js index 0d2453d..9787a11 100644 --- a/src/slashdbclient.js +++ b/src/slashdbclient.js @@ -173,9 +173,9 @@ class SlashDBClient { const urlParams = getUrlParms(); if (isSSOredirect(urlParams)){ - const ssoConfig = await this._getSsoConfig(); + const jwtConfig = await this._getJWTConfig(); const url = window.location.href; - const pkce = new PKCE(ssoConfig); + const pkce = new PKCE(jwtConfig); this.sso.idpId = sessionStorage.getItem('ssoApp.idp_id'); pkce.codeVerifier = sessionStorage.getItem('ssoApp.code_verifier'); @@ -206,11 +206,11 @@ class SlashDBClient { popUp = popUp ? popUp : this.sso.popUp; - if (!jwtSettings.identity_providers.hasOwnProperty(idpId)) { + if (jwtSettings.identity_providers.hasOwnProperty(idpId)) { return this.loginJWT(popUp); } - if (!samlSettings.identity_providers.hasOwnProperty(idpId)) { + if (samlSettings.identity_providers.hasOwnProperty(idpId)) { return this.loginSAML(popUp); } @@ -223,8 +223,8 @@ class SlashDBClient { async loginJWT(popUp) { - const ssoConfig = await this._getSsoConfig(); - const pkce = new PKCE(ssoConfig); + const jwtConfig = await this._getJWTConfig(); + const pkce = new PKCE(jwtConfig); const additionalParams = await this._buildSession(); let loginUrl = await pkce.authorizeUrl(additionalParams); @@ -240,7 +240,7 @@ class SlashDBClient { return new Promise((resolve, reject) => { const checkPopup = setInterval(() => { - const pkce = new PKCE(ssoConfig); + const pkce = new PKCE(jwtConfig); let popUpHref = ""; try { popUpHref = popupWindow.window.location.href; @@ -274,10 +274,10 @@ class SlashDBClient { const idpId = sso.idpId; let path = `/sso/saml/${idpId}?return_to=${ref}`; - let url = this.sdbConfig._buildEndpointString(path); + let loginUrl = this.sdbConfig._buildEndpointString(path); if (!popUp) { - window.location.replace(url); + window.location.replace(loginUrl); } const width = 500; @@ -308,8 +308,8 @@ class SlashDBClient { */ async refreshSSOToken(){ - const ssoConfig = await this._getSsoConfig(); - const pkce = new PKCE(ssoConfig); + const jwtConfig = await this._getJWTConfig(); + const pkce = new PKCE(jwtConfig); const refreshToken = this.ssoCredentials.refresh_token; return new Promise((resolve, reject) => { @@ -508,7 +508,7 @@ class SlashDBClient { return queries; } - async _getSsoConfig() { + async _getJWTConfig() { let response = (await this.sdbConfig.get(this.settingsEP)).data; let idpId = this.sso.idpId; let redirectUri = this.sso.redirectUri; @@ -530,7 +530,7 @@ class SlashDBClient { redirectUri = idpSettings.redirect_uri; } - const ssoConfig = { + const jwtConfig = { idp_id: idpId, client_id: clientId, redirect_uri: redirectUri, @@ -539,7 +539,7 @@ class SlashDBClient { requested_scopes: requestedScopes, } - return ssoConfig; + return jwtConfig; } async _buildSession() { From c062ff8216b8f21a7b6f76ab09da0b82facf2e08 Mon Sep 17 00:00:00 2001 From: Nelson Carrasquel Date: Mon, 26 Aug 2024 14:52:04 -0400 Subject: [PATCH 3/5] ssoCredentials renamed to jwtCredentials --- src/baserequesthandler.js | 4 ++-- src/slashdbclient.js | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/baserequesthandler.js b/src/baserequesthandler.js index 0612ca1..a6c2ff4 100644 --- a/src/baserequesthandler.js +++ b/src/baserequesthandler.js @@ -134,8 +134,8 @@ class BaseRequestHandler { 'Content-Type': this.contentTypeHeader, ...this.extraHeaders }; - } else if (this.sdbClient.ssoCredentials) { - const token = btoa(this.sdbClient.ssoCredentials.id_token) + } else if (this.sdbClient.jwtCredentials) { + const token = btoa(this.sdbClient.jwtCredentials.id_token) headers = { Authorization: "Bearer " + token, "X-Identity-Provider-Id": this.sdbClient.sso.idpId, diff --git a/src/slashdbclient.js b/src/slashdbclient.js index 9787a11..a512787 100644 --- a/src/slashdbclient.js +++ b/src/slashdbclient.js @@ -79,7 +79,7 @@ class SlashDBClient { this.sso.popUp = popUp; } - this.ssoCredentials = null; + this.jwtCredentials = null; // create the special case BaseRequestHandler object for interacting with config endpoints this.sdbConfig = new BaseRequestHandler(this); @@ -139,7 +139,7 @@ class SlashDBClient { } } else if (sso.idpId && sso.redirectUri) { await this.loginSSO(sso.popUp).then((resp) => { - this.ssoCredentials = resp; + this.jwtCredentials = resp; }); let settings = (await this.sdbConfig.get(this.settingsEP)).data; this.username = settings.user; @@ -181,7 +181,7 @@ class SlashDBClient { return new Promise((resolve, reject) => { pkce.exchangeForAccessToken(url).then((resp) => { - this.ssoCredentials = resp; + this.jwtCredentials = resp; resolve(true); }); }); @@ -310,11 +310,11 @@ class SlashDBClient { const jwtConfig = await this._getJWTConfig(); const pkce = new PKCE(jwtConfig); - const refreshToken = this.ssoCredentials.refresh_token; + const refreshToken = this.jwtCredentials.refresh_token; return new Promise((resolve, reject) => { pkce.refreshAccessToken(refreshToken).then((resp) => { - this.ssoCredentials = resp; + this.jwtCredentials = resp; resolve(true); }); }); @@ -348,7 +348,7 @@ class SlashDBClient { async logout() { try { await this.sdbConfig.get(this.logoutEP); - this.ssoCredentials = null; + this.jwtCredentials = null; this._clearSession(); } catch(e) { From a360b3144871bcc07316a2f2fcd92db2f9022e08 Mon Sep 17 00:00:00 2001 From: Nelson Carrasquel Date: Mon, 26 Aug 2024 21:52:34 -0400 Subject: [PATCH 4/5] redirect handling improved --- examples/login_demo.html | 6 +- examples/success.html | 134 ++++++++++++++++++++++++++++++++++++++- src/slashdbclient.js | 11 +++- src/utils.js | 12 ++-- 4 files changed, 150 insertions(+), 13 deletions(-) diff --git a/examples/login_demo.html b/examples/login_demo.html index fe657f6..73015b7 100644 --- a/examples/login_demo.html +++ b/examples/login_demo.html @@ -142,7 +142,7 @@ sso: { idpId: idpId, redirectUri: redirectUri, - popUp: true + popUp: false } } @@ -242,7 +242,7 @@

SDK - SSO Login Demo

- +
@@ -296,7 +296,7 @@

SDK - SSO Login Demo

const sdbClient = new SlashDBClient(sdbConfig); let status = await sdbClient.login(); - +
diff --git a/examples/success.html b/examples/success.html index 54c195f..7df7b63 100644 --- a/examples/success.html +++ b/examples/success.html @@ -1,13 +1,145 @@ SlashDB JavaScript SDK Demo + +

Login was successful, you can close this window...

-
+
+ +
+ SlashDB Single Sign-On redirect page
+ Requires a proper build of redirect parameters +
        const sdbConfig = {
+            host: 'http://localhost:6543',
+            sso: {
+                idpId: 'keycloak',
+                redirectUri: 'http://localhost:8081/examples/success.html',
+                popUp: true
+            }
+        }
+        const sdbClient = new SlashDBClient(sdbConfig);
+        const success = await sdbClient.buildSSORedirect();
+        const isAuthenticated = await sdbClient.isAuthenticated();
+        
+ +
diff --git a/src/slashdbclient.js b/src/slashdbclient.js index a512787..75899c8 100644 --- a/src/slashdbclient.js +++ b/src/slashdbclient.js @@ -2,7 +2,7 @@ import { DataDiscoveryDatabase } from './datadiscovery.js' import { SQLPassThruQuery } from './sqlpassthru.js' import { BaseRequestHandler } from './baserequesthandler.js'; import { PKCE, generateCodeVerifier, generateCodeChallenge } from './pkce.js'; -import { getUrlParms, isSSOredirect, popupCenter } from "./utils.js"; +import { getUrlParms, isJWTredirect, popupCenter } from "./utils.js"; const SDB_SDBC_INVALID_HOSTNAME = 'Invalid hostname parameter, must be string'; const SDB_SDBC_INVALID_USERNAME = 'Invalid username parameter, must be string'; @@ -172,7 +172,7 @@ class SlashDBClient { async buildSSORedirect(){ const urlParams = getUrlParms(); - if (isSSOredirect(urlParams)){ + if (isJWTredirect(urlParams)){ const jwtConfig = await this._getJWTConfig(); const url = window.location.href; const pkce = new PKCE(jwtConfig); @@ -231,6 +231,7 @@ class SlashDBClient { if (!popUp) { window.location.replace(loginUrl); + return; } const width = 500; @@ -278,6 +279,7 @@ class SlashDBClient { if (!popUp) { window.location.replace(loginUrl); + return; } const width = 500; @@ -326,7 +328,10 @@ class SlashDBClient { * @returns {boolean} boolean - to indicate if currently authenticated */ async isAuthenticated() { - const url = `${this.userEP}/${this.username}.json`; + + const settings = await this.getSettings(); + const username = settings.user; + const url = `${this.userEP}/${username}.json`; try { let response = (await this.sdbConfig.get(url)).res diff --git a/src/utils.js b/src/utils.js index 95d000b..36eea27 100644 --- a/src/utils.js +++ b/src/utils.js @@ -42,15 +42,15 @@ function isObjectEmpty(objectName) { return Object.keys(objectName).length === 0 } -function isSSOredirect(ssoParams) { +function isJWTredirect(jwtParams) { - if (isObjectEmpty(ssoParams)){ + if (isObjectEmpty(jwtParams)){ return false; } - const state = ssoParams.state; - const sessionState = ssoParams.session_state; - const code = ssoParams.code; + const state = jwtParams.state; + const sessionState = jwtParams.session_state; + const code = jwtParams.code; if (!state || typeof(state) !== 'string') { return false @@ -68,4 +68,4 @@ function isSSOredirect(ssoParams) { } -export { getUrlParms, popupCenter, isSSOredirect } \ No newline at end of file +export { getUrlParms, popupCenter, isJWTredirect } \ No newline at end of file From 5a0592251c836fb103027d411f16b4260853fa12 Mon Sep 17 00:00:00 2001 From: Nelson Carrasquel Date: Mon, 26 Aug 2024 22:01:23 -0400 Subject: [PATCH 5/5] redirect handling improved for popup --- examples/login_demo.html | 4 ++-- examples/popup_success.html | 13 +++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 examples/popup_success.html diff --git a/examples/login_demo.html b/examples/login_demo.html index 73015b7..3dc876a 100644 --- a/examples/login_demo.html +++ b/examples/login_demo.html @@ -142,7 +142,7 @@ sso: { idpId: idpId, redirectUri: redirectUri, - popUp: false + popUp: true } } @@ -246,7 +246,7 @@

SDK - SSO Login Demo

- +
diff --git a/examples/popup_success.html b/examples/popup_success.html new file mode 100644 index 0000000..85fe791 --- /dev/null +++ b/examples/popup_success.html @@ -0,0 +1,13 @@ + + + SlashDB JavaScript SDK Demo + + + +
+

Login was successful, you can close this window...

+
+ + + + \ No newline at end of file