@thoughtspot/visual-embed-sdk
Version:
ThoughtSpot Embed SDK
457 lines • 17.4 kB
JavaScript
;
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