@thoughtspot/visual-embed-sdk 1.29.0-alpha.2 → 1.29.0-alpha.authRefactor
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/cjs/package.json +1 -1
- package/cjs/src/embed/liveboard.js +1 -1
- package/cjs/src/embed/liveboard.js.map +1 -1
- package/cjs/src/utils/sessionInfoService.d.ts +66 -0
- package/cjs/src/utils/sessionInfoService.d.ts.map +1 -0
- package/cjs/src/utils/sessionInfoService.js +92 -0
- package/cjs/src/utils/sessionInfoService.js.map +1 -0
- package/dist/tsembed-react.es.js +2 -2
- package/dist/tsembed-react.js +2 -2
- package/dist/tsembed.es.js +2 -2
- package/dist/tsembed.js +2 -2
- package/lib/package.json +1 -1
- package/lib/src/embed/liveboard.js +1 -1
- package/lib/src/embed/liveboard.js.map +1 -1
- package/lib/src/utils/sessionInfoService.d.ts +66 -0
- package/lib/src/utils/sessionInfoService.d.ts.map +1 -0
- package/lib/src/utils/sessionInfoService.js +85 -0
- package/lib/src/utils/sessionInfoService.js.map +1 -0
- package/package.json +1 -2
- package/src/auth.spec.ts +66 -72
- package/src/auth.ts +43 -59
- package/src/authToken.ts +10 -4
- package/src/embed/app.spec.ts +4 -2
- package/src/embed/base.spec.ts +1 -0
- package/src/embed/base.ts +3 -0
- package/src/embed/embed.spec.ts +2 -0
- package/src/embed/events.spec.ts +2 -0
- package/src/embed/liveboard.spec.ts +2 -0
- package/src/embed/pinboard.spec.ts +2 -0
- package/src/embed/sage.spec.ts +3 -0
- package/src/embed/search.spec.ts +1 -0
- package/src/embed/ts-embed-trigger.spec.ts +3 -0
- package/src/embed/ts-embed.spec.ts +8 -0
- package/src/embed/ts-embed.ts +1 -0
- package/src/index.ts +2 -1
- package/src/mixpanel-service.spec.ts +4 -3
- package/src/mixpanel-service.ts +3 -1
- package/src/react/index.spec.tsx +7 -0
- package/src/react/index.tsx +1 -0
- package/src/utils/authService/authService.spec.ts +9 -4
- package/src/utils/authService/authService.ts +1 -0
- package/src/utils/authService/tokenizedAuthService.ts +38 -8
- package/src/utils/processData.spec.ts +3 -2
- package/src/utils/processData.ts +1 -3
- package/src/utils/sessionInfoService.ts +101 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { getEmbedConfig } from '../embed/embedConfig';
|
|
2
|
+
import { fetchSessionInfoService } from './authService';
|
|
3
|
+
let sessionInfo = null;
|
|
4
|
+
/**
|
|
5
|
+
* Returns the session info object and caches it for future use.
|
|
6
|
+
* Once fetched the session info object is cached and returned from the cache on
|
|
7
|
+
* subsequent calls.
|
|
8
|
+
*
|
|
9
|
+
* @example ```js
|
|
10
|
+
* const sessionInfo = await getSessionInfo();
|
|
11
|
+
* console.log(sessionInfo);
|
|
12
|
+
* ```
|
|
13
|
+
* @version SDK: 1.28.3 | ThoughtSpot: *
|
|
14
|
+
* @returns {Promise<SessionInfo>} The session info object.
|
|
15
|
+
*/
|
|
16
|
+
export async function getSessionInfo() {
|
|
17
|
+
if (!sessionInfo) {
|
|
18
|
+
const host = getEmbedConfig().thoughtSpotHost;
|
|
19
|
+
const sessionResponse = await fetchSessionInfoService(host);
|
|
20
|
+
const processedSessionInfo = getSessionDetails(sessionResponse);
|
|
21
|
+
sessionInfo = processedSessionInfo;
|
|
22
|
+
}
|
|
23
|
+
return sessionInfo;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Returns the cached session info object. If the client is not authenticated the
|
|
27
|
+
* function will return null.
|
|
28
|
+
*
|
|
29
|
+
* @example ```js
|
|
30
|
+
* const sessionInfo = getSessionInfoSync();
|
|
31
|
+
* if (sessionInfo) {
|
|
32
|
+
* console.log(sessionInfo);
|
|
33
|
+
* } else {
|
|
34
|
+
* console.log('Not authenticated');
|
|
35
|
+
* }
|
|
36
|
+
* ```
|
|
37
|
+
* @returns {SessionInfo | null} The session info object.
|
|
38
|
+
* @version SDK: 1.28.3 | ThoughtSpot: *
|
|
39
|
+
*/
|
|
40
|
+
export function getSessionInfoSync() {
|
|
41
|
+
return sessionInfo;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Processes the session info response and returns the session info object.
|
|
45
|
+
*
|
|
46
|
+
* @param sessionInfoResp {any} Response from the session info API.
|
|
47
|
+
* @returns {SessionInfo} The session info object.
|
|
48
|
+
* @example ```js
|
|
49
|
+
* const sessionInfoResp = await fetch(sessionInfoPath);
|
|
50
|
+
* const sessionInfo = getSessionDetails(sessionInfoResp);
|
|
51
|
+
* console.log(sessionInfo);
|
|
52
|
+
* ```
|
|
53
|
+
* @version SDK: 1.28.3 | ThoughtSpot: *
|
|
54
|
+
*/
|
|
55
|
+
export const getSessionDetails = (sessionInfoResp) => {
|
|
56
|
+
const devMixpanelToken = sessionInfoResp.configInfo.mixpanelConfig.devSdkKey;
|
|
57
|
+
const prodMixpanelToken = sessionInfoResp.configInfo.mixpanelConfig.prodSdkKey;
|
|
58
|
+
const mixpanelToken = sessionInfoResp.configInfo.mixpanelConfig.production
|
|
59
|
+
? prodMixpanelToken
|
|
60
|
+
: devMixpanelToken;
|
|
61
|
+
return {
|
|
62
|
+
userGUID: sessionInfoResp.userGUID,
|
|
63
|
+
mixpanelToken,
|
|
64
|
+
isPublicUser: sessionInfoResp.configInfo.isPublicUser,
|
|
65
|
+
releaseVersion: sessionInfoResp.releaseVersion,
|
|
66
|
+
clusterId: sessionInfoResp.configInfo.selfClusterId,
|
|
67
|
+
clusterName: sessionInfoResp.configInfo.selfClusterName,
|
|
68
|
+
...sessionInfoResp,
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Resets the cached session info object and forces a new fetch on the next call.
|
|
73
|
+
*
|
|
74
|
+
* @example ```js
|
|
75
|
+
* resetCachedSessionInfo();
|
|
76
|
+
* const sessionInfo = await getSessionInfo();
|
|
77
|
+
* console.log(sessionInfo);
|
|
78
|
+
* ```
|
|
79
|
+
* @version SDK: 1.28.3 | ThoughtSpot ts7.april.cl, 7.2.1
|
|
80
|
+
* @returns {void}
|
|
81
|
+
*/
|
|
82
|
+
export function resetCachedSessionInfo() {
|
|
83
|
+
sessionInfo = null;
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=sessionInfoService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessionInfoService.js","sourceRoot":"","sources":["../../../src/utils/sessionInfoService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAcxD,IAAI,WAAW,GAAuB,IAAI,CAAC;AAE3C;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc;IAChC,IAAI,CAAC,WAAW,EAAE;QACd,MAAM,IAAI,GAAG,cAAc,EAAE,CAAC,eAAe,CAAC;QAC9C,MAAM,eAAe,GAAG,MAAM,uBAAuB,CAAC,IAAI,CAAC,CAAC;QAC5D,MAAM,oBAAoB,GAAG,iBAAiB,CAAC,eAAe,CAAC,CAAC;QAChE,WAAW,GAAG,oBAAoB,CAAC;KACtC;IACD,OAAO,WAAW,CAAC;AACvB,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,kBAAkB;IAC9B,OAAO,WAAW,CAAC;AACvB,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,eAAoB,EAAe,EAAE;IACnE,MAAM,gBAAgB,GAAG,eAAe,CAAC,UAAU,CAAC,cAAc,CAAC,SAAS,CAAC;IAC7E,MAAM,iBAAiB,GAAG,eAAe,CAAC,UAAU,CAAC,cAAc,CAAC,UAAU,CAAC;IAC/E,MAAM,aAAa,GAAG,eAAe,CAAC,UAAU,CAAC,cAAc,CAAC,UAAU;QACtE,CAAC,CAAC,iBAAiB;QACnB,CAAC,CAAC,gBAAgB,CAAC;IACvB,OAAO;QACH,QAAQ,EAAE,eAAe,CAAC,QAAQ;QAClC,aAAa;QACb,YAAY,EAAE,eAAe,CAAC,UAAU,CAAC,YAAY;QACrD,cAAc,EAAE,eAAe,CAAC,cAAc;QAC9C,SAAS,EAAE,eAAe,CAAC,UAAU,CAAC,aAAa;QACnD,WAAW,EAAE,eAAe,CAAC,UAAU,CAAC,eAAe;QACvD,GAAG,eAAe;KACrB,CAAC;AACN,CAAC,CAAC;AAEF;;;;;;;;;;GAUG;AACH,MAAM,UAAU,sBAAsB;IAClC,WAAW,GAAG,IAAI,CAAC;AACvB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thoughtspot/visual-embed-sdk",
|
|
3
|
-
"version": "1.29.0-alpha.
|
|
3
|
+
"version": "1.29.0-alpha.authRefactor",
|
|
4
4
|
"description": "ThoughtSpot Embed SDK",
|
|
5
5
|
"module": "lib/src/index.js",
|
|
6
6
|
"main": "dist/tsembed.js",
|
|
@@ -64,7 +64,6 @@
|
|
|
64
64
|
"test": "npm run test-sdk && npm run test-docs",
|
|
65
65
|
"posttest": "cat ./coverage/sdk/lcov.info | coveralls",
|
|
66
66
|
"is-publish-allowed": "node scripts/is-publish-allowed.js",
|
|
67
|
-
"prepublishOnly": "npm run is-publish-allowed && npm run test && npm run tsc && npm run bundle-dts-file && npm run bundle-dts && npm run bundle-dts-react && npm run bundle-dts-react-full && npm run build",
|
|
68
67
|
"check-size": "npm run build && size-limit",
|
|
69
68
|
"publish-dev": "npm publish --tag dev",
|
|
70
69
|
"publish-prod": "npm publish --tag latest"
|
package/src/auth.spec.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import * as authInstance from './auth';
|
|
2
|
-
import * as
|
|
3
|
-
import * as tokenAuthService from './utils/authService/tokenizedAuthService';
|
|
4
|
-
import * as checkReleaseVersionInBetaInstance from './utils';
|
|
5
|
-
import * as mixPanelService from './mixpanel-service';
|
|
2
|
+
import * as authTokenService from './authToken';
|
|
6
3
|
import * as EmbedConfig from './embed/embedConfig';
|
|
7
|
-
import
|
|
4
|
+
import * as mixPanelService from './mixpanel-service';
|
|
8
5
|
import { executeAfterWait } from './test/test-utils';
|
|
9
|
-
import {
|
|
6
|
+
import { AuthType, EmbedEvent } from './types';
|
|
7
|
+
import * as checkReleaseVersionInBetaInstance from './utils';
|
|
8
|
+
import * as authService from './utils/authService/authService';
|
|
9
|
+
import * as tokenAuthService from './utils/authService/tokenizedAuthService';
|
|
10
|
+
import * as SessionService from './utils/sessionInfoService';
|
|
10
11
|
|
|
11
12
|
const thoughtSpotHost = 'http://localhost:3000';
|
|
12
13
|
const username = 'tsuser';
|
|
@@ -18,7 +19,7 @@ export const embedConfig: any = {
|
|
|
18
19
|
thoughtSpotHost,
|
|
19
20
|
username,
|
|
20
21
|
authEndpoint: 'auth',
|
|
21
|
-
authType: AuthType.
|
|
22
|
+
authType: AuthType.TrustedAuthToken,
|
|
22
23
|
getAuthToken: jest.fn(() => Promise.resolve(token)),
|
|
23
24
|
}),
|
|
24
25
|
doTokenAuthWithCookieDetect: {
|
|
@@ -112,31 +113,47 @@ export const mockSessionInfo = {
|
|
|
112
113
|
},
|
|
113
114
|
};
|
|
114
115
|
|
|
116
|
+
export const mockSessionInfoApiResponse = {
|
|
117
|
+
userGUID: '1234',
|
|
118
|
+
releaseVersion: 'test',
|
|
119
|
+
configInfo: {
|
|
120
|
+
isPublicUser: false,
|
|
121
|
+
mixpanelConfig: {
|
|
122
|
+
production: true,
|
|
123
|
+
devSdkKey: 'devKey',
|
|
124
|
+
prodSdkKey: 'prodKey',
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
|
|
115
129
|
describe('Unit test for auth', () => {
|
|
116
130
|
beforeEach(() => {
|
|
131
|
+
jest.resetAllMocks();
|
|
117
132
|
global.fetch = window.fetch;
|
|
118
133
|
});
|
|
119
134
|
afterEach(() => {
|
|
120
|
-
resetCachedAuthToken();
|
|
135
|
+
authTokenService.resetCachedAuthToken();
|
|
136
|
+
SessionService.resetCachedSessionInfo();
|
|
121
137
|
});
|
|
122
138
|
test('endpoints, SAML_LOGIN_TEMPLATE', () => {
|
|
123
139
|
const ssoTemplateUrl = authService.EndPoints.SAML_LOGIN_TEMPLATE(thoughtSpotHost);
|
|
124
140
|
expect(ssoTemplateUrl).toBe(`/callosum/v1/saml/login?targetURLPath=${thoughtSpotHost}`);
|
|
125
141
|
});
|
|
126
142
|
|
|
127
|
-
test('when session info giving response', async () => {
|
|
128
|
-
jest.spyOn(
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
expect(sessionInfo).
|
|
143
|
+
test('when session info giving response, it is cached', async () => {
|
|
144
|
+
jest.spyOn(tokenAuthService, 'fetchSessionInfoService').mockResolvedValueOnce(mockSessionInfoApiResponse);
|
|
145
|
+
const sessionInfo = await SessionService.getSessionInfo();
|
|
146
|
+
expect(sessionInfo.mixpanelToken).toEqual('prodKey');
|
|
147
|
+
expect(sessionInfo.isPublicUser).toEqual(false);
|
|
148
|
+
await SessionService.getSessionInfo();
|
|
149
|
+
expect(tokenAuthService.fetchSessionInfoService).toHaveBeenCalledTimes(1);
|
|
132
150
|
});
|
|
133
151
|
|
|
134
152
|
test('Disable mixpanel when disableSDKTracking flag is set', () => {
|
|
135
|
-
jest.clearAllMocks();
|
|
136
|
-
jest.resetAllMocks();
|
|
137
153
|
jest.spyOn(mixPanelService, 'initMixpanel');
|
|
154
|
+
jest.spyOn(SessionService, 'getSessionInfo').mockReturnValue(mockSessionInfo);
|
|
138
155
|
jest.spyOn(EmbedConfig, 'getEmbedConfig').mockReturnValue({ disableSDKTracking: true });
|
|
139
|
-
authInstance.
|
|
156
|
+
authInstance.postLoginService();
|
|
140
157
|
expect(mixPanelService.initMixpanel).not.toBeCalled();
|
|
141
158
|
});
|
|
142
159
|
|
|
@@ -163,28 +180,22 @@ describe('Unit test for auth', () => {
|
|
|
163
180
|
});
|
|
164
181
|
|
|
165
182
|
test('doTokenAuth: when user is loggedIn', async () => {
|
|
166
|
-
jest.spyOn(
|
|
167
|
-
|
|
168
|
-
status: 200,
|
|
169
|
-
}));
|
|
170
|
-
jest.spyOn(authInstance, 'getSessionDetails').mockReturnValue(mockSessionInfo);
|
|
171
|
-
jest.spyOn(authInstance, 'initSession').mockReturnValue(null);
|
|
183
|
+
const getAuthenticationTokenMock = jest.spyOn(authTokenService, 'getAuthenticationToken');
|
|
184
|
+
jest.spyOn(tokenAuthService, 'isActiveService').mockImplementation(async () => true);
|
|
172
185
|
await authInstance.doTokenAuth(embedConfig.doTokenAuthSuccess('authToken'));
|
|
173
|
-
expect(
|
|
186
|
+
expect(authTokenService.getAuthenticationToken).not.toBeCalled();
|
|
174
187
|
expect(authInstance.loggedInStatus).toBe(true);
|
|
188
|
+
getAuthenticationTokenMock.mockRestore();
|
|
175
189
|
});
|
|
176
190
|
|
|
177
191
|
test('doTokenAuth: when user is not loggedIn & getAuthToken have response', async () => {
|
|
178
|
-
jest.spyOn(tokenAuthService, '
|
|
179
|
-
jest.spyOn(authService, 'fetchAuthTokenService').mockImplementation(() => ({
|
|
180
|
-
text: () => Promise.resolve('abc'),
|
|
181
|
-
}));
|
|
192
|
+
jest.spyOn(tokenAuthService, 'isActiveService').mockImplementation(async () => false);
|
|
182
193
|
jest.spyOn(authService, 'fetchAuthService').mockImplementation(() => Promise.resolve({
|
|
183
194
|
status: 200,
|
|
195
|
+
ok: true,
|
|
184
196
|
}));
|
|
185
197
|
jest.spyOn(authService, 'verifyTokenService').mockResolvedValueOnce(true);
|
|
186
198
|
await authInstance.doTokenAuth(embedConfig.doTokenAuthSuccess('authToken2'));
|
|
187
|
-
expect(tokenAuthService.fetchSessionInfoService).toBeCalled();
|
|
188
199
|
expect(authService.fetchAuthService).toBeCalledWith(
|
|
189
200
|
thoughtSpotHost,
|
|
190
201
|
username,
|
|
@@ -193,26 +204,25 @@ describe('Unit test for auth', () => {
|
|
|
193
204
|
});
|
|
194
205
|
|
|
195
206
|
test('doTokenAuth: when user is not loggedIn & getAuthToken not present, isLoggedIn should called', async () => {
|
|
196
|
-
jest.spyOn(tokenAuthService, '
|
|
197
|
-
jest.spyOn(authService, 'fetchAuthTokenService').mockImplementation(() =>
|
|
207
|
+
jest.spyOn(tokenAuthService, 'isActiveService').mockImplementation(async () => false);
|
|
208
|
+
jest.spyOn(authService, 'fetchAuthTokenService').mockImplementation(() => ({
|
|
209
|
+
text: () => Promise.resolve('abc'),
|
|
210
|
+
}));
|
|
198
211
|
jest.spyOn(authService, 'fetchAuthService').mockImplementation(() => Promise.resolve({
|
|
199
212
|
status: 200,
|
|
200
213
|
ok: true,
|
|
201
214
|
}));
|
|
202
215
|
jest.spyOn(authService, 'verifyTokenService').mockResolvedValueOnce(true);
|
|
203
216
|
await authInstance.doTokenAuth(embedConfig.doTokenAuthFailureWithoutGetAuthToken);
|
|
217
|
+
expect(authService.fetchAuthTokenService).toBeCalledWith('auth');
|
|
204
218
|
await executeAfterWait(() => {
|
|
205
219
|
expect(authInstance.loggedInStatus).toBe(true);
|
|
206
|
-
expect(
|
|
207
|
-
expect(authService.fetchAuthService).toBeCalledWith(
|
|
208
|
-
thoughtSpotHost,
|
|
209
|
-
username,
|
|
210
|
-
'authToken2',
|
|
211
|
-
);
|
|
220
|
+
expect(authService.fetchAuthService).toBeCalledWith(thoughtSpotHost, username, 'abc');
|
|
212
221
|
});
|
|
213
222
|
});
|
|
214
223
|
|
|
215
224
|
test('doTokenAuth: Should raise error when duplicate token is used', async () => {
|
|
225
|
+
jest.spyOn(tokenAuthService, 'isActiveService').mockImplementation(async () => false);
|
|
216
226
|
jest.spyOn(tokenAuthService, 'fetchSessionInfoService').mockResolvedValue({
|
|
217
227
|
status: 401,
|
|
218
228
|
});
|
|
@@ -254,8 +264,10 @@ describe('Unit test for auth', () => {
|
|
|
254
264
|
ok: true,
|
|
255
265
|
}));
|
|
256
266
|
jest.spyOn(authService, 'verifyTokenService').mockResolvedValueOnce(true);
|
|
267
|
+
jest.spyOn(tokenAuthService, 'isActiveService').mockResolvedValueOnce(false);
|
|
268
|
+
jest.spyOn(tokenAuthService, 'isActiveService').mockResolvedValueOnce(false);
|
|
257
269
|
const isLoggedIn = await authInstance.doTokenAuth(embedConfig.doTokenAuthWithCookieDetect);
|
|
258
|
-
expect(tokenAuthService.
|
|
270
|
+
expect(tokenAuthService.isActiveService).toHaveBeenCalledTimes(2);
|
|
259
271
|
expect(isLoggedIn).toBe(false);
|
|
260
272
|
});
|
|
261
273
|
|
|
@@ -278,7 +290,6 @@ describe('Unit test for auth', () => {
|
|
|
278
290
|
expect(await authInstance.doTokenAuth(embedConfig.doTokenAuthSuccess('authToken2'))).toBe(
|
|
279
291
|
true,
|
|
280
292
|
);
|
|
281
|
-
expect(tokenAuthService.fetchSessionInfoService).toBeCalled();
|
|
282
293
|
expect(authService.fetchAuthPostService).toBeCalledWith(
|
|
283
294
|
thoughtSpotHost,
|
|
284
295
|
username,
|
|
@@ -297,20 +308,9 @@ describe('Unit test for auth', () => {
|
|
|
297
308
|
});
|
|
298
309
|
|
|
299
310
|
it('when user is loggedIn', async () => {
|
|
300
|
-
spyOn(
|
|
301
|
-
jest.spyOn(authInstance, 'getSessionDetails').mockReturnValue(mockSessionInfo);
|
|
302
|
-
jest.spyOn(authInstance, 'initSession').mockReturnValue(null);
|
|
303
|
-
jest.spyOn(tokenAuthService, 'fetchSessionInfoService').mockImplementation(
|
|
304
|
-
async () => ({
|
|
305
|
-
json: () => mockSessionInfo,
|
|
306
|
-
status: 200,
|
|
307
|
-
}),
|
|
308
|
-
);
|
|
311
|
+
jest.spyOn(tokenAuthService, 'isActiveService').mockResolvedValueOnce(true);
|
|
309
312
|
await authInstance.doBasicAuth(embedConfig.doBasicAuth);
|
|
310
|
-
expect(tokenAuthService.fetchSessionInfoService).toBeCalled();
|
|
311
313
|
expect(authInstance.loggedInStatus).toBe(true);
|
|
312
|
-
expect(authInstance.getSessionDetails).toBeCalled();
|
|
313
|
-
expect(authInstance.initSession).toBeCalled();
|
|
314
314
|
});
|
|
315
315
|
|
|
316
316
|
it('when user is not loggedIn', async () => {
|
|
@@ -321,7 +321,7 @@ describe('Unit test for auth', () => {
|
|
|
321
321
|
}));
|
|
322
322
|
|
|
323
323
|
await authInstance.doBasicAuth(embedConfig.doBasicAuth);
|
|
324
|
-
expect(tokenAuthService.fetchSessionInfoService).toBeCalled();
|
|
324
|
+
// expect(tokenAuthService.fetchSessionInfoService).toBeCalled();
|
|
325
325
|
expect(authService.fetchBasicAuthService).toBeCalled();
|
|
326
326
|
expect(authInstance.loggedInStatus).toBe(true);
|
|
327
327
|
});
|
|
@@ -349,10 +349,8 @@ describe('Unit test for auth', () => {
|
|
|
349
349
|
status: 200,
|
|
350
350
|
}),
|
|
351
351
|
);
|
|
352
|
-
jest.spyOn(
|
|
353
|
-
jest.spyOn(authInstance, 'initSession').mockReturnValue(null);
|
|
352
|
+
jest.spyOn(tokenAuthService, 'isActiveService').mockReturnValue(true);
|
|
354
353
|
await authInstance.doSamlAuth(embedConfig.doSamlAuth);
|
|
355
|
-
expect(tokenAuthService.fetchSessionInfoService).toBeCalled();
|
|
356
354
|
expect(window.location.hash).toBe('');
|
|
357
355
|
expect(authInstance.loggedInStatus).toBe(true);
|
|
358
356
|
});
|
|
@@ -360,7 +358,6 @@ describe('Unit test for auth', () => {
|
|
|
360
358
|
it('when user is not loggedIn & isAtSSORedirectUrl is true', async () => {
|
|
361
359
|
jest.spyOn(tokenAuthService, 'fetchSessionInfoService').mockImplementation(() => Promise.reject());
|
|
362
360
|
await authInstance.doSamlAuth(embedConfig.doSamlAuth);
|
|
363
|
-
expect(tokenAuthService.fetchSessionInfoService).toBeCalled();
|
|
364
361
|
expect(window.location.hash).toBe('');
|
|
365
362
|
expect(authInstance.loggedInStatus).toBe(false);
|
|
366
363
|
});
|
|
@@ -374,7 +371,6 @@ describe('Unit test for auth', () => {
|
|
|
374
371
|
});
|
|
375
372
|
jest.spyOn(tokenAuthService, 'fetchSessionInfoService').mockImplementation(() => Promise.reject());
|
|
376
373
|
await authInstance.doSamlAuth(embedConfig.doSamlAuth);
|
|
377
|
-
expect(tokenAuthService.fetchSessionInfoService).toBeCalled();
|
|
378
374
|
expect(global.window.location.href).toBe(samalLoginUrl);
|
|
379
375
|
});
|
|
380
376
|
|
|
@@ -387,14 +383,9 @@ describe('Unit test for auth', () => {
|
|
|
387
383
|
});
|
|
388
384
|
spyOn(authInstance, 'samlCompletionPromise');
|
|
389
385
|
global.window.open = jest.fn();
|
|
390
|
-
jest.spyOn(tokenAuthService, '
|
|
391
|
-
.
|
|
392
|
-
.
|
|
393
|
-
json: () => mockSessionInfo,
|
|
394
|
-
status: 200,
|
|
395
|
-
}));
|
|
396
|
-
jest.spyOn(authInstance, 'getSessionDetails').mockReturnValue(mockSessionInfo);
|
|
397
|
-
jest.spyOn(authInstance, 'initSession').mockReturnValue(null);
|
|
386
|
+
jest.spyOn(tokenAuthService, 'isActiveService')
|
|
387
|
+
.mockReturnValueOnce(false)
|
|
388
|
+
.mockReturnValueOnce(true);
|
|
398
389
|
expect(await authInstance.samlCompletionPromise).not.toBe(null);
|
|
399
390
|
expect(
|
|
400
391
|
await authInstance.doSamlAuth({
|
|
@@ -404,15 +395,13 @@ describe('Unit test for auth', () => {
|
|
|
404
395
|
document.getElementById('ts-auth-btn').click();
|
|
405
396
|
window.postMessage({ type: EmbedEvent.SAMLComplete }, '*');
|
|
406
397
|
await authInstance.samlCompletionPromise;
|
|
407
|
-
expect(
|
|
408
|
-
expect(authInstance.getSessionDetails).toBeCalled();
|
|
409
|
-
expect(authInstance.initSession).toBeCalled();
|
|
398
|
+
expect(authInstance.loggedInStatus).toBe(true);
|
|
410
399
|
});
|
|
411
400
|
});
|
|
412
401
|
|
|
413
402
|
describe('doOIDCAuth', () => {
|
|
414
403
|
afterEach(() => {
|
|
415
|
-
resetCachedAuthToken();
|
|
404
|
+
authTokenService.resetCachedAuthToken();
|
|
416
405
|
delete global.window;
|
|
417
406
|
global.window = Object.create(originalWindow);
|
|
418
407
|
global.window.open = jest.fn();
|
|
@@ -422,7 +411,6 @@ describe('Unit test for auth', () => {
|
|
|
422
411
|
it('when user is not loggedIn & isAtSSORedirectUrl is true', async () => {
|
|
423
412
|
jest.spyOn(tokenAuthService, 'fetchSessionInfoService').mockImplementation(() => Promise.reject());
|
|
424
413
|
await authInstance.doOIDCAuth(embedConfig.doOidcAuth);
|
|
425
|
-
expect(tokenAuthService.fetchSessionInfoService).toBeCalled();
|
|
426
414
|
expect(window.location.hash).toBe('');
|
|
427
415
|
expect(authInstance.loggedInStatus).toBe(false);
|
|
428
416
|
});
|
|
@@ -465,6 +453,7 @@ describe('Unit test for auth', () => {
|
|
|
465
453
|
|
|
466
454
|
it('authenticate: when authType is Basic', async () => {
|
|
467
455
|
jest.spyOn(authInstance, 'doBasicAuth');
|
|
456
|
+
jest.spyOn(authService, 'fetchBasicAuthService').mockImplementation(() => Promise.resolve({ status: 200, ok: true }));
|
|
468
457
|
await authInstance.authenticate(embedConfig.basicAuthSuccess);
|
|
469
458
|
expect(authInstance.doBasicAuth).toBeCalled();
|
|
470
459
|
expect(authInstance.loggedInStatus).toBe(true);
|
|
@@ -481,6 +470,7 @@ describe('Unit test for auth', () => {
|
|
|
481
470
|
});
|
|
482
471
|
|
|
483
472
|
it('doCookielessTokenAuth should resolve to true if valid token is passed', async () => {
|
|
473
|
+
jest.clearAllMocks();
|
|
484
474
|
jest.spyOn(authService, 'verifyTokenService').mockResolvedValueOnce(true);
|
|
485
475
|
const isLoggedIn = await authInstance.doCookielessTokenAuth(
|
|
486
476
|
embedConfig.doCookielessAuth('testToken'),
|
|
@@ -500,11 +490,11 @@ describe('Unit test for auth', () => {
|
|
|
500
490
|
authInstance.setAuthEE(testObject as any);
|
|
501
491
|
expect(authInstance.getAuthEE()).toBe(testObject);
|
|
502
492
|
});
|
|
503
|
-
it('getSessionDetails returns the correct details given sessionInfo', () => {
|
|
493
|
+
it('getSessionDetails returns the correct details given sessionInfo', async () => {
|
|
504
494
|
jest.clearAllMocks();
|
|
505
495
|
jest.restoreAllMocks();
|
|
506
496
|
|
|
507
|
-
|
|
497
|
+
jest.spyOn(tokenAuthService, 'fetchSessionInfoService').mockReturnValue({
|
|
508
498
|
userGUID: '1234',
|
|
509
499
|
releaseVersion: '1',
|
|
510
500
|
configInfo: {
|
|
@@ -515,13 +505,14 @@ describe('Unit test for auth', () => {
|
|
|
515
505
|
},
|
|
516
506
|
},
|
|
517
507
|
});
|
|
508
|
+
const details = await SessionService.getSessionInfo();
|
|
518
509
|
expect(details).toEqual(
|
|
519
510
|
expect.objectContaining({
|
|
520
511
|
mixpanelToken: 'devKey',
|
|
521
512
|
}),
|
|
522
513
|
);
|
|
523
514
|
|
|
524
|
-
|
|
515
|
+
jest.spyOn(tokenAuthService, 'fetchSessionInfoService').mockReturnValue({
|
|
525
516
|
configInfo: {
|
|
526
517
|
mixpanelConfig: {
|
|
527
518
|
devSdkKey: 'devKey',
|
|
@@ -530,6 +521,9 @@ describe('Unit test for auth', () => {
|
|
|
530
521
|
},
|
|
531
522
|
},
|
|
532
523
|
});
|
|
524
|
+
|
|
525
|
+
SessionService.resetCachedSessionInfo();
|
|
526
|
+
const details2 = await SessionService.getSessionInfo();
|
|
533
527
|
expect(details2).toEqual(
|
|
534
528
|
expect.objectContaining({
|
|
535
529
|
mixpanelToken: 'prodKey',
|
package/src/auth.ts
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
import EventEmitter from 'eventemitter3';
|
|
2
|
+
import { getAuthenticationToken, resetCachedAuthToken } from './authToken';
|
|
3
|
+
import { getEmbedConfig } from './embed/embedConfig';
|
|
2
4
|
import { initMixpanel } from './mixpanel-service';
|
|
3
5
|
import {
|
|
4
|
-
AuthType, DOMSelector, EmbedConfig, EmbedEvent,
|
|
6
|
+
AuthType, DOMSelector, EmbedConfig, EmbedEvent,
|
|
5
7
|
} from './types';
|
|
6
8
|
import { getDOMNode, getRedirectUrl } from './utils';
|
|
7
9
|
import {
|
|
8
|
-
|
|
10
|
+
EndPoints,
|
|
11
|
+
fetchAuthPostService,
|
|
9
12
|
fetchAuthService,
|
|
10
13
|
fetchBasicAuthService,
|
|
11
14
|
fetchLogoutService,
|
|
12
|
-
fetchAuthPostService,
|
|
13
|
-
EndPoints,
|
|
14
15
|
} from './utils/authService';
|
|
15
|
-
import {
|
|
16
|
+
import { isActiveService } from './utils/authService/tokenizedAuthService';
|
|
16
17
|
import { logger } from './utils/logger';
|
|
17
|
-
import {
|
|
18
|
+
import { getSessionInfo, getSessionInfoSync } from './utils/sessionInfoService';
|
|
18
19
|
|
|
19
20
|
// eslint-disable-next-line import/no-mutable-exports
|
|
20
21
|
export let loggedInStatus = false;
|
|
@@ -22,11 +23,7 @@ export let loggedInStatus = false;
|
|
|
22
23
|
export let samlAuthWindow: Window = null;
|
|
23
24
|
// eslint-disable-next-line import/no-mutable-exports
|
|
24
25
|
export let samlCompletionPromise: Promise<void> = null;
|
|
25
|
-
|
|
26
|
-
let sessionInfoResolver: (value: sessionInfoInterface) => void = null;
|
|
27
|
-
const sessionInfoPromise = new Promise((resolve: (value: sessionInfoInterface) => void) => {
|
|
28
|
-
sessionInfoResolver = resolve;
|
|
29
|
-
});
|
|
26
|
+
|
|
30
27
|
let releaseVersion = '';
|
|
31
28
|
|
|
32
29
|
export const SSO_REDIRECTION_MARKER_GUID = '5e16222e-ef02-43e9-9fbd-24226bf3ce5b';
|
|
@@ -185,6 +182,10 @@ export function notifyAuthSuccess(): void {
|
|
|
185
182
|
logger.error('SDK not initialized');
|
|
186
183
|
return;
|
|
187
184
|
}
|
|
185
|
+
const sessionInfo = getSessionInfoSync();
|
|
186
|
+
if (!sessionInfo) {
|
|
187
|
+
logger.error('Session info not available');
|
|
188
|
+
}
|
|
188
189
|
authEE.emit(AuthStatus.SUCCESS, sessionInfo);
|
|
189
190
|
}
|
|
190
191
|
|
|
@@ -211,70 +212,46 @@ export function notifyLogout(): void {
|
|
|
211
212
|
authEE.emit(AuthStatus.LOGOUT);
|
|
212
213
|
}
|
|
213
214
|
|
|
214
|
-
export const initSession = (sessionDetails: sessionInfoInterface) => {
|
|
215
|
-
const embedConfig = getEmbedConfig();
|
|
216
|
-
if (sessionInfo == null) {
|
|
217
|
-
sessionInfo = sessionDetails;
|
|
218
|
-
if (!embedConfig.disableSDKTracking) {
|
|
219
|
-
initMixpanel(sessionInfo);
|
|
220
|
-
}
|
|
221
|
-
sessionInfoResolver(sessionInfo);
|
|
222
|
-
}
|
|
223
|
-
};
|
|
224
|
-
|
|
225
|
-
export const getSessionDetails = (sessionInfoResp: any): sessionInfoInterface => {
|
|
226
|
-
const devMixpanelToken = sessionInfoResp.configInfo.mixpanelConfig.devSdkKey;
|
|
227
|
-
const prodMixpanelToken = sessionInfoResp.configInfo.mixpanelConfig.prodSdkKey;
|
|
228
|
-
const mixpanelToken = sessionInfoResp.configInfo.mixpanelConfig.production
|
|
229
|
-
? prodMixpanelToken
|
|
230
|
-
: devMixpanelToken;
|
|
231
|
-
return {
|
|
232
|
-
userGUID: sessionInfoResp.userGUID,
|
|
233
|
-
mixpanelToken,
|
|
234
|
-
isPublicUser: sessionInfoResp.configInfo.isPublicUser,
|
|
235
|
-
releaseVersion: sessionInfoResp.releaseVersion,
|
|
236
|
-
clusterId: sessionInfoResp.configInfo.selfClusterId,
|
|
237
|
-
clusterName: sessionInfoResp.configInfo.selfClusterName,
|
|
238
|
-
...sessionInfoResp,
|
|
239
|
-
};
|
|
240
|
-
};
|
|
241
|
-
|
|
242
215
|
/**
|
|
243
216
|
* Check if we are logged into the ThoughtSpot cluster
|
|
244
217
|
*
|
|
245
218
|
* @param thoughtSpotHost The ThoughtSpot cluster hostname or IP
|
|
246
219
|
*/
|
|
247
220
|
async function isLoggedIn(thoughtSpotHost: string): Promise<boolean> {
|
|
248
|
-
const authVerificationUrl = `${thoughtSpotHost}${EndPoints.AUTH_VERIFICATION}`;
|
|
249
|
-
let response = null;
|
|
250
221
|
try {
|
|
251
|
-
response = await
|
|
252
|
-
|
|
253
|
-
const sessionDetails = getSessionDetails(sessionInfoResp);
|
|
254
|
-
// Store user session details from session info
|
|
255
|
-
initSession(sessionDetails);
|
|
256
|
-
releaseVersion = sessionInfoResp.releaseVersion;
|
|
222
|
+
const response = await isActiveService(thoughtSpotHost);
|
|
223
|
+
return response;
|
|
257
224
|
} catch (e) {
|
|
258
225
|
return false;
|
|
259
226
|
}
|
|
260
|
-
return response.status === 200;
|
|
261
227
|
}
|
|
262
228
|
|
|
263
229
|
/**
|
|
264
|
-
*
|
|
230
|
+
* Services to be called after the login is successful,
|
|
231
|
+
* This should be called after the cookie is set for cookie auth or
|
|
232
|
+
* after the token is set for cookieless.
|
|
233
|
+
*
|
|
234
|
+
* @return {Promise<void>}
|
|
235
|
+
* @example
|
|
236
|
+
* ```js
|
|
237
|
+
* await postLoginService();
|
|
238
|
+
* ```
|
|
239
|
+
* @version SDK: 1.28.3 | ThoughtSpot: *
|
|
265
240
|
*/
|
|
266
|
-
export function
|
|
267
|
-
|
|
241
|
+
export async function postLoginService(): Promise<void> {
|
|
242
|
+
const sessionInfo = await getSessionInfo();
|
|
243
|
+
releaseVersion = sessionInfo.releaseVersion;
|
|
244
|
+
const embedConfig = getEmbedConfig();
|
|
245
|
+
if (!embedConfig.disableSDKTracking) {
|
|
246
|
+
initMixpanel(sessionInfo);
|
|
247
|
+
}
|
|
268
248
|
}
|
|
269
249
|
|
|
270
250
|
/**
|
|
271
|
-
* Return
|
|
272
|
-
* authentication is successful. And info is available.
|
|
273
|
-
*
|
|
274
|
-
* @group Global methods
|
|
251
|
+
* Return releaseVersion if available
|
|
275
252
|
*/
|
|
276
|
-
export function
|
|
277
|
-
return
|
|
253
|
+
export function getReleaseVersion() {
|
|
254
|
+
return releaseVersion;
|
|
278
255
|
}
|
|
279
256
|
|
|
280
257
|
/**
|
|
@@ -309,8 +286,15 @@ export const doTokenAuth = async (embedConfig: EmbedConfig): Promise<boolean> =>
|
|
|
309
286
|
throw new Error('Either auth endpoint or getAuthToken function must be provided');
|
|
310
287
|
}
|
|
311
288
|
loggedInStatus = await isLoggedIn(thoughtSpotHost);
|
|
289
|
+
|
|
312
290
|
if (!loggedInStatus) {
|
|
313
|
-
|
|
291
|
+
let authToken: string;
|
|
292
|
+
try {
|
|
293
|
+
authToken = await getAuthenticationToken(embedConfig);
|
|
294
|
+
} catch (e) {
|
|
295
|
+
loggedInStatus = false;
|
|
296
|
+
throw e;
|
|
297
|
+
}
|
|
314
298
|
let resp;
|
|
315
299
|
try {
|
|
316
300
|
resp = await fetchAuthPostService(thoughtSpotHost, username, authToken);
|
|
@@ -492,7 +476,7 @@ export const logout = async (embedConfig: EmbedConfig): Promise<boolean> => {
|
|
|
492
476
|
const { thoughtSpotHost } = embedConfig;
|
|
493
477
|
await fetchLogoutService(thoughtSpotHost);
|
|
494
478
|
resetCachedAuthToken();
|
|
495
|
-
const thoughtspotIframes = document.querySelectorAll(
|
|
479
|
+
const thoughtspotIframes = document.querySelectorAll("[data-ts-iframe='true']");
|
|
496
480
|
if (thoughtspotIframes?.length) {
|
|
497
481
|
thoughtspotIframes.forEach((el) => {
|
|
498
482
|
el.parentElement.innerHTML = embedConfig.loginFailedMessage;
|
package/src/authToken.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { EmbedConfig } from './types';
|
|
2
2
|
import { fetchAuthTokenService, verifyTokenService } from './utils/authService/authService';
|
|
3
|
+
import { logger } from './utils/logger';
|
|
3
4
|
|
|
4
5
|
const DUPLICATE_TOKEN_ERR = 'Duplicate token, please issue a new token every time getAuthToken callback is called.'
|
|
5
6
|
+ 'See https://developers.thoughtspot.com/docs/?pageid=embed-auth#trusted-auth-embed for more details.';
|
|
@@ -9,7 +10,7 @@ const INVALID_TOKEN_ERR = 'Invalid token received form token callback or authTok
|
|
|
9
10
|
let cachedAuthToken: string | null = null;
|
|
10
11
|
|
|
11
12
|
// This method can be used to get the authToken using the embedConfig
|
|
12
|
-
export
|
|
13
|
+
export async function getAuthenticationToken(embedConfig: EmbedConfig): Promise<string> {
|
|
13
14
|
if (cachedAuthToken) {
|
|
14
15
|
let isCachedTokenStillValid;
|
|
15
16
|
try {
|
|
@@ -31,12 +32,17 @@ export const getAuthenticationToken = async (embedConfig: EmbedConfig): Promise<
|
|
|
31
32
|
authToken = await response.text();
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
try {
|
|
36
|
+
// this will throw error if the token is not valid
|
|
37
|
+
await validateAuthToken(embedConfig, authToken);
|
|
38
|
+
} catch (e) {
|
|
39
|
+
logger.error(`Received invalid token from getAuthToken callback or authToken endpoint. Error : ${e.message}`);
|
|
40
|
+
throw e;
|
|
41
|
+
}
|
|
36
42
|
|
|
37
43
|
cachedAuthToken = authToken;
|
|
38
44
|
return authToken;
|
|
39
|
-
}
|
|
45
|
+
}
|
|
40
46
|
|
|
41
47
|
const validateAuthToken = async (
|
|
42
48
|
embedConfig: EmbedConfig,
|