@pega/custom-dx-components 8.8.10 → 8.8.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/auth.js CHANGED
@@ -1,256 +1,272 @@
1
1
  class PegaAuth {
2
- constructor(ssKeyConfig) {
3
- if (typeof ssKeyConfig === "string") {
4
- this.ssKeyConfig = ssKeyConfig;
5
- this.#reloadConfig();
6
- } else {
7
- // object with config structure is passed in
8
- this.config = ssKeyConfig;
9
- }
10
- this.urlencoded = 'application/x-www-form-urlencoded';
11
- this.isNode = typeof window === 'undefined';
12
-
13
- // For isNode path the below attributes are initialized on first method invocation
14
- if (!this.isNode) {
15
- this.crypto = window.crypto;
16
- this.subtle = window.crypto.subtle;
17
- }
18
- if (!this.config.serverType) {
19
- this.config.serverType = 'infinity';
20
- }
2
+ constructor(ssKeyConfig) {
3
+ if (typeof ssKeyConfig === 'string') {
4
+ this.ssKeyConfig = ssKeyConfig;
5
+ this.#reloadConfig();
6
+ } else {
7
+ // object with config structure is passed in
8
+ this.config = ssKeyConfig;
21
9
  }
10
+ this.urlencoded = 'application/x-www-form-urlencoded';
11
+ this.isNode = typeof window === 'undefined';
22
12
 
23
- #reloadConfig() {
24
- if (!this.ssKeyConfig) {
25
- return;
26
- }
27
- const peConfig = window.sessionStorage.getItem(this.ssKeyConfig);
28
- let obj = {};
29
- if (peConfig) {
30
- try {
31
- obj = JSON.parse(peConfig);
32
- } catch (e) {
33
- try {
34
- obj = JSON.parse(this.#atob(peConfig));
35
- } catch (err) {
36
- obj = {};
37
- }
38
- }
39
- }
40
- this.config = peConfig ? obj : {};
13
+ // For isNode path the below attributes are initialized on first method invocation
14
+ if (!this.isNode) {
15
+ this.crypto = window.crypto;
16
+ this.subtle = window.crypto.subtle;
41
17
  }
18
+ if (!this.config.serverType) {
19
+ this.config.serverType = 'infinity';
20
+ }
21
+ }
42
22
 
43
- #updateConfig() {
44
- if (this.ssKeyConfig) {
45
- window.sessionStorage.setItem(this.ssKeyConfig, this.#btoa(JSON.stringify(this.config)));
23
+ #reloadConfig() {
24
+ if (!this.ssKeyConfig) {
25
+ return;
26
+ }
27
+ const peConfig = window.sessionStorage.getItem(this.ssKeyConfig);
28
+ let obj = {};
29
+ if (peConfig) {
30
+ try {
31
+ obj = JSON.parse(peConfig);
32
+ } catch (e) {
33
+ try {
34
+ obj = JSON.parse(this.#atob(peConfig));
35
+ } catch (err) {
36
+ obj = {};
46
37
  }
38
+ }
47
39
  }
40
+ this.config = peConfig ? obj : {};
41
+ }
48
42
 
49
- async #importSingleLib(libName, libProp) {
50
- return import(libName).then((mod) => {
51
- this[libProp] = mod.default;
52
- }).catch((e) => {
53
- console.error(`Library ${libName} failed to load`);
54
- });
43
+ #updateConfig() {
44
+ if (this.ssKeyConfig) {
45
+ window.sessionStorage.setItem(this.ssKeyConfig, this.#btoa(JSON.stringify(this.config)));
55
46
  }
56
-
57
- async #importNodeLibs() {
58
- // Also current assumption is using Node 18 or better
59
- return Promise.all([
60
- this.#importSingleLib('node-fetch', 'fetch'),
61
- this.#importSingleLib('open', 'open'),
62
- this.#importSingleLib('node:crypto', 'crypto'),
63
- this.#importSingleLib('node:https', 'https'),
64
- this.#importSingleLib('node:http', 'http'),
65
- this.#importSingleLib('node:fs', 'fs')
66
- ]).then(() => {
67
- this.subtle = this.crypto.subtle || this.crypto.webcrypto.subtle;
68
- /* eslint-disable-next-line no-global-assign */
69
- fetch = this.fetch;
70
- });
47
+ }
48
+
49
+ async #importSingleLib(libName, libProp) {
50
+ return import(libName)
51
+ .then((mod) => {
52
+ this[libProp] = mod.default;
53
+ })
54
+ .catch((e) => {
55
+ console.error(`Library ${libName} failed to load`);
56
+ });
57
+ }
58
+
59
+ async #importNodeLibs() {
60
+ // Also current assumption is using Node 18 or better
61
+ return Promise.all([
62
+ this.#importSingleLib('node-fetch', 'fetch'),
63
+ this.#importSingleLib('open', 'open'),
64
+ this.#importSingleLib('node:crypto', 'crypto'),
65
+ this.#importSingleLib('node:https', 'https'),
66
+ this.#importSingleLib('node:http', 'http'),
67
+ this.#importSingleLib('node:fs', 'fs')
68
+ ]).then(() => {
69
+ this.subtle = this.crypto.subtle || this.crypto.webcrypto.subtle;
70
+ /* eslint-disable-next-line no-global-assign */
71
+ fetch = this.fetch;
72
+ });
73
+ }
74
+
75
+ // For PKCE the authorize includes a code_challenge & code_challenge_method as well
76
+ async #buildAuthorizeUrl(state) {
77
+ const {
78
+ serverType,
79
+ clientId,
80
+ redirectUri,
81
+ authorizeUri,
82
+ authService,
83
+ sessionIndex,
84
+ appAlias,
85
+ userIdentifier,
86
+ password,
87
+ noPKCE,
88
+ isolationId
89
+ } = this.config;
90
+ const bInfinity = serverType === 'infinity';
91
+
92
+ if (!noPKCE) {
93
+ // Generate random string of 64 chars for verifier. RFC 7636 says from 43-128 chars
94
+ const buf = new Uint8Array(64);
95
+ this.crypto.getRandomValues(buf);
96
+ this.config.codeVerifier = this.#base64UrlSafeEncode(buf);
71
97
  }
72
98
 
73
- // For PKCE the authorize includes a code_challenge & code_challenge_method as well
74
- async #buildAuthorizeUrl(state) {
75
- const { serverType, clientId, redirectUri, authorizeUri, authService, sessionIndex, appAlias,
76
- userIdentifier, password, noPKCE, isolationId } = this.config;
77
- const bInfinity = serverType === 'infinity';
78
-
79
- if (!noPKCE) {
80
- // Generate random string of 64 chars for verifier. RFC 7636 says from 43-128 chars
81
- const buf = new Uint8Array(64);
82
- this.crypto.getRandomValues(buf);
83
- this.config.codeVerifier = this.#base64UrlSafeEncode(buf);
84
- }
85
-
86
- // If sessionIndex exists then increment attempts count (we will stop sending session_index after two failures)
87
- if (sessionIndex) {
88
- this.config.sessionIndexAttempts += 1;
89
- }
90
- // Persist codeVerifier in session storage so it survives the redirects that are to follow
91
- this.#updateConfig();
92
-
93
- if (!state) {
94
- // Calc random state variable
95
- const buf = new Uint8Array(32);
96
- this.crypto.getRandomValues(buf);
97
- state = this.#base64UrlSafeEncode(buf);
98
- }
99
-
100
- // Trim alias to include just the real alias piece
101
- const addtlScope = appAlias ? `+app.alias.${appAlias.replace(/^app\//, '')}` : '';
102
- const scope = (bInfinity ? `openid${addtlScope}` : 'user_info');
103
-
104
- // Add explicit creds if specified to try to avoid login popup
105
- const authServiceArg = authService ? `&authentication_service=${encodeURIComponent(authService)}` : '';
106
- const sessionIndexArg = sessionIndex && this.config.sessionIndexAttempts < 3 ? `&session_index=${sessionIndex}` : '';
107
- const userIdentifierArg = userIdentifier ? `&UserIdentifier=${encodeURIComponent(userIdentifier)}` : '';
108
- const passwordArg = password && userIdentifier ? `&Password=${encodeURIComponent(window.atob(password))}` : '';
109
- const moreAuthArgs = bInfinity
110
- ? `&enable_psyncid=true${authServiceArg}${sessionIndexArg}${userIdentifierArg}${passwordArg}`
111
- : `&isolationID=${isolationId}`;
112
-
113
- let pkceArgs = '';
114
- if (!noPKCE) {
115
- const cc = await this.#getCodeChallenge(this.config.codeVerifier);
116
- pkceArgs = `&code_challenge=${cc}&code_challenge_method=S256`;
117
- }
118
- return `${authorizeUri}?client_id=${clientId}&response_type=code&redirect_uri=${redirectUri}&scope=${scope}&state=${state}${pkceArgs}${moreAuthArgs}`;
99
+ // If sessionIndex exists then increment attempts count (we will stop sending session_index after two failures)
100
+ if (sessionIndex) {
101
+ this.config.sessionIndexAttempts += 1;
102
+ }
103
+ // Persist codeVerifier in session storage so it survives the redirects that are to follow
104
+ this.#updateConfig();
105
+
106
+ if (!state) {
107
+ // Calc random state variable
108
+ const buf = new Uint8Array(32);
109
+ this.crypto.getRandomValues(buf);
110
+ state = this.#base64UrlSafeEncode(buf);
119
111
  }
120
112
 
121
- async login() {
122
- if (this.isNode && !this.crypto) {
123
- // Deferring dynamic loading of node libraries til this first method to avoid doing this in constructor
124
- await this.#importNodeLibs();
125
- }
126
- const { grantType } = this.config;
127
- if (grantType && grantType !== 'authCode') {
128
- return this.getToken();
129
- }
130
- return this.#authCodeStart();
113
+ // Trim alias to include just the real alias piece
114
+ const addtlScope = appAlias ? `+app.alias.${appAlias.replace(/^app\//, '')}` : '';
115
+ const scope = bInfinity ? `openid${addtlScope}` : 'user_info';
116
+
117
+ // Add explicit creds if specified to try to avoid login popup
118
+ const authServiceArg = authService ? `&authentication_service=${encodeURIComponent(authService)}` : '';
119
+ const sessionIndexArg =
120
+ sessionIndex && this.config.sessionIndexAttempts < 3 ? `&session_index=${sessionIndex}` : '';
121
+ const userIdentifierArg = userIdentifier ? `&UserIdentifier=${encodeURIComponent(userIdentifier)}` : '';
122
+ const passwordArg = password && userIdentifier ? `&Password=${encodeURIComponent(window.atob(password))}` : '';
123
+ const moreAuthArgs = bInfinity
124
+ ? `&enable_psyncid=true${authServiceArg}${sessionIndexArg}${userIdentifierArg}${passwordArg}`
125
+ : `&isolationID=${isolationId}`;
126
+
127
+ let pkceArgs = '';
128
+ if (!noPKCE) {
129
+ const cc = await this.#getCodeChallenge(this.config.codeVerifier);
130
+ pkceArgs = `&code_challenge=${cc}&code_challenge_method=S256`;
131
131
  }
132
+ return `${authorizeUri}?client_id=${clientId}&response_type=code&redirect_uri=${redirectUri}&scope=${scope}&state=${state}${pkceArgs}${moreAuthArgs}`;
133
+ }
132
134
 
133
- // authCode login issues the authorize endpoint transaction and deals with redirects
134
- /* eslint-disable-next-line sonarjs/cognitive-complexity */
135
- async #authCodeStart() {
136
- const fnGetRedirectUriOrigin = () => {
137
- const redirectUri = this.config.redirectUri;
138
- const nRootOffset = redirectUri.indexOf("//");
139
- const nFirstPathOffset = nRootOffset !== -1 ? redirectUri.indexOf("/", nRootOffset + 2) : -1;
140
- return nFirstPathOffset !== -1 ? redirectUri.substring(0, nFirstPathOffset) : redirectUri;
135
+ async login() {
136
+ if (this.isNode && !this.crypto) {
137
+ // Deferring dynamic loading of node libraries til this first method to avoid doing this in constructor
138
+ await this.#importNodeLibs();
139
+ }
140
+ const { grantType } = this.config;
141
+ if (grantType && grantType !== 'authCode') {
142
+ return this.getToken();
143
+ }
144
+ return this.#authCodeStart();
145
+ }
146
+
147
+ // authCode login issues the authorize endpoint transaction and deals with redirects
148
+ /* eslint-disable-next-line sonarjs/cognitive-complexity */
149
+ async #authCodeStart() {
150
+ const fnGetRedirectUriOrigin = () => {
151
+ const redirectUri = this.config.redirectUri;
152
+ const nRootOffset = redirectUri.indexOf('//');
153
+ const nFirstPathOffset = nRootOffset !== -1 ? redirectUri.indexOf('/', nRootOffset + 2) : -1;
154
+ return nFirstPathOffset !== -1 ? redirectUri.substring(0, nFirstPathOffset) : redirectUri;
155
+ };
156
+
157
+ const redirectOrigin = fnGetRedirectUriOrigin();
158
+ const state = this.isNode ? '' : this.#btoa(window.location.origin);
159
+
160
+ return new Promise((resolve, reject) => {
161
+ this.#buildAuthorizeUrl(state).then((url) => {
162
+ let myWindow = null; // popup or iframe
163
+ let elIframe = null;
164
+ let elCloseBtn = null;
165
+ const iframeTimeout = this.config.silentTimeout !== undefined ? this.config.silentTimeout : 5000;
166
+ let bWinIframe =
167
+ !this.isNode &&
168
+ iframeTimeout > 0 &&
169
+ ((!!this.config.userIdentifier && !!this.config.password) ||
170
+ this.config.iframeLoginUI ||
171
+ this.config.authService !== 'pega');
172
+ let tmrAuthComplete = null;
173
+ let checkWindowClosed = null;
174
+ const myWinOnLoad = () => {
175
+ try {
176
+ if (bWinIframe) {
177
+ elIframe.contentWindow.postMessage({ type: 'PegaAuth' }, redirectOrigin);
178
+ } else {
179
+ myWindow.postMessage({ type: 'PegaAuth' }, redirectOrigin);
180
+ }
181
+ } catch (e) {
182
+ // Exception trying to postMessage on load (perhaps should console.warn)
183
+ }
141
184
  };
142
-
143
- const redirectOrigin = fnGetRedirectUriOrigin();
144
- const state = this.isNode ? '' : this.#btoa(window.location.origin);
145
-
146
- return new Promise((resolve, reject) => {
147
- this.#buildAuthorizeUrl(state).then((url) => {
148
- let myWindow = null; // popup or iframe
149
- let elIframe = null;
150
- let elCloseBtn = null;
151
- const iframeTimeout = this.config.silentTimeout !== undefined ? this.config.silentTimeout : 5000;
152
- let bWinIframe = !this.isNode &&
153
- iframeTimeout > 0 &&
154
- ((!!this.config.userIdentifier && !!this.config.password) ||
155
- this.config.iframeLoginUI ||
156
- this.config.authService !== 'pega');
157
- let tmrAuthComplete = null;
158
- let checkWindowClosed = null;
159
- const myWinOnLoad = () => {
160
- try {
161
- if (bWinIframe) {
162
- elIframe.contentWindow.postMessage({ type: "PegaAuth" }, redirectOrigin);
163
- console.log("authjs(login): loaded a page in iFrame");
164
- } else {
165
- myWindow.postMessage({ type: "PegaAuth" }, redirectOrigin);
166
- }
167
- } catch (e) {
168
- console.log("authjs(login): Exception trying to postMessage on load");
169
- }
170
- };
171
- /* eslint-disable prefer-promise-reject-errors */
172
- const fnOpenPopup = () => {
173
- if (this.config.noPopups) {
174
- return reject("no-popups");
175
- }
176
- myWindow = (this.isNode ? this.open : window.open)(url, '_blank', 'width=700,height=500,left=200,top=100');
177
- if (!myWindow) {
178
- // Blocked by popup-blocker
179
- return reject("blocked");
180
- }
181
- checkWindowClosed = setInterval(() => {
182
- if (myWindow.closed) {
183
- clearInterval(checkWindowClosed);
184
- reject("closed");
185
- }
186
- }, 500);
187
- if (!this.isNode) {
188
- try {
189
- myWindow.addEventListener("load", myWinOnLoad, true);
190
- } catch (e) {
191
- console.log("authjs(login): Exception trying to add onload handler to opened window;");
192
- }
193
- }
194
- };
195
- /* eslint-enable prefer-promise-reject-errors */
196
- const fnCloseIframe = () => {
197
- elIframe.parentNode.removeChild(elIframe);
198
- elCloseBtn.parentNode.removeChild(elCloseBtn);
199
- elIframe = null;
200
- elCloseBtn = null;
201
- bWinIframe = false;
202
- };
203
- const fnCloseAndReject = () => {
204
- fnCloseIframe();
205
- /* eslint-disable-next-line prefer-promise-reject-errors */
206
- reject("closed");
207
- };
208
- let authMessageReceiver = null;
209
- /* Retrieve token(s) and close login window */
210
- const fnGetTokenAndFinish = (code) => {
211
- if (!this.isNode) {
212
- window.removeEventListener("message", authMessageReceiver, false);
213
- }
214
- this.getToken(code).then(token => {
215
- if (bWinIframe) {
216
- clearTimeout(tmrAuthComplete);
217
- fnCloseIframe();
218
- } else if (!this.isNode) {
219
- clearInterval(checkWindowClosed);
220
- myWindow.close();
221
- }
222
- resolve(token);
223
- })
224
- .catch(e => {
225
- reject(e);
226
- });
227
- };
228
- // If there is a userIdentifier and password specified or an external SSO auth service,
229
- // we can try to use this silently in an iFrame first
230
- if (bWinIframe) {
231
- const nFrameZLevel = 99999;
232
- elIframe = document.createElement('iframe');
233
- elIframe.id = `pe${this.config.clientId}`;
234
- const loginBoxWidth = 500;
235
- const loginBoxHeight = 700;
236
- const oStyle = elIframe.style;
237
- oStyle.position = 'absolute';
238
- oStyle.display = 'none';
239
- oStyle.zIndex = nFrameZLevel;
240
- oStyle.top = `${Math.round(Math.max(window.innerHeight - loginBoxHeight, 0) / 2)}px`;
241
- oStyle.left = `${Math.round(Math.max(window.innerWidth - loginBoxWidth, 0) / 2)}px`;
242
- oStyle.width = '500px';
243
- oStyle.height = '700px';
244
- // Add Iframe to top of document DOM to have it load
245
- document.body.insertBefore(elIframe, document.body.firstChild);
246
- // document.getElementsByTagName('body')[0].appendChild(elIframe);
247
- elIframe.addEventListener('load', myWinOnLoad, true);
248
- // Disallow iframe content attempts to navigate main window
249
- elIframe.setAttribute('sandbox', 'allow-scripts allow-forms allow-same-origin');
250
- // Adding prompt=none as this is standard OIDC way to communicate no UI is expected (expecting Pega security to support this one day)
251
- elIframe.setAttribute('src', `${url}&prompt=none`);
252
-
253
- const svgCloseBtn = `<?xml version="1.0" encoding="UTF-8"?>
185
+ /* eslint-disable prefer-promise-reject-errors */
186
+ const fnOpenPopup = () => {
187
+ if (this.config.noPopups) {
188
+ return reject('no-popups');
189
+ }
190
+ myWindow = (this.isNode ? this.open : window.open)(url, '_blank', 'width=700,height=500,left=200,top=100');
191
+ if (!myWindow) {
192
+ // Blocked by popup-blocker
193
+ return reject('blocked');
194
+ }
195
+ checkWindowClosed = setInterval(() => {
196
+ if (myWindow.closed) {
197
+ clearInterval(checkWindowClosed);
198
+ reject('closed');
199
+ }
200
+ }, 500);
201
+ if (!this.isNode) {
202
+ try {
203
+ myWindow.addEventListener('load', myWinOnLoad, true);
204
+ } catch (e) {
205
+ // Exception trying to add onload handler to opened window
206
+ console.error(`Error adding event listener on popup window: ${e}`);
207
+ }
208
+ }
209
+ };
210
+ /* eslint-enable prefer-promise-reject-errors */
211
+ const fnCloseIframe = () => {
212
+ elIframe.parentNode.removeChild(elIframe);
213
+ elCloseBtn.parentNode.removeChild(elCloseBtn);
214
+ elIframe = null;
215
+ elCloseBtn = null;
216
+ bWinIframe = false;
217
+ };
218
+ const fnCloseAndReject = () => {
219
+ fnCloseIframe();
220
+ /* eslint-disable-next-line prefer-promise-reject-errors */
221
+ reject('closed');
222
+ };
223
+ let authMessageReceiver = null;
224
+ /* Retrieve token(s) and close login window */
225
+ const fnGetTokenAndFinish = (code) => {
226
+ if (!this.isNode) {
227
+ window.removeEventListener('message', authMessageReceiver, false);
228
+ }
229
+ this.getToken(code)
230
+ .then((token) => {
231
+ if (bWinIframe) {
232
+ clearTimeout(tmrAuthComplete);
233
+ fnCloseIframe();
234
+ } else if (!this.isNode) {
235
+ clearInterval(checkWindowClosed);
236
+ myWindow.close();
237
+ }
238
+ resolve(token);
239
+ })
240
+ .catch((e) => {
241
+ reject(e);
242
+ });
243
+ };
244
+ // If there is a userIdentifier and password specified or an external SSO auth service,
245
+ // we can try to use this silently in an iFrame first
246
+ if (bWinIframe) {
247
+ const nFrameZLevel = 99999;
248
+ elIframe = document.createElement('iframe');
249
+ elIframe.id = `pe${this.config.clientId}`;
250
+ const loginBoxWidth = 500;
251
+ const loginBoxHeight = 700;
252
+ const oStyle = elIframe.style;
253
+ oStyle.position = 'absolute';
254
+ oStyle.display = 'none';
255
+ oStyle.zIndex = nFrameZLevel;
256
+ oStyle.top = `${Math.round(Math.max(window.innerHeight - loginBoxHeight, 0) / 2)}px`;
257
+ oStyle.left = `${Math.round(Math.max(window.innerWidth - loginBoxWidth, 0) / 2)}px`;
258
+ oStyle.width = '500px';
259
+ oStyle.height = '700px';
260
+ // Add Iframe to top of document DOM to have it load
261
+ document.body.insertBefore(elIframe, document.body.firstChild);
262
+ // document.getElementsByTagName('body')[0].appendChild(elIframe);
263
+ elIframe.addEventListener('load', myWinOnLoad, true);
264
+ // Disallow iframe content attempts to navigate main window
265
+ elIframe.setAttribute('sandbox', 'allow-scripts allow-forms allow-same-origin');
266
+ // Adding prompt=none as this is standard OIDC way to communicate no UI is expected (expecting Pega security to support this one day)
267
+ elIframe.setAttribute('src', `${url}&prompt=none`);
268
+
269
+ const svgCloseBtn = `<?xml version="1.0" encoding="UTF-8"?>
254
270
  <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">
255
271
  <title>Dismiss - Black</title>
256
272
  <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
@@ -262,347 +278,365 @@ class PegaAuth {
262
278
  </g>
263
279
  </g>
264
280
  </svg>`;
265
- const bCloseWithinFrame = false;
266
- elCloseBtn = document.createElement('img');
267
- elCloseBtn.onclick = fnCloseAndReject;
268
- elCloseBtn.src = `data:image/svg+xml;base64,${this.#btoa(svgCloseBtn)}`;
269
- const oBtnStyle = elCloseBtn.style;
270
- oBtnStyle.cursor = 'pointer';
271
- // If svg doesn't set width and height might want to set oBtStyle width and height to something like '2em'
272
- oBtnStyle.position = 'absolute';
273
- oBtnStyle.display = 'none';
274
- oBtnStyle.zIndex = nFrameZLevel + 1;
275
- const nTopOffset = bCloseWithinFrame ? 5 : -10;
276
- const nRightOffset = bCloseWithinFrame ? -34 : -20;
277
- oBtnStyle.top = `${Math.round(Math.max(window.innerHeight - loginBoxHeight, 0) / 2) + nTopOffset}px`;
278
- oBtnStyle.left = `${Math.round(Math.max(window.innerWidth - loginBoxWidth, 0) / 2) + loginBoxWidth + nRightOffset
279
- }px`;
280
- document.body.insertBefore(elCloseBtn, document.body.firstChild);
281
- // If the password was wrong, then the login screen will be in the iframe
282
- // ..and with Pega without realization of US-372314 it may replace the top (main portal) window
283
- // For now set a timer and if the timer expires, remove the iFrame and use same url within
284
- // visible window
285
- tmrAuthComplete = setTimeout(() => {
286
- clearTimeout(tmrAuthComplete);
287
- // remove password from config
288
- if (this.config.password) {
289
- delete this.config.password;
290
- this.#updateConfig();
291
- }
292
- // Display the iframe where the redirects did not succeed (or invoke a popup window)
293
- if (this.config.iframeLoginUI) {
294
- elIframe.style.display = 'block';
295
- elCloseBtn.style.display = 'block';
296
- } else {
297
- fnCloseIframe();
298
- fnOpenPopup();
299
- }
300
- }, iframeTimeout);
301
- } else {
302
- if (this.isNode) {
303
- // Determine port to listen to by extracting it from redirect uri
304
- const { redirectUri, cert, key } = this.config;
305
- const isHttp = redirectUri.startsWith('http:');
306
- const nLocalhost = redirectUri.indexOf("localhost:");
307
- const nSlash = redirectUri.indexOf('/', nLocalhost + 10);
308
- const nPort = parseInt(redirectUri.substring(nLocalhost + 10, nSlash), 10);
309
- if (nLocalhost !== -1) {
310
- const options = key && cert && !isHttp ? {
311
- key: this.fs.readFileSync(key),
312
- cert: this.fs.readFileSync(cert)
313
- } : {};
314
- const server = (isHttp ? this.http : this.https).createServer(options, (req, res) => {
315
- const { winTitle, winBodyHtml } = this.config;
316
- // console.log(`HTTP request received: ${req.url}`);
317
- res.writeHead(200, { 'Content-Type': 'text/html' });
318
- // Auto closing window for now. Can always leave it up and allow authConfig props to set title and bodyHtml
319
- res.end(`<html><head><title>${winTitle}</title><script>window.close();</script></head><body>${winBodyHtml}</body></html>`);
320
- const queryString = req.url.split('?')[1];
321
- const urlParams = new URLSearchParams(queryString);
322
- const code = urlParams.get('code');
323
- if (code) {
324
- // console.log(`Auth code received: ${code}`);
325
- // Stop receiving connections and close when all are handled.
326
- server.close();
327
- fnGetTokenAndFinish(code);
328
- }
329
- });
330
- /* eslint-enable no-undef */
331
- server.listen(nPort);
332
- }
333
- }
334
- fnOpenPopup();
335
- }
336
-
337
- if (!this.isNode) {
338
- /* Handler to receive the auth code */
339
- authMessageReceiver = (event) => {
340
- // Check origin to make sure it is the redirect origin
341
- if (event.origin !== redirectOrigin) return;
342
- if (!event.data || !event.data.type || event.data.type !== 'PegaAuth') return;
343
- console.log('authjs(login): postMessage received with code');
344
- const code = event.data.code.toString();
345
- fnGetTokenAndFinish(code);
346
- };
347
- window.addEventListener('message', authMessageReceiver, false);
348
- window.authCodeCallback = (code) => {
349
- console.log('authjs(login): authCodeCallback used with code');
350
- fnGetTokenAndFinish(code);
351
- };
281
+ const bCloseWithinFrame = false;
282
+ elCloseBtn = document.createElement('img');
283
+ elCloseBtn.onclick = fnCloseAndReject;
284
+ elCloseBtn.src = `data:image/svg+xml;base64,${this.#btoa(svgCloseBtn)}`;
285
+ const oBtnStyle = elCloseBtn.style;
286
+ oBtnStyle.cursor = 'pointer';
287
+ // If svg doesn't set width and height might want to set oBtStyle width and height to something like '2em'
288
+ oBtnStyle.position = 'absolute';
289
+ oBtnStyle.display = 'none';
290
+ oBtnStyle.zIndex = nFrameZLevel + 1;
291
+ const nTopOffset = bCloseWithinFrame ? 5 : -10;
292
+ const nRightOffset = bCloseWithinFrame ? -34 : -20;
293
+ oBtnStyle.top = `${Math.round(Math.max(window.innerHeight - loginBoxHeight, 0) / 2) + nTopOffset}px`;
294
+ oBtnStyle.left = `${Math.round(Math.max(window.innerWidth - loginBoxWidth, 0) / 2) + loginBoxWidth + nRightOffset
295
+ }px`;
296
+ document.body.insertBefore(elCloseBtn, document.body.firstChild);
297
+ // If the password was wrong, then the login screen will be in the iframe
298
+ // ..and with Pega without realization of US-372314 it may replace the top (main portal) window
299
+ // For now set a timer and if the timer expires, remove the iFrame and use same url within
300
+ // visible window
301
+ tmrAuthComplete = setTimeout(() => {
302
+ clearTimeout(tmrAuthComplete);
303
+ // remove password from config
304
+ if (this.config.password) {
305
+ delete this.config.password;
306
+ this.#updateConfig();
307
+ }
308
+ // Display the iframe where the redirects did not succeed (or invoke a popup window)
309
+ if (this.config.iframeLoginUI) {
310
+ elIframe.style.display = 'block';
311
+ elCloseBtn.style.display = 'block';
312
+ } else {
313
+ fnCloseIframe();
314
+ fnOpenPopup();
315
+ }
316
+ }, iframeTimeout);
317
+ } else {
318
+ if (this.isNode) {
319
+ // Determine port to listen to by extracting it from redirect uri
320
+ const { redirectUri, cert, key } = this.config;
321
+ const isHttp = redirectUri.startsWith('http:');
322
+ const nLocalhost = redirectUri.indexOf('localhost:');
323
+ const nSlash = redirectUri.indexOf('/', nLocalhost + 10);
324
+ const nPort = parseInt(redirectUri.substring(nLocalhost + 10, nSlash), 10);
325
+ if (nLocalhost !== -1) {
326
+ const options =
327
+ key && cert && !isHttp
328
+ ? {
329
+ key: this.fs.readFileSync(key),
330
+ cert: this.fs.readFileSync(cert)
331
+ }
332
+ : {};
333
+ const server = (isHttp ? this.http : this.https).createServer(options, (req, res) => {
334
+ const { winTitle, winBodyHtml } = this.config;
335
+ res.writeHead(200, { 'Content-Type': 'text/html' });
336
+ // Auto closing window for now. Can always leave it up and allow authConfig props to set title and bodyHtml
337
+ res.end(
338
+ `<html><head><title>${winTitle}</title><script>window.close();</script></head><body>${winBodyHtml}</body></html>`
339
+ );
340
+ const queryString = req.url.split('?')[1];
341
+ const urlParams = new URLSearchParams(queryString);
342
+ const code = urlParams.get('code');
343
+ if (code) {
344
+ // Stop receiving connections and close when all are handled.
345
+ server.close();
346
+ fnGetTokenAndFinish(code);
352
347
  }
353
- });
354
- });
355
- }
356
-
357
- // For PKCE token endpoint includes code_verifier
358
- /* eslint-disable-next-line sonarjs/cognitive-complexity */
359
- getToken(authCode) {
360
- // Reload config to pick up the previously stored codeVerifier
361
- this.#reloadConfig();
362
-
363
- const { serverType, isolationId, clientId, clientSecret, redirectUri, tokenUri, codeVerifier, grantType,
364
- sessionIndex, customTokenParams, userIdentifier, password, noPKCE } = this.config;
365
-
366
- const bAuthCode = !grantType || grantType === 'authCode';
367
- if (bAuthCode && !authCode && !this.isNode) {
368
- const queryString = window.location.search;
369
- const urlParams = new URLSearchParams(queryString);
370
- authCode = urlParams.get('code');
371
- }
372
-
373
- const formData = new URLSearchParams();
374
- formData.append('client_id', clientId);
375
- if (clientSecret) {
376
- formData.append('client_secret', clientSecret);
377
- }
378
- /* eslint-disable camelcase */
379
- const fullGTName = {
380
- "authCode": "authorization_code",
381
- "clientCreds": "client_credentials",
382
- "customBearer": "custom-bearer",
383
- "passwordCreds": "password"
384
- }[grantType];
385
- const grant_type = fullGTName || grantType || 'authorization_code';
386
- formData.append('grant_type', grant_type);
387
- if (serverType === 'launchpad' && grantType !== 'authCode') {
388
- formData.append('isolation_ids', isolationId);
389
- }
390
- if (bAuthCode) {
391
- formData.append('code', authCode);
392
- formData.append('redirect_uri', redirectUri);
393
- if (!noPKCE) {
394
- formData.append('code_verifier', codeVerifier);
348
+ });
349
+ /* eslint-enable no-undef */
350
+ server.listen(nPort);
395
351
  }
396
- } else if (sessionIndex) {
397
- formData.append('session_index', sessionIndex);
352
+ }
353
+ fnOpenPopup();
398
354
  }
399
- /* eslint-enable camelcase */
400
- if (grantType === 'customBearer' && customTokenParams) {
401
- Object.keys(customTokenParams).forEach((param) => {
402
- formData.append(param, customTokenParams[param]);
403
- });
404
- }
405
- if (grantType === 'passwordCreds') {
406
- formData.append('username', userIdentifier);
407
- formData.append('password', this.#atob(password));
408
- }
409
-
410
- return fetch(tokenUri, {
411
- agent: this.#getAgent(),
412
- method: 'POST',
413
- headers: new Headers({
414
- 'content-type': this.urlencoded
415
- }),
416
355
 
417
- body: formData.toString()
418
- })
419
- .then((response) => response.json())
420
- .then((token) => {
421
- if (token.errors || token.error) {
422
- // console.log(`formData: ${formData.toString()}`);
423
- console.log(`Token endpoint error: ${JSON.stringify(token.errors || token.error)}`);
424
- } else {
425
- // .expires_in contains the # of seconds before access token expires
426
- // add property to keep track of current time when the token expires
427
- token.eA = Date.now() + token.expires_in * 1000;
428
- if (this.config.codeVerifier) {
429
- delete this.config.codeVerifier;
430
- }
431
- // If there is a session_index then move this to the peConfig structure (as used on authorize)
432
- if (token.session_index) {
433
- this.config.sessionIndex = token.session_index;
434
- }
435
- // If we got a token and have a session index, then reset the sessionIndexAttempts
436
- if (this.config.sessionIndex) {
437
- this.config.sessionIndexAttempts = 0;
438
- }
439
- this.#updateConfig();
440
- }
441
- return token;
442
- })
443
- .catch((e) => {
444
- console.log(e);
445
- });
356
+ if (!this.isNode) {
357
+ /* Handler to receive the auth code */
358
+ authMessageReceiver = (event) => {
359
+ // Check origin to make sure it is the redirect origin
360
+ if (event.origin !== redirectOrigin) return;
361
+ if (!event.data || !event.data.type || event.data.type !== 'PegaAuth') return;
362
+ const code = event.data.code.toString();
363
+ fnGetTokenAndFinish(code);
364
+ };
365
+ window.addEventListener('message', authMessageReceiver, false);
366
+ window.authCodeCallback = (code) => {
367
+ fnGetTokenAndFinish(code);
368
+ };
369
+ }
370
+ });
371
+ });
372
+ }
373
+
374
+ // For PKCE token endpoint includes code_verifier
375
+ /* eslint-disable-next-line sonarjs/cognitive-complexity */
376
+ getToken(authCode) {
377
+ // Reload config to pick up the previously stored codeVerifier
378
+ this.#reloadConfig();
379
+
380
+ const {
381
+ serverType,
382
+ isolationId,
383
+ clientId,
384
+ clientSecret,
385
+ redirectUri,
386
+ tokenUri,
387
+ codeVerifier,
388
+ grantType,
389
+ sessionIndex,
390
+ customTokenParams,
391
+ userIdentifier,
392
+ password,
393
+ noPKCE
394
+ } = this.config;
395
+
396
+ const bAuthCode = !grantType || grantType === 'authCode';
397
+ if (bAuthCode && !authCode && !this.isNode) {
398
+ const queryString = window.location.search;
399
+ const urlParams = new URLSearchParams(queryString);
400
+ authCode = urlParams.get('code');
446
401
  }
447
402
 
403
+ const formData = new URLSearchParams();
404
+ formData.append('client_id', clientId);
405
+ if (clientSecret) {
406
+ formData.append('client_secret', clientSecret);
407
+ }
448
408
  /* eslint-disable camelcase */
449
- async refreshToken(refresh_token) {
450
- const { clientId, clientSecret, tokenUri } = this.config;
451
-
452
- if (this.isNode && !this.crypto) {
453
- // Deferring dynamic loading of node libraries til this first method to avoid doing this in constructor
454
- await this.#importNodeLibs();
455
- }
456
-
457
- const formData = new URLSearchParams();
458
- formData.append('client_id', clientId);
459
- if (clientSecret) {
460
- formData.append('client_secret', clientSecret);
461
- }
462
- formData.append('grant_type', 'refresh_token');
463
- formData.append('refresh_token', refresh_token);
464
-
465
- return fetch(tokenUri, {
466
- agent: this.#getAgent(),
467
- method: 'POST',
468
- headers: new Headers({
469
- 'content-type': this.urlencoded
470
- }),
471
-
472
- body: formData.toString()
473
- })
474
- .then((response) => {
475
- if (!response.ok && response.status === 401) {
476
- return null;
477
- }
478
- return response.json();
479
- })
480
- .then((token) => {
481
- if (token) {
482
- // .expires_in contains the # of seconds before access token expires
483
- // add property to keep track of current time when the token expires
484
- token.eA = Date.now() + token.expires_in * 1000;
485
- }
486
- return token;
487
- })
488
- .catch((e) => {
489
- console.log(e);
490
- });
409
+ const fullGTName = {
410
+ authCode: 'authorization_code',
411
+ clientCreds: 'client_credentials',
412
+ customBearer: 'custom-bearer',
413
+ passwordCreds: 'password'
414
+ }[grantType];
415
+ const grant_type = fullGTName || grantType || 'authorization_code';
416
+ formData.append('grant_type', grant_type);
417
+ if (serverType === 'launchpad' && grantType !== 'authCode') {
418
+ formData.append('isolation_ids', isolationId);
491
419
  }
492
-
493
- async revokeTokens(access_token, refresh_token = null) {
494
- if (Object.keys(this.config).length === 0) {
495
- // Must have a config structure to proceed
496
- return;
497
- }
498
- const { clientId, clientSecret, revokeUri } = this.config;
499
-
500
- if (this.isNode && !this.crypto) {
501
- // Deferring dynamic loading of node libraries til this first method to avoid doing this in constructor
502
- await this.#importNodeLibs();
503
- }
504
-
505
- const hdrs = { 'content-type': this.urlencoded };
506
- if (clientSecret) {
507
- const basicCreds = window.btoa(`${clientId}:${clientSecret}`);
508
- hdrs.authorization = `Basic ${basicCreds}`;
509
- }
510
- const aTknProps = ['access_token'];
511
- if (refresh_token) {
512
- aTknProps.push('refresh_token');
513
- }
514
- aTknProps.forEach((prop) => {
515
- const formData = new URLSearchParams();
516
- if (!clientSecret) {
517
- formData.append('client_id', clientId);
518
- }
519
- formData.append('token', prop === 'access_token' ? access_token : refresh_token);
520
- formData.append('token_type_hint', prop);
521
- fetch(revokeUri, {
522
- agent: this.#getAgent(),
523
- method: 'POST',
524
- headers: new Headers(hdrs),
525
- body: formData.toString()
526
- })
527
- .then((response) => {
528
- if (!response.ok) {
529
- console.log(`Error revoking ${prop}:${response.status}`);
530
- }
531
- })
532
- .catch((e) => {
533
- console.log(e);
534
- });
535
- });
536
- // Also clobber any sessionIndex
537
- if (this.config.sessionIndex) {
538
- delete this.config.sessionIndex;
539
- this.#updateConfig();
540
- }
420
+ if (bAuthCode) {
421
+ formData.append('code', authCode);
422
+ formData.append('redirect_uri', redirectUri);
423
+ if (!noPKCE) {
424
+ formData.append('code_verifier', codeVerifier);
425
+ }
426
+ } else if (sessionIndex) {
427
+ formData.append('session_index', sessionIndex);
541
428
  }
542
429
  /* eslint-enable camelcase */
543
-
544
- #sha256Hash(str) {
545
- // Found that the Node implementation of subtle.digest is yielding incorrect results
546
- // so using a different set of apis to get expected results.
547
- if (this.isNode) {
548
- return new Promise((resolve) => {
549
- return resolve(this.crypto.createHash("sha256").update(str).digest());
550
- });
551
- }
552
- return this.subtle.digest('SHA-256', new TextEncoder().encode(str));
430
+ if (grantType === 'customBearer' && customTokenParams) {
431
+ Object.keys(customTokenParams).forEach((param) => {
432
+ formData.append(param, customTokenParams[param]);
433
+ });
434
+ }
435
+ if (grantType !== 'authCode') {
436
+ formData.append('enable_psyncId', 'true');
437
+ }
438
+ if (grantType === 'passwordCreds') {
439
+ formData.append('username', userIdentifier);
440
+ formData.append('password', this.#atob(password));
553
441
  }
554
442
 
555
- /* btoa to work with node or browser - create B64 string */
556
- #btoa(str) {
557
- if (this.isNode) {
558
- return Buffer.from(str, 'binary').toString('base64');
443
+ return fetch(tokenUri, {
444
+ agent: this.#getAgent(),
445
+ method: 'POST',
446
+ headers: new Headers({
447
+ 'content-type': this.urlencoded
448
+ }),
449
+
450
+ body: formData.toString()
451
+ })
452
+ .then((response) => response.json())
453
+ .then((token) => {
454
+ if (token.errors || token.error) {
455
+ console.error(`Token endpoint error: ${JSON.stringify(token.errors || token.error)}`);
456
+ } else {
457
+ // .expires_in contains the # of seconds before access token expires
458
+ // add property to keep track of current time when the token expires
459
+ token.eA = Date.now() + token.expires_in * 1000;
460
+ if (this.config.codeVerifier) {
461
+ delete this.config.codeVerifier;
462
+ }
463
+ // If there is a session_index then move this to the peConfig structure (as used on authorize)
464
+ if (token.session_index) {
465
+ this.config.sessionIndex = token.session_index;
466
+ }
467
+ // If we got a token and have a session index, then reset the sessionIndexAttempts
468
+ if (this.config.sessionIndex) {
469
+ this.config.sessionIndexAttempts = 0;
470
+ }
471
+ this.#updateConfig();
559
472
  }
560
- return window.btoa(str);
473
+ return token;
474
+ })
475
+ .catch((e) => {
476
+ console.error(`Token endpoint error: ${e}`);
477
+ });
478
+ }
479
+
480
+ /* eslint-disable camelcase */
481
+ async refreshToken(refresh_token) {
482
+ const { clientId, clientSecret, tokenUri } = this.config;
483
+
484
+ if (this.isNode && !this.crypto) {
485
+ // Deferring dynamic loading of node libraries til this first method to avoid doing this in constructor
486
+ await this.#importNodeLibs();
561
487
  }
562
488
 
563
- /* atoa to work with node or browser - convert B64 to binary */
564
- #atob(str) {
565
- if (this.isNode) {
566
- return Buffer.from(str, 'base64').toString('binary');
489
+ const formData = new URLSearchParams();
490
+ formData.append('client_id', clientId);
491
+ if (clientSecret) {
492
+ formData.append('client_secret', clientSecret);
493
+ }
494
+ formData.append('grant_type', 'refresh_token');
495
+ formData.append('refresh_token', refresh_token);
496
+
497
+ return fetch(tokenUri, {
498
+ agent: this.#getAgent(),
499
+ method: 'POST',
500
+ headers: new Headers({
501
+ 'content-type': this.urlencoded
502
+ }),
503
+
504
+ body: formData.toString()
505
+ })
506
+ .then((response) => {
507
+ if (!response.ok && response.status === 401) {
508
+ return null;
567
509
  }
568
- return window.atob(str);
510
+ return response.json();
511
+ })
512
+ .then((token) => {
513
+ if (token) {
514
+ // .expires_in contains the # of seconds before access token expires
515
+ // add property to keep track of current time when the token expires
516
+ token.eA = Date.now() + token.expires_in * 1000;
517
+ }
518
+ return token;
519
+ })
520
+ .catch((e) => {
521
+ console.warn(`Refresh token failed: ${e}`);
522
+ });
523
+ }
524
+
525
+ async revokeTokens(access_token, refresh_token = null) {
526
+ if (Object.keys(this.config).length === 0) {
527
+ // Must have a config structure to proceed
528
+ return;
569
529
  }
530
+ const { clientId, clientSecret, revokeUri } = this.config;
570
531
 
571
- // Base64 encode
572
- #encode64(buff) {
573
- return this.#btoa(new Uint8Array(buff).reduce((s, b) => s + String.fromCharCode(b), ''));
532
+ if (this.isNode && !this.crypto) {
533
+ // Deferring dynamic loading of node libraries til this first method to avoid doing this in constructor
534
+ await this.#importNodeLibs();
574
535
  }
575
536
 
576
- /*
577
- * Base64 url safe encoding of an array
578
- */
579
- #base64UrlSafeEncode(buf) {
580
- return this.#encode64(buf).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
537
+ const hdrs = { 'content-type': this.urlencoded };
538
+ if (clientSecret) {
539
+ const basicCreds = window.btoa(`${clientId}:${clientSecret}`);
540
+ hdrs.authorization = `Basic ${basicCreds}`;
541
+ }
542
+ const aTknProps = ['access_token'];
543
+ if (refresh_token) {
544
+ aTknProps.push('refresh_token');
545
+ }
546
+ aTknProps.forEach((prop) => {
547
+ const formData = new URLSearchParams();
548
+ if (!clientSecret) {
549
+ formData.append('client_id', clientId);
550
+ }
551
+ formData.append('token', prop === 'access_token' ? access_token : refresh_token);
552
+ formData.append('token_type_hint', prop);
553
+ fetch(revokeUri, {
554
+ agent: this.#getAgent(),
555
+ method: 'POST',
556
+ headers: new Headers(hdrs),
557
+ body: formData.toString()
558
+ })
559
+ .then((response) => {
560
+ if (!response.ok) {
561
+ console.error(`Error revoking ${prop}:${response.status}`);
562
+ }
563
+ })
564
+ .catch((e) => {
565
+ console.error(`Error revoking ${prop}`);
566
+ });
567
+ });
568
+ // Also clobber any sessionIndex
569
+ if (this.config.sessionIndex) {
570
+ delete this.config.sessionIndex;
571
+ this.#updateConfig();
572
+ }
573
+ }
574
+ /* eslint-enable camelcase */
575
+
576
+ #sha256Hash(str) {
577
+ // Found that the Node implementation of subtle.digest is yielding incorrect results
578
+ // so using a different set of apis to get expected results.
579
+ if (this.isNode) {
580
+ return new Promise((resolve) => {
581
+ return resolve(this.crypto.createHash('sha256').update(str).digest());
582
+ });
581
583
  }
584
+ return this.subtle.digest('SHA-256', new TextEncoder().encode(str));
585
+ }
582
586
 
583
- /* Calc code verifier if necessary
584
- */
585
- /* eslint-disable camelcase */
586
- async #getCodeChallenge(code_verifier) {
587
- return this.#sha256Hash(code_verifier)
588
- .then((hashed) => {
589
- return this.#base64UrlSafeEncode(hashed);
590
- })
591
- .catch((error) => {
592
- console.log(error);
593
- })
594
- .finally(() => {
595
- return null;
596
- });
587
+ /* btoa to work with node or browser - create B64 string */
588
+ #btoa(str) {
589
+ if (this.isNode) {
590
+ return Buffer.from(str, 'binary').toString('base64');
597
591
  }
598
- /* eslint-enable camelcase */
592
+ return window.btoa(str);
593
+ }
599
594
 
600
- /*
601
- * Return agent value for POST commands
602
- */
603
- #getAgent() {
604
- return this.isNode && this.config.ignoreInvalidCerts ? new this.https.Agent({ rejectUnauthorized: false }) : undefined;
595
+ /* atoa to work with node or browser - convert B64 to binary */
596
+ #atob(str) {
597
+ if (this.isNode) {
598
+ return Buffer.from(str, 'base64').toString('binary');
605
599
  }
600
+ return window.atob(str);
601
+ }
602
+
603
+ // Base64 encode
604
+ #encode64(buff) {
605
+ return this.#btoa(new Uint8Array(buff).reduce((s, b) => s + String.fromCharCode(b), ''));
606
+ }
607
+
608
+ /*
609
+ * Base64 url safe encoding of an array
610
+ */
611
+ #base64UrlSafeEncode(buf) {
612
+ return this.#encode64(buf).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
613
+ }
614
+
615
+ /* Calc code verifier if necessary
616
+ */
617
+ /* eslint-disable camelcase */
618
+ async #getCodeChallenge(code_verifier) {
619
+ return this.#sha256Hash(code_verifier)
620
+ .then((hashed) => {
621
+ return this.#base64UrlSafeEncode(hashed);
622
+ })
623
+ .catch((error) => {
624
+ console.error(`Error calculation code challenge for PKCE: ${error}`);
625
+ })
626
+ .finally(() => {
627
+ return null;
628
+ });
629
+ }
630
+ /* eslint-enable camelcase */
631
+
632
+ /*
633
+ * Return agent value for POST commands
634
+ */
635
+ #getAgent() {
636
+ return this.isNode && this.config.ignoreInvalidCerts
637
+ ? new this.https.Agent({ rejectUnauthorized: false })
638
+ : undefined;
639
+ }
606
640
  }
607
641
 
608
642
  export default PegaAuth;