@pega/auth 0.1.0

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.
Files changed (40) hide show
  1. package/LICENSE +201 -0
  2. package/lib/auth-code-redirect.d.ts +2 -0
  3. package/lib/auth-code-redirect.d.ts.map +1 -0
  4. package/lib/auth-code-redirect.js +2 -0
  5. package/lib/auth-code-redirect.js.map +1 -0
  6. package/lib/index.d.ts +2 -0
  7. package/lib/index.d.ts.map +1 -0
  8. package/lib/index.js +2 -0
  9. package/lib/index.js.map +1 -0
  10. package/lib/oauth-client/auth.d.ts +18 -0
  11. package/lib/oauth-client/auth.d.ts.map +1 -0
  12. package/lib/oauth-client/auth.js +760 -0
  13. package/lib/oauth-client/auth.js.map +1 -0
  14. package/lib/oauth-client/authCodeDone.d.ts +2 -0
  15. package/lib/oauth-client/authCodeDone.d.ts.map +1 -0
  16. package/lib/oauth-client/authCodeDone.js +84 -0
  17. package/lib/oauth-client/authCodeDone.js.map +1 -0
  18. package/lib/oauth-client/authDone.d.ts +2 -0
  19. package/lib/oauth-client/authDone.d.ts.map +1 -0
  20. package/lib/oauth-client/authDone.html +10 -0
  21. package/lib/oauth-client/authDone.js +87 -0
  22. package/lib/oauth-client/authDone.js.map +1 -0
  23. package/lib/oauth-client/lock-closed-solid.svg +5 -0
  24. package/lib/sdk-auth-manager/authManager.d.ts +21 -0
  25. package/lib/sdk-auth-manager/authManager.d.ts.map +1 -0
  26. package/lib/sdk-auth-manager/authManager.js +873 -0
  27. package/lib/sdk-auth-manager/authManager.js.map +1 -0
  28. package/lib/sdk-auth-manager/common-utils.d.ts +2 -0
  29. package/lib/sdk-auth-manager/common-utils.d.ts.map +1 -0
  30. package/lib/sdk-auth-manager/common-utils.js +4 -0
  31. package/lib/sdk-auth-manager/common-utils.js.map +1 -0
  32. package/lib/sdk-auth-manager/config_access.d.ts +3 -0
  33. package/lib/sdk-auth-manager/config_access.d.ts.map +1 -0
  34. package/lib/sdk-auth-manager/config_access.js +170 -0
  35. package/lib/sdk-auth-manager/config_access.js.map +1 -0
  36. package/lib/sdk-auth-manager.d.ts +3 -0
  37. package/lib/sdk-auth-manager.d.ts.map +1 -0
  38. package/lib/sdk-auth-manager.js +3 -0
  39. package/lib/sdk-auth-manager.js.map +1 -0
  40. package/package.json +54 -0
@@ -0,0 +1,873 @@
1
+ // This file wraps various calls related to logging in, logging out, etc.
2
+ // that use the auth.html/auth.js to do the work of logging in via OAuth 2.0.
3
+ // It utilizes a JS Class and private members to protect any sensitive tokens
4
+ // and token obfuscation routines
5
+ import { PegaAuth } from '../oauth-client/auth.js';
6
+ import { isEmptyObject } from './common-utils.js';
7
+ import { getSdkConfig, SdkConfigAccess } from './config_access.js';
8
+ // Meant to be a singleton...only one instance per page
9
+ class AuthManager {
10
+ #ssKeyPrefix = 'rs';
11
+ // will store the PegaAuth (OAuth 2.0 client library) instance
12
+ #pegaAuth = null;
13
+ #ssKeyConfigInfo = '';
14
+ #ssKeySessionInfo = '';
15
+ #ssKeyTokenInfo = '';
16
+ #ssKeyState = `${this.#ssKeyPrefix}State`;
17
+ #authConfig = {};
18
+ #authDynState = {};
19
+ #authHeader = null;
20
+ // state that should be persisted across loads
21
+ state = { usePopup: false, noInitialRedirect: false };
22
+ bC11NBootstrapInProgress = false;
23
+ bCustomAuth = false;
24
+ #tokenInfo;
25
+ #userInfo;
26
+ onLoadDone = false;
27
+ msReauthStart = null;
28
+ initInProgress = false;
29
+ isLoggedIn = false;
30
+ // Whether to pass a session storage key or structure to auth library
31
+ #usePASS = false;
32
+ #pageHideAdded = false;
33
+ #tokenStorage = 'temp';
34
+ #transform = true;
35
+ #foldSpot = 2;
36
+ constructor() {
37
+ // Auth Manager specific state is saved within session storage as important in redirect and popup window scenarios
38
+ this.#loadState();
39
+ }
40
+ #transformAndParse(ssKey, ssItem, bForce = false) {
41
+ let obj = {};
42
+ try {
43
+ obj = JSON.parse(this.#transformer(ssKey, ssItem, false, bForce));
44
+ }
45
+ catch (e) {
46
+ // fall thru and return empty object
47
+ }
48
+ return obj;
49
+ }
50
+ // helper routine to retrieve JSON object stored in a session storage key
51
+ // a 2nd optional arg can also retrieve an individual attribute
52
+ #getStorage(ssKey, sAttrib = null) {
53
+ const ssItem = ssKey ? window.sessionStorage.getItem(ssKey) : null;
54
+ let obj = {};
55
+ if (ssItem) {
56
+ try {
57
+ obj = JSON.parse(ssItem);
58
+ }
59
+ catch (e) {
60
+ obj = this.#transformAndParse(ssKey, ssItem, true);
61
+ }
62
+ }
63
+ return sAttrib ? obj[sAttrib] : obj;
64
+ }
65
+ // helper routine to set storage to the passed in JSON
66
+ #setStorage(ssKey, obj) {
67
+ // Set storage only if obj is not empty, else delete the storage
68
+ if (!obj || isEmptyObject(obj)) {
69
+ window.sessionStorage.removeItem(ssKey);
70
+ }
71
+ else {
72
+ // const bClear = (ssKey === this.#ssKeyState || ssKey === this.#ssKeySessionInfo);
73
+ const bClear = false;
74
+ const sValue = bClear
75
+ ? JSON.stringify(obj)
76
+ : this.#transformer(ssKey, JSON.stringify(obj), true);
77
+ window.sessionStorage.setItem(ssKey, sValue);
78
+ }
79
+ }
80
+ #calcFoldSpot(s) {
81
+ const nOffset = 1;
82
+ const sChar = s.length > nOffset ? s.charAt(nOffset) : '2';
83
+ const nSpot = parseInt(sChar, 10);
84
+ this.#foldSpot = Number.isNaN(nSpot) ? 2 : (nSpot % 4) + 2;
85
+ }
86
+ // helper function to encode storage
87
+ #transformer(ssKey, s, bIn, bForce = false) {
88
+ const bTransform = bForce || this.#transform;
89
+ const fnFold = (x) => {
90
+ const nLen = x.length;
91
+ const nExtra = nLen % this.#foldSpot;
92
+ const nOffset = Math.floor(nLen / this.#foldSpot) + nExtra;
93
+ const nRem = x.length - nOffset;
94
+ return x.substring(bIn ? nOffset : nRem) + x.substring(0, bIn ? nOffset : nRem);
95
+ };
96
+ const bTknInfo = ssKey === this.#ssKeyTokenInfo;
97
+ if (bTknInfo && !bIn && bTransform) {
98
+ s = window.atob(fnFold(s));
99
+ }
100
+ // eslint-disable-next-line no-nested-ternary
101
+ let result = bTransform ? (bIn ? window.btoa(s) : window.atob(s)) : s;
102
+ if (bTknInfo && bIn && bTransform) {
103
+ result = fnFold(window.btoa(result));
104
+ }
105
+ return result;
106
+ }
107
+ // Setter for authHeader (no getter)
108
+ set authHeader(value) {
109
+ this.#authHeader = value;
110
+ // setAuthorizationHeader method not available til 8.8 so do safety check
111
+ if (window.PCore?.getAuthUtils().setAuthorizationHeader) {
112
+ const authHdr = value === null ? '' : value;
113
+ window.PCore.getAuthUtils().setAuthorizationHeader(authHdr);
114
+ }
115
+ this.#updateLoginStatus();
116
+ }
117
+ // Setter/getter for usePopupForRestOfSession
118
+ set usePopupForRestOfSession(usePopup) {
119
+ this.state.usePopup = usePopup;
120
+ this.#setStorage(this.#ssKeyState, this.state);
121
+ }
122
+ get usePopupForRestOfSession() {
123
+ return this.state.usePopup;
124
+ }
125
+ // Setter/getter for noInitialRedirect
126
+ set noInitialRedirect(bNoInitialRedirect) {
127
+ if (bNoInitialRedirect) {
128
+ this.usePopupForRestOfSession = true;
129
+ }
130
+ this.state.noInitialRedirect = bNoInitialRedirect;
131
+ this.#setStorage(this.#ssKeyState, this.state);
132
+ }
133
+ get noInitialRedirect() {
134
+ return this.state.noInitialRedirect || false;
135
+ }
136
+ // Init/getter for loginStart
137
+ set loginStart(msValue) {
138
+ if (msValue) {
139
+ this.state.msLoginStart = msValue;
140
+ }
141
+ else if (this.state.msLoginStart) {
142
+ delete this.state.msLoginStart;
143
+ }
144
+ this.#setStorage(this.#ssKeyState, this.state);
145
+ }
146
+ get loginStart() {
147
+ return this.state.msLoginStart || 0;
148
+ }
149
+ // Init/getter for reauthStart
150
+ set reauthStart(msValue) {
151
+ if (msValue) {
152
+ this.msReauthStart = msValue;
153
+ }
154
+ else if (this.msReauthStart) {
155
+ delete this.msReauthStart;
156
+ }
157
+ this.#setStorage(this.#ssKeyState, this.state);
158
+ }
159
+ get reauthStart() {
160
+ return this.msReauthStart || 0;
161
+ }
162
+ // Setter for clientId
163
+ set keySuffix(s) {
164
+ this.state.sfx = s || undefined;
165
+ this.#setStorage(this.#ssKeyState, this.state);
166
+ if (s) {
167
+ // To make it a bit more obtuse reverse the string and use that as the actual suffix
168
+ const sSfx = s.split('').reverse().join('');
169
+ this.#ssKeyConfigInfo = `${this.#ssKeyPrefix}CI_${sSfx}`;
170
+ this.#ssKeySessionInfo = `${this.#ssKeyPrefix}SI_${sSfx}`;
171
+ this.#ssKeyTokenInfo = `${this.#ssKeyPrefix}TI_${sSfx}`;
172
+ this.#calcFoldSpot(sSfx);
173
+ }
174
+ }
175
+ isLoginExpired() {
176
+ let bExpired = true;
177
+ if (this.loginStart) {
178
+ const currTime = Date.now();
179
+ bExpired = currTime - this.loginStart > 60000;
180
+ }
181
+ return bExpired;
182
+ }
183
+ /**
184
+ * Clean up any session storage allocated for the user session.
185
+ */
186
+ clear(bFullReauth = false) {
187
+ if (!this.bCustomAuth) {
188
+ this.#authHeader = null;
189
+ }
190
+ if (!bFullReauth) {
191
+ if (this.#usePASS) {
192
+ sessionStorage.removeItem(this.#ssKeyConfigInfo);
193
+ }
194
+ else {
195
+ this.#authConfig = {};
196
+ this.#authDynState = {};
197
+ }
198
+ sessionStorage.removeItem(this.#ssKeySessionInfo);
199
+ }
200
+ // Clear any established auth tokens
201
+ this.#tokenInfo = null;
202
+ sessionStorage.removeItem(this.#ssKeyTokenInfo);
203
+ this.loginStart = 0;
204
+ this.isLoggedIn = false;
205
+ // reset the initial redirect as well by using this setter
206
+ this.usePopupForRestOfSession = bFullReauth;
207
+ this.keySuffix = '';
208
+ }
209
+ #doPageHide() {
210
+ // Safari and particularly Safari on mobile devices doesn't seem to load this on first main redirect or
211
+ // reliably, so have moved to having PegaAuth manage writing all state props to session storage
212
+ this.#setStorage(this.#ssKeyState, this.state);
213
+ this.#setStorage(this.#ssKeySessionInfo, this.#authDynState);
214
+ // If tokenStorage was always, token would already be there
215
+ if (this.#tokenStorage === 'temp') {
216
+ this.#setStorage(this.#ssKeyTokenInfo, this.#tokenInfo);
217
+ }
218
+ }
219
+ #loadState() {
220
+ // Note: State storage key doesn't have a client id associated with it
221
+ const oState = this.#getStorage(this.#ssKeyState);
222
+ if (oState) {
223
+ Object.assign(this.state, oState);
224
+ if (this.state.sfx) {
225
+ // Setter sets up the ssKey values as well
226
+ this.keySuffix = this.state.sfx;
227
+ }
228
+ }
229
+ }
230
+ // This is only called from initialize after #ssKey values are setup
231
+ #doOnLoad() {
232
+ if (!this.onLoadDone) {
233
+ // This authConfig state doesn't collide with other calculated static state...so load it first
234
+ // Note: transform setting will have already been loaded into #authConfig at this point
235
+ this.#authDynState = this.#getStorage(this.#ssKeySessionInfo);
236
+ this.#tokenInfo = this.#getStorage(this.#ssKeyTokenInfo);
237
+ if (this.#tokenStorage !== 'always') {
238
+ sessionStorage.removeItem(this.#ssKeyTokenInfo);
239
+ sessionStorage.removeItem(this.#ssKeySessionInfo);
240
+ }
241
+ this.onLoadDone = true;
242
+ }
243
+ }
244
+ // Callback when auth dynamic state has changed. Decide whether to persisting it based on
245
+ // config settings
246
+ #doAuthDynStateChanged() {
247
+ // If tokenStorage is setup for always then always persist the auth dynamic state as well
248
+ if (this.#tokenStorage === 'always') {
249
+ this.#setStorage(this.#ssKeySessionInfo, this.#authDynState);
250
+ }
251
+ }
252
+ /**
253
+ * Initialize OAuth config structure members and create authMgr instance (if necessary)
254
+ * bNew - governs whether to create new sessionStorage or load existing one
255
+ */
256
+ async #initialize(bNew = false) {
257
+ return new Promise(resolve => {
258
+ if (!this.initInProgress && (bNew || isEmptyObject(this.#authConfig) || !this.#pegaAuth)) {
259
+ this.initInProgress = true;
260
+ getSdkConfig().then(sdkConfig => {
261
+ const sdkConfigAuth = sdkConfig.authConfig;
262
+ const sdkConfigServer = sdkConfig.serverConfig;
263
+ let pegaUrl = sdkConfigServer.infinityRestServerUrl;
264
+ const bNoInitialRedirect = this.noInitialRedirect;
265
+ // Construct default OAuth endpoints (if not explicitly specified)
266
+ if (pegaUrl) {
267
+ // Cope with trailing slash being present
268
+ if (!pegaUrl.endsWith('/')) {
269
+ pegaUrl += '/';
270
+ }
271
+ if (!sdkConfigAuth.authorize) {
272
+ sdkConfigAuth.authorize = `${pegaUrl}PRRestService/oauth2/v1/authorize`;
273
+ }
274
+ if (!sdkConfigAuth.token) {
275
+ sdkConfigAuth.token = `${pegaUrl}PRRestService/oauth2/v1/token`;
276
+ }
277
+ if (!sdkConfigAuth.revoke) {
278
+ sdkConfigAuth.revoke = `${pegaUrl}PRRestService/oauth2/v1/revoke`;
279
+ }
280
+ if (!sdkConfigAuth.redirectUri) {
281
+ sdkConfigAuth.redirectUri = `${window.location.origin}${window.location.pathname}`;
282
+ }
283
+ if (!sdkConfigAuth.userinfo) {
284
+ const appAliasSeg = sdkConfigServer.appAlias
285
+ ? `app/${sdkConfigServer.appAlias}/`
286
+ : '';
287
+ sdkConfigAuth.userinfo = `${pegaUrl}${appAliasSeg}api/oauthclients/v1/userinfo/JSON`;
288
+ }
289
+ }
290
+ // Auth service alias
291
+ if (!sdkConfigAuth.authService) {
292
+ sdkConfigAuth.authService = 'pega';
293
+ }
294
+ // mashupAuthService provides way to have a different auth service for embedded
295
+ if (!sdkConfigAuth.mashupAuthService) {
296
+ sdkConfigAuth.mashupAuthService = sdkConfigAuth.authService;
297
+ }
298
+ // Construct path to auth.html (used for case when not doing a main window redirect)
299
+ let sNoMainRedirectUri = sdkConfigAuth.redirectUri;
300
+ const nLastPathSep = sNoMainRedirectUri.lastIndexOf('/');
301
+ sNoMainRedirectUri =
302
+ nLastPathSep !== -1
303
+ ? `${sNoMainRedirectUri.substring(0, nLastPathSep + 1)}auth.html`
304
+ : `${sNoMainRedirectUri}/auth.html`;
305
+ const portalGrantType = sdkConfigAuth.portalGrantType || 'authCode';
306
+ const mashupGrantType = sdkConfigAuth.mashupGrantType || 'authCode';
307
+ const pegaAuthConfig = {
308
+ clientId: bNoInitialRedirect
309
+ ? sdkConfigAuth.mashupClientId
310
+ : sdkConfigAuth.portalClientId,
311
+ grantType: bNoInitialRedirect ? mashupGrantType : portalGrantType,
312
+ tokenUri: sdkConfigAuth.token,
313
+ revokeUri: sdkConfigAuth.revoke,
314
+ userinfoUri: sdkConfigAuth.userinfo,
315
+ authService: bNoInitialRedirect
316
+ ? sdkConfigAuth.mashupAuthService
317
+ : sdkConfigAuth.authService,
318
+ appAlias: sdkConfigServer.appAlias || '',
319
+ useLocking: true
320
+ };
321
+ // Invoke keySuffix setter
322
+ // Was using pegaAuthConfig.clientId as key but more secure to just use a random string as getting
323
+ // both a clientId and the refresh token could yield a new access token.
324
+ // Suffix is so we might in future move to an array of suffixes based on the appName, so might store
325
+ // both portal and embedded tokens/session info at same time
326
+ if (!this.state?.sfx) {
327
+ // Just using a random number to make the suffix unique on each session
328
+ this.keySuffix = `${Math.ceil(Math.random() * 100000000)}`;
329
+ }
330
+ this.#authConfig.transform =
331
+ sdkConfigAuth.transform !== undefined ? sdkConfigAuth.transform : this.#transform;
332
+ // Using property in class as authConfig may be empty at times
333
+ this.#transform = this.#authConfig.transform;
334
+ if (sdkConfigAuth.tokenStorage !== undefined) {
335
+ this.#tokenStorage = sdkConfigAuth.tokenStorage;
336
+ }
337
+ // Get latest state once client ids, transform and tokenStorage have been established
338
+ this.#doOnLoad();
339
+ // If no clientId is specified assume not OAuth but custom auth
340
+ if (!pegaAuthConfig.clientId) {
341
+ this.bCustomAuth = true;
342
+ return;
343
+ }
344
+ if (pegaAuthConfig.grantType === 'authCode') {
345
+ const authCodeProps = {
346
+ authorizeUri: sdkConfigAuth.authorize,
347
+ // If we have already specified a redirect on the authorize redirect, we need to continue to use that
348
+ // on token endpoint
349
+ redirectUri: bNoInitialRedirect || this.usePopupForRestOfSession
350
+ ? sNoMainRedirectUri
351
+ : sdkConfigAuth.redirectUri
352
+ };
353
+ if ('silentTimeout' in sdkConfigAuth) {
354
+ authCodeProps.silentTimeout = sdkConfigAuth.silentTimeout;
355
+ }
356
+ if (bNoInitialRedirect &&
357
+ pegaAuthConfig.authService === 'pega' &&
358
+ sdkConfigAuth.mashupUserIdentifier &&
359
+ sdkConfigAuth.mashupPassword) {
360
+ authCodeProps.userIdentifier = sdkConfigAuth.mashupUserIdentifier;
361
+ authCodeProps.password = sdkConfigAuth.mashupPassword;
362
+ }
363
+ if ('iframeLoginUI' in sdkConfigAuth) {
364
+ authCodeProps.iframeLoginUI =
365
+ sdkConfigAuth.iframeLoginUI.toString().toLowerCase() === 'true';
366
+ }
367
+ Object.assign(pegaAuthConfig, authCodeProps);
368
+ }
369
+ Object.assign(this.#authConfig, pegaAuthConfig);
370
+ // Add an on page hide handler to write out key properties that we want to survive a
371
+ // browser reload
372
+ if (!this.#pageHideAdded && (!this.#usePASS || this.#tokenStorage !== 'always')) {
373
+ window.addEventListener('pagehide', this.#doPageHide.bind(this));
374
+ this.#pageHideAdded = true;
375
+ }
376
+ // Initialize PegaAuth OAuth 2.0 client library
377
+ if (this.#usePASS) {
378
+ this.#setStorage(this.#ssKeyConfigInfo, this.#authConfig);
379
+ this.#setStorage(this.#ssKeySessionInfo, this.#authDynState);
380
+ this.#pegaAuth = new PegaAuth(this.#ssKeyConfigInfo, this.#ssKeySessionInfo);
381
+ }
382
+ else {
383
+ this.#authConfig.fnDynStateChangedCB = this.#doAuthDynStateChanged.bind(this);
384
+ this.#pegaAuth = new PegaAuth(this.#authConfig, this.#authDynState);
385
+ }
386
+ this.initInProgress = false;
387
+ resolve(this.#pegaAuth);
388
+ });
389
+ }
390
+ else {
391
+ let idNextCheck;
392
+ const fnCheckForAuthMgr = () => {
393
+ if (!this.initInProgress) {
394
+ if (idNextCheck) {
395
+ clearInterval(idNextCheck);
396
+ }
397
+ resolve(this.#pegaAuth);
398
+ }
399
+ };
400
+ fnCheckForAuthMgr();
401
+ idNextCheck = setInterval(fnCheckForAuthMgr, 100);
402
+ }
403
+ });
404
+ }
405
+ /**
406
+ * Initiate the process to get the Constellation bootstrap shell loaded and initialized
407
+ * @param {Object} authConfig
408
+ * @param {Object} tokenInfo
409
+ * @param {Function} authTokenUpdated - callback invoked when Constellation JS Engine silently updates
410
+ * an expired access_token
411
+ * @param {Function} fnReauth - callback invoked when a full or custom reauth is needed
412
+ */
413
+ #constellationInit(authConfig, tokenInfo, authTokenUpdated, fnReauth) {
414
+ const constellationBootConfig = {};
415
+ const sdkConfigServer = SdkConfigAccess.getSdkConfigServer();
416
+ // Set up constellationConfig with data that bootstrapWithAuthHeader expects
417
+ constellationBootConfig.customRendering = true;
418
+ constellationBootConfig.restServerUrl = sdkConfigServer.infinityRestServerUrl;
419
+ // NOTE: Needs a trailing slash! So add one if not provided
420
+ if (!sdkConfigServer.sdkContentServerUrl.endsWith('/')) {
421
+ sdkConfigServer.sdkContentServerUrl = `${sdkConfigServer.sdkContentServerUrl}/`;
422
+ }
423
+ constellationBootConfig.staticContentServerUrl = `${sdkConfigServer.sdkContentServerUrl}constellation/`;
424
+ if (!constellationBootConfig.staticContentServerUrl.endsWith('/')) {
425
+ constellationBootConfig.staticContentServerUrl = `${constellationBootConfig.staticContentServerUrl}/`;
426
+ }
427
+ // If appAlias specified, use it
428
+ if (sdkConfigServer.appAlias) {
429
+ constellationBootConfig.appAlias = sdkConfigServer.appAlias;
430
+ }
431
+ if (tokenInfo) {
432
+ // Pass in auth info to Constellation
433
+ constellationBootConfig.authInfo = {
434
+ authType: 'OAuth2.0',
435
+ tokenInfo,
436
+ // Set whether we want constellation to try to do a full re-Auth or not ()
437
+ // true doesn't seem to be working in SDK scenario so always passing false for now
438
+ popupReauth: false /* !this.noInitialRedirect */,
439
+ client_id: authConfig.clientId,
440
+ authentication_service: authConfig.authService,
441
+ redirect_uri: authConfig.redirectUri,
442
+ endPoints: {
443
+ authorize: authConfig.authorizeUri,
444
+ token: authConfig.tokenUri,
445
+ revoke: authConfig.revokeUri
446
+ },
447
+ // TODO: setup callback so we can update own storage
448
+ onTokenRetrieval: this.#authTokenUpdated.bind(this)
449
+ };
450
+ }
451
+ else {
452
+ constellationBootConfig.authorizationHeader = this.#authHeader;
453
+ }
454
+ // Turn off dynamic load components (should be able to do it here instead of after load?)
455
+ constellationBootConfig.dynamicLoadComponents = false;
456
+ if (this.bC11NBootstrapInProgress) {
457
+ return;
458
+ }
459
+ this.bC11NBootstrapInProgress = true;
460
+ // Note that staticContentServerUrl already ends with a slash (see above), so no slash added.
461
+ // In order to have this import succeed and to have it done with the webpackIgnore magic comment tag.
462
+ // See: https://webpack.js.org/api/module-methods/
463
+ import(
464
+ /* webpackIgnore: true */ `${constellationBootConfig.staticContentServerUrl}bootstrap-shell.js`).then(bootstrapShell => {
465
+ // NOTE: once this callback is done, we lose the ability to access loadMashup.
466
+ // So, create a reference to it
467
+ window.myLoadMashup = bootstrapShell.loadMashup;
468
+ window.myLoadPortal = bootstrapShell.loadPortal;
469
+ window.myLoadDefaultPortal = bootstrapShell.loadDefaultPortal;
470
+ bootstrapShell
471
+ .bootstrapWithAuthHeader(constellationBootConfig, 'pega-root')
472
+ .then(() => {
473
+ // eslint-disable-next-line no-console
474
+ console.log('ConstellationJS bootstrap successful!');
475
+ this.bC11NBootstrapInProgress = false;
476
+ // Setup listener for the reauth event
477
+ if (tokenInfo) {
478
+ PCore.getPubSubUtils().subscribe(PCore.getConstants().PUB_SUB_EVENTS.EVENT_FULL_REAUTH, fnReauth, 'authFullReauth');
479
+ }
480
+ else {
481
+ // customReauth event introduced with 8.8
482
+ const sEvent = PCore.getConstants().PUB_SUB_EVENTS.EVENT_CUSTOM_REAUTH;
483
+ if (sEvent) {
484
+ PCore.getPubSubUtils().subscribe(sEvent, fnReauth, 'doReauth');
485
+ }
486
+ }
487
+ // Fire SdkConstellationReady event so bridge and app route can do expected post PCore initializations
488
+ const event = new CustomEvent('SdkConstellationReady', {});
489
+ document.dispatchEvent(event);
490
+ })
491
+ .catch(e => {
492
+ // Assume error caught is because token is not valid and attempt a full reauth
493
+ // eslint-disable-next-line no-console
494
+ console.error(`ConstellationJS bootstrap failed. ${e}`);
495
+ this.bC11NBootstrapInProgress = false;
496
+ fnReauth();
497
+ });
498
+ });
499
+ /* Ends here */
500
+ }
501
+ #customConstellationInit(fnReauth) {
502
+ this.#constellationInit(null, null, null, fnReauth);
503
+ }
504
+ #fireTokenAvailable(token, bLoadC11N = true) {
505
+ if (!token) {
506
+ // This is used on page reload to load the token from sessionStorage and carry on
507
+ token = this.#tokenInfo;
508
+ if (!token) {
509
+ return;
510
+ }
511
+ }
512
+ this.#tokenInfo = token;
513
+ if (this.#tokenStorage === 'always') {
514
+ this.#setStorage(this.#ssKeyTokenInfo, this.#tokenInfo);
515
+ }
516
+ this.#updateLoginStatus();
517
+ // this.isLoggedIn is getting updated in updateLoginStatus
518
+ this.isLoggedIn = true;
519
+ this.loginStart = 0;
520
+ this.usePopupForRestOfSession = true;
521
+ if (!window.PCore && bLoadC11N) {
522
+ this.#constellationInit(this.#authConfig, token, this.#authTokenUpdated.bind(this), this.#authFullReauth.bind(this));
523
+ }
524
+ /*
525
+ // Create and dispatch the SdkLoggedIn event to trigger constellationInit
526
+ const event = new CustomEvent('SdkLoggedIn', { detail: { authConfig, tokenInfo: token } });
527
+ document.dispatchEvent(event);
528
+ */
529
+ }
530
+ #processTokenOnLogin(token, bLoadC11N = true) {
531
+ this.#tokenInfo = token;
532
+ if (this.#tokenStorage === 'always') {
533
+ this.#setStorage(this.#ssKeyTokenInfo, this.#tokenInfo);
534
+ }
535
+ if (window.PCore) {
536
+ PCore.getAuthUtils().setTokens(token);
537
+ }
538
+ else {
539
+ this.#fireTokenAvailable(token, bLoadC11N);
540
+ }
541
+ }
542
+ updateRedirectUri(sRedirectUri) {
543
+ this.#authConfig.redirectUri = sRedirectUri;
544
+ }
545
+ /**
546
+ * Get available portals which supports SDK
547
+ * @returns list of available portals (portals other than excludingPortals list)
548
+ */
549
+ async getAvailablePortals() {
550
+ return getSdkConfig().then(sdkConfig => {
551
+ const serverConfig = sdkConfig.serverConfig;
552
+ const userAccessGroup = PCore.getEnvironmentInfo().getAccessGroup();
553
+ const dataPageName = 'D_OperatorAccessGroups';
554
+ const serverUrl = serverConfig.infinityRestServerUrl;
555
+ const appAlias = serverConfig.appAlias;
556
+ const appAliasPath = appAlias ? `/app/${appAlias}` : '';
557
+ const arExcludedPortals = serverConfig.excludePortals;
558
+ const headers = {
559
+ Authorization: this.#authHeader === null ? '' : this.#authHeader,
560
+ 'Content-Type': 'application/json'
561
+ };
562
+ // Using v1 API here as v2 data_views is not able to access same data page currently. Should move to avoid having this logic to find
563
+ // a default portal or constellation portal and rather have Constellation JS Engine API just load the default portal
564
+ return fetch(`${serverUrl}${appAliasPath}/api/v1/data/${dataPageName}`, {
565
+ method: 'GET',
566
+ headers
567
+ })
568
+ .then(response => {
569
+ if (response.ok && response.status === 200) {
570
+ return response.json();
571
+ }
572
+ if (response.status === 401) {
573
+ // Might be either a real token expiration or revoke, but more likely that the "api" service package is misconfigured
574
+ throw new Error(`Attempt to access ${dataPageName} failed. The "api" service package is likely not configured to use "OAuth 2.0"`);
575
+ }
576
+ throw new Error(`HTTP Error: ${response.status}`);
577
+ })
578
+ .then(async (agData) => {
579
+ const arAccessGroups = agData.pxResults;
580
+ const availablePortals = [];
581
+ for (const ag of arAccessGroups) {
582
+ if (ag.pyAccessGroup === userAccessGroup) {
583
+ // eslint-disable-next-line no-console
584
+ console.error(`Default portal for current operator (${ag.pyPortal}) is not compatible with SDK.\nConsider using a different operator, adjusting the default portal for this operator, or using "appPortal" setting within sdk-config.json to specify a specific portal to load.`);
585
+ let portal = null;
586
+ for (portal of ag.pyUserPortals) {
587
+ if (!arExcludedPortals.includes(portal.pyPortalLayout)) {
588
+ availablePortals.push(portal.pyPortalLayout);
589
+ }
590
+ }
591
+ break;
592
+ }
593
+ }
594
+ // Found operator's current access group. Use its portal
595
+ // eslint-disable-next-line no-console
596
+ console.log(`Available portals: ${availablePortals}`);
597
+ return availablePortals;
598
+ })
599
+ .catch(e => {
600
+ // eslint-disable-next-line no-console
601
+ console.error(e.message);
602
+ // check specific error if 401, and wiped out if so stored token is stale. Fetch new tokens.
603
+ });
604
+ });
605
+ }
606
+ #updateLoginStatus() {
607
+ if (!this.#authHeader && this.#tokenInfo?.access_token) {
608
+ // Use setter to set this securely
609
+ this.authHeader = `${this.#tokenInfo.token_type} ${this.#tokenInfo.access_token}`;
610
+ }
611
+ this.isLoggedIn = !!(this.#authHeader && this.#authHeader.length > 0);
612
+ }
613
+ // Initiate a full OAuth re-authorization (any refresh token has also expired).
614
+ #authFullReauth() {
615
+ const bHandleHere = true; // Other alternative is to raise an event and have someone else handle it
616
+ if (this.reauthStart) {
617
+ const reauthIgnoreInterval = 300000; // 5 minutes
618
+ const currTime = Date.now();
619
+ const bReauthInProgress = currTime - this.reauthStart <= reauthIgnoreInterval;
620
+ if (bReauthInProgress) {
621
+ return;
622
+ }
623
+ }
624
+ if (bHandleHere) {
625
+ // Don't want to do a full clear of authMgr as will loose state props (like sessionIndex). Rather just clear the tokens
626
+ this.clear(true);
627
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
628
+ login(true);
629
+ }
630
+ else {
631
+ // Fire the SdkFullReauth event to indicate a new token is needed (PCore.getAuthUtils.setTokens method
632
+ // should be used to communicate the new token to Constellation JS Engine.
633
+ const event = new CustomEvent('SdkFullReauth', {
634
+ detail: this.#processTokenOnLogin.bind(this)
635
+ });
636
+ document.dispatchEvent(event);
637
+ }
638
+ }
639
+ // Passive update where just session storage is updated so can be used on a window refresh
640
+ #authTokenUpdated(tokenInfo) {
641
+ this.#tokenInfo = tokenInfo;
642
+ }
643
+ // TODO: Cope with 401 and refresh token if possible (or just hope that it succeeds during login)
644
+ /**
645
+ * Retrieve UserInfo for current authentication service
646
+ */
647
+ getUserInfo() {
648
+ if (this.#userInfo) {
649
+ return this.#userInfo;
650
+ }
651
+ return this.#initialize(false).then(aMgr => {
652
+ return aMgr.getUserinfo(this.#tokenInfo.access_token).then(data => {
653
+ this.#userInfo = data;
654
+ return this.#userInfo;
655
+ });
656
+ });
657
+ }
658
+ login(bFullReauth = false) {
659
+ if (this.bCustomAuth)
660
+ return;
661
+ // Needed so a redirect to login screen and back will know we are still in process of logging in
662
+ this.loginStart = Date.now();
663
+ this.#initialize(!bFullReauth).then(aMgr => {
664
+ const bMainRedirect = !this.noInitialRedirect;
665
+ const sdkConfigAuth = SdkConfigAccess.getSdkConfigAuth();
666
+ let sRedirectUri = sdkConfigAuth.redirectUri;
667
+ // If initial main redirect is OK, redirect to main page, otherwise will authorize in a popup window
668
+ if (bMainRedirect && !bFullReauth) {
669
+ // update redirect uri to be the root
670
+ this.updateRedirectUri(sRedirectUri);
671
+ aMgr.loginRedirect();
672
+ // Don't have token til after the redirect
673
+ return Promise.resolve(undefined);
674
+ }
675
+ // Construct path to redirect uri
676
+ const nLastPathSep = sRedirectUri.lastIndexOf('/');
677
+ sRedirectUri =
678
+ nLastPathSep !== -1
679
+ ? `${sRedirectUri.substring(0, nLastPathSep + 1)}auth.html`
680
+ : `${sRedirectUri}/auth.html`;
681
+ // Set redirectUri to static auth.html
682
+ this.updateRedirectUri(sRedirectUri);
683
+ return new Promise((resolve, reject) => {
684
+ aMgr
685
+ .login()
686
+ .then(token => {
687
+ this.#processTokenOnLogin(token);
688
+ // this.getUserInfo();
689
+ resolve(token.access_token);
690
+ })
691
+ .catch(e => {
692
+ // Use setter to update state
693
+ this.loginStart = 0;
694
+ // eslint-disable-next-line no-console
695
+ console.log(e);
696
+ reject(e);
697
+ });
698
+ });
699
+ });
700
+ }
701
+ authRedirectCallback(href, fnLoggedInCB = null) {
702
+ // Get code from href and swap for token
703
+ const aHrefParts = href.split('?');
704
+ const urlParams = new URLSearchParams(aHrefParts.length > 1 ? `?${aHrefParts[1]}` : '');
705
+ const code = urlParams.get('code');
706
+ const state = urlParams.get('state');
707
+ // If state should also match before accepting code
708
+ if (code) {
709
+ this.#initialize(false).then(aMgr => {
710
+ if (aMgr.checkStateMatch(state)) {
711
+ aMgr.getToken(code).then(token => {
712
+ if (token && token.access_token) {
713
+ this.#processTokenOnLogin(token, false);
714
+ // this.getUserInfo();
715
+ if (fnLoggedInCB) {
716
+ fnLoggedInCB(token.access_token);
717
+ }
718
+ }
719
+ });
720
+ }
721
+ });
722
+ }
723
+ else {
724
+ const error = urlParams.get('error');
725
+ const errorDesc = urlParams.get('errorDesc');
726
+ fnLoggedInCB(null, error, errorDesc);
727
+ }
728
+ }
729
+ loginIfNecessary(loginProps) {
730
+ const { appName, deferLogin, redirectDoneCB } = loginProps;
731
+ const noMainRedirect = !loginProps.mainRedirect;
732
+ // We need to load state before making any decisions
733
+ this.#loadState();
734
+ // If no initial redirect status of page changed...clear AuthMgr
735
+ const currNoMainRedirect = this.noInitialRedirect;
736
+ if (appName !== this.state.appName || noMainRedirect !== currNoMainRedirect) {
737
+ this.clear(false);
738
+ this.state.appName = appName;
739
+ this.#setStorage(this.#ssKeyState, this.state);
740
+ }
741
+ this.noInitialRedirect = noMainRedirect;
742
+ // If custom auth no need to do any OAuth logic
743
+ if (this.bCustomAuth) {
744
+ this.#updateLoginStatus();
745
+ if (!window.PCore) {
746
+ this.#customConstellationInit(() => {
747
+ // Fire the SdkCustomReauth event to indicate a new authHeader is needed. Event listener should invoke sdkSetAuthHeader
748
+ // to communicate the new token to sdk (and Constellation JS Engine)
749
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
750
+ const event = new CustomEvent('SdkCustomReauth', { detail: sdkSetAuthHeader });
751
+ document.dispatchEvent(event);
752
+ });
753
+ }
754
+ return;
755
+ }
756
+ if (window.location.href.includes('?code')) {
757
+ // initialize authMgr (now initialize in constructor?)
758
+ return this.#initialize(false).then(() => {
759
+ const cbDefault = () => {
760
+ window.location.href = window.location.pathname;
761
+ };
762
+ // eslint-disable-next-line no-console
763
+ console.log('About to invoke PegaAuth authRedirectCallback');
764
+ this.authRedirectCallback(window.location.href, redirectDoneCB || cbDefault);
765
+ // });
766
+ });
767
+ }
768
+ if (!deferLogin && (!this.loginStart || this.isLoginExpired())) {
769
+ return this.#initialize(false).then(() => {
770
+ this.#updateLoginStatus();
771
+ if (this.isLoggedIn) {
772
+ this.#fireTokenAvailable(this.#tokenInfo);
773
+ // this.getUserInfo();
774
+ }
775
+ else {
776
+ return this.login();
777
+ }
778
+ // });
779
+ });
780
+ }
781
+ }
782
+ logout() {
783
+ sessionStorage.removeItem('sdk_portalName');
784
+ return new Promise(resolve => {
785
+ const fnClearAndResolve = () => {
786
+ this.clear();
787
+ const event = new Event('SdkLoggedOut');
788
+ document.dispatchEvent(event);
789
+ resolve(null);
790
+ };
791
+ if (this.bCustomAuth) {
792
+ fnClearAndResolve();
793
+ return;
794
+ }
795
+ if (this.#tokenInfo && this.#tokenInfo.access_token) {
796
+ if (window.PCore) {
797
+ window.PCore.getAuthUtils()
798
+ .revokeTokens()
799
+ .then(() => {
800
+ fnClearAndResolve();
801
+ })
802
+ .catch(err => {
803
+ // eslint-disable-next-line no-console
804
+ console.log('Error:', err?.message);
805
+ });
806
+ }
807
+ else {
808
+ this.#initialize(false).then(aMgr => {
809
+ aMgr
810
+ .revokeTokens(this.#tokenInfo.access_token, this.#tokenInfo.refresh_token)
811
+ .then(() => {
812
+ // Go to finally
813
+ })
814
+ .finally(() => {
815
+ fnClearAndResolve();
816
+ });
817
+ });
818
+ }
819
+ }
820
+ else {
821
+ fnClearAndResolve();
822
+ }
823
+ });
824
+ }
825
+ }
826
+ const gAuthMgr = new AuthManager();
827
+ // TODO: Cope with 401 and refresh token if possible (or just hope that it succeeds during login)
828
+ /**
829
+ * Retrieve UserInfo for current authentication service
830
+ */
831
+ export const getUserInfo = () => {
832
+ return gAuthMgr.getUserInfo();
833
+ };
834
+ export const login = (bFullReauth = false) => {
835
+ return gAuthMgr.login(bFullReauth);
836
+ };
837
+ export const authRedirectCallback = (href, fnLoggedInCB = null) => {
838
+ gAuthMgr.authRedirectCallback(href, fnLoggedInCB);
839
+ };
840
+ /**
841
+ * Silent or visible login based on login status
842
+ * @param {string} appName - unique name for application route (will be used to clear an session storage for another route)
843
+ * @param {boolean} noMainRedirect - avoid the initial main window redirect that happens in scenarios where it is OK to transition
844
+ * away from the main page
845
+ * @param {boolean} deferLogin - defer logging in (if not already authenticated)
846
+ */
847
+ export const loginIfNecessary = (loginProps) => {
848
+ gAuthMgr.loginIfNecessary(loginProps);
849
+ };
850
+ export const getHomeUrl = () => {
851
+ return `${window.location.origin}/`;
852
+ };
853
+ export const authIsMainRedirect = () => {
854
+ // Even with main redirect, we want to use it only for the first login (so it doesn't wipe out any state or the reload)
855
+ return !gAuthMgr.noInitialRedirect && !gAuthMgr.usePopupForRestOfSession;
856
+ };
857
+ export const sdkIsLoggedIn = () => {
858
+ return gAuthMgr.isLoggedIn;
859
+ };
860
+ export const logout = () => {
861
+ return gAuthMgr.logout();
862
+ };
863
+ // Set the custom authorization header for the SDK (and Constellation JS Engine) to
864
+ // utilize for every DX API request
865
+ export const sdkSetAuthHeader = authHeader => {
866
+ gAuthMgr.bCustomAuth = !!authHeader;
867
+ // Use setter to set this securely
868
+ gAuthMgr.authHeader = authHeader;
869
+ };
870
+ export const getAvailablePortals = async () => {
871
+ return gAuthMgr.getAvailablePortals();
872
+ };
873
+ //# sourceMappingURL=authManager.js.map