@pega/auth 0.2.23 → 0.2.25

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.
@@ -1,22 +1,35 @@
1
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
2
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
3
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
4
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
5
+ };
6
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
7
+ if (kind === "m") throw new TypeError("Private method is not writable");
8
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
9
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
10
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
11
+ };
12
+ var _PegaAuth_instances, _PegaAuth_config, _PegaAuth_dynState, _PegaAuth_reloadSS, _PegaAuth_reloadConfig, _PegaAuth_updateConfig, _PegaAuth_importSingleLib, _PegaAuth_importNodeLibs, _PegaAuth_buildAuthorizeUrl, _PegaAuth_authCodeStart, _PegaAuth_updateSessionIndex, _PegaAuth_sha256Hash, _PegaAuth_encode64, _PegaAuth_base64UrlSafeEncode, _PegaAuth_getRandomString, _PegaAuth_getCodeChallenge, _PegaAuth_getAgent;
1
13
  export class PegaAuth {
2
- // The properties within config structure are expected to be more static config values that are then
3
- // used to properly make various OAuth endpoint calls.
4
- #config = null;
5
- // Any dynamic state is stored separately in its own structure. If a sessionStorage key is passed in
6
- // without a Dynamic State key.
7
- #dynState = {};
8
14
  // Current properties within dynState structure:
9
15
  // codeVerifier, state, sessionIndex, sessionIndexAttempts, acRedirectUri, silentAuthFailed
10
16
  constructor(ssKeyConfig, ssKeyDynState = '') {
17
+ _PegaAuth_instances.add(this);
18
+ // The properties within config structure are expected to be more static config values that are then
19
+ // used to properly make various OAuth endpoint calls.
20
+ _PegaAuth_config.set(this, null);
21
+ // Any dynamic state is stored separately in its own structure. If a sessionStorage key is passed in
22
+ // without a Dynamic State key.
23
+ _PegaAuth_dynState.set(this, {});
11
24
  if (typeof ssKeyConfig === 'string') {
12
25
  this.ssKeyConfig = ssKeyConfig;
13
26
  this.ssKeyDynState = ssKeyDynState || `${ssKeyConfig}_DS`;
14
- this.#reloadConfig();
27
+ __classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_reloadConfig).call(this);
15
28
  }
16
29
  else {
17
30
  // object with config structure is passed in
18
- this.#config = ssKeyConfig;
19
- this.#dynState = ssKeyDynState || {};
31
+ __classPrivateFieldSet(this, _PegaAuth_config, ssKeyConfig, "f");
32
+ __classPrivateFieldSet(this, _PegaAuth_dynState, ssKeyDynState || {}, "f");
20
33
  }
21
34
  this.urlencoded = 'application/x-www-form-urlencoded';
22
35
  this.tokenError = 'Token endpoint error';
@@ -26,153 +39,21 @@ export class PegaAuth {
26
39
  this.crypto = window.crypto;
27
40
  this.subtle = window.crypto.subtle;
28
41
  }
29
- if (Object.keys(this.#config).length > 0) {
30
- if (!this.#config.serverType) {
31
- this.#config.serverType = 'infinity';
42
+ if (Object.keys(__classPrivateFieldGet(this, _PegaAuth_config, "f")).length > 0) {
43
+ if (!__classPrivateFieldGet(this, _PegaAuth_config, "f").serverType) {
44
+ __classPrivateFieldGet(this, _PegaAuth_config, "f").serverType = 'infinity';
32
45
  }
33
46
  }
34
47
  else {
35
48
  throw new Error('invalid config settings');
36
49
  }
37
50
  }
38
- #reloadSS(ssKey) {
39
- const sItem = window.sessionStorage.getItem(ssKey);
40
- let obj = {};
41
- if (sItem) {
42
- try {
43
- obj = JSON.parse(sItem);
44
- }
45
- catch (e) {
46
- try {
47
- obj = JSON.parse(atob(sItem));
48
- }
49
- catch (err) {
50
- obj = {};
51
- }
52
- }
53
- }
54
- if (ssKey === this.ssKeyConfig) {
55
- this.#config = sItem ? obj : {};
56
- }
57
- else {
58
- this.#dynState = sItem ? obj : {};
59
- }
60
- }
61
- #reloadConfig() {
62
- if (this.ssKeyConfig) {
63
- this.#reloadSS(this.ssKeyConfig);
64
- }
65
- if (this.ssKeyDynState) {
66
- this.#reloadSS(this.ssKeyDynState);
67
- }
68
- }
69
- #updateConfig() {
70
- // transform must occur unless it is explicitly disabled
71
- const transform = this.#config.transform !== false;
72
- // May not need to write out Config info all the time, but there is a scenario where a
73
- // non obfuscated value is passed in and then it needs to be obfuscated
74
- if (this.ssKeyConfig) {
75
- const sConfig = JSON.stringify(this.#config);
76
- window.sessionStorage.setItem(this.ssKeyConfig, transform ? btoa(sConfig) : sConfig);
77
- }
78
- if (this.ssKeyDynState) {
79
- const sDynState = JSON.stringify(this.#dynState);
80
- window.sessionStorage.setItem(this.ssKeyDynState, transform ? btoa(sDynState) : sDynState);
81
- }
82
- if (this.#config.fnDynStateChangedCB) {
83
- this.#config.fnDynStateChangedCB();
84
- }
85
- }
86
- async #importSingleLib(libName, libProp, bLoadAlways = false) {
87
- if (!bLoadAlways && typeof (this.isNode ? global : window)[libProp] !== 'undefined') {
88
- this[libProp] = (this.isNode ? global : window)[libProp];
89
- return this[libProp];
90
- }
91
- // Needed to explicitly make import argument a string by using template literals to fix a compile
92
- // error: Critical dependency: the request of a dependency is an expression
93
- return import(/* webpackIgnore: true */ `${libName}`)
94
- .then(mod => {
95
- this[libProp] = mod.default;
96
- })
97
- .catch(e => {
98
- // eslint-disable-next-line no-console
99
- console.error(`Library ${libName} failed to load. ${e}`);
100
- throw e;
101
- });
102
- }
103
- async #importNodeLibs() {
104
- // Also current assumption is using Node 18 or better
105
- // With 18.3 there is now a native fetch (but may want to force use of node-fetch)
106
- const useNodeFetch = !!this.#config.useNodeFetch;
107
- return Promise.all([
108
- this.#importSingleLib('node-fetch', 'fetch', useNodeFetch),
109
- this.#importSingleLib('open', 'open'),
110
- this.#importSingleLib('node:crypto', 'crypto', true),
111
- this.#importSingleLib('node:https', 'https'),
112
- this.#importSingleLib('node:http', 'http'),
113
- this.#importSingleLib('node:fs', 'fs')
114
- ]).then(() => {
115
- this.subtle = this.crypto?.subtle || this.crypto.webcrypto.subtle;
116
- if ((typeof fetch === 'undefined' || useNodeFetch) && this.fetch) {
117
- /* eslint-disable-next-line no-global-assign */
118
- fetch = this.fetch;
119
- }
120
- });
121
- }
122
- // For PKCE the authorize includes a code_challenge & code_challenge_method as well
123
- async #buildAuthorizeUrl(state) {
124
- const { serverType, clientId, redirectUri, authorizeUri, userinfoUri, authService, appAlias, userIdentifier, password, noPKCE } = this.#config;
125
- const { sessionIndex } = this.#dynState;
126
- const bInfinity = serverType === 'infinity';
127
- if (!noPKCE) {
128
- // Generate random string of 64 chars for verifier. RFC 7636 says from 43-128 chars
129
- const buf = new Uint8Array(64);
130
- this.crypto.getRandomValues(buf);
131
- this.#dynState.codeVerifier = this.#base64UrlSafeEncode(buf);
132
- }
133
- // If sessionIndex exists then increment attempts count (we will stop sending session_index after two failures)
134
- // With Infinity '24 we can now properly detect a invalid_session_index error, but can't for earlier versions
135
- if (sessionIndex) {
136
- this.#dynState.sessionIndexAttempts += 1;
137
- }
138
- // We use state to verify that the received code is for the right authorize transaction
139
- this.#dynState.state = `${state || ''}.${this.#getRandomString(32)}`;
140
- // The same redirectUri needs to be provided to token endpoint, so save this away incase redirectUri is
141
- // adjusted for next authorize
142
- this.#dynState.acRedirectUri = redirectUri;
143
- // Persist codeVerifier in session storage so it survives the redirects that are to follow
144
- this.#updateConfig();
145
- // Trim alias to include just the real alias piece
146
- const additionalScope = (userinfoUri ? '+profile' : '') +
147
- (appAlias ? `+app.alias.${appAlias.replace(/^app\//, '')}` : '');
148
- const scope = bInfinity ? `openid+email${additionalScope}` : 'user_info';
149
- const authServiceArg = authService
150
- ? `&${bInfinity ? 'authentication_service' : 'alias'}=${encodeURIComponent(authService)}`
151
- : '';
152
- const sessionIndexArg = sessionIndex && this.#dynState.sessionIndexAttempts < 3
153
- ? `&session_index=${sessionIndex}`
154
- : '';
155
- // Add specified creds to try to avoid login popup (for Infinity only, and only with 'pega' authService)
156
- const userIdentifierArg = userIdentifier
157
- ? `&UserIdentifier=${encodeURIComponent(userIdentifier)}`
158
- : '';
159
- const passwordArg = password && userIdentifier ? `&Password=${encodeURIComponent(atob(password))}` : '';
160
- const moreAuthArgs = bInfinity
161
- ? `&enable_psyncId=true&cookies=none${sessionIndexArg}${userIdentifierArg}${passwordArg}`
162
- : '';
163
- let pkceArgs = '';
164
- if (!noPKCE) {
165
- const cc = await this.#getCodeChallenge(this.#dynState.codeVerifier);
166
- pkceArgs = `&code_challenge=${cc}&code_challenge_method=S256`;
167
- }
168
- return `${authorizeUri}?client_id=${clientId}&response_type=code&redirect_uri=${redirectUri}&scope=${scope}&state=${encodeURIComponent(this.#dynState.state)}${pkceArgs}${authServiceArg}${moreAuthArgs}`;
169
- }
170
51
  async login() {
171
52
  if (this.isNode && !this.crypto) {
172
53
  // Deferring dynamic loading of node libraries til this first method to avoid doing this in constructor
173
- await this.#importNodeLibs();
54
+ await __classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_importNodeLibs).call(this);
174
55
  }
175
- const { grantType, noPKCE } = this.#config;
56
+ const { grantType, noPKCE } = __classPrivateFieldGet(this, _PegaAuth_config, "f");
176
57
  if (grantType && grantType !== 'authCode') {
177
58
  return this.getToken();
178
59
  }
@@ -180,373 +61,39 @@ export class PegaAuth {
180
61
  if (!this.isNode && !noPKCE && !window.isSecureContext) {
181
62
  throw new Error(`Authorization code grant flow failed due to insecure browser context at ${window.location.origin}. Use localhost or https.`);
182
63
  }
183
- return this.#authCodeStart();
184
- }
185
- // authCode login issues the authorize endpoint transaction and deals with redirects
186
- async #authCodeStart() {
187
- const fnGetRedirectUriOrigin = () => {
188
- const redirectUri = this.#config.redirectUri;
189
- const nRootOffset = redirectUri.indexOf('//');
190
- const nFirstPathOffset = nRootOffset !== -1 ? redirectUri.indexOf('/', nRootOffset + 2) : -1;
191
- return nFirstPathOffset !== -1 ? redirectUri.substring(0, nFirstPathOffset) : redirectUri;
192
- };
193
- const redirectOrigin = fnGetRedirectUriOrigin();
194
- const startState = this.isNode ? '' : btoa(window.location.origin);
195
- return new Promise((resolve, reject) => {
196
- let theUrl = null; // holds the crafted authorize url
197
- let myWindow = null; // popup or iframe
198
- let elIframe = null;
199
- let elCloseBtn = null;
200
- const iframeTimeout = this.#config.silentTimeout !== undefined ? this.#config.silentTimeout : 5000;
201
- let bWinIframe = true;
202
- let tmrAuthComplete = null;
203
- let checkWindowClosed = null;
204
- let bDisablePromptNone = false;
205
- // Onload handlers fire for iframe content (but not for popup windows)
206
- const myWinOnLoad = () => {
207
- try {
208
- elIframe.contentWindow.postMessage({ type: 'PegaAuth' }, redirectOrigin);
209
- }
210
- catch (e) {
211
- // Exception trying to postMessage on load (perhaps should console.warn)
212
- }
213
- };
214
- const fnSetSilentAuthFailed = bSet => {
215
- this.#dynState.silentAuthFailed = bSet;
216
- this.#updateConfig();
217
- };
218
- /* eslint-disable prefer-promise-reject-errors */
219
- const fnOpenPopup = () => {
220
- if (this.#config.noPopups) {
221
- return reject('no-popups');
222
- }
223
- // Since displaying a visible window, clear the silent auth failed flag
224
- fnSetSilentAuthFailed(false);
225
- myWindow = (this.isNode ? this.open : window.open)(theUrl, '_blank', 'width=700,height=500,left=200,top=100');
226
- if (!myWindow) {
227
- // Blocked by popup-blocker
228
- return reject('blocked');
229
- }
230
- checkWindowClosed = setInterval(() => {
231
- if (myWindow.closed) {
232
- clearInterval(checkWindowClosed);
233
- reject('closed');
234
- }
235
- // Post to the popup window to cover scenario where window.opener is disabled on
236
- // popup window by some security library (like helmet.js)
237
- if (!this.isNode) {
238
- myWindow.postMessage({ type: 'PegaAuth' }, redirectOrigin);
239
- }
240
- }, 500);
241
- };
242
- /* eslint-enable prefer-promise-reject-errors */
243
- const fnCloseIframe = () => {
244
- elIframe.parentNode.removeChild(elIframe);
245
- elCloseBtn.parentNode.removeChild(elCloseBtn);
246
- elIframe = null;
247
- elCloseBtn = null;
248
- bWinIframe = false;
249
- };
250
- const fnCloseAndReject = () => {
251
- fnCloseIframe();
252
- /* eslint-disable-next-line prefer-promise-reject-errors */
253
- reject('closed');
254
- };
255
- const fnAuthMessageReceiver = event => {
256
- // Check origin to make sure it is the redirect origin
257
- if (event.origin !== redirectOrigin) {
258
- if (event.data?.type === 'PegaAuth') {
259
- // eslint-disable-next-line no-console
260
- console.error(`Authorization code grant flow error: Unexpected origin: ${event.origin} ... expecting: ${redirectOrigin}`);
261
- }
262
- return;
263
- }
264
- if (!event.data || !event.data.type || event.data.type !== 'PegaAuth')
265
- return;
266
- const aArgs = ['code', 'state', 'error', 'errorDesc'];
267
- const aValues = [];
268
- for (let i = 0; i < aArgs.length; i += 1) {
269
- const arg = aArgs[i];
270
- aValues[arg] = event.data[arg] ? event.data[arg].toString() : null;
271
- }
272
- const { code, state, error, errorDesc } = aValues;
273
- if (error) {
274
- // eslint-disable-next-line no-console
275
- console.error(`Authorization code grant flow error (${error}): ${errorDesc}`);
276
- }
277
- if (code && state !== this.#dynState.state) {
278
- // eslint-disable-next-line no-console
279
- console.error(`Authorization code transfer error: state mismatch: ${state} ... expecting: ${this.#dynState.state}`);
280
- }
281
- if (error || (code && state === this.#dynState.state)) {
282
- // eslint-disable-next-line no-use-before-define
283
- fnGetTokenAndFinish(code, error, errorDesc);
284
- }
285
- };
286
- const fnEnableMessageReceiver = bEnable => {
287
- if (bEnable) {
288
- window.addEventListener('message', fnAuthMessageReceiver, false);
289
- window.authCodeCallback = (code, state, error, errorDesc) => {
290
- if (error) {
291
- // eslint-disable-next-line no-console
292
- console.error(`Authorization code grant flow error (${error}): ${errorDesc}`);
293
- }
294
- if (code && state !== this.#dynState.state) {
295
- // eslint-disable-next-line no-console
296
- console.error(`Authorization code transfer error: state mismatch: ${state} ... expecting: ${this.#dynState.state}`);
297
- }
298
- if (error || (code && state === this.#dynState.state)) {
299
- // eslint-disable-next-line no-use-before-define
300
- fnGetTokenAndFinish(code, error, errorDesc);
301
- }
302
- };
303
- }
304
- else {
305
- window.removeEventListener('message', fnAuthMessageReceiver, false);
306
- delete window.authCodeCallback;
307
- }
308
- };
309
- const doAuthorize = () => {
310
- // If there is a userIdentifier and password specified or an external SSO auth service,
311
- // we can try to use this silently in an iFrame first
312
- bWinIframe =
313
- !this.isNode &&
314
- !this.#dynState.silentAuthFailed &&
315
- iframeTimeout > 0 &&
316
- ((!!this.#config.userIdentifier && !!this.#config.password) ||
317
- this.#config.iframeLoginUI ||
318
- this.#config.authService !== 'pega');
319
- // Enable message receiver
320
- if (!this.isNode) {
321
- fnEnableMessageReceiver(true);
322
- }
323
- if (bWinIframe) {
324
- const nFrameZLevel = 99999;
325
- elIframe = document.createElement('iframe');
326
- elIframe.id = `pe${this.#config.clientId}`;
327
- const loginBoxWidth = 500;
328
- const loginBoxHeight = 700;
329
- const oStyle = elIframe.style;
330
- oStyle.position = 'absolute';
331
- oStyle.display = 'none';
332
- oStyle.zIndex = nFrameZLevel;
333
- oStyle.top = `${Math.round(Math.max(window.innerHeight - loginBoxHeight, 0) / 2)}px`;
334
- oStyle.left = `${Math.round(Math.max(window.innerWidth - loginBoxWidth, 0) / 2)}px`;
335
- oStyle.width = '500px';
336
- oStyle.height = '700px';
337
- // Add Iframe to top of document DOM to have it load
338
- document.body.insertBefore(elIframe, document.body.firstChild);
339
- // document.getElementsByTagName('body')[0].appendChild(elIframe);
340
- elIframe.addEventListener('load', myWinOnLoad, true);
341
- // Disallow iframe content attempts to navigate main window
342
- elIframe.setAttribute('sandbox', 'allow-scripts allow-forms allow-same-origin');
343
- // Adding prompt=none as this is standard OIDC way to communicate no UI is expected (With Infinity '23 or better, this is passed on to
344
- // configured OIDC authentication services). cookies=none disables the temporary Pega-Rules cookie otherwise created on auth code
345
- // grant flow. For now these two args are either both set or not set, but might have a cookies="partitioned" one day.
346
- elIframe.setAttribute('src', bDisablePromptNone ? theUrl : `${theUrl}&prompt=none`);
347
- const svgCloseBtn = `<?xml version="1.0" encoding="UTF-8"?>
348
- <svg width="34px" height="34px" viewBox="0 0 34 34" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
349
- <title>Dismiss - Black</title>
350
- <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
351
- <g transform="translate(1.000000, 1.000000)">
352
- <circle fill="#252C32" cx="16" cy="16" r="16"></circle>
353
- <g transform="translate(9.109375, 9.214844)" fill="#FFFFFF" fill-rule="nonzero">
354
- <path d="M12.7265625,0 L0,12.6210938 L1.0546875,13.5703125 L13.78125,1.0546875 L12.7265625,0 Z M13.7460938,12.5507812 L1.01953125,0 L0,1.01953125 L12.7617188,13.6054688 L13.7460938,12.5507812 Z"></path>
355
- </g>
356
- </g>
357
- </g>
358
- </svg>`;
359
- const bCloseWithinFrame = false;
360
- elCloseBtn = document.createElement('img');
361
- elCloseBtn.onclick = fnCloseAndReject;
362
- elCloseBtn.src = `data:image/svg+xml;base64,${btoa(svgCloseBtn)}`;
363
- const oBtnStyle = elCloseBtn.style;
364
- oBtnStyle.cursor = 'pointer';
365
- // If svg doesn't set width and height might want to set oBtStyle width and height to something like '2em'
366
- oBtnStyle.position = 'absolute';
367
- oBtnStyle.display = 'none';
368
- oBtnStyle.zIndex = nFrameZLevel + 1;
369
- const nTopOffset = bCloseWithinFrame ? 5 : -10;
370
- const nRightOffset = bCloseWithinFrame ? -34 : -20;
371
- const nTop = Math.round(Math.max(window.innerHeight - loginBoxHeight, 0) / 2) + nTopOffset;
372
- oBtnStyle.top = `${nTop}px`;
373
- const nLeft = Math.round(Math.max(window.innerWidth - loginBoxWidth, 0) / 2) +
374
- loginBoxWidth +
375
- nRightOffset;
376
- oBtnStyle.left = `${nLeft}px`;
377
- document.body.insertBefore(elCloseBtn, document.body.firstChild);
378
- // If the password was wrong, then the login screen will be in the iframe
379
- // ..and with Pega without realization of US-372314 it may replace the top (main portal) window
380
- // For now set a timer and if the timer expires, remove the iFrame and use same url within
381
- // visible window
382
- tmrAuthComplete = setTimeout(() => {
383
- clearTimeout(tmrAuthComplete);
384
- /*
385
- // remove password from config
386
- if (this.#config.password) {
387
- delete this.#config.password;
388
- this.#updateConfig();
389
- }
390
- */
391
- // Display the iframe where the redirects did not succeed (or invoke a popup window)
392
- if (this.#config.iframeLoginUI) {
393
- elIframe.style.display = 'block';
394
- elCloseBtn.style.display = 'block';
395
- }
396
- else {
397
- fnCloseIframe();
398
- fnOpenPopup();
399
- }
400
- }, iframeTimeout);
401
- }
402
- else {
403
- if (this.isNode) {
404
- // Determine port to listen to by extracting it from redirect uri
405
- const { redirectUri, cert, key } = this.#config;
406
- const isHttp = redirectUri.startsWith('http:');
407
- const nLocalhost = redirectUri.indexOf('localhost:');
408
- const nSlash = redirectUri.indexOf('/', nLocalhost + 10);
409
- const nPort = parseInt(redirectUri.substring(nLocalhost + 10, nSlash), 10);
410
- if (nLocalhost !== -1) {
411
- const options = key && cert && !isHttp
412
- ? {
413
- key: this.fs.readFileSync(key),
414
- cert: this.fs.readFileSync(cert)
415
- }
416
- : {};
417
- const server = (isHttp ? this.http : this.https).createServer(options, (req, res) => {
418
- const { winTitle, winBodyHtml } = this.#config;
419
- res.writeHead(200, { 'Content-Type': 'text/html' });
420
- // Auto closing window for now. Can always leave it up and allow authConfig props to set title and bodyHtml
421
- res.end(`<html><head><title>${winTitle}</title><script>window.close();</script></head><body>${winBodyHtml}</body></html>`);
422
- const queryString = req.url.split('?')[1];
423
- const urlParams = new URLSearchParams(queryString);
424
- const code = urlParams.get('code');
425
- const state = urlParams.get('state');
426
- const error = urlParams.get('error');
427
- const errorDesc = urlParams.get('error_description');
428
- if (error || (code && state === this.#dynState.state)) {
429
- // Stop receiving connections and close when all are handled.
430
- server.close();
431
- // eslint-disable-next-line no-use-before-define
432
- fnGetTokenAndFinish(code, error, errorDesc);
433
- }
434
- });
435
- server.listen(nPort);
436
- }
437
- }
438
- fnOpenPopup();
439
- }
440
- };
441
- /* Retrieve token(s) and close login window */
442
- const fnGetTokenAndFinish = (code, error, errorDesc) => {
443
- // Can clear state in session info at this point
444
- delete this.#dynState.state;
445
- this.#updateConfig();
446
- if (!this.isNode) {
447
- fnEnableMessageReceiver(false);
448
- if (bWinIframe) {
449
- clearTimeout(tmrAuthComplete);
450
- fnCloseIframe();
451
- }
452
- else {
453
- clearInterval(checkWindowClosed);
454
- try {
455
- if (myWindow) {
456
- myWindow.close();
457
- myWindow = null;
458
- }
459
- }
460
- catch (e) {
461
- // keep going and process code or error even though issue closing popup
462
- // eslint-disable-next-line no-console
463
- console.warn(`Error closing window: ${e}`);
464
- }
465
- }
466
- }
467
- if (code) {
468
- this.getToken(code)
469
- .then(token => {
470
- resolve(token);
471
- })
472
- .catch(e => {
473
- reject(e);
474
- });
475
- }
476
- else if (error) {
477
- // Handle some errors in a special manner and pass others back to client
478
- if (error === 'login_required') {
479
- // eslint-disable-next-line no-console
480
- console.warn('silent authentication failed...starting full authentication');
481
- const bSpecialDebugPath = false;
482
- if (bSpecialDebugPath) {
483
- fnSetSilentAuthFailed(false);
484
- bDisablePromptNone = true;
485
- }
486
- else {
487
- fnSetSilentAuthFailed(true);
488
- bDisablePromptNone = false;
489
- }
490
- this.#buildAuthorizeUrl(startState).then(url => {
491
- theUrl = url;
492
- doAuthorize();
493
- });
494
- }
495
- else if (error === 'invalid_session_index') {
496
- // eslint-disable-next-line no-console
497
- console.warn('auth session no longer valid...starting new session');
498
- // In these scenarios, not much user can do without just starting a new session, so do that
499
- this.#updateSessionIndex(null);
500
- fnSetSilentAuthFailed(false);
501
- this.#buildAuthorizeUrl(startState).then(url => {
502
- theUrl = url;
503
- doAuthorize();
504
- });
505
- }
506
- else {
507
- // eslint-disable-next-line no-console
508
- console.warn(`Authorize failed: ${error}. ${errorDesc}\nFailing authorize url: ${theUrl}`);
509
- throw new Error(error, { cause: errorDesc });
510
- }
511
- }
512
- };
513
- this.#buildAuthorizeUrl(startState).then(url => {
514
- theUrl = url;
515
- doAuthorize();
516
- });
517
- });
64
+ return __classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_authCodeStart).call(this);
518
65
  }
519
66
  // Login redirect
520
67
  loginRedirect() {
521
68
  // eslint-disable-next-line no-restricted-globals
522
69
  const startState = btoa(location.origin);
523
- this.#buildAuthorizeUrl(startState).then(url => {
70
+ __classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_buildAuthorizeUrl).call(this, startState).then(url => {
71
+ // Determine if this is in an iframe, and if so, add &cookies=none
72
+ let cookiesArg = '&cookies=none';
73
+ try {
74
+ // No need for &cookies=none if this is a top level window
75
+ if (window.top === window.self) {
76
+ cookiesArg = '';
77
+ }
78
+ }
79
+ catch (e) {
80
+ // do nothing;
81
+ }
524
82
  // eslint-disable-next-line no-restricted-globals
525
- location.href = url;
83
+ location.href = `${url}${cookiesArg}`;
526
84
  });
527
85
  }
528
86
  // check state
529
87
  checkStateMatch(state) {
530
- return state === this.#dynState.state;
531
- }
532
- // Clear session index within config
533
- #updateSessionIndex(sessionIndex) {
534
- if (sessionIndex) {
535
- this.#dynState.sessionIndex = sessionIndex;
536
- this.#dynState.sessionIndexAttempts = 0;
537
- }
538
- else if (this.#dynState.sessionIndex) {
539
- delete this.#dynState.sessionIndex;
540
- }
541
- this.#updateConfig();
88
+ return state === __classPrivateFieldGet(this, _PegaAuth_dynState, "f").state;
542
89
  }
543
90
  // For PKCE token endpoint includes code_verifier
544
91
  getToken(authCode) {
545
92
  // Reload config to pick up the previously stored codeVerifier
546
- this.#reloadConfig();
547
- const { serverType, clientId, clientSecret, tokenUri, grantType, customTokenParams, userIdentifier, password, noPKCE, secureCookie } = this.#config;
93
+ __classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_reloadConfig).call(this);
94
+ const { serverType, clientId, clientSecret, tokenUri, grantType, customTokenParams, userIdentifier, password, noPKCE, secureCookie } = __classPrivateFieldGet(this, _PegaAuth_config, "f");
548
95
  const bInfinity = serverType === 'infinity';
549
- const { sessionIndex, acRedirectUri, codeVerifier } = this.#dynState;
96
+ const { sessionIndex, acRedirectUri, codeVerifier } = __classPrivateFieldGet(this, _PegaAuth_dynState, "f");
550
97
  const bAuthCode = !grantType || grantType === 'authCode';
551
98
  if (bAuthCode && !authCode && !this.isNode) {
552
99
  const queryString = window.location.search;
@@ -591,7 +138,7 @@ export class PegaAuth {
591
138
  formData.append('password', atob(password));
592
139
  }
593
140
  return fetch(tokenUri, {
594
- agent: this.#getAgent(),
141
+ agent: __classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_getAgent).call(this),
595
142
  method: 'POST',
596
143
  headers: new Headers({
597
144
  'content-type': this.urlencoded
@@ -615,22 +162,22 @@ export class PegaAuth {
615
162
  // add property to keep track of current time when the token expires
616
163
  token.eA = Date.now() + token.expires_in * 1000;
617
164
  // Clear authCode related config state: state, codeVerifier, acRedirectUri
618
- if (this.#dynState.state) {
619
- delete this.#dynState.state;
165
+ if (__classPrivateFieldGet(this, _PegaAuth_dynState, "f").state) {
166
+ delete __classPrivateFieldGet(this, _PegaAuth_dynState, "f").state;
620
167
  }
621
- if (this.#dynState.codeVerifier) {
622
- delete this.#dynState.codeVerifier;
168
+ if (__classPrivateFieldGet(this, _PegaAuth_dynState, "f").codeVerifier) {
169
+ delete __classPrivateFieldGet(this, _PegaAuth_dynState, "f").codeVerifier;
623
170
  }
624
- if (this.#dynState.acRedirectUri) {
625
- delete this.#dynState.acRedirectUri;
171
+ if (__classPrivateFieldGet(this, _PegaAuth_dynState, "f").acRedirectUri) {
172
+ delete __classPrivateFieldGet(this, _PegaAuth_dynState, "f").acRedirectUri;
626
173
  }
627
174
  // If there is a session_index then move this to the peConfig structure (as used on authorize)
628
175
  if (token.session_index) {
629
- this.#updateSessionIndex(token.session_index);
176
+ __classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_updateSessionIndex).call(this, token.session_index);
630
177
  // does an #updateConfig within #updateSessionIndex
631
178
  }
632
179
  else {
633
- this.#updateConfig();
180
+ __classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_updateConfig).call(this);
634
181
  }
635
182
  }
636
183
  return token;
@@ -643,10 +190,10 @@ export class PegaAuth {
643
190
  });
644
191
  }
645
192
  async refreshToken(refreshToken) {
646
- const { clientId, clientSecret, tokenUri, secureCookie } = this.#config;
193
+ const { clientId, clientSecret, tokenUri, secureCookie } = __classPrivateFieldGet(this, _PegaAuth_config, "f");
647
194
  if (this.isNode && !this.crypto) {
648
195
  // Deferring dynamic loading of node libraries til this first method to avoid doing this in constructor
649
- await this.#importNodeLibs();
196
+ await __classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_importNodeLibs).call(this);
650
197
  }
651
198
  if (!secureCookie && !refreshToken) {
652
199
  return null;
@@ -664,7 +211,7 @@ export class PegaAuth {
664
211
  formData.append('refresh_token', refreshToken);
665
212
  }
666
213
  return fetch(tokenUri, {
667
- agent: this.#getAgent(),
214
+ agent: __classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_getAgent).call(this),
668
215
  method: 'POST',
669
216
  headers: new Headers({
670
217
  'content-type': this.urlencoded
@@ -703,15 +250,15 @@ export class PegaAuth {
703
250
  * @returns
704
251
  */
705
252
  async revokeTokens(accessToken, refreshToken = null) {
706
- if (Object.keys(this.#config).length === 0) {
253
+ if (Object.keys(__classPrivateFieldGet(this, _PegaAuth_config, "f")).length === 0) {
707
254
  // Must have a config structure to proceed
708
255
  return;
709
256
  }
710
- const { serverType, clientId, clientSecret, revokeUri, secureCookie } = this.#config;
257
+ const { serverType, clientId, clientSecret, revokeUri, secureCookie } = __classPrivateFieldGet(this, _PegaAuth_config, "f");
711
258
  const bLaunchpad = serverType === 'launchpad';
712
259
  if (this.isNode && !this.crypto) {
713
260
  // Deferring dynamic loading of node libraries til this first method to avoid doing this in constructor
714
- await this.#importNodeLibs();
261
+ await __classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_importNodeLibs).call(this);
715
262
  }
716
263
  const headers = { 'content-type': this.urlencoded };
717
264
  if (clientSecret && !bLaunchpad) {
@@ -739,7 +286,7 @@ export class PegaAuth {
739
286
  formData.append('token_type_hint', prop);
740
287
  if (revokeUri) {
741
288
  return fetch(revokeUri, {
742
- agent: this.#getAgent(),
289
+ agent: __classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_getAgent).call(this),
743
290
  method: 'POST',
744
291
  headers: new Headers(headers),
745
292
  credentials: secureCookie ? 'include' : 'omit',
@@ -756,19 +303,19 @@ export class PegaAuth {
756
303
  console.error(`Error revoking ${prop}; ${e}`);
757
304
  })
758
305
  .finally(() => {
759
- this.#dynState.silentAuthFailed = false;
306
+ __classPrivateFieldGet(this, _PegaAuth_dynState, "f").silentAuthFailed = false;
760
307
  // Also clobber any sessionIndex
761
- this.#updateSessionIndex(null);
308
+ __classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_updateSessionIndex).call(this, null);
762
309
  });
763
310
  }
764
- this.#dynState.silentAuthFailed = false;
311
+ __classPrivateFieldGet(this, _PegaAuth_dynState, "f").silentAuthFailed = false;
765
312
  // Also clobber any sessionIndex
766
- this.#updateSessionIndex(null);
313
+ __classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_updateSessionIndex).call(this, null);
767
314
  }
768
315
  // For userinfo endpoint to return meaningful data, endpoint must include appAlias (if specified) and authorize must
769
316
  // specify profile and optionally email scope to get such info returned
770
317
  async getUserinfo(accessToken) {
771
- if (!this.#config || !this.#config.userinfoUri) {
318
+ if (!__classPrivateFieldGet(this, _PegaAuth_config, "f") || !__classPrivateFieldGet(this, _PegaAuth_config, "f").userinfoUri) {
772
319
  // Must have a config structure and userInfo to proceed
773
320
  return {};
774
321
  }
@@ -776,8 +323,8 @@ export class PegaAuth {
776
323
  authorization: `bearer ${accessToken}`,
777
324
  'content-type': 'application/json;charset=UTF-8'
778
325
  };
779
- return fetch(this.#config.userinfoUri, {
780
- agent: this.#getAgent(),
326
+ return fetch(__classPrivateFieldGet(this, _PegaAuth_config, "f").userinfoUri, {
327
+ agent: __classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_getAgent).call(this),
781
328
  method: 'GET',
782
329
  headers: new Headers(headers)
783
330
  })
@@ -796,64 +343,520 @@ export class PegaAuth {
796
343
  console.log(e);
797
344
  });
798
345
  }
799
- #sha256Hash(str) {
800
- // Found that the Node implementation of subtle.digest is yielding incorrect results
801
- // so using a different set of apis to get expected results.
802
- if (this.isNode) {
803
- return new Promise(resolve => {
804
- resolve(this.crypto.createHash('sha256').update(str).digest());
805
- });
346
+ }
347
+ _PegaAuth_config = new WeakMap(), _PegaAuth_dynState = new WeakMap(), _PegaAuth_instances = new WeakSet(), _PegaAuth_reloadSS = function _PegaAuth_reloadSS(ssKey) {
348
+ const sItem = window.sessionStorage.getItem(ssKey);
349
+ let obj = {};
350
+ if (sItem) {
351
+ try {
352
+ obj = JSON.parse(sItem);
353
+ }
354
+ catch (e) {
355
+ try {
356
+ obj = JSON.parse(atob(sItem));
357
+ }
358
+ catch (err) {
359
+ obj = {};
360
+ }
806
361
  }
807
- return this.subtle.digest('SHA-256', new TextEncoder().encode(str));
808
362
  }
809
- // Base64 encode
810
- /* eslint-disable-next-line class-methods-use-this */
811
- #encode64(buff) {
812
- return btoa(new Uint8Array(buff).reduce((s, b) => s + String.fromCharCode(b), ''));
363
+ if (ssKey === this.ssKeyConfig) {
364
+ __classPrivateFieldSet(this, _PegaAuth_config, sItem ? obj : {}, "f");
813
365
  }
814
- /*
815
- * Base64 url safe encoding of an array
816
- */
817
- #base64UrlSafeEncode(buf) {
818
- return this.#encode64(buf).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
366
+ else {
367
+ __classPrivateFieldSet(this, _PegaAuth_dynState, sItem ? obj : {}, "f");
819
368
  }
820
- /*
821
- * Get Random string starting with buffer of specified size
822
- */
823
- #getRandomString(nSize) {
824
- const buf = new Uint8Array(nSize);
369
+ }, _PegaAuth_reloadConfig = function _PegaAuth_reloadConfig() {
370
+ if (this.ssKeyConfig) {
371
+ __classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_reloadSS).call(this, this.ssKeyConfig);
372
+ }
373
+ if (this.ssKeyDynState) {
374
+ __classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_reloadSS).call(this, this.ssKeyDynState);
375
+ }
376
+ }, _PegaAuth_updateConfig = function _PegaAuth_updateConfig() {
377
+ // transform must occur unless it is explicitly disabled
378
+ const transform = __classPrivateFieldGet(this, _PegaAuth_config, "f").transform !== false;
379
+ // May not need to write out Config info all the time, but there is a scenario where a
380
+ // non obfuscated value is passed in and then it needs to be obfuscated
381
+ if (this.ssKeyConfig) {
382
+ const sConfig = JSON.stringify(__classPrivateFieldGet(this, _PegaAuth_config, "f"));
383
+ window.sessionStorage.setItem(this.ssKeyConfig, transform ? btoa(sConfig) : sConfig);
384
+ }
385
+ if (this.ssKeyDynState) {
386
+ const sDynState = JSON.stringify(__classPrivateFieldGet(this, _PegaAuth_dynState, "f"));
387
+ window.sessionStorage.setItem(this.ssKeyDynState, transform ? btoa(sDynState) : sDynState);
388
+ }
389
+ if (__classPrivateFieldGet(this, _PegaAuth_config, "f").fnDynStateChangedCB) {
390
+ __classPrivateFieldGet(this, _PegaAuth_config, "f").fnDynStateChangedCB();
391
+ }
392
+ }, _PegaAuth_importSingleLib = async function _PegaAuth_importSingleLib(libName, libProp, bLoadAlways = false) {
393
+ if (!bLoadAlways && typeof (this.isNode ? global : window)[libProp] !== 'undefined') {
394
+ this[libProp] = (this.isNode ? global : window)[libProp];
395
+ return this[libProp];
396
+ }
397
+ // Needed to explicitly make import argument a string by using template literals to fix a compile
398
+ // error: Critical dependency: the request of a dependency is an expression
399
+ return import(/* webpackIgnore: true */ `${libName}`)
400
+ .then(mod => {
401
+ this[libProp] = mod.default;
402
+ })
403
+ .catch(e => {
404
+ // eslint-disable-next-line no-console
405
+ console.error(`Library ${libName} failed to load. ${e}`);
406
+ throw e;
407
+ });
408
+ }, _PegaAuth_importNodeLibs = async function _PegaAuth_importNodeLibs() {
409
+ // Also current assumption is using Node 18 or better
410
+ // With 18.3 there is now a native fetch (but may want to force use of node-fetch)
411
+ const useNodeFetch = !!__classPrivateFieldGet(this, _PegaAuth_config, "f").useNodeFetch;
412
+ return Promise.all([
413
+ __classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_importSingleLib).call(this, 'node-fetch', 'fetch', useNodeFetch),
414
+ __classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_importSingleLib).call(this, 'open', 'open'),
415
+ __classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_importSingleLib).call(this, 'node:crypto', 'crypto', true),
416
+ __classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_importSingleLib).call(this, 'node:https', 'https'),
417
+ __classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_importSingleLib).call(this, 'node:http', 'http'),
418
+ __classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_importSingleLib).call(this, 'node:fs', 'fs')
419
+ ]).then(() => {
420
+ this.subtle = this.crypto?.subtle || this.crypto.webcrypto.subtle;
421
+ if ((typeof fetch === 'undefined' || useNodeFetch) && this.fetch) {
422
+ /* eslint-disable-next-line no-global-assign */
423
+ fetch = this.fetch;
424
+ }
425
+ });
426
+ }, _PegaAuth_buildAuthorizeUrl =
427
+ // For PKCE the authorize includes a code_challenge & code_challenge_method as well
428
+ async function _PegaAuth_buildAuthorizeUrl(state) {
429
+ const { serverType, clientId, redirectUri, authorizeUri, userinfoUri, authService, appAlias, userIdentifier, password, noPKCE } = __classPrivateFieldGet(this, _PegaAuth_config, "f");
430
+ const { sessionIndex } = __classPrivateFieldGet(this, _PegaAuth_dynState, "f");
431
+ const bInfinity = serverType === 'infinity';
432
+ if (!noPKCE) {
433
+ // Generate random string of 64 chars for verifier. RFC 7636 says from 43-128 chars
434
+ const buf = new Uint8Array(64);
825
435
  this.crypto.getRandomValues(buf);
826
- return this.#base64UrlSafeEncode(buf);
436
+ __classPrivateFieldGet(this, _PegaAuth_dynState, "f").codeVerifier = __classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_base64UrlSafeEncode).call(this, buf);
827
437
  }
828
- /*
829
- * Calc code verifier if necessary
830
- */
831
- async #getCodeChallenge(codeVerifier) {
832
- return this.#sha256Hash(codeVerifier)
833
- .then(hashed => {
834
- return this.#base64UrlSafeEncode(hashed);
835
- })
836
- .catch(error => {
837
- // eslint-disable-next-line no-console
838
- console.error(`Error calculation code challenge for PKCE: ${error}`);
839
- })
840
- .finally(() => {
841
- return null;
842
- });
438
+ // If sessionIndex exists then increment attempts count (we will stop sending session_index after two failures)
439
+ // With Infinity '24 we can now properly detect a invalid_session_index error, but can't for earlier versions
440
+ if (sessionIndex) {
441
+ __classPrivateFieldGet(this, _PegaAuth_dynState, "f").sessionIndexAttempts += 1;
843
442
  }
844
- /*
845
- * Return agent value for POST commands
846
- */
847
- #getAgent() {
848
- if (this.isNode && this.#config.ignoreInvalidCerts) {
849
- const options = { rejectUnauthorized: false };
850
- if (this.#config.legacyTLS) {
851
- options.secureOptions = this.crypto.constants.SSL_OP_LEGACY_SERVER_CONNECT;
443
+ // We use state to verify that the received code is for the right authorize transaction
444
+ __classPrivateFieldGet(this, _PegaAuth_dynState, "f").state = `${state || ''}.${__classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_getRandomString).call(this, 32)}`;
445
+ // The same redirectUri needs to be provided to token endpoint, so save this away in case redirectUri is
446
+ // adjusted for next authorize
447
+ __classPrivateFieldGet(this, _PegaAuth_dynState, "f").acRedirectUri = redirectUri;
448
+ // Persist codeVerifier in session storage so it survives the redirects that are to follow
449
+ __classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_updateConfig).call(this);
450
+ // Trim alias to include just the real alias piece
451
+ const additionalScope = (userinfoUri ? '+profile' : '') +
452
+ (appAlias ? `+app.alias.${appAlias.replace(/^app\//, '')}` : '');
453
+ const scope = bInfinity ? `openid+email${additionalScope}` : 'user_info';
454
+ const authServiceArg = authService
455
+ ? `&${bInfinity ? 'authentication_service' : 'alias'}=${encodeURIComponent(authService)}`
456
+ : '';
457
+ const sessionIndexArg = sessionIndex && __classPrivateFieldGet(this, _PegaAuth_dynState, "f").sessionIndexAttempts < 3
458
+ ? `&session_index=${sessionIndex}`
459
+ : '';
460
+ // Add specified creds to try to avoid login popup (for Infinity only, and only with 'pega' authService)
461
+ const userIdentifierArg = userIdentifier
462
+ ? `&UserIdentifier=${encodeURIComponent(userIdentifier)}`
463
+ : '';
464
+ const passwordArg = password && userIdentifier ? `&Password=${encodeURIComponent(atob(password))}` : '';
465
+ const moreAuthArgs = bInfinity
466
+ ? `&enable_psyncId=true${sessionIndexArg}${userIdentifierArg}${passwordArg}`
467
+ : '';
468
+ let pkceArgs = '';
469
+ if (!noPKCE) {
470
+ const cc = await __classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_getCodeChallenge).call(this, __classPrivateFieldGet(this, _PegaAuth_dynState, "f").codeVerifier);
471
+ pkceArgs = `&code_challenge=${cc}&code_challenge_method=S256`;
472
+ }
473
+ return `${authorizeUri}?client_id=${clientId}&response_type=code&redirect_uri=${redirectUri}&scope=${scope}&state=${encodeURIComponent(__classPrivateFieldGet(this, _PegaAuth_dynState, "f").state)}${pkceArgs}${authServiceArg}${moreAuthArgs}`;
474
+ }, _PegaAuth_authCodeStart =
475
+ // authCode login issues the authorize endpoint transaction and deals with redirects
476
+ async function _PegaAuth_authCodeStart() {
477
+ const fnGetRedirectUriOrigin = () => {
478
+ const redirectUri = __classPrivateFieldGet(this, _PegaAuth_config, "f").redirectUri;
479
+ const nRootOffset = redirectUri.indexOf('//');
480
+ const nFirstPathOffset = nRootOffset !== -1 ? redirectUri.indexOf('/', nRootOffset + 2) : -1;
481
+ return nFirstPathOffset !== -1 ? redirectUri.substring(0, nFirstPathOffset) : redirectUri;
482
+ };
483
+ const redirectOrigin = fnGetRedirectUriOrigin();
484
+ // Found that if loaded within a iframe srcdoc tag, then window.location.origin is string 'null
485
+ const startState = this.isNode || window.location.origin === 'null' ? '' : btoa(window.location.origin);
486
+ return new Promise((resolve, reject) => {
487
+ let theUrl = null; // holds the crafted authorize url
488
+ let myWindow = null; // popup or iframe
489
+ let elIframe = null;
490
+ let elCloseBtn = null;
491
+ const iframeTimeout = __classPrivateFieldGet(this, _PegaAuth_config, "f").silentTimeout !== undefined ? __classPrivateFieldGet(this, _PegaAuth_config, "f").silentTimeout : 5000;
492
+ let bWinIframe = true;
493
+ let tmrAuthComplete = null;
494
+ let checkWindowClosed = null;
495
+ let bDisablePromptNone = false;
496
+ // Onload handlers fire for iframe content (but not for popup windows)
497
+ const myWinOnLoad = () => {
498
+ try {
499
+ elIframe.contentWindow.postMessage({ type: 'PegaAuth' }, redirectOrigin);
852
500
  }
853
- return new this.https.Agent(options);
854
- }
855
- return undefined;
501
+ catch (e) {
502
+ // Exception trying to postMessage on load (perhaps should console.warn)
503
+ }
504
+ };
505
+ const fnSetSilentAuthFailed = bSet => {
506
+ __classPrivateFieldGet(this, _PegaAuth_dynState, "f").silentAuthFailed = bSet;
507
+ __classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_updateConfig).call(this);
508
+ };
509
+ /* eslint-disable prefer-promise-reject-errors */
510
+ const fnOpenPopup = () => {
511
+ if (__classPrivateFieldGet(this, _PegaAuth_config, "f").noPopups) {
512
+ return reject('no-popups');
513
+ }
514
+ // Since displaying a visible window, clear the silent auth failed flag
515
+ fnSetSilentAuthFailed(false);
516
+ myWindow = (this.isNode ? this.open : window.open)(theUrl, '_blank', 'width=700,height=500,left=200,top=100');
517
+ if (!myWindow) {
518
+ // Blocked by popup-blocker
519
+ return reject('blocked');
520
+ }
521
+ checkWindowClosed = setInterval(() => {
522
+ if (myWindow.closed) {
523
+ clearInterval(checkWindowClosed);
524
+ reject('closed');
525
+ }
526
+ // Post to the popup window to cover scenario where window.opener is disabled on
527
+ // popup window by some security library (like helmet.js)
528
+ if (!this.isNode) {
529
+ myWindow.postMessage({ type: 'PegaAuth' }, redirectOrigin);
530
+ }
531
+ }, 500);
532
+ };
533
+ /* eslint-enable prefer-promise-reject-errors */
534
+ const fnCloseIframe = () => {
535
+ elIframe.parentNode.removeChild(elIframe);
536
+ elCloseBtn.parentNode.removeChild(elCloseBtn);
537
+ elIframe = null;
538
+ elCloseBtn = null;
539
+ bWinIframe = false;
540
+ };
541
+ const fnCloseAndReject = () => {
542
+ fnCloseIframe();
543
+ /* eslint-disable-next-line prefer-promise-reject-errors */
544
+ reject('closed');
545
+ };
546
+ const fnAuthMessageReceiver = event => {
547
+ // Check origin to make sure it is the redirect origin
548
+ if (event.origin !== redirectOrigin) {
549
+ if (event.data?.type === 'PegaAuth') {
550
+ // eslint-disable-next-line no-console
551
+ console.error(`Authorization code grant flow error: Unexpected origin: ${event.origin} ... expecting: ${redirectOrigin}`);
552
+ }
553
+ return;
554
+ }
555
+ if (!event.data || !event.data.type || event.data.type !== 'PegaAuth')
556
+ return;
557
+ const aArgs = ['code', 'state', 'error', 'errorDesc'];
558
+ const aValues = [];
559
+ for (let i = 0; i < aArgs.length; i += 1) {
560
+ const arg = aArgs[i];
561
+ aValues[arg] = event.data[arg] ? event.data[arg].toString() : null;
562
+ }
563
+ const { code, state, error, errorDesc } = aValues;
564
+ if (error) {
565
+ // eslint-disable-next-line no-console
566
+ console.error(`Authorization code grant flow error (${error}): ${errorDesc}`);
567
+ }
568
+ if (code && state !== __classPrivateFieldGet(this, _PegaAuth_dynState, "f").state) {
569
+ // eslint-disable-next-line no-console
570
+ console.error(`Authorization code transfer error: state mismatch: ${state} ... expecting: ${__classPrivateFieldGet(this, _PegaAuth_dynState, "f").state}`);
571
+ }
572
+ if (error || (code && state === __classPrivateFieldGet(this, _PegaAuth_dynState, "f").state)) {
573
+ // eslint-disable-next-line no-use-before-define
574
+ fnGetTokenAndFinish(code, error, errorDesc);
575
+ }
576
+ };
577
+ const fnEnableMessageReceiver = bEnable => {
578
+ if (bEnable) {
579
+ window.addEventListener('message', fnAuthMessageReceiver, false);
580
+ window.authCodeCallback = (code, state, error, errorDesc) => {
581
+ if (error) {
582
+ // eslint-disable-next-line no-console
583
+ console.error(`Authorization code grant flow error (${error}): ${errorDesc}`);
584
+ }
585
+ if (code && state !== __classPrivateFieldGet(this, _PegaAuth_dynState, "f").state) {
586
+ // eslint-disable-next-line no-console
587
+ console.error(`Authorization code transfer error: state mismatch: ${state} ... expecting: ${__classPrivateFieldGet(this, _PegaAuth_dynState, "f").state}`);
588
+ }
589
+ if (error || (code && state === __classPrivateFieldGet(this, _PegaAuth_dynState, "f").state)) {
590
+ // eslint-disable-next-line no-use-before-define
591
+ fnGetTokenAndFinish(code, error, errorDesc);
592
+ }
593
+ };
594
+ }
595
+ else {
596
+ window.removeEventListener('message', fnAuthMessageReceiver, false);
597
+ delete window.authCodeCallback;
598
+ }
599
+ };
600
+ const doAuthorize = () => {
601
+ // If there is a userIdentifier and password specified or an external SSO auth service,
602
+ // we can try to use this silently in an iFrame first
603
+ bWinIframe =
604
+ !this.isNode &&
605
+ !__classPrivateFieldGet(this, _PegaAuth_dynState, "f").silentAuthFailed &&
606
+ iframeTimeout > 0 &&
607
+ ((!!__classPrivateFieldGet(this, _PegaAuth_config, "f").userIdentifier && !!__classPrivateFieldGet(this, _PegaAuth_config, "f").password) ||
608
+ __classPrivateFieldGet(this, _PegaAuth_config, "f").iframeLoginUI ||
609
+ __classPrivateFieldGet(this, _PegaAuth_config, "f").authService !== 'pega');
610
+ // Enable message receiver
611
+ if (!this.isNode) {
612
+ fnEnableMessageReceiver(true);
613
+ }
614
+ if (bWinIframe) {
615
+ const nFrameZLevel = 99999;
616
+ elIframe = document.createElement('iframe');
617
+ elIframe.id = `pe${__classPrivateFieldGet(this, _PegaAuth_config, "f").clientId}`;
618
+ const loginBoxWidth = 500;
619
+ const loginBoxHeight = 700;
620
+ const oStyle = elIframe.style;
621
+ oStyle.position = 'absolute';
622
+ oStyle.display = 'none';
623
+ oStyle.zIndex = nFrameZLevel;
624
+ oStyle.top = `${Math.round(Math.max(window.innerHeight - loginBoxHeight, 0) / 2)}px`;
625
+ oStyle.left = `${Math.round(Math.max(window.innerWidth - loginBoxWidth, 0) / 2)}px`;
626
+ oStyle.width = '500px';
627
+ oStyle.height = '700px';
628
+ // Add Iframe to top of document DOM to have it load
629
+ document.body.insertBefore(elIframe, document.body.firstChild);
630
+ // document.getElementsByTagName('body')[0].appendChild(elIframe);
631
+ elIframe.addEventListener('load', myWinOnLoad, true);
632
+ // Disallow iframe content attempts to navigate main window
633
+ elIframe.setAttribute('sandbox', 'allow-scripts allow-forms allow-same-origin');
634
+ // Adding prompt=none as this is standard OIDC way to communicate no UI is expected (With Infinity '23 or better, this is passed on to
635
+ // configured OIDC authentication services). cookies=none disables the temporary Pega-Rules cookie otherwise created on auth code
636
+ // grant flow. For now these two args are either both set or not set, but might have a cookies="partitioned" one day.
637
+ elIframe.setAttribute('src', bDisablePromptNone ? `${theUrl}&cookies=none` : `${theUrl}&cookies=none&prompt=none`);
638
+ const svgCloseBtn = `<?xml version="1.0" encoding="UTF-8"?>
639
+ <svg width="34px" height="34px" viewBox="0 0 34 34" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
640
+ <title>Dismiss - Black</title>
641
+ <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
642
+ <g transform="translate(1.000000, 1.000000)">
643
+ <circle fill="#252C32" cx="16" cy="16" r="16"></circle>
644
+ <g transform="translate(9.109375, 9.214844)" fill="#FFFFFF" fill-rule="nonzero">
645
+ <path d="M12.7265625,0 L0,12.6210938 L1.0546875,13.5703125 L13.78125,1.0546875 L12.7265625,0 Z M13.7460938,12.5507812 L1.01953125,0 L0,1.01953125 L12.7617188,13.6054688 L13.7460938,12.5507812 Z"></path>
646
+ </g>
647
+ </g>
648
+ </g>
649
+ </svg>`;
650
+ const bCloseWithinFrame = false;
651
+ elCloseBtn = document.createElement('img');
652
+ elCloseBtn.onclick = fnCloseAndReject;
653
+ elCloseBtn.src = `data:image/svg+xml;base64,${btoa(svgCloseBtn)}`;
654
+ const oBtnStyle = elCloseBtn.style;
655
+ oBtnStyle.cursor = 'pointer';
656
+ // If svg doesn't set width and height might want to set oBtStyle width and height to something like '2em'
657
+ oBtnStyle.position = 'absolute';
658
+ oBtnStyle.display = 'none';
659
+ oBtnStyle.zIndex = nFrameZLevel + 1;
660
+ const nTopOffset = bCloseWithinFrame ? 5 : -10;
661
+ const nRightOffset = bCloseWithinFrame ? -34 : -20;
662
+ const nTop = Math.round(Math.max(window.innerHeight - loginBoxHeight, 0) / 2) + nTopOffset;
663
+ oBtnStyle.top = `${nTop}px`;
664
+ const nLeft = Math.round(Math.max(window.innerWidth - loginBoxWidth, 0) / 2) +
665
+ loginBoxWidth +
666
+ nRightOffset;
667
+ oBtnStyle.left = `${nLeft}px`;
668
+ document.body.insertBefore(elCloseBtn, document.body.firstChild);
669
+ // If the password was wrong, then the login screen will be in the iframe
670
+ // ..and with Pega without realization of US-372314 it may replace the top (main portal) window
671
+ // For now set a timer and if the timer expires, remove the iFrame and use same url within
672
+ // visible window
673
+ tmrAuthComplete = setTimeout(() => {
674
+ clearTimeout(tmrAuthComplete);
675
+ /*
676
+ // remove password from config
677
+ if (this.#config.password) {
678
+ delete this.#config.password;
679
+ this.#updateConfig();
680
+ }
681
+ */
682
+ // Display the iframe where the redirects did not succeed (or invoke a popup window)
683
+ if (__classPrivateFieldGet(this, _PegaAuth_config, "f").iframeLoginUI) {
684
+ elIframe.style.display = 'block';
685
+ elCloseBtn.style.display = 'block';
686
+ }
687
+ else {
688
+ fnCloseIframe();
689
+ fnOpenPopup();
690
+ }
691
+ }, iframeTimeout);
692
+ }
693
+ else {
694
+ if (this.isNode) {
695
+ // Determine port to listen to by extracting it from redirect uri
696
+ const { redirectUri, cert, key } = __classPrivateFieldGet(this, _PegaAuth_config, "f");
697
+ const isHttp = redirectUri.startsWith('http:');
698
+ const nLocalhost = redirectUri.indexOf('localhost:');
699
+ const nSlash = redirectUri.indexOf('/', nLocalhost + 10);
700
+ const nPort = parseInt(redirectUri.substring(nLocalhost + 10, nSlash), 10);
701
+ if (nLocalhost !== -1) {
702
+ const options = key && cert && !isHttp
703
+ ? {
704
+ key: this.fs.readFileSync(key),
705
+ cert: this.fs.readFileSync(cert)
706
+ }
707
+ : {};
708
+ const server = (isHttp ? this.http : this.https).createServer(options, (req, res) => {
709
+ const { winTitle, winBodyHtml } = __classPrivateFieldGet(this, _PegaAuth_config, "f");
710
+ res.writeHead(200, { 'Content-Type': 'text/html' });
711
+ // Auto closing window for now. Can always leave it up and allow authConfig props to set title and bodyHtml
712
+ res.end(`<html><head><title>${winTitle}</title><script>window.close();</script></head><body>${winBodyHtml}</body></html>`);
713
+ const queryString = req.url.split('?')[1];
714
+ const urlParams = new URLSearchParams(queryString);
715
+ const code = urlParams.get('code');
716
+ const state = urlParams.get('state');
717
+ const error = urlParams.get('error');
718
+ const errorDesc = urlParams.get('error_description');
719
+ if (error || (code && state === __classPrivateFieldGet(this, _PegaAuth_dynState, "f").state)) {
720
+ // Stop receiving connections and close when all are handled.
721
+ server.close();
722
+ // eslint-disable-next-line no-use-before-define
723
+ fnGetTokenAndFinish(code, error, errorDesc);
724
+ }
725
+ });
726
+ server.listen(nPort);
727
+ }
728
+ }
729
+ fnOpenPopup();
730
+ }
731
+ };
732
+ /* Retrieve token(s) and close login window */
733
+ const fnGetTokenAndFinish = (code, error, errorDesc) => {
734
+ // Can clear state in session info at this point
735
+ delete __classPrivateFieldGet(this, _PegaAuth_dynState, "f").state;
736
+ __classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_updateConfig).call(this);
737
+ if (!this.isNode) {
738
+ fnEnableMessageReceiver(false);
739
+ if (bWinIframe) {
740
+ clearTimeout(tmrAuthComplete);
741
+ fnCloseIframe();
742
+ }
743
+ else {
744
+ clearInterval(checkWindowClosed);
745
+ try {
746
+ if (myWindow) {
747
+ myWindow.close();
748
+ myWindow = null;
749
+ }
750
+ }
751
+ catch (e) {
752
+ // keep going and process code or error even though issue closing popup
753
+ // eslint-disable-next-line no-console
754
+ console.warn(`Error closing window: ${e}`);
755
+ }
756
+ }
757
+ }
758
+ if (code) {
759
+ this.getToken(code)
760
+ .then(token => {
761
+ resolve(token);
762
+ })
763
+ .catch(e => {
764
+ reject(e);
765
+ });
766
+ }
767
+ else if (error) {
768
+ // Handle some errors in a special manner and pass others back to client
769
+ if (error === 'login_required') {
770
+ // eslint-disable-next-line no-console
771
+ console.warn('silent authentication failed...starting full authentication');
772
+ const bSpecialDebugPath = false;
773
+ if (bSpecialDebugPath) {
774
+ fnSetSilentAuthFailed(false);
775
+ bDisablePromptNone = true;
776
+ }
777
+ else {
778
+ fnSetSilentAuthFailed(true);
779
+ bDisablePromptNone = false;
780
+ }
781
+ __classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_buildAuthorizeUrl).call(this, startState).then(url => {
782
+ theUrl = url;
783
+ doAuthorize();
784
+ });
785
+ }
786
+ else if (error === 'invalid_session_index') {
787
+ // eslint-disable-next-line no-console
788
+ console.warn('auth session no longer valid...starting new session');
789
+ // In these scenarios, not much user can do without just starting a new session, so do that
790
+ __classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_updateSessionIndex).call(this, null);
791
+ fnSetSilentAuthFailed(false);
792
+ __classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_buildAuthorizeUrl).call(this, startState).then(url => {
793
+ theUrl = url;
794
+ doAuthorize();
795
+ });
796
+ }
797
+ else {
798
+ // eslint-disable-next-line no-console
799
+ console.warn(`Authorize failed: ${error}. ${errorDesc}\nFailing authorize url: ${theUrl}`);
800
+ throw new Error(error, { cause: errorDesc });
801
+ }
802
+ }
803
+ };
804
+ __classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_buildAuthorizeUrl).call(this, startState).then(url => {
805
+ theUrl = url;
806
+ doAuthorize();
807
+ });
808
+ });
809
+ }, _PegaAuth_updateSessionIndex = function _PegaAuth_updateSessionIndex(sessionIndex) {
810
+ if (sessionIndex) {
811
+ __classPrivateFieldGet(this, _PegaAuth_dynState, "f").sessionIndex = sessionIndex;
812
+ __classPrivateFieldGet(this, _PegaAuth_dynState, "f").sessionIndexAttempts = 0;
856
813
  }
857
- }
814
+ else if (__classPrivateFieldGet(this, _PegaAuth_dynState, "f").sessionIndex) {
815
+ delete __classPrivateFieldGet(this, _PegaAuth_dynState, "f").sessionIndex;
816
+ }
817
+ __classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_updateConfig).call(this);
818
+ }, _PegaAuth_sha256Hash = function _PegaAuth_sha256Hash(str) {
819
+ // Found that the Node implementation of subtle.digest is yielding incorrect results
820
+ // so using a different set of apis to get expected results.
821
+ if (this.isNode) {
822
+ return new Promise(resolve => {
823
+ resolve(this.crypto.createHash('sha256').update(str).digest());
824
+ });
825
+ }
826
+ return this.subtle.digest('SHA-256', new TextEncoder().encode(str));
827
+ }, _PegaAuth_encode64 = function _PegaAuth_encode64(buff) {
828
+ return btoa(new Uint8Array(buff).reduce((s, b) => s + String.fromCharCode(b), ''));
829
+ }, _PegaAuth_base64UrlSafeEncode = function _PegaAuth_base64UrlSafeEncode(buf) {
830
+ return __classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_encode64).call(this, buf).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
831
+ }, _PegaAuth_getRandomString = function _PegaAuth_getRandomString(nSize) {
832
+ const buf = new Uint8Array(nSize);
833
+ this.crypto.getRandomValues(buf);
834
+ return __classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_base64UrlSafeEncode).call(this, buf);
835
+ }, _PegaAuth_getCodeChallenge =
836
+ /*
837
+ * Calc code verifier if necessary
838
+ */
839
+ async function _PegaAuth_getCodeChallenge(codeVerifier) {
840
+ return __classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_sha256Hash).call(this, codeVerifier)
841
+ .then(hashed => {
842
+ return __classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_base64UrlSafeEncode).call(this, hashed);
843
+ })
844
+ .catch(error => {
845
+ // eslint-disable-next-line no-console
846
+ console.error(`Error calculation code challenge for PKCE: ${error}`);
847
+ })
848
+ .finally(() => {
849
+ return null;
850
+ });
851
+ }, _PegaAuth_getAgent = function _PegaAuth_getAgent() {
852
+ if (this.isNode && __classPrivateFieldGet(this, _PegaAuth_config, "f").ignoreInvalidCerts) {
853
+ const options = { rejectUnauthorized: false };
854
+ if (__classPrivateFieldGet(this, _PegaAuth_config, "f").legacyTLS) {
855
+ options.secureOptions = this.crypto.constants.SSL_OP_LEGACY_SERVER_CONNECT;
856
+ }
857
+ return new this.https.Agent(options);
858
+ }
859
+ return undefined;
860
+ };
858
861
  export default PegaAuth;
859
862
  //# sourceMappingURL=auth.js.map