@pega/auth 0.2.24 → 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.
- package/lib/oauth-client/auth.d.ts.map +1 -1
- package/lib/oauth-client/auth.js +574 -572
- package/lib/oauth-client/auth.js.map +1 -1
- package/lib/sdk-auth-manager/authManager.js +671 -652
- package/lib/sdk-auth-manager/authManager.js.map +1 -1
- package/lib/sdk-auth-manager/config_access.js +75 -50
- package/lib/sdk-auth-manager/config_access.js.map +1 -1
- package/package.json +6 -2
package/lib/oauth-client/auth.js
CHANGED
|
@@ -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
|
|
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
|
|
19
|
-
this
|
|
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
|
|
30
|
-
if (!this
|
|
31
|
-
this
|
|
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
|
|
54
|
+
await __classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_importNodeLibs).call(this);
|
|
174
55
|
}
|
|
175
|
-
const { grantType, noPKCE } = this
|
|
56
|
+
const { grantType, noPKCE } = __classPrivateFieldGet(this, _PegaAuth_config, "f");
|
|
176
57
|
if (grantType && grantType !== 'authCode') {
|
|
177
58
|
return this.getToken();
|
|
178
59
|
}
|
|
@@ -180,374 +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
|
|
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
|
-
// Found that if loaded within a iframe srcdoc tag, then window.location.origin is string 'null
|
|
195
|
-
const startState = this.isNode || window.location.origin === 'null' ? '' : btoa(window.location.origin);
|
|
196
|
-
return new Promise((resolve, reject) => {
|
|
197
|
-
let theUrl = null; // holds the crafted authorize url
|
|
198
|
-
let myWindow = null; // popup or iframe
|
|
199
|
-
let elIframe = null;
|
|
200
|
-
let elCloseBtn = null;
|
|
201
|
-
const iframeTimeout = this.#config.silentTimeout !== undefined ? this.#config.silentTimeout : 5000;
|
|
202
|
-
let bWinIframe = true;
|
|
203
|
-
let tmrAuthComplete = null;
|
|
204
|
-
let checkWindowClosed = null;
|
|
205
|
-
let bDisablePromptNone = false;
|
|
206
|
-
// Onload handlers fire for iframe content (but not for popup windows)
|
|
207
|
-
const myWinOnLoad = () => {
|
|
208
|
-
try {
|
|
209
|
-
elIframe.contentWindow.postMessage({ type: 'PegaAuth' }, redirectOrigin);
|
|
210
|
-
}
|
|
211
|
-
catch (e) {
|
|
212
|
-
// Exception trying to postMessage on load (perhaps should console.warn)
|
|
213
|
-
}
|
|
214
|
-
};
|
|
215
|
-
const fnSetSilentAuthFailed = bSet => {
|
|
216
|
-
this.#dynState.silentAuthFailed = bSet;
|
|
217
|
-
this.#updateConfig();
|
|
218
|
-
};
|
|
219
|
-
/* eslint-disable prefer-promise-reject-errors */
|
|
220
|
-
const fnOpenPopup = () => {
|
|
221
|
-
if (this.#config.noPopups) {
|
|
222
|
-
return reject('no-popups');
|
|
223
|
-
}
|
|
224
|
-
// Since displaying a visible window, clear the silent auth failed flag
|
|
225
|
-
fnSetSilentAuthFailed(false);
|
|
226
|
-
myWindow = (this.isNode ? this.open : window.open)(theUrl, '_blank', 'width=700,height=500,left=200,top=100');
|
|
227
|
-
if (!myWindow) {
|
|
228
|
-
// Blocked by popup-blocker
|
|
229
|
-
return reject('blocked');
|
|
230
|
-
}
|
|
231
|
-
checkWindowClosed = setInterval(() => {
|
|
232
|
-
if (myWindow.closed) {
|
|
233
|
-
clearInterval(checkWindowClosed);
|
|
234
|
-
reject('closed');
|
|
235
|
-
}
|
|
236
|
-
// Post to the popup window to cover scenario where window.opener is disabled on
|
|
237
|
-
// popup window by some security library (like helmet.js)
|
|
238
|
-
if (!this.isNode) {
|
|
239
|
-
myWindow.postMessage({ type: 'PegaAuth' }, redirectOrigin);
|
|
240
|
-
}
|
|
241
|
-
}, 500);
|
|
242
|
-
};
|
|
243
|
-
/* eslint-enable prefer-promise-reject-errors */
|
|
244
|
-
const fnCloseIframe = () => {
|
|
245
|
-
elIframe.parentNode.removeChild(elIframe);
|
|
246
|
-
elCloseBtn.parentNode.removeChild(elCloseBtn);
|
|
247
|
-
elIframe = null;
|
|
248
|
-
elCloseBtn = null;
|
|
249
|
-
bWinIframe = false;
|
|
250
|
-
};
|
|
251
|
-
const fnCloseAndReject = () => {
|
|
252
|
-
fnCloseIframe();
|
|
253
|
-
/* eslint-disable-next-line prefer-promise-reject-errors */
|
|
254
|
-
reject('closed');
|
|
255
|
-
};
|
|
256
|
-
const fnAuthMessageReceiver = event => {
|
|
257
|
-
// Check origin to make sure it is the redirect origin
|
|
258
|
-
if (event.origin !== redirectOrigin) {
|
|
259
|
-
if (event.data?.type === 'PegaAuth') {
|
|
260
|
-
// eslint-disable-next-line no-console
|
|
261
|
-
console.error(`Authorization code grant flow error: Unexpected origin: ${event.origin} ... expecting: ${redirectOrigin}`);
|
|
262
|
-
}
|
|
263
|
-
return;
|
|
264
|
-
}
|
|
265
|
-
if (!event.data || !event.data.type || event.data.type !== 'PegaAuth')
|
|
266
|
-
return;
|
|
267
|
-
const aArgs = ['code', 'state', 'error', 'errorDesc'];
|
|
268
|
-
const aValues = [];
|
|
269
|
-
for (let i = 0; i < aArgs.length; i += 1) {
|
|
270
|
-
const arg = aArgs[i];
|
|
271
|
-
aValues[arg] = event.data[arg] ? event.data[arg].toString() : null;
|
|
272
|
-
}
|
|
273
|
-
const { code, state, error, errorDesc } = aValues;
|
|
274
|
-
if (error) {
|
|
275
|
-
// eslint-disable-next-line no-console
|
|
276
|
-
console.error(`Authorization code grant flow error (${error}): ${errorDesc}`);
|
|
277
|
-
}
|
|
278
|
-
if (code && state !== this.#dynState.state) {
|
|
279
|
-
// eslint-disable-next-line no-console
|
|
280
|
-
console.error(`Authorization code transfer error: state mismatch: ${state} ... expecting: ${this.#dynState.state}`);
|
|
281
|
-
}
|
|
282
|
-
if (error || (code && state === this.#dynState.state)) {
|
|
283
|
-
// eslint-disable-next-line no-use-before-define
|
|
284
|
-
fnGetTokenAndFinish(code, error, errorDesc);
|
|
285
|
-
}
|
|
286
|
-
};
|
|
287
|
-
const fnEnableMessageReceiver = bEnable => {
|
|
288
|
-
if (bEnable) {
|
|
289
|
-
window.addEventListener('message', fnAuthMessageReceiver, false);
|
|
290
|
-
window.authCodeCallback = (code, state, error, errorDesc) => {
|
|
291
|
-
if (error) {
|
|
292
|
-
// eslint-disable-next-line no-console
|
|
293
|
-
console.error(`Authorization code grant flow error (${error}): ${errorDesc}`);
|
|
294
|
-
}
|
|
295
|
-
if (code && state !== this.#dynState.state) {
|
|
296
|
-
// eslint-disable-next-line no-console
|
|
297
|
-
console.error(`Authorization code transfer error: state mismatch: ${state} ... expecting: ${this.#dynState.state}`);
|
|
298
|
-
}
|
|
299
|
-
if (error || (code && state === this.#dynState.state)) {
|
|
300
|
-
// eslint-disable-next-line no-use-before-define
|
|
301
|
-
fnGetTokenAndFinish(code, error, errorDesc);
|
|
302
|
-
}
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
|
-
else {
|
|
306
|
-
window.removeEventListener('message', fnAuthMessageReceiver, false);
|
|
307
|
-
delete window.authCodeCallback;
|
|
308
|
-
}
|
|
309
|
-
};
|
|
310
|
-
const doAuthorize = () => {
|
|
311
|
-
// If there is a userIdentifier and password specified or an external SSO auth service,
|
|
312
|
-
// we can try to use this silently in an iFrame first
|
|
313
|
-
bWinIframe =
|
|
314
|
-
!this.isNode &&
|
|
315
|
-
!this.#dynState.silentAuthFailed &&
|
|
316
|
-
iframeTimeout > 0 &&
|
|
317
|
-
((!!this.#config.userIdentifier && !!this.#config.password) ||
|
|
318
|
-
this.#config.iframeLoginUI ||
|
|
319
|
-
this.#config.authService !== 'pega');
|
|
320
|
-
// Enable message receiver
|
|
321
|
-
if (!this.isNode) {
|
|
322
|
-
fnEnableMessageReceiver(true);
|
|
323
|
-
}
|
|
324
|
-
if (bWinIframe) {
|
|
325
|
-
const nFrameZLevel = 99999;
|
|
326
|
-
elIframe = document.createElement('iframe');
|
|
327
|
-
elIframe.id = `pe${this.#config.clientId}`;
|
|
328
|
-
const loginBoxWidth = 500;
|
|
329
|
-
const loginBoxHeight = 700;
|
|
330
|
-
const oStyle = elIframe.style;
|
|
331
|
-
oStyle.position = 'absolute';
|
|
332
|
-
oStyle.display = 'none';
|
|
333
|
-
oStyle.zIndex = nFrameZLevel;
|
|
334
|
-
oStyle.top = `${Math.round(Math.max(window.innerHeight - loginBoxHeight, 0) / 2)}px`;
|
|
335
|
-
oStyle.left = `${Math.round(Math.max(window.innerWidth - loginBoxWidth, 0) / 2)}px`;
|
|
336
|
-
oStyle.width = '500px';
|
|
337
|
-
oStyle.height = '700px';
|
|
338
|
-
// Add Iframe to top of document DOM to have it load
|
|
339
|
-
document.body.insertBefore(elIframe, document.body.firstChild);
|
|
340
|
-
// document.getElementsByTagName('body')[0].appendChild(elIframe);
|
|
341
|
-
elIframe.addEventListener('load', myWinOnLoad, true);
|
|
342
|
-
// Disallow iframe content attempts to navigate main window
|
|
343
|
-
elIframe.setAttribute('sandbox', 'allow-scripts allow-forms allow-same-origin');
|
|
344
|
-
// 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
|
|
345
|
-
// configured OIDC authentication services). cookies=none disables the temporary Pega-Rules cookie otherwise created on auth code
|
|
346
|
-
// grant flow. For now these two args are either both set or not set, but might have a cookies="partitioned" one day.
|
|
347
|
-
elIframe.setAttribute('src', bDisablePromptNone ? theUrl : `${theUrl}&prompt=none`);
|
|
348
|
-
const svgCloseBtn = `<?xml version="1.0" encoding="UTF-8"?>
|
|
349
|
-
<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">
|
|
350
|
-
<title>Dismiss - Black</title>
|
|
351
|
-
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
|
352
|
-
<g transform="translate(1.000000, 1.000000)">
|
|
353
|
-
<circle fill="#252C32" cx="16" cy="16" r="16"></circle>
|
|
354
|
-
<g transform="translate(9.109375, 9.214844)" fill="#FFFFFF" fill-rule="nonzero">
|
|
355
|
-
<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>
|
|
356
|
-
</g>
|
|
357
|
-
</g>
|
|
358
|
-
</g>
|
|
359
|
-
</svg>`;
|
|
360
|
-
const bCloseWithinFrame = false;
|
|
361
|
-
elCloseBtn = document.createElement('img');
|
|
362
|
-
elCloseBtn.onclick = fnCloseAndReject;
|
|
363
|
-
elCloseBtn.src = `data:image/svg+xml;base64,${btoa(svgCloseBtn)}`;
|
|
364
|
-
const oBtnStyle = elCloseBtn.style;
|
|
365
|
-
oBtnStyle.cursor = 'pointer';
|
|
366
|
-
// If svg doesn't set width and height might want to set oBtStyle width and height to something like '2em'
|
|
367
|
-
oBtnStyle.position = 'absolute';
|
|
368
|
-
oBtnStyle.display = 'none';
|
|
369
|
-
oBtnStyle.zIndex = nFrameZLevel + 1;
|
|
370
|
-
const nTopOffset = bCloseWithinFrame ? 5 : -10;
|
|
371
|
-
const nRightOffset = bCloseWithinFrame ? -34 : -20;
|
|
372
|
-
const nTop = Math.round(Math.max(window.innerHeight - loginBoxHeight, 0) / 2) + nTopOffset;
|
|
373
|
-
oBtnStyle.top = `${nTop}px`;
|
|
374
|
-
const nLeft = Math.round(Math.max(window.innerWidth - loginBoxWidth, 0) / 2) +
|
|
375
|
-
loginBoxWidth +
|
|
376
|
-
nRightOffset;
|
|
377
|
-
oBtnStyle.left = `${nLeft}px`;
|
|
378
|
-
document.body.insertBefore(elCloseBtn, document.body.firstChild);
|
|
379
|
-
// If the password was wrong, then the login screen will be in the iframe
|
|
380
|
-
// ..and with Pega without realization of US-372314 it may replace the top (main portal) window
|
|
381
|
-
// For now set a timer and if the timer expires, remove the iFrame and use same url within
|
|
382
|
-
// visible window
|
|
383
|
-
tmrAuthComplete = setTimeout(() => {
|
|
384
|
-
clearTimeout(tmrAuthComplete);
|
|
385
|
-
/*
|
|
386
|
-
// remove password from config
|
|
387
|
-
if (this.#config.password) {
|
|
388
|
-
delete this.#config.password;
|
|
389
|
-
this.#updateConfig();
|
|
390
|
-
}
|
|
391
|
-
*/
|
|
392
|
-
// Display the iframe where the redirects did not succeed (or invoke a popup window)
|
|
393
|
-
if (this.#config.iframeLoginUI) {
|
|
394
|
-
elIframe.style.display = 'block';
|
|
395
|
-
elCloseBtn.style.display = 'block';
|
|
396
|
-
}
|
|
397
|
-
else {
|
|
398
|
-
fnCloseIframe();
|
|
399
|
-
fnOpenPopup();
|
|
400
|
-
}
|
|
401
|
-
}, iframeTimeout);
|
|
402
|
-
}
|
|
403
|
-
else {
|
|
404
|
-
if (this.isNode) {
|
|
405
|
-
// Determine port to listen to by extracting it from redirect uri
|
|
406
|
-
const { redirectUri, cert, key } = this.#config;
|
|
407
|
-
const isHttp = redirectUri.startsWith('http:');
|
|
408
|
-
const nLocalhost = redirectUri.indexOf('localhost:');
|
|
409
|
-
const nSlash = redirectUri.indexOf('/', nLocalhost + 10);
|
|
410
|
-
const nPort = parseInt(redirectUri.substring(nLocalhost + 10, nSlash), 10);
|
|
411
|
-
if (nLocalhost !== -1) {
|
|
412
|
-
const options = key && cert && !isHttp
|
|
413
|
-
? {
|
|
414
|
-
key: this.fs.readFileSync(key),
|
|
415
|
-
cert: this.fs.readFileSync(cert)
|
|
416
|
-
}
|
|
417
|
-
: {};
|
|
418
|
-
const server = (isHttp ? this.http : this.https).createServer(options, (req, res) => {
|
|
419
|
-
const { winTitle, winBodyHtml } = this.#config;
|
|
420
|
-
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
421
|
-
// Auto closing window for now. Can always leave it up and allow authConfig props to set title and bodyHtml
|
|
422
|
-
res.end(`<html><head><title>${winTitle}</title><script>window.close();</script></head><body>${winBodyHtml}</body></html>`);
|
|
423
|
-
const queryString = req.url.split('?')[1];
|
|
424
|
-
const urlParams = new URLSearchParams(queryString);
|
|
425
|
-
const code = urlParams.get('code');
|
|
426
|
-
const state = urlParams.get('state');
|
|
427
|
-
const error = urlParams.get('error');
|
|
428
|
-
const errorDesc = urlParams.get('error_description');
|
|
429
|
-
if (error || (code && state === this.#dynState.state)) {
|
|
430
|
-
// Stop receiving connections and close when all are handled.
|
|
431
|
-
server.close();
|
|
432
|
-
// eslint-disable-next-line no-use-before-define
|
|
433
|
-
fnGetTokenAndFinish(code, error, errorDesc);
|
|
434
|
-
}
|
|
435
|
-
});
|
|
436
|
-
server.listen(nPort);
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
fnOpenPopup();
|
|
440
|
-
}
|
|
441
|
-
};
|
|
442
|
-
/* Retrieve token(s) and close login window */
|
|
443
|
-
const fnGetTokenAndFinish = (code, error, errorDesc) => {
|
|
444
|
-
// Can clear state in session info at this point
|
|
445
|
-
delete this.#dynState.state;
|
|
446
|
-
this.#updateConfig();
|
|
447
|
-
if (!this.isNode) {
|
|
448
|
-
fnEnableMessageReceiver(false);
|
|
449
|
-
if (bWinIframe) {
|
|
450
|
-
clearTimeout(tmrAuthComplete);
|
|
451
|
-
fnCloseIframe();
|
|
452
|
-
}
|
|
453
|
-
else {
|
|
454
|
-
clearInterval(checkWindowClosed);
|
|
455
|
-
try {
|
|
456
|
-
if (myWindow) {
|
|
457
|
-
myWindow.close();
|
|
458
|
-
myWindow = null;
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
catch (e) {
|
|
462
|
-
// keep going and process code or error even though issue closing popup
|
|
463
|
-
// eslint-disable-next-line no-console
|
|
464
|
-
console.warn(`Error closing window: ${e}`);
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
if (code) {
|
|
469
|
-
this.getToken(code)
|
|
470
|
-
.then(token => {
|
|
471
|
-
resolve(token);
|
|
472
|
-
})
|
|
473
|
-
.catch(e => {
|
|
474
|
-
reject(e);
|
|
475
|
-
});
|
|
476
|
-
}
|
|
477
|
-
else if (error) {
|
|
478
|
-
// Handle some errors in a special manner and pass others back to client
|
|
479
|
-
if (error === 'login_required') {
|
|
480
|
-
// eslint-disable-next-line no-console
|
|
481
|
-
console.warn('silent authentication failed...starting full authentication');
|
|
482
|
-
const bSpecialDebugPath = false;
|
|
483
|
-
if (bSpecialDebugPath) {
|
|
484
|
-
fnSetSilentAuthFailed(false);
|
|
485
|
-
bDisablePromptNone = true;
|
|
486
|
-
}
|
|
487
|
-
else {
|
|
488
|
-
fnSetSilentAuthFailed(true);
|
|
489
|
-
bDisablePromptNone = false;
|
|
490
|
-
}
|
|
491
|
-
this.#buildAuthorizeUrl(startState).then(url => {
|
|
492
|
-
theUrl = url;
|
|
493
|
-
doAuthorize();
|
|
494
|
-
});
|
|
495
|
-
}
|
|
496
|
-
else if (error === 'invalid_session_index') {
|
|
497
|
-
// eslint-disable-next-line no-console
|
|
498
|
-
console.warn('auth session no longer valid...starting new session');
|
|
499
|
-
// In these scenarios, not much user can do without just starting a new session, so do that
|
|
500
|
-
this.#updateSessionIndex(null);
|
|
501
|
-
fnSetSilentAuthFailed(false);
|
|
502
|
-
this.#buildAuthorizeUrl(startState).then(url => {
|
|
503
|
-
theUrl = url;
|
|
504
|
-
doAuthorize();
|
|
505
|
-
});
|
|
506
|
-
}
|
|
507
|
-
else {
|
|
508
|
-
// eslint-disable-next-line no-console
|
|
509
|
-
console.warn(`Authorize failed: ${error}. ${errorDesc}\nFailing authorize url: ${theUrl}`);
|
|
510
|
-
throw new Error(error, { cause: errorDesc });
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
};
|
|
514
|
-
this.#buildAuthorizeUrl(startState).then(url => {
|
|
515
|
-
theUrl = url;
|
|
516
|
-
doAuthorize();
|
|
517
|
-
});
|
|
518
|
-
});
|
|
64
|
+
return __classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_authCodeStart).call(this);
|
|
519
65
|
}
|
|
520
66
|
// Login redirect
|
|
521
67
|
loginRedirect() {
|
|
522
68
|
// eslint-disable-next-line no-restricted-globals
|
|
523
69
|
const startState = btoa(location.origin);
|
|
524
|
-
this
|
|
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
|
+
}
|
|
525
82
|
// eslint-disable-next-line no-restricted-globals
|
|
526
|
-
location.href = url
|
|
83
|
+
location.href = `${url}${cookiesArg}`;
|
|
527
84
|
});
|
|
528
85
|
}
|
|
529
86
|
// check state
|
|
530
87
|
checkStateMatch(state) {
|
|
531
|
-
return state === this
|
|
532
|
-
}
|
|
533
|
-
// Clear session index within config
|
|
534
|
-
#updateSessionIndex(sessionIndex) {
|
|
535
|
-
if (sessionIndex) {
|
|
536
|
-
this.#dynState.sessionIndex = sessionIndex;
|
|
537
|
-
this.#dynState.sessionIndexAttempts = 0;
|
|
538
|
-
}
|
|
539
|
-
else if (this.#dynState.sessionIndex) {
|
|
540
|
-
delete this.#dynState.sessionIndex;
|
|
541
|
-
}
|
|
542
|
-
this.#updateConfig();
|
|
88
|
+
return state === __classPrivateFieldGet(this, _PegaAuth_dynState, "f").state;
|
|
543
89
|
}
|
|
544
90
|
// For PKCE token endpoint includes code_verifier
|
|
545
91
|
getToken(authCode) {
|
|
546
92
|
// Reload config to pick up the previously stored codeVerifier
|
|
547
|
-
this
|
|
548
|
-
const { serverType, clientId, clientSecret, tokenUri, grantType, customTokenParams, userIdentifier, password, noPKCE, secureCookie } = this
|
|
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");
|
|
549
95
|
const bInfinity = serverType === 'infinity';
|
|
550
|
-
const { sessionIndex, acRedirectUri, codeVerifier } = this
|
|
96
|
+
const { sessionIndex, acRedirectUri, codeVerifier } = __classPrivateFieldGet(this, _PegaAuth_dynState, "f");
|
|
551
97
|
const bAuthCode = !grantType || grantType === 'authCode';
|
|
552
98
|
if (bAuthCode && !authCode && !this.isNode) {
|
|
553
99
|
const queryString = window.location.search;
|
|
@@ -592,7 +138,7 @@ export class PegaAuth {
|
|
|
592
138
|
formData.append('password', atob(password));
|
|
593
139
|
}
|
|
594
140
|
return fetch(tokenUri, {
|
|
595
|
-
agent: this
|
|
141
|
+
agent: __classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_getAgent).call(this),
|
|
596
142
|
method: 'POST',
|
|
597
143
|
headers: new Headers({
|
|
598
144
|
'content-type': this.urlencoded
|
|
@@ -616,22 +162,22 @@ export class PegaAuth {
|
|
|
616
162
|
// add property to keep track of current time when the token expires
|
|
617
163
|
token.eA = Date.now() + token.expires_in * 1000;
|
|
618
164
|
// Clear authCode related config state: state, codeVerifier, acRedirectUri
|
|
619
|
-
if (this
|
|
620
|
-
delete this
|
|
165
|
+
if (__classPrivateFieldGet(this, _PegaAuth_dynState, "f").state) {
|
|
166
|
+
delete __classPrivateFieldGet(this, _PegaAuth_dynState, "f").state;
|
|
621
167
|
}
|
|
622
|
-
if (this
|
|
623
|
-
delete this
|
|
168
|
+
if (__classPrivateFieldGet(this, _PegaAuth_dynState, "f").codeVerifier) {
|
|
169
|
+
delete __classPrivateFieldGet(this, _PegaAuth_dynState, "f").codeVerifier;
|
|
624
170
|
}
|
|
625
|
-
if (this
|
|
626
|
-
delete this
|
|
171
|
+
if (__classPrivateFieldGet(this, _PegaAuth_dynState, "f").acRedirectUri) {
|
|
172
|
+
delete __classPrivateFieldGet(this, _PegaAuth_dynState, "f").acRedirectUri;
|
|
627
173
|
}
|
|
628
174
|
// If there is a session_index then move this to the peConfig structure (as used on authorize)
|
|
629
175
|
if (token.session_index) {
|
|
630
|
-
this
|
|
176
|
+
__classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_updateSessionIndex).call(this, token.session_index);
|
|
631
177
|
// does an #updateConfig within #updateSessionIndex
|
|
632
178
|
}
|
|
633
179
|
else {
|
|
634
|
-
this
|
|
180
|
+
__classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_updateConfig).call(this);
|
|
635
181
|
}
|
|
636
182
|
}
|
|
637
183
|
return token;
|
|
@@ -644,10 +190,10 @@ export class PegaAuth {
|
|
|
644
190
|
});
|
|
645
191
|
}
|
|
646
192
|
async refreshToken(refreshToken) {
|
|
647
|
-
const { clientId, clientSecret, tokenUri, secureCookie } = this
|
|
193
|
+
const { clientId, clientSecret, tokenUri, secureCookie } = __classPrivateFieldGet(this, _PegaAuth_config, "f");
|
|
648
194
|
if (this.isNode && !this.crypto) {
|
|
649
195
|
// Deferring dynamic loading of node libraries til this first method to avoid doing this in constructor
|
|
650
|
-
await this
|
|
196
|
+
await __classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_importNodeLibs).call(this);
|
|
651
197
|
}
|
|
652
198
|
if (!secureCookie && !refreshToken) {
|
|
653
199
|
return null;
|
|
@@ -665,7 +211,7 @@ export class PegaAuth {
|
|
|
665
211
|
formData.append('refresh_token', refreshToken);
|
|
666
212
|
}
|
|
667
213
|
return fetch(tokenUri, {
|
|
668
|
-
agent: this
|
|
214
|
+
agent: __classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_getAgent).call(this),
|
|
669
215
|
method: 'POST',
|
|
670
216
|
headers: new Headers({
|
|
671
217
|
'content-type': this.urlencoded
|
|
@@ -704,15 +250,15 @@ export class PegaAuth {
|
|
|
704
250
|
* @returns
|
|
705
251
|
*/
|
|
706
252
|
async revokeTokens(accessToken, refreshToken = null) {
|
|
707
|
-
if (Object.keys(this
|
|
253
|
+
if (Object.keys(__classPrivateFieldGet(this, _PegaAuth_config, "f")).length === 0) {
|
|
708
254
|
// Must have a config structure to proceed
|
|
709
255
|
return;
|
|
710
256
|
}
|
|
711
|
-
const { serverType, clientId, clientSecret, revokeUri, secureCookie } = this
|
|
257
|
+
const { serverType, clientId, clientSecret, revokeUri, secureCookie } = __classPrivateFieldGet(this, _PegaAuth_config, "f");
|
|
712
258
|
const bLaunchpad = serverType === 'launchpad';
|
|
713
259
|
if (this.isNode && !this.crypto) {
|
|
714
260
|
// Deferring dynamic loading of node libraries til this first method to avoid doing this in constructor
|
|
715
|
-
await this
|
|
261
|
+
await __classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_importNodeLibs).call(this);
|
|
716
262
|
}
|
|
717
263
|
const headers = { 'content-type': this.urlencoded };
|
|
718
264
|
if (clientSecret && !bLaunchpad) {
|
|
@@ -740,7 +286,7 @@ export class PegaAuth {
|
|
|
740
286
|
formData.append('token_type_hint', prop);
|
|
741
287
|
if (revokeUri) {
|
|
742
288
|
return fetch(revokeUri, {
|
|
743
|
-
agent: this
|
|
289
|
+
agent: __classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_getAgent).call(this),
|
|
744
290
|
method: 'POST',
|
|
745
291
|
headers: new Headers(headers),
|
|
746
292
|
credentials: secureCookie ? 'include' : 'omit',
|
|
@@ -757,19 +303,19 @@ export class PegaAuth {
|
|
|
757
303
|
console.error(`Error revoking ${prop}; ${e}`);
|
|
758
304
|
})
|
|
759
305
|
.finally(() => {
|
|
760
|
-
this
|
|
306
|
+
__classPrivateFieldGet(this, _PegaAuth_dynState, "f").silentAuthFailed = false;
|
|
761
307
|
// Also clobber any sessionIndex
|
|
762
|
-
this
|
|
308
|
+
__classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_updateSessionIndex).call(this, null);
|
|
763
309
|
});
|
|
764
310
|
}
|
|
765
|
-
this
|
|
311
|
+
__classPrivateFieldGet(this, _PegaAuth_dynState, "f").silentAuthFailed = false;
|
|
766
312
|
// Also clobber any sessionIndex
|
|
767
|
-
this
|
|
313
|
+
__classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_updateSessionIndex).call(this, null);
|
|
768
314
|
}
|
|
769
315
|
// For userinfo endpoint to return meaningful data, endpoint must include appAlias (if specified) and authorize must
|
|
770
316
|
// specify profile and optionally email scope to get such info returned
|
|
771
317
|
async getUserinfo(accessToken) {
|
|
772
|
-
if (!this
|
|
318
|
+
if (!__classPrivateFieldGet(this, _PegaAuth_config, "f") || !__classPrivateFieldGet(this, _PegaAuth_config, "f").userinfoUri) {
|
|
773
319
|
// Must have a config structure and userInfo to proceed
|
|
774
320
|
return {};
|
|
775
321
|
}
|
|
@@ -777,8 +323,8 @@ export class PegaAuth {
|
|
|
777
323
|
authorization: `bearer ${accessToken}`,
|
|
778
324
|
'content-type': 'application/json;charset=UTF-8'
|
|
779
325
|
};
|
|
780
|
-
return fetch(this
|
|
781
|
-
agent: this
|
|
326
|
+
return fetch(__classPrivateFieldGet(this, _PegaAuth_config, "f").userinfoUri, {
|
|
327
|
+
agent: __classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_getAgent).call(this),
|
|
782
328
|
method: 'GET',
|
|
783
329
|
headers: new Headers(headers)
|
|
784
330
|
})
|
|
@@ -797,64 +343,520 @@ export class PegaAuth {
|
|
|
797
343
|
console.log(e);
|
|
798
344
|
});
|
|
799
345
|
}
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
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
|
+
}
|
|
807
361
|
}
|
|
808
|
-
return this.subtle.digest('SHA-256', new TextEncoder().encode(str));
|
|
809
362
|
}
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
#encode64(buff) {
|
|
813
|
-
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");
|
|
814
365
|
}
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
*/
|
|
818
|
-
#base64UrlSafeEncode(buf) {
|
|
819
|
-
return this.#encode64(buf).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
|
|
366
|
+
else {
|
|
367
|
+
__classPrivateFieldSet(this, _PegaAuth_dynState, sItem ? obj : {}, "f");
|
|
820
368
|
}
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
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);
|
|
826
435
|
this.crypto.getRandomValues(buf);
|
|
827
|
-
|
|
436
|
+
__classPrivateFieldGet(this, _PegaAuth_dynState, "f").codeVerifier = __classPrivateFieldGet(this, _PegaAuth_instances, "m", _PegaAuth_base64UrlSafeEncode).call(this, buf);
|
|
828
437
|
}
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
return this.#sha256Hash(codeVerifier)
|
|
834
|
-
.then(hashed => {
|
|
835
|
-
return this.#base64UrlSafeEncode(hashed);
|
|
836
|
-
})
|
|
837
|
-
.catch(error => {
|
|
838
|
-
// eslint-disable-next-line no-console
|
|
839
|
-
console.error(`Error calculation code challenge for PKCE: ${error}`);
|
|
840
|
-
})
|
|
841
|
-
.finally(() => {
|
|
842
|
-
return null;
|
|
843
|
-
});
|
|
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;
|
|
844
442
|
}
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
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);
|
|
853
500
|
}
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
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;
|
|
857
813
|
}
|
|
858
|
-
|
|
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
|
+
};
|
|
859
861
|
export default PegaAuth;
|
|
860
862
|
//# sourceMappingURL=auth.js.map
|