UNPKG

@thoughtspot/visual-embed-sdk

Version:
457 lines 17.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.isAuthenticated = exports.authenticate = exports.logout = exports.doOIDCAuth = exports.doSamlAuth = exports.doBasicAuth = exports.doCookielessTokenAuth = exports.doTokenAuth = exports.getReleaseVersion = exports.postLoginService = exports.notifyLogout = exports.notifyAuthFailure = exports.notifyAuthSuccess = exports.notifyAuthSDKSuccess = exports.setAuthEE = exports.getAuthEE = exports.AuthEvent = exports.AuthStatus = exports.AuthFailureType = exports.SSO_REDIRECTION_MARKER_GUID = exports.samlCompletionPromise = exports.samlAuthWindow = exports.loggedInStatus = void 0; const authToken_1 = require("./authToken"); const embedConfig_1 = require("./embed/embedConfig"); const mixpanel_service_1 = require("./mixpanel-service"); const types_1 = require("./types"); const utils_1 = require("./utils"); const authService_1 = require("./utils/authService"); const tokenizedAuthService_1 = require("./utils/authService/tokenizedAuthService"); const logger_1 = require("./utils/logger"); const sessionInfoService_1 = require("./utils/sessionInfoService"); const errors_1 = require("./errors"); const resetServices_1 = require("./utils/resetServices"); // eslint-disable-next-line import/no-mutable-exports exports.loggedInStatus = false; // eslint-disable-next-line import/no-mutable-exports exports.samlAuthWindow = null; // eslint-disable-next-line import/no-mutable-exports exports.samlCompletionPromise = null; let releaseVersion = ''; exports.SSO_REDIRECTION_MARKER_GUID = '5e16222e-ef02-43e9-9fbd-24226bf3ce5b'; /** * Enum for auth failure types. This is the parameter passed to the listner * of {@link AuthStatus.FAILURE}. * @group Authentication / Init */ var AuthFailureType; (function (AuthFailureType) { AuthFailureType["SDK"] = "SDK"; AuthFailureType["NO_COOKIE_ACCESS"] = "NO_COOKIE_ACCESS"; AuthFailureType["EXPIRY"] = "EXPIRY"; AuthFailureType["OTHER"] = "OTHER"; AuthFailureType["IDLE_SESSION_TIMEOUT"] = "IDLE_SESSION_TIMEOUT"; AuthFailureType["UNAUTHENTICATED_FAILURE"] = "UNAUTHENTICATED_FAILURE"; })(AuthFailureType = exports.AuthFailureType || (exports.AuthFailureType = {})); /** * Enum for auth status emitted by the emitter returned from {@link init}. * @group Authentication / Init */ var AuthStatus; (function (AuthStatus) { /** * Emits when the SDK fails to authenticate */ AuthStatus["FAILURE"] = "FAILURE"; /** * Emits when the SDK authenticates successfully */ AuthStatus["SDK_SUCCESS"] = "SDK_SUCCESS"; /** * @hidden * Emits when iframe is loaded and session info is available */ AuthStatus["SESSION_INFO_SUCCESS"] = "SESSION_INFO_SUCCESS"; /** * Emits when the app sends an authentication success message */ AuthStatus["SUCCESS"] = "SUCCESS"; /** * Emits when a user logs out */ AuthStatus["LOGOUT"] = "LOGOUT"; /** * Emitted when inPopup is true in the SAMLRedirect flow and the * popup is waiting to be triggered either programmatically * or by the trigger button. * @version SDK: 1.19.0 */ AuthStatus["WAITING_FOR_POPUP"] = "WAITING_FOR_POPUP"; /** * Emitted when the SAML popup is closed without authentication */ AuthStatus["SAML_POPUP_CLOSED_NO_AUTH"] = "SAML_POPUP_CLOSED_NO_AUTH"; })(AuthStatus = exports.AuthStatus || (exports.AuthStatus = {})); /** * Events which can be triggered on the emitter returned from {@link init}. * @group Authentication / Init */ var AuthEvent; (function (AuthEvent) { /** * Manually trigger the SSO popup. This is useful when * authStatus is SAMLRedirect/OIDCRedirect and inPopup is set to true */ AuthEvent["TRIGGER_SSO_POPUP"] = "TRIGGER_SSO_POPUP"; })(AuthEvent = exports.AuthEvent || (exports.AuthEvent = {})); let authEE; /** * */ function getAuthEE() { return authEE; } exports.getAuthEE = getAuthEE; /** * * @param eventEmitter */ function setAuthEE(eventEmitter) { authEE = eventEmitter; } exports.setAuthEE = setAuthEE; /** * */ function notifyAuthSDKSuccess() { if (!authEE) { logger_1.logger.error(errors_1.ERROR_MESSAGE.SDK_NOT_INITIALIZED); return; } authEE.emit(AuthStatus.SDK_SUCCESS); } exports.notifyAuthSDKSuccess = notifyAuthSDKSuccess; /** * */ async function notifyAuthSuccess() { if (!authEE) { logger_1.logger.error(errors_1.ERROR_MESSAGE.SDK_NOT_INITIALIZED); return; } try { (0, sessionInfoService_1.getPreauthInfo)(); const sessionInfo = await (0, sessionInfoService_1.getSessionInfo)(); authEE.emit(AuthStatus.SUCCESS, sessionInfo); } catch (e) { logger_1.logger.error(errors_1.ERROR_MESSAGE.SESSION_INFO_FAILED); } } exports.notifyAuthSuccess = notifyAuthSuccess; /** * * @param failureType */ function notifyAuthFailure(failureType) { if (!authEE) { logger_1.logger.error(errors_1.ERROR_MESSAGE.SDK_NOT_INITIALIZED); return; } authEE.emit(AuthStatus.FAILURE, failureType); } exports.notifyAuthFailure = notifyAuthFailure; /** * */ function notifyLogout() { if (!authEE) { logger_1.logger.error(errors_1.ERROR_MESSAGE.SDK_NOT_INITIALIZED); return; } authEE.emit(AuthStatus.LOGOUT); } exports.notifyLogout = notifyLogout; /** * Check if we are logged into the ThoughtSpot cluster * @param thoughtSpotHost The ThoughtSpot cluster hostname or IP */ async function isLoggedIn(thoughtSpotHost) { try { const response = await (0, tokenizedAuthService_1.isActiveService)(thoughtSpotHost); return response; } catch (e) { return false; } } /** * Services to be called after the login is successful, * This should be called after the cookie is set for cookie auth or * after the token is set for cookieless. * @return {Promise<void>} * @example * ```js * await postLoginService(); * ``` * @version SDK: 1.28.3 | ThoughtSpot: * */ async function postLoginService() { try { (0, sessionInfoService_1.getPreauthInfo)(); const sessionInfo = await (0, sessionInfoService_1.getSessionInfo)(); releaseVersion = sessionInfo.releaseVersion; const embedConfig = (0, embedConfig_1.getEmbedConfig)(); if (!embedConfig.disableSDKTracking) { (0, mixpanel_service_1.initMixpanel)(sessionInfo); } } catch (e) { logger_1.logger.error('Post login services failed.', e.message, e); } } exports.postLoginService = postLoginService; /** * Return releaseVersion if available */ function getReleaseVersion() { return releaseVersion; } exports.getReleaseVersion = getReleaseVersion; /** * Check if we are stuck at the SSO redirect URL */ function isAtSSORedirectUrl() { return window.location.href.indexOf((0, utils_1.getSSOMarker)(exports.SSO_REDIRECTION_MARKER_GUID)) >= 0; } /** * Remove the SSO redirect URL marker */ function removeSSORedirectUrlMarker() { // Note (sunny): This will leave a # around even if it was not in the URL // to begin with. Trying to remove the hash by changing window.location will // reload the page which we don't want. We'll live with adding an // unnecessary hash to the parent page URL until we find any use case where // that creates an issue. // Replace any occurences of ?ssoMarker=guid or &ssoMarker=guid. let updatedHash = window.location.hash.replace(`?${(0, utils_1.getSSOMarker)(exports.SSO_REDIRECTION_MARKER_GUID)}`, ''); updatedHash = updatedHash.replace(`&${(0, utils_1.getSSOMarker)(exports.SSO_REDIRECTION_MARKER_GUID)}`, ''); window.location.hash = updatedHash; } /** * Perform token based authentication * @param embedConfig The embed configuration */ const doTokenAuth = async (embedConfig) => { const { thoughtSpotHost, username, authEndpoint, getAuthToken, } = embedConfig; if (!authEndpoint && !getAuthToken) { throw new Error('Either auth endpoint or getAuthToken function must be provided'); } exports.loggedInStatus = await isLoggedIn(thoughtSpotHost); if (!exports.loggedInStatus) { let authToken; try { authToken = await (0, authToken_1.getAuthenticationToken)(embedConfig); } catch (e) { exports.loggedInStatus = false; throw e; } let resp; try { resp = await (0, authService_1.fetchAuthPostService)(thoughtSpotHost, username, authToken); } catch (e) { resp = await (0, authService_1.fetchAuthService)(thoughtSpotHost, username, authToken); } // token login issues a 302 when successful exports.loggedInStatus = resp.ok || resp.type === 'opaqueredirect'; if (exports.loggedInStatus && embedConfig.detectCookieAccessSlow) { // When 3rd party cookie access is blocked, this will fail because // cookies will not be sent with the call. exports.loggedInStatus = await isLoggedIn(thoughtSpotHost); } } return exports.loggedInStatus; }; exports.doTokenAuth = doTokenAuth; /** * Validate embedConfig parameters required for cookielessTokenAuth * @param embedConfig The embed configuration */ const doCookielessTokenAuth = async (embedConfig) => { const { authEndpoint, getAuthToken } = embedConfig; if (!authEndpoint && !getAuthToken) { throw new Error('Either auth endpoint or getAuthToken function must be provided'); } let authSuccess = false; try { const authToken = await (0, authToken_1.getAuthenticationToken)(embedConfig); if (authToken) { authSuccess = true; } } catch { authSuccess = false; } return authSuccess; }; exports.doCookielessTokenAuth = doCookielessTokenAuth; /** * Perform basic authentication to the ThoughtSpot cluster using the cluster * credentials. * * Warning: This feature is primarily intended for developer testing. It is * strongly advised not to use this authentication method in production. * @param embedConfig The embed configuration */ const doBasicAuth = async (embedConfig) => { const { thoughtSpotHost, username, password } = embedConfig; const loggedIn = await isLoggedIn(thoughtSpotHost); if (!loggedIn) { const response = await (0, authService_1.fetchBasicAuthService)(thoughtSpotHost, username, password); exports.loggedInStatus = response.ok; if (embedConfig.detectCookieAccessSlow) { exports.loggedInStatus = await isLoggedIn(thoughtSpotHost); } } else { exports.loggedInStatus = true; } return exports.loggedInStatus; }; exports.doBasicAuth = doBasicAuth; /** * * @param ssoURL * @param triggerContainer * @param triggerText */ async function samlPopupFlow(ssoURL, triggerContainer, triggerText) { let popupClosedCheck; const openPopup = () => { if (exports.samlAuthWindow === null || exports.samlAuthWindow.closed) { exports.samlAuthWindow = window.open(ssoURL, '_blank', 'location=no,height=570,width=520,scrollbars=yes,status=yes'); if (exports.samlAuthWindow) { popupClosedCheck = setInterval(() => { if (exports.samlAuthWindow.closed) { clearInterval(popupClosedCheck); if (exports.samlCompletionPromise && !samlCompletionResolved) { authEE === null || authEE === void 0 ? void 0 : authEE.emit(AuthStatus.SAML_POPUP_CLOSED_NO_AUTH); } } }, 500); } } else { exports.samlAuthWindow.focus(); } }; let samlCompletionResolved = false; authEE === null || authEE === void 0 ? void 0 : authEE.emit(AuthStatus.WAITING_FOR_POPUP); const containerEl = (0, utils_1.getDOMNode)(triggerContainer); if (containerEl) { containerEl.innerHTML = '<button id="ts-auth-btn" class="ts-auth-btn" style="margin: auto;"></button>'; const authElem = document.getElementById('ts-auth-btn'); authElem.textContent = triggerText; authElem.addEventListener('click', openPopup, { once: true }); } exports.samlCompletionPromise = exports.samlCompletionPromise || new Promise((resolve, reject) => { window.addEventListener('message', (e) => { if (e.data.type === types_1.EmbedEvent.SAMLComplete) { samlCompletionResolved = true; if (popupClosedCheck) { clearInterval(popupClosedCheck); } e.source.close(); resolve(); } }); }); authEE === null || authEE === void 0 ? void 0 : authEE.once(AuthEvent.TRIGGER_SSO_POPUP, openPopup); return exports.samlCompletionPromise; } /** * Perform SAML authentication * @param embedConfig The embed configuration * @param ssoEndPoint */ const doSSOAuth = async (embedConfig, ssoEndPoint) => { const { thoughtSpotHost } = embedConfig; const loggedIn = await isLoggedIn(thoughtSpotHost); if (loggedIn) { if (isAtSSORedirectUrl()) { removeSSORedirectUrlMarker(); } exports.loggedInStatus = true; return; } // we have already tried authentication and it did not succeed, restore // the current URL to the original one and invoke the callback. if (isAtSSORedirectUrl()) { removeSSORedirectUrlMarker(); exports.loggedInStatus = false; return; } const ssoURL = `${thoughtSpotHost}${ssoEndPoint}`; if (embedConfig.inPopup) { await samlPopupFlow(ssoURL, embedConfig.authTriggerContainer, embedConfig.authTriggerText); exports.loggedInStatus = await isLoggedIn(thoughtSpotHost); return; } window.location.href = ssoURL; }; const doSamlAuth = async (embedConfig) => { const { thoughtSpotHost } = embedConfig; // redirect for SSO, when the SSO authentication is done, this page will be // loaded again and the same JS will execute again. const ssoRedirectUrl = embedConfig.inPopup ? `${thoughtSpotHost}/v2/#/embed/saml-complete` : (0, utils_1.getRedirectUrl)(window.location.href, exports.SSO_REDIRECTION_MARKER_GUID, embedConfig.redirectPath); // bring back the page to the same URL const ssoEndPoint = `${authService_1.EndPoints.SAML_LOGIN_TEMPLATE(encodeURIComponent(ssoRedirectUrl))}`; await doSSOAuth(embedConfig, ssoEndPoint); return exports.loggedInStatus; }; exports.doSamlAuth = doSamlAuth; const doOIDCAuth = async (embedConfig) => { const { thoughtSpotHost } = embedConfig; // redirect for SSO, when the SSO authentication is done, this page will be // loaded again and the same JS will execute again. const ssoRedirectUrl = embedConfig.noRedirect || embedConfig.inPopup ? `${thoughtSpotHost}/v2/#/embed/saml-complete` : (0, utils_1.getRedirectUrl)(window.location.href, exports.SSO_REDIRECTION_MARKER_GUID, embedConfig.redirectPath); // bring back the page to the same URL const baseEndpoint = `${authService_1.EndPoints.OIDC_LOGIN_TEMPLATE(encodeURIComponent(ssoRedirectUrl))}`; const ssoEndPoint = `${baseEndpoint}${baseEndpoint.includes('?') ? '&' : '?'}forceSAMLAutoRedirect=true`; await doSSOAuth(embedConfig, ssoEndPoint); return exports.loggedInStatus; }; exports.doOIDCAuth = doOIDCAuth; const logout = async (embedConfig) => { const { thoughtSpotHost } = embedConfig; await (0, authService_1.fetchLogoutService)(thoughtSpotHost); (0, resetServices_1.resetAllCachedServices)(); const thoughtspotIframes = document.querySelectorAll("[data-ts-iframe='true']"); if (thoughtspotIframes === null || thoughtspotIframes === void 0 ? void 0 : thoughtspotIframes.length) { thoughtspotIframes.forEach((el) => { el.parentElement.innerHTML = embedConfig.loginFailedMessage; }); } exports.loggedInStatus = false; return exports.loggedInStatus; }; exports.logout = logout; /** * Perform authentication on the ThoughtSpot cluster * @param embedConfig The embed configuration */ const authenticate = async (embedConfig) => { const { authType } = embedConfig; switch (authType) { case types_1.AuthType.SSO: case types_1.AuthType.SAMLRedirect: case types_1.AuthType.SAML: return (0, exports.doSamlAuth)(embedConfig); case types_1.AuthType.OIDC: case types_1.AuthType.OIDCRedirect: return (0, exports.doOIDCAuth)(embedConfig); case types_1.AuthType.AuthServer: case types_1.AuthType.TrustedAuthToken: return (0, exports.doTokenAuth)(embedConfig); case types_1.AuthType.TrustedAuthTokenCookieless: return (0, exports.doCookielessTokenAuth)(embedConfig); case types_1.AuthType.Basic: return (0, exports.doBasicAuth)(embedConfig); default: return Promise.resolve(true); } }; exports.authenticate = authenticate; /** * Check if we are authenticated to the ThoughtSpot cluster */ const isAuthenticated = () => exports.loggedInStatus; exports.isAuthenticated = isAuthenticated; //# sourceMappingURL=auth.js.map