@thoughtspot/visual-embed-sdk 1.10.0 → 1.10.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/auth.d.ts +18 -5
- package/dist/src/embed/base.d.ts +36 -6
- package/dist/src/embed/pinboard.d.ts +91 -0
- package/dist/src/index.d.ts +3 -2
- package/dist/src/types.d.ts +24 -0
- package/dist/src/utils/authService.d.ts +1 -0
- package/dist/src/utils/plugin.d.ts +0 -0
- package/dist/src/utils/processData.d.ts +1 -1
- package/dist/src/utils.d.ts +1 -0
- package/dist/src/v1/api.d.ts +19 -0
- package/dist/tsembed.es.js +544 -35
- package/dist/tsembed.js +542 -34
- package/lib/package.json +2 -1
- package/lib/src/auth.d.ts +18 -5
- package/lib/src/auth.js +51 -12
- package/lib/src/auth.js.map +1 -1
- package/lib/src/auth.spec.js +69 -11
- package/lib/src/auth.spec.js.map +1 -1
- package/lib/src/embed/base.d.ts +36 -6
- package/lib/src/embed/base.js +79 -11
- package/lib/src/embed/base.js.map +1 -1
- package/lib/src/embed/base.spec.js +51 -3
- package/lib/src/embed/base.spec.js.map +1 -1
- package/lib/src/embed/embed.spec.js +1 -1
- package/lib/src/embed/embed.spec.js.map +1 -1
- package/lib/src/embed/pinboard.d.ts +91 -0
- package/lib/src/embed/pinboard.js +110 -0
- package/lib/src/embed/pinboard.js.map +1 -0
- package/lib/src/embed/ts-embed.js +9 -10
- package/lib/src/embed/ts-embed.js.map +1 -1
- package/lib/src/embed/ts-embed.spec.js +16 -6
- package/lib/src/embed/ts-embed.spec.js.map +1 -1
- package/lib/src/index.d.ts +3 -2
- package/lib/src/index.js +3 -2
- package/lib/src/index.js.map +1 -1
- package/lib/src/test/test-utils.js +1 -1
- package/lib/src/test/test-utils.js.map +1 -1
- package/lib/src/types.d.ts +24 -0
- package/lib/src/types.js +10 -0
- package/lib/src/types.js.map +1 -1
- package/lib/src/utils/authService.d.ts +1 -0
- package/lib/src/utils/authService.js +23 -3
- package/lib/src/utils/authService.js.map +1 -1
- package/lib/src/utils/authService.spec.js +21 -5
- package/lib/src/utils/authService.spec.js.map +1 -1
- package/lib/src/utils/plugin.d.ts +0 -0
- package/lib/src/utils/plugin.js +1 -0
- package/lib/src/utils/plugin.js.map +1 -0
- package/lib/src/utils/processData.d.ts +1 -1
- package/lib/src/utils/processData.js +37 -3
- package/lib/src/utils/processData.js.map +1 -1
- package/lib/src/utils/processData.spec.js +106 -4
- package/lib/src/utils/processData.spec.js.map +1 -1
- package/lib/src/utils.d.ts +1 -0
- package/lib/src/utils.js +4 -0
- package/lib/src/utils.js.map +1 -1
- package/lib/src/utils.spec.js +14 -1
- package/lib/src/utils.spec.js.map +1 -1
- package/lib/src/visual-embed-sdk.d.ts +124 -8
- package/package.json +2 -1
- package/src/auth.spec.ts +90 -11
- package/src/auth.ts +74 -16
- package/src/embed/base.spec.ts +58 -4
- package/src/embed/base.ts +98 -17
- package/src/embed/embed.spec.ts +1 -1
- package/src/embed/ts-embed.spec.ts +19 -9
- package/src/embed/ts-embed.ts +15 -12
- package/src/index.ts +5 -1
- package/src/test/test-utils.ts +1 -1
- package/src/types.ts +26 -0
- package/src/utils/authService.spec.ts +31 -5
- package/src/utils/authService.ts +29 -3
- package/src/utils/processData.spec.ts +139 -4
- package/src/utils/processData.ts +54 -4
- package/src/utils.spec.ts +26 -0
- package/src/utils.ts +5 -0
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
// Generated by dts-bundle v0.7.3
|
|
2
|
+
// Dependencies for this module:
|
|
3
|
+
// ../../eventemitter3
|
|
2
4
|
|
|
3
5
|
declare module '@thoughtspot/visual-embed-sdk' {
|
|
4
6
|
/**
|
|
@@ -11,11 +13,12 @@ declare module '@thoughtspot/visual-embed-sdk' {
|
|
|
11
13
|
* @author Ayon Ghosh <ayon.ghosh@thoughtspot.com>
|
|
12
14
|
*/
|
|
13
15
|
import { AppEmbed, Page, AppViewConfig } from '@thoughtspot/visual-embed-sdk/embed/app';
|
|
14
|
-
import { init, prefetch } from '@thoughtspot/visual-embed-sdk/embed/base';
|
|
16
|
+
import { init, prefetch, logout } from '@thoughtspot/visual-embed-sdk/embed/base';
|
|
15
17
|
import { PinboardEmbed, LiveboardViewConfig, LiveboardEmbed } from '@thoughtspot/visual-embed-sdk/embed/liveboard';
|
|
16
18
|
import { SearchEmbed, SearchViewConfig } from '@thoughtspot/visual-embed-sdk/embed/search';
|
|
19
|
+
import { AuthFailureType, AuthStatus } from '@thoughtspot/visual-embed-sdk/auth';
|
|
17
20
|
import { AuthType, RuntimeFilter, RuntimeFilterOp, EmbedEvent, HostEvent, DataSourceVisualMode, Action, EmbedConfig } from '@thoughtspot/visual-embed-sdk/types';
|
|
18
|
-
export { init, prefetch, SearchEmbed, PinboardEmbed, LiveboardEmbed, AppEmbed, Page, AuthType, RuntimeFilter, RuntimeFilterOp, EmbedEvent, HostEvent, DataSourceVisualMode, Action, EmbedConfig, SearchViewConfig, LiveboardViewConfig, AppViewConfig, };
|
|
21
|
+
export { init, logout, prefetch, SearchEmbed, PinboardEmbed, LiveboardEmbed, AppEmbed, AuthFailureType, AuthStatus, Page, AuthType, RuntimeFilter, RuntimeFilterOp, EmbedEvent, HostEvent, DataSourceVisualMode, Action, EmbedConfig, SearchViewConfig, LiveboardViewConfig, AppViewConfig, };
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
declare module '@thoughtspot/visual-embed-sdk/embed/app' {
|
|
@@ -134,14 +137,27 @@ declare module '@thoughtspot/visual-embed-sdk/embed/app' {
|
|
|
134
137
|
}
|
|
135
138
|
|
|
136
139
|
declare module '@thoughtspot/visual-embed-sdk/embed/base' {
|
|
140
|
+
/**
|
|
141
|
+
* Copyright (c) 2022
|
|
142
|
+
*
|
|
143
|
+
* Base classes
|
|
144
|
+
*
|
|
145
|
+
* @summary Base classes
|
|
146
|
+
* @author Ayon Ghosh <ayon.ghosh@thoughtspot.com>
|
|
147
|
+
*/
|
|
148
|
+
import EventEmitter from 'eventemitter3';
|
|
137
149
|
import { EmbedConfig } from '@thoughtspot/visual-embed-sdk/types';
|
|
138
|
-
|
|
150
|
+
import { AuthFailureType } from '@thoughtspot/visual-embed-sdk/auth';
|
|
151
|
+
export let authPromise: Promise<boolean>;
|
|
152
|
+
export const getEmbedConfig: () => EmbedConfig;
|
|
153
|
+
export const getAuthPromise: () => Promise<boolean>;
|
|
154
|
+
export function notifyAuthSuccess(): void;
|
|
155
|
+
export function notifyAuthFailure(failureType: AuthFailureType): void;
|
|
156
|
+
export function notifyLogout(): void;
|
|
139
157
|
/**
|
|
140
158
|
* Perform authentication on the ThoughtSpot app as applicable.
|
|
141
159
|
*/
|
|
142
|
-
export const handleAuth: () => Promise<
|
|
143
|
-
export const getEmbedConfig: () => EmbedConfig;
|
|
144
|
-
export const getAuthPromise: () => Promise<void>;
|
|
160
|
+
export const handleAuth: () => Promise<boolean>;
|
|
145
161
|
/**
|
|
146
162
|
* Prefetches static resources from the specified URL. Web browsers can then cache the prefetched resources and serve them from the user's local disk to provide faster access to your app.
|
|
147
163
|
* @param url The URL provided for prefetch
|
|
@@ -153,14 +169,31 @@ declare module '@thoughtspot/visual-embed-sdk/embed/base' {
|
|
|
153
169
|
* @param embedConfig The configuration object containing ThoughtSpot host,
|
|
154
170
|
* authentication mechanism and so on.
|
|
155
171
|
*
|
|
156
|
-
*
|
|
172
|
+
* eg: authStatus = init(config);
|
|
173
|
+
* authStatus.on(AuthStatus.FAILURE, (reason) => { // do something here });
|
|
174
|
+
*
|
|
175
|
+
* @returns event emitter which emits events on authentication success, failure and logout. {@link AuthStatus}
|
|
157
176
|
*/
|
|
158
|
-
export const init: (embedConfig: EmbedConfig) =>
|
|
177
|
+
export const init: (embedConfig: EmbedConfig) => EventEmitter;
|
|
178
|
+
export function disableAutoLogin(): void;
|
|
179
|
+
/**
|
|
180
|
+
* Logout from ThoughtSpot. This also sets the autoLogin flag to false, to prevent
|
|
181
|
+
* the SDK from automatically logging in again.
|
|
182
|
+
*
|
|
183
|
+
* You can call the `init` method again to re login, if autoLogin is set to true in this
|
|
184
|
+
* second call it will be honored.
|
|
185
|
+
*
|
|
186
|
+
* @param doNotDisableAutoLogin This flag when passed will not disable autoLogin
|
|
187
|
+
* @returns Promise which resolves when logout completes.
|
|
188
|
+
* @version SDK: 1.10.1 | ThoughtSpot: *
|
|
189
|
+
*/
|
|
190
|
+
export const logout: (doNotDisableAutoLogin?: boolean) => Promise<boolean>;
|
|
159
191
|
/**
|
|
160
192
|
* Renders functions in a queue, resolves to next function only after the callback next is called
|
|
161
193
|
* @param fn The function being registered
|
|
162
194
|
*/
|
|
163
195
|
export const renderInQueue: (fn: (next?: (val?: any) => void) => void) => void;
|
|
196
|
+
export function reset(): void;
|
|
164
197
|
}
|
|
165
198
|
|
|
166
199
|
declare module '@thoughtspot/visual-embed-sdk/embed/liveboard' {
|
|
@@ -357,6 +390,65 @@ declare module '@thoughtspot/visual-embed-sdk/embed/search' {
|
|
|
357
390
|
export {};
|
|
358
391
|
}
|
|
359
392
|
|
|
393
|
+
declare module '@thoughtspot/visual-embed-sdk/auth' {
|
|
394
|
+
import { EmbedConfig } from '@thoughtspot/visual-embed-sdk/types';
|
|
395
|
+
export let loggedInStatus: boolean;
|
|
396
|
+
export let samlAuthWindow: Window;
|
|
397
|
+
export let samlCompletionPromise: Promise<void>;
|
|
398
|
+
export let sessionInfo: any;
|
|
399
|
+
export const SSO_REDIRECTION_MARKER_GUID = "5e16222e-ef02-43e9-9fbd-24226bf3ce5b";
|
|
400
|
+
export const EndPoints: {
|
|
401
|
+
AUTH_VERIFICATION: string;
|
|
402
|
+
SAML_LOGIN_TEMPLATE: (targetUrl: string) => string;
|
|
403
|
+
OIDC_LOGIN_TEMPLATE: (targetUrl: string) => string;
|
|
404
|
+
TOKEN_LOGIN: string;
|
|
405
|
+
BASIC_LOGIN: string;
|
|
406
|
+
LOGOUT: string;
|
|
407
|
+
};
|
|
408
|
+
export enum AuthFailureType {
|
|
409
|
+
SDK = "SDK",
|
|
410
|
+
NO_COOKIE_ACCESS = "NO_COOKIE_ACCESS",
|
|
411
|
+
EXPIRY = "EXPIRY",
|
|
412
|
+
OTHER = "OTHER"
|
|
413
|
+
}
|
|
414
|
+
export enum AuthStatus {
|
|
415
|
+
FAILURE = "FAILURE",
|
|
416
|
+
SUCCESS = "SUCCESS",
|
|
417
|
+
LOGOUT = "LOGOUT"
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Return sessionInfo if available else make a loggedIn check to fetch the sessionInfo
|
|
421
|
+
*/
|
|
422
|
+
export function getSessionInfo(): any;
|
|
423
|
+
export function initSession(sessionDetails: any): void;
|
|
424
|
+
/**
|
|
425
|
+
* Perform token based authentication
|
|
426
|
+
* @param embedConfig The embed configuration
|
|
427
|
+
*/
|
|
428
|
+
export const doTokenAuth: (embedConfig: EmbedConfig) => Promise<boolean>;
|
|
429
|
+
/**
|
|
430
|
+
* Perform basic authentication to the ThoughtSpot cluster using the cluster
|
|
431
|
+
* credentials.
|
|
432
|
+
*
|
|
433
|
+
* Warning: This feature is primarily intended for developer testing. It is
|
|
434
|
+
* strongly advised not to use this authentication method in production.
|
|
435
|
+
* @param embedConfig The embed configuration
|
|
436
|
+
*/
|
|
437
|
+
export const doBasicAuth: (embedConfig: EmbedConfig) => Promise<boolean>;
|
|
438
|
+
export const doSamlAuth: (embedConfig: EmbedConfig) => Promise<boolean>;
|
|
439
|
+
export const doOIDCAuth: (embedConfig: EmbedConfig) => Promise<boolean>;
|
|
440
|
+
export const logout: (embedConfig: EmbedConfig) => Promise<boolean>;
|
|
441
|
+
/**
|
|
442
|
+
* Perform authentication on the ThoughtSpot cluster
|
|
443
|
+
* @param embedConfig The embed configuration
|
|
444
|
+
*/
|
|
445
|
+
export const authenticate: (embedConfig: EmbedConfig) => Promise<boolean>;
|
|
446
|
+
/**
|
|
447
|
+
* Check if we are authenticated to the ThoughtSpot cluster
|
|
448
|
+
*/
|
|
449
|
+
export const isAuthenticated: () => boolean;
|
|
450
|
+
}
|
|
451
|
+
|
|
360
452
|
declare module '@thoughtspot/visual-embed-sdk/types' {
|
|
361
453
|
/**
|
|
362
454
|
* The authentication mechanism for allowing access to the
|
|
@@ -442,6 +534,15 @@ declare module '@thoughtspot/visual-embed-sdk/types' {
|
|
|
442
534
|
* @default false
|
|
443
535
|
*/
|
|
444
536
|
noRedirect?: boolean;
|
|
537
|
+
/**
|
|
538
|
+
* [SSO] For SSO Authentication, one can supply an optional path param,
|
|
539
|
+
* this will be the path on the host origin where the SAML flow will be
|
|
540
|
+
* terminated.
|
|
541
|
+
*
|
|
542
|
+
* Eg: "/dashboard", "#/foo" [Do not include the host]
|
|
543
|
+
* @version SDK: 1.10.2 | ThoughtSpot: *
|
|
544
|
+
*/
|
|
545
|
+
redirectPath?: string;
|
|
445
546
|
/** @internal */
|
|
446
547
|
basepath?: string;
|
|
447
548
|
/**
|
|
@@ -471,6 +572,11 @@ declare module '@thoughtspot/visual-embed-sdk/types' {
|
|
|
471
572
|
* @default false
|
|
472
573
|
*/
|
|
473
574
|
disableLoginRedirect?: boolean;
|
|
575
|
+
/**
|
|
576
|
+
* This message is displayed on the embed view when the login fails.
|
|
577
|
+
* @version 1.10.1 | ThoughtSpot: *
|
|
578
|
+
*/
|
|
579
|
+
loginFailedMessage?: string;
|
|
474
580
|
/**
|
|
475
581
|
* Calls the prefetch method internally when set to true
|
|
476
582
|
* @default false
|
|
@@ -684,6 +790,16 @@ declare module '@thoughtspot/visual-embed-sdk/types' {
|
|
|
684
790
|
* The ThoughtSpot auth session has expired.
|
|
685
791
|
*/
|
|
686
792
|
AuthExpire = "ThoughtspotAuthExpired",
|
|
793
|
+
/**
|
|
794
|
+
* ThoughtSpot failed to validate the auth session.
|
|
795
|
+
* @hidden
|
|
796
|
+
*/
|
|
797
|
+
AuthFailure = "ThoughtspotAuthFailure",
|
|
798
|
+
/**
|
|
799
|
+
* ThoughtSpot failed to validate the auth session.
|
|
800
|
+
* @hidden
|
|
801
|
+
*/
|
|
802
|
+
AuthLogout = "ThoughtspotAuthLogout",
|
|
687
803
|
/**
|
|
688
804
|
* The height of the embedded Liveboard or visualization has been computed.
|
|
689
805
|
* @return data - The height of the embedded Liveboard or visualization
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thoughtspot/visual-embed-sdk",
|
|
3
|
-
"version": "1.10.
|
|
3
|
+
"version": "1.10.3",
|
|
4
4
|
"description": "ThoughtSpot Embed SDK",
|
|
5
5
|
"module": "lib/src/index.js",
|
|
6
6
|
"main": "dist/tsembed.js",
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"algoliasearch": "^4.10.5",
|
|
46
46
|
"classnames": "^2.3.1",
|
|
47
|
+
"eventemitter3": "^4.0.7",
|
|
47
48
|
"mixpanel-browser": "^2.41.0"
|
|
48
49
|
},
|
|
49
50
|
"devDependencies": {
|
package/src/auth.spec.ts
CHANGED
|
@@ -9,12 +9,12 @@ const password = '12345678';
|
|
|
9
9
|
const samalLoginUrl = `${thoughtSpotHost}/callosum/v1/saml/login?targetURLPath=%235e16222e-ef02-43e9-9fbd-24226bf3ce5b`;
|
|
10
10
|
|
|
11
11
|
const embedConfig: any = {
|
|
12
|
-
doTokenAuthSuccess: {
|
|
12
|
+
doTokenAuthSuccess: (token: string) => ({
|
|
13
13
|
thoughtSpotHost,
|
|
14
14
|
username,
|
|
15
15
|
authEndpoint: 'auth',
|
|
16
|
-
getAuthToken: jest.fn(() => Promise.resolve(
|
|
17
|
-
},
|
|
16
|
+
getAuthToken: jest.fn(() => Promise.resolve(token)),
|
|
17
|
+
}),
|
|
18
18
|
doTokenAuthFailureWithoutAuthEndPoint: {
|
|
19
19
|
thoughtSpotHost,
|
|
20
20
|
username,
|
|
@@ -35,9 +35,15 @@ const embedConfig: any = {
|
|
|
35
35
|
doSamlAuth: {
|
|
36
36
|
thoughtSpotHost,
|
|
37
37
|
},
|
|
38
|
+
doOidcAuth: {
|
|
39
|
+
thoughtSpotHost,
|
|
40
|
+
},
|
|
38
41
|
SSOAuth: {
|
|
39
42
|
authType: AuthType.SSO,
|
|
40
43
|
},
|
|
44
|
+
OIDCAuth: {
|
|
45
|
+
authType: AuthType.OIDC,
|
|
46
|
+
},
|
|
41
47
|
authServerFailure: {
|
|
42
48
|
thoughtSpotHost,
|
|
43
49
|
username,
|
|
@@ -107,12 +113,14 @@ describe('Unit test for auth', () => {
|
|
|
107
113
|
status: 200,
|
|
108
114
|
}),
|
|
109
115
|
);
|
|
110
|
-
await authInstance.doTokenAuth(
|
|
116
|
+
await authInstance.doTokenAuth(
|
|
117
|
+
embedConfig.doTokenAuthSuccess('authToken'),
|
|
118
|
+
);
|
|
111
119
|
expect(authService.fetchSessionInfoService).toBeCalled();
|
|
112
120
|
expect(authInstance.loggedInStatus).toBe(true);
|
|
113
121
|
});
|
|
114
122
|
|
|
115
|
-
test('doTokenAuth: when user is not loggedIn & getAuthToken have response
|
|
123
|
+
test('doTokenAuth: when user is not loggedIn & getAuthToken have response', async () => {
|
|
116
124
|
jest.spyOn(authService, 'fetchSessionInfoService').mockImplementation(
|
|
117
125
|
() => false,
|
|
118
126
|
);
|
|
@@ -120,13 +128,19 @@ describe('Unit test for auth', () => {
|
|
|
120
128
|
authService,
|
|
121
129
|
'fetchAuthTokenService',
|
|
122
130
|
).mockImplementation(() => ({ text: () => Promise.resolve('abc') }));
|
|
123
|
-
jest.spyOn(authService, 'fetchAuthService')
|
|
124
|
-
|
|
131
|
+
jest.spyOn(authService, 'fetchAuthService').mockImplementation(() =>
|
|
132
|
+
Promise.resolve({
|
|
133
|
+
status: 200,
|
|
134
|
+
}),
|
|
135
|
+
);
|
|
136
|
+
await authInstance.doTokenAuth(
|
|
137
|
+
embedConfig.doTokenAuthSuccess('authToken2'),
|
|
138
|
+
);
|
|
125
139
|
expect(authService.fetchSessionInfoService).toBeCalled();
|
|
126
140
|
expect(authService.fetchAuthService).toBeCalledWith(
|
|
127
141
|
thoughtSpotHost,
|
|
128
142
|
username,
|
|
129
|
-
'
|
|
143
|
+
'authToken2',
|
|
130
144
|
);
|
|
131
145
|
});
|
|
132
146
|
|
|
@@ -140,7 +154,12 @@ describe('Unit test for auth', () => {
|
|
|
140
154
|
).mockImplementation(() =>
|
|
141
155
|
Promise.resolve({ text: () => Promise.resolve('abc') }),
|
|
142
156
|
);
|
|
143
|
-
jest.spyOn(authService, 'fetchAuthService')
|
|
157
|
+
jest.spyOn(authService, 'fetchAuthService').mockImplementation(() =>
|
|
158
|
+
Promise.resolve({
|
|
159
|
+
status: 200,
|
|
160
|
+
ok: true,
|
|
161
|
+
}),
|
|
162
|
+
);
|
|
144
163
|
await authInstance.doTokenAuth(
|
|
145
164
|
embedConfig.doTokenAuthFailureWithoutGetAuthToken,
|
|
146
165
|
);
|
|
@@ -155,6 +174,38 @@ describe('Unit test for auth', () => {
|
|
|
155
174
|
});
|
|
156
175
|
});
|
|
157
176
|
|
|
177
|
+
test('doTokenAuth: Should raise error when duplicate token is used', async () => {
|
|
178
|
+
jest.spyOn(authService, 'fetchSessionInfoService').mockResolvedValue({
|
|
179
|
+
status: 401,
|
|
180
|
+
});
|
|
181
|
+
jest.spyOn(window, 'alert').mockClear();
|
|
182
|
+
jest.spyOn(window, 'alert').mockReturnValue(undefined);
|
|
183
|
+
jest.spyOn(authService, 'fetchAuthService').mockReset();
|
|
184
|
+
jest.spyOn(authService, 'fetchAuthService').mockImplementation(() =>
|
|
185
|
+
Promise.resolve({
|
|
186
|
+
status: 200,
|
|
187
|
+
ok: true,
|
|
188
|
+
}),
|
|
189
|
+
);
|
|
190
|
+
await authInstance.doTokenAuth(
|
|
191
|
+
embedConfig.doTokenAuthSuccess('authToken3'),
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
await authInstance.doTokenAuth(
|
|
196
|
+
embedConfig.doTokenAuthSuccess('authToken3'),
|
|
197
|
+
);
|
|
198
|
+
expect(false).toBe(true);
|
|
199
|
+
} catch (e) {
|
|
200
|
+
expect(e.message).toContain('Duplicate token');
|
|
201
|
+
}
|
|
202
|
+
await executeAfterWait(() => {
|
|
203
|
+
expect(authInstance.loggedInStatus).toBe(false);
|
|
204
|
+
expect(window.alert).toBeCalled();
|
|
205
|
+
expect(authService.fetchAuthService).toHaveBeenCalledTimes(1);
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
158
209
|
describe('doBasicAuth', () => {
|
|
159
210
|
beforeEach(() => {
|
|
160
211
|
global.fetch = window.fetch;
|
|
@@ -181,7 +232,7 @@ describe('Unit test for auth', () => {
|
|
|
181
232
|
jest.spyOn(
|
|
182
233
|
authService,
|
|
183
234
|
'fetchBasicAuthService',
|
|
184
|
-
).mockImplementation(() => ({ status: 200 }));
|
|
235
|
+
).mockImplementation(() => ({ status: 200, ok: true }));
|
|
185
236
|
|
|
186
237
|
await authInstance.doBasicAuth(embedConfig.doBasicAuth);
|
|
187
238
|
expect(authService.fetchSessionInfoService).toBeCalled();
|
|
@@ -253,6 +304,7 @@ describe('Unit test for auth', () => {
|
|
|
253
304
|
},
|
|
254
305
|
});
|
|
255
306
|
spyOn(authInstance, 'samlCompletionPromise');
|
|
307
|
+
global.window.open = jest.fn();
|
|
256
308
|
jest.spyOn(
|
|
257
309
|
authService,
|
|
258
310
|
'fetchSessionInfoService',
|
|
@@ -263,8 +315,28 @@ describe('Unit test for auth', () => {
|
|
|
263
315
|
...embedConfig.doSamlAuth,
|
|
264
316
|
noRedirect: true,
|
|
265
317
|
}),
|
|
266
|
-
).toBe(
|
|
318
|
+
).toBe(true);
|
|
319
|
+
expect(authService.fetchSessionInfoService).toBeCalled();
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
describe('doOIDCAuth', () => {
|
|
324
|
+
afterEach(() => {
|
|
325
|
+
delete global.window;
|
|
326
|
+
global.window = Object.create(originalWindow);
|
|
327
|
+
global.window.open = jest.fn();
|
|
328
|
+
global.fetch = window.fetch;
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it('when user is not loggedIn & isAtSSORedirectUrl is true', async () => {
|
|
332
|
+
jest.spyOn(
|
|
333
|
+
authService,
|
|
334
|
+
'fetchSessionInfoService',
|
|
335
|
+
).mockImplementation(() => Promise.reject());
|
|
336
|
+
await authInstance.doOIDCAuth(embedConfig.doOidcAuth);
|
|
267
337
|
expect(authService.fetchSessionInfoService).toBeCalled();
|
|
338
|
+
expect(window.location.hash).toBe('');
|
|
339
|
+
expect(authInstance.loggedInStatus).toBe(false);
|
|
268
340
|
});
|
|
269
341
|
});
|
|
270
342
|
|
|
@@ -275,6 +347,13 @@ describe('Unit test for auth', () => {
|
|
|
275
347
|
expect(authInstance.doSamlAuth).toBeCalled();
|
|
276
348
|
});
|
|
277
349
|
|
|
350
|
+
it('authenticate: when authType is OIDC', async () => {
|
|
351
|
+
jest.spyOn(authInstance, 'doOIDCAuth');
|
|
352
|
+
await authInstance.authenticate(embedConfig.OIDCAuth);
|
|
353
|
+
expect(window.location.hash).toBe('');
|
|
354
|
+
expect(authInstance.doOIDCAuth).toBeCalled();
|
|
355
|
+
});
|
|
356
|
+
|
|
278
357
|
it('authenticate: when authType is AuthServer', async () => {
|
|
279
358
|
spyOn(authInstance, 'doTokenAuth');
|
|
280
359
|
await authInstance.authenticate(embedConfig.authServerFailure);
|
package/src/auth.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { initMixpanel } from './mixpanel-service';
|
|
2
2
|
import { AuthType, EmbedConfig, EmbedEvent } from './types';
|
|
3
|
-
import {
|
|
3
|
+
import { getRedirectUrl } from './utils';
|
|
4
4
|
// eslint-disable-next-line import/no-cycle
|
|
5
5
|
import {
|
|
6
6
|
fetchSessionInfoService,
|
|
7
7
|
fetchAuthTokenService,
|
|
8
8
|
fetchAuthService,
|
|
9
9
|
fetchBasicAuthService,
|
|
10
|
+
fetchLogoutService,
|
|
10
11
|
} from './utils/authService';
|
|
11
12
|
|
|
12
13
|
// eslint-disable-next-line import/no-mutable-exports
|
|
@@ -29,8 +30,22 @@ export const EndPoints = {
|
|
|
29
30
|
`/callosum/v1/oidc/login?targetURLPath=${targetUrl}`,
|
|
30
31
|
TOKEN_LOGIN: '/callosum/v1/session/login/token',
|
|
31
32
|
BASIC_LOGIN: '/callosum/v1/session/login',
|
|
33
|
+
LOGOUT: '/callosum/v1/session/logout',
|
|
32
34
|
};
|
|
33
35
|
|
|
36
|
+
export enum AuthFailureType {
|
|
37
|
+
SDK = 'SDK',
|
|
38
|
+
NO_COOKIE_ACCESS = 'NO_COOKIE_ACCESS',
|
|
39
|
+
EXPIRY = 'EXPIRY',
|
|
40
|
+
OTHER = 'OTHER',
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export enum AuthStatus {
|
|
44
|
+
FAILURE = 'FAILURE',
|
|
45
|
+
SUCCESS = 'SUCCESS',
|
|
46
|
+
LOGOUT = 'LOGOUT',
|
|
47
|
+
}
|
|
48
|
+
|
|
34
49
|
/**
|
|
35
50
|
* Check if we are logged into the ThoughtSpot cluster
|
|
36
51
|
* @param thoughtSpotHost The ThoughtSpot cluster hostname or IP
|
|
@@ -58,6 +73,19 @@ export function initSession(sessionDetails: any) {
|
|
|
58
73
|
initMixpanel(sessionInfo);
|
|
59
74
|
}
|
|
60
75
|
|
|
76
|
+
const DUPLICATE_TOKEN_ERR =
|
|
77
|
+
'Duplicate token, please issue a new token every time getAuthToken callback is called.' +
|
|
78
|
+
'See https://developers.thoughtspot.com/docs/?pageid=embed-auth#trusted-auth-embed for more details.';
|
|
79
|
+
let prevAuthToken: string = null;
|
|
80
|
+
function alertForDuplicateToken(authtoken: string) {
|
|
81
|
+
if (prevAuthToken === authtoken) {
|
|
82
|
+
// eslint-disable-next-line no-alert
|
|
83
|
+
alert(DUPLICATE_TOKEN_ERR);
|
|
84
|
+
throw new Error(DUPLICATE_TOKEN_ERR);
|
|
85
|
+
}
|
|
86
|
+
prevAuthToken = authtoken;
|
|
87
|
+
}
|
|
88
|
+
|
|
61
89
|
/**
|
|
62
90
|
* Check if we are stuck at the SSO redirect URL
|
|
63
91
|
*/
|
|
@@ -83,7 +111,9 @@ function removeSSORedirectUrlMarker(): void {
|
|
|
83
111
|
* Perform token based authentication
|
|
84
112
|
* @param embedConfig The embed configuration
|
|
85
113
|
*/
|
|
86
|
-
export const doTokenAuth = async (
|
|
114
|
+
export const doTokenAuth = async (
|
|
115
|
+
embedConfig: EmbedConfig,
|
|
116
|
+
): Promise<boolean> => {
|
|
87
117
|
const {
|
|
88
118
|
thoughtSpotHost,
|
|
89
119
|
username,
|
|
@@ -95,20 +125,25 @@ export const doTokenAuth = async (embedConfig: EmbedConfig): Promise<void> => {
|
|
|
95
125
|
'Either auth endpoint or getAuthToken function must be provided',
|
|
96
126
|
);
|
|
97
127
|
}
|
|
98
|
-
|
|
99
|
-
if (!
|
|
128
|
+
loggedInStatus = await isLoggedIn(thoughtSpotHost);
|
|
129
|
+
if (!loggedInStatus) {
|
|
100
130
|
let authToken = null;
|
|
101
131
|
if (getAuthToken) {
|
|
102
132
|
authToken = await getAuthToken();
|
|
133
|
+
alertForDuplicateToken(authToken);
|
|
103
134
|
} else {
|
|
104
135
|
const response = await fetchAuthTokenService(authEndpoint);
|
|
105
136
|
authToken = await response.text();
|
|
106
137
|
}
|
|
107
|
-
await fetchAuthService(
|
|
108
|
-
|
|
138
|
+
const resp = await fetchAuthService(
|
|
139
|
+
thoughtSpotHost,
|
|
140
|
+
username,
|
|
141
|
+
authToken,
|
|
142
|
+
);
|
|
143
|
+
// token login issues a 302 when successful
|
|
144
|
+
loggedInStatus = resp.ok || resp.type === 'opaqueredirect';
|
|
109
145
|
}
|
|
110
|
-
|
|
111
|
-
loggedInStatus = true;
|
|
146
|
+
return loggedInStatus;
|
|
112
147
|
};
|
|
113
148
|
|
|
114
149
|
/**
|
|
@@ -119,7 +154,9 @@ export const doTokenAuth = async (embedConfig: EmbedConfig): Promise<void> => {
|
|
|
119
154
|
* strongly advised not to use this authentication method in production.
|
|
120
155
|
* @param embedConfig The embed configuration
|
|
121
156
|
*/
|
|
122
|
-
export const doBasicAuth = async (
|
|
157
|
+
export const doBasicAuth = async (
|
|
158
|
+
embedConfig: EmbedConfig,
|
|
159
|
+
): Promise<boolean> => {
|
|
123
160
|
const { thoughtSpotHost, username, password } = embedConfig;
|
|
124
161
|
const loggedIn = await isLoggedIn(thoughtSpotHost);
|
|
125
162
|
if (!loggedIn) {
|
|
@@ -128,10 +165,11 @@ export const doBasicAuth = async (embedConfig: EmbedConfig): Promise<void> => {
|
|
|
128
165
|
username,
|
|
129
166
|
password,
|
|
130
167
|
);
|
|
131
|
-
loggedInStatus = response.
|
|
168
|
+
loggedInStatus = response.ok;
|
|
169
|
+
} else {
|
|
170
|
+
loggedInStatus = true;
|
|
132
171
|
}
|
|
133
|
-
|
|
134
|
-
loggedInStatus = true;
|
|
172
|
+
return loggedInStatus;
|
|
135
173
|
};
|
|
136
174
|
|
|
137
175
|
async function samlPopupFlow(ssoURL: string) {
|
|
@@ -198,6 +236,7 @@ const doSSOAuth = async (
|
|
|
198
236
|
const ssoURL = `${thoughtSpotHost}${ssoEndPoint}`;
|
|
199
237
|
if (embedConfig.noRedirect) {
|
|
200
238
|
await samlPopupFlow(ssoURL);
|
|
239
|
+
loggedInStatus = true;
|
|
201
240
|
return;
|
|
202
241
|
}
|
|
203
242
|
|
|
@@ -210,7 +249,11 @@ export const doSamlAuth = async (embedConfig: EmbedConfig) => {
|
|
|
210
249
|
// again and the same JS will execute again.
|
|
211
250
|
const ssoRedirectUrl = embedConfig.noRedirect
|
|
212
251
|
? `${thoughtSpotHost}/v2/#/embed/saml-complete`
|
|
213
|
-
:
|
|
252
|
+
: getRedirectUrl(
|
|
253
|
+
window.location.href,
|
|
254
|
+
SSO_REDIRECTION_MARKER_GUID,
|
|
255
|
+
embedConfig.redirectPath,
|
|
256
|
+
);
|
|
214
257
|
|
|
215
258
|
// bring back the page to the same URL
|
|
216
259
|
const ssoEndPoint = `${EndPoints.SAML_LOGIN_TEMPLATE(
|
|
@@ -218,6 +261,7 @@ export const doSamlAuth = async (embedConfig: EmbedConfig) => {
|
|
|
218
261
|
)}`;
|
|
219
262
|
|
|
220
263
|
await doSSOAuth(embedConfig, ssoEndPoint);
|
|
264
|
+
return loggedInStatus;
|
|
221
265
|
};
|
|
222
266
|
|
|
223
267
|
export const doOIDCAuth = async (embedConfig: EmbedConfig) => {
|
|
@@ -226,7 +270,11 @@ export const doOIDCAuth = async (embedConfig: EmbedConfig) => {
|
|
|
226
270
|
// again and the same JS will execute again.
|
|
227
271
|
const ssoRedirectUrl = embedConfig.noRedirect
|
|
228
272
|
? `${thoughtSpotHost}/v2/#/embed/saml-complete`
|
|
229
|
-
:
|
|
273
|
+
: getRedirectUrl(
|
|
274
|
+
window.location.href,
|
|
275
|
+
SSO_REDIRECTION_MARKER_GUID,
|
|
276
|
+
embedConfig.redirectPath,
|
|
277
|
+
);
|
|
230
278
|
|
|
231
279
|
// bring back the page to the same URL
|
|
232
280
|
const ssoEndPoint = `${EndPoints.OIDC_LOGIN_TEMPLATE(
|
|
@@ -234,13 +282,23 @@ export const doOIDCAuth = async (embedConfig: EmbedConfig) => {
|
|
|
234
282
|
)}`;
|
|
235
283
|
|
|
236
284
|
await doSSOAuth(embedConfig, ssoEndPoint);
|
|
285
|
+
return loggedInStatus;
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
export const logout = async (embedConfig: EmbedConfig): Promise<boolean> => {
|
|
289
|
+
const { thoughtSpotHost } = embedConfig;
|
|
290
|
+
const response = await fetchLogoutService(thoughtSpotHost);
|
|
291
|
+
loggedInStatus = false;
|
|
292
|
+
return loggedInStatus;
|
|
237
293
|
};
|
|
238
294
|
|
|
239
295
|
/**
|
|
240
296
|
* Perform authentication on the ThoughtSpot cluster
|
|
241
297
|
* @param embedConfig The embed configuration
|
|
242
298
|
*/
|
|
243
|
-
export const authenticate = async (
|
|
299
|
+
export const authenticate = async (
|
|
300
|
+
embedConfig: EmbedConfig,
|
|
301
|
+
): Promise<boolean> => {
|
|
244
302
|
const { authType } = embedConfig;
|
|
245
303
|
switch (authType) {
|
|
246
304
|
case AuthType.SSO:
|
|
@@ -252,7 +310,7 @@ export const authenticate = async (embedConfig: EmbedConfig): Promise<void> => {
|
|
|
252
310
|
case AuthType.Basic:
|
|
253
311
|
return doBasicAuth(embedConfig);
|
|
254
312
|
default:
|
|
255
|
-
return Promise.resolve();
|
|
313
|
+
return Promise.resolve(true);
|
|
256
314
|
}
|
|
257
315
|
};
|
|
258
316
|
|
package/src/embed/base.spec.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import EventEmitter from 'eventemitter3';
|
|
2
|
+
import * as auth from '../auth';
|
|
1
3
|
import * as index from '../index';
|
|
4
|
+
import * as base from './base';
|
|
2
5
|
import {
|
|
3
6
|
executeAfterWait,
|
|
4
7
|
getAllIframeEl,
|
|
@@ -9,10 +12,11 @@ import {
|
|
|
9
12
|
} from '../test/test-utils';
|
|
10
13
|
|
|
11
14
|
const thoughtSpotHost = 'tshost';
|
|
15
|
+
let authEE: EventEmitter;
|
|
12
16
|
|
|
13
17
|
describe('Base TS Embed', () => {
|
|
14
18
|
beforeAll(() => {
|
|
15
|
-
index.init({
|
|
19
|
+
authEE = index.init({
|
|
16
20
|
thoughtSpotHost,
|
|
17
21
|
authType: index.AuthType.None,
|
|
18
22
|
});
|
|
@@ -38,10 +42,12 @@ describe('Base TS Embed', () => {
|
|
|
38
42
|
},
|
|
39
43
|
'*',
|
|
40
44
|
);
|
|
41
|
-
|
|
42
|
-
jest.spyOn(window, 'alert').mockImplementation(() =>
|
|
45
|
+
jest.spyOn(window, 'alert').mockReset();
|
|
46
|
+
jest.spyOn(window, 'alert').mockImplementation(() => undefined);
|
|
47
|
+
authEE.on(auth.AuthStatus.FAILURE, (reason) => {
|
|
48
|
+
expect(reason).toEqual(auth.AuthFailureType.NO_COOKIE_ACCESS);
|
|
43
49
|
expect(window.alert).toBeCalledWith(
|
|
44
|
-
'Third party cookie access is blocked on this browser, please allow third party cookies for
|
|
50
|
+
'Third party cookie access is blocked on this browser, please allow third party cookies for this to work properly. \nYou can use `suppressNoCookieAccessAlert` to suppress this message.',
|
|
45
51
|
);
|
|
46
52
|
done();
|
|
47
53
|
});
|
|
@@ -92,4 +98,52 @@ describe('Base TS Embed', () => {
|
|
|
92
98
|
expect(getIFrameSrc()).toContain('disableLoginRedirect=true');
|
|
93
99
|
});
|
|
94
100
|
});
|
|
101
|
+
|
|
102
|
+
test('handleAuth notifies for SDK auth failure', (done) => {
|
|
103
|
+
jest.spyOn(auth, 'authenticate').mockResolvedValue(false);
|
|
104
|
+
const authEmitter = index.init({
|
|
105
|
+
thoughtSpotHost,
|
|
106
|
+
authType: index.AuthType.Basic,
|
|
107
|
+
username: 'test',
|
|
108
|
+
password: 'test',
|
|
109
|
+
});
|
|
110
|
+
authEmitter.on(auth.AuthStatus.FAILURE, (reason) => {
|
|
111
|
+
expect(reason).toBe(auth.AuthFailureType.SDK);
|
|
112
|
+
done();
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('Logout method should disable autoLogin', () => {
|
|
117
|
+
jest.spyOn(window, 'fetch').mockResolvedValue({
|
|
118
|
+
type: 'opaque',
|
|
119
|
+
});
|
|
120
|
+
index.init({
|
|
121
|
+
thoughtSpotHost,
|
|
122
|
+
authType: index.AuthType.None,
|
|
123
|
+
autoLogin: true,
|
|
124
|
+
});
|
|
125
|
+
index.logout();
|
|
126
|
+
expect(window.fetch).toHaveBeenCalledWith(
|
|
127
|
+
`http://${thoughtSpotHost}${auth.EndPoints.LOGOUT}`,
|
|
128
|
+
{
|
|
129
|
+
credentials: 'include',
|
|
130
|
+
headers: {
|
|
131
|
+
'x-requested-by': 'ThoughtSpot',
|
|
132
|
+
},
|
|
133
|
+
method: 'POST',
|
|
134
|
+
},
|
|
135
|
+
);
|
|
136
|
+
expect(base.getEmbedConfig().autoLogin).toBe(false);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
describe('Base without init', () => {
|
|
141
|
+
test('notify should error when called without init', () => {
|
|
142
|
+
base.reset();
|
|
143
|
+
jest.spyOn(global.console, 'error').mockImplementation(() => undefined);
|
|
144
|
+
base.notifyAuthSuccess();
|
|
145
|
+
base.notifyAuthFailure(auth.AuthFailureType.SDK);
|
|
146
|
+
base.notifyLogout();
|
|
147
|
+
expect(global.console.error).toHaveBeenCalledTimes(3);
|
|
148
|
+
});
|
|
95
149
|
});
|