@pega/custom-dx-components 2.0.20 → 2.0.22
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/package.json +6 -3
- package/src/auth.js +563 -0
- package/src/constants.js +32 -2
- package/src/tasks/authenticate/helper.js +58 -6
- package/src/tasks/authenticate/index.js +105 -50
- package/src/tasks/create/templates/Constellation/Field/Text-URL/index.jsx.mustache +1 -1
- package/src/tasks/create/templates/Constellation/Field/Text-URL/{text-url.js.mustache → text-url.jsx.mustache} +2 -1
- package/src/tasks/init/index.js +2 -2
- package/src/tasks/init/template/package.json.mustache +2 -2
- package/src/tasks/init/template/tasks.config.json +2 -0
- package/src/util.js +2 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pega/custom-dx-components",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.22",
|
|
4
4
|
"description": "Utility for building custom UI components",
|
|
5
5
|
"bin": "./bin/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -23,19 +23,22 @@
|
|
|
23
23
|
"babel-loader": "^8.2.5",
|
|
24
24
|
"chalk": "^5.0.1",
|
|
25
25
|
"compression-webpack-plugin": "^10.0.0",
|
|
26
|
+
"crypto": "^1.0.1",
|
|
26
27
|
"css-loader": "6.7.1",
|
|
27
28
|
"css-minimizer-webpack-plugin": "^4.1.0",
|
|
28
|
-
"execa": "^6.1.0",
|
|
29
29
|
"eslint": "^8.4.1",
|
|
30
|
+
"execa": "^6.1.0",
|
|
30
31
|
"file-loader": "6.2.0",
|
|
31
32
|
"fs-extra": "^10.1.0",
|
|
32
33
|
"gitignore": "^0.7.0",
|
|
34
|
+
"http": "^0.0.1-security",
|
|
33
35
|
"inquirer": "^9.1.1",
|
|
34
36
|
"listr": "^0.14.3",
|
|
35
37
|
"mini-css-extract-plugin": "2.6.1",
|
|
36
38
|
"mustache": "^4.2.0",
|
|
37
39
|
"ncp": "^2.0.0",
|
|
38
40
|
"node-fetch": "^3.2.10",
|
|
41
|
+
"open": "^8.4.2",
|
|
39
42
|
"ora": "^6.1.2",
|
|
40
43
|
"pascalcase": "^2.0.0",
|
|
41
44
|
"postcss": "^8.4.16",
|
|
@@ -51,7 +54,7 @@
|
|
|
51
54
|
"ts-loader": "^9.3.1",
|
|
52
55
|
"typescript": "^4.8.3",
|
|
53
56
|
"url-loader": "4.1.1",
|
|
54
|
-
"webpack": "5.
|
|
57
|
+
"webpack": "5.76.1"
|
|
55
58
|
},
|
|
56
59
|
"author": "Custom component dev",
|
|
57
60
|
"license": "SEE LICENSE IN LICENSE",
|
package/src/auth.js
ADDED
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
// Below imports needed for when this file is used for a Node apps/Jest tests
|
|
2
|
+
// (can't do conditional imports...so should remove them for regular browser scenarios)
|
|
3
|
+
|
|
4
|
+
import fetch from 'node-fetch';
|
|
5
|
+
import open from 'open';
|
|
6
|
+
import crypto from 'crypto';
|
|
7
|
+
import http from 'http';
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class PegaAuth {
|
|
11
|
+
constructor(ssKeyConfig) {
|
|
12
|
+
if (typeof ssKeyConfig === "string") {
|
|
13
|
+
this.ssKeyConfig = ssKeyConfig;
|
|
14
|
+
this.#reloadConfig();
|
|
15
|
+
} else {
|
|
16
|
+
// object with config structure is passed in
|
|
17
|
+
this.config = ssKeyConfig;
|
|
18
|
+
}
|
|
19
|
+
this.isNode = typeof window === 'undefined';
|
|
20
|
+
this.crypto = this.isNode ? crypto : window.crypto;
|
|
21
|
+
|
|
22
|
+
// Tried both preferring the subtle and the webcrypto but both still yileded different behavior on digest
|
|
23
|
+
|
|
24
|
+
// Prefer webcrypto.subtle over regular subtle
|
|
25
|
+
// this.subtle = this.isNode ? (crypto.webcrypto ? crypto.webcrypto.subtle : crypto.subtle) : window.crypto.subtle;
|
|
26
|
+
|
|
27
|
+
// Prefer regular subtle over webcrypto.suble for now
|
|
28
|
+
this.subtle = this.isNode ? (crypto.subtle ? crypto.subtle : crypto.webcrypto.subtle) : window.crypto.subtle;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
#reloadConfig() {
|
|
32
|
+
if (!this.ssKeyConfig) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const peConfig = window.sessionStorage.getItem(this.ssKeyConfig);
|
|
36
|
+
let obj = {};
|
|
37
|
+
if (peConfig) {
|
|
38
|
+
try {
|
|
39
|
+
obj = JSON.parse(peConfig);
|
|
40
|
+
} catch (e) {
|
|
41
|
+
try {
|
|
42
|
+
obj = JSON.parse(this.#atob(peConfig));
|
|
43
|
+
} catch (e) {
|
|
44
|
+
obj = {};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
this.config = peConfig ? obj : {};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
#updateConfig() {
|
|
52
|
+
if (this.ssKeyConfig) {
|
|
53
|
+
window.sessionStorage.setItem(this.ssKeyConfig, this.#btoa(JSON.stringify(this.config)));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// For PKCE the authorize includes a code_challenge & code_challenge_method as well
|
|
58
|
+
async #buildAuthorizeUrl(state) {
|
|
59
|
+
const { serverType, clientId, redirectUri, authorizeUri, authService, sessionIndex, appAlias, userIdentifier, password, noPKCE } =
|
|
60
|
+
this.config;
|
|
61
|
+
|
|
62
|
+
if (!noPKCE) {
|
|
63
|
+
// Generate random string of 64 chars for verifier. RFC 7636 says from 43-128 chars
|
|
64
|
+
const buf = new Uint8Array(64);
|
|
65
|
+
this.crypto.getRandomValues(buf);
|
|
66
|
+
this.config.codeVerifier = this.#base64UrlSafeEncode(buf);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// If sessionIndex exists then increment attempts count (we will stop sending session_index after two failures)
|
|
70
|
+
if (sessionIndex) {
|
|
71
|
+
this.config.sessionIndexAttempts += 1;
|
|
72
|
+
}
|
|
73
|
+
// Persist codeVerifier in session storage so it survives the redirects that are to follow
|
|
74
|
+
this.#updateConfig();
|
|
75
|
+
|
|
76
|
+
if (!state) {
|
|
77
|
+
// Calc random state variable
|
|
78
|
+
const buf = new Uint8Array(32);
|
|
79
|
+
this.crypto.getRandomValues(buf);
|
|
80
|
+
state = this.#base64UrlSafeEncode(buf);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Trim alias to include just the real alias piece
|
|
84
|
+
const addtlScope = appAlias ? '+app.alias.' + appAlias.replace(/^app\//, '') : '';
|
|
85
|
+
const scope = (serverType === 'infinity' ? `openid${addtlScope}` : 'user_info');
|
|
86
|
+
|
|
87
|
+
// Add explicit creds if specified to try to avoid login popup
|
|
88
|
+
const moreAuthArgs = (serverType === 'infinity' ?
|
|
89
|
+
'&enable_psyncid=true' +
|
|
90
|
+
(authService ? `&authentication_service=${encodeURIComponent(authService)}` : '') +
|
|
91
|
+
(sessionIndex && this.config.sessionIndexAttempts < 3 ? `&session_index=${sessionIndex}` : '') +
|
|
92
|
+
(userIdentifier
|
|
93
|
+
? `&UserIdentifier=${encodeURIComponent(userIdentifier)}${password ? `&Password=${encodeURIComponent(this.#atob(password))}` : ''}` : '')
|
|
94
|
+
: '');
|
|
95
|
+
|
|
96
|
+
let pkceArgs = '';
|
|
97
|
+
if (!noPKCE) {
|
|
98
|
+
const cc = await this.#getCodeChallenge(this.config.codeVerifier);
|
|
99
|
+
pkceArgs = `&code_challenge=${cc}&code_challenge_method=S256`;
|
|
100
|
+
}
|
|
101
|
+
const authUrl = `${authorizeUri}?client_id=${clientId}&response_type=code&redirect_uri=${redirectUri}&scope=${scope}&state=${state}${pkceArgs}${moreAuthArgs}`;
|
|
102
|
+
// console.log(`authUrl: ${authUrl}`);
|
|
103
|
+
return authUrl;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async login() {
|
|
107
|
+
const { grantType } = this.config;
|
|
108
|
+
if (grantType && grantType !== 'authCode') {
|
|
109
|
+
return this.getToken();
|
|
110
|
+
} else {
|
|
111
|
+
return this.#authCodeStart();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// authCode login issues the authorize endpoint transaction and deals with redirects
|
|
116
|
+
async #authCodeStart() {
|
|
117
|
+
const fnGetRedirectUriOrigin = () => {
|
|
118
|
+
let redirectUri = this.config.redirectUri;
|
|
119
|
+
let nRootOffset = redirectUri.indexOf("//");
|
|
120
|
+
let nFirstPathOffset = nRootOffset !== -1 ? redirectUri.indexOf("/", nRootOffset + 2) : -1;
|
|
121
|
+
return nFirstPathOffset !== -1 ? redirectUri.substring(0, nFirstPathOffset) : redirectUri;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const redirectOrigin = fnGetRedirectUriOrigin();
|
|
125
|
+
const state = this.isNode ? '' : this.#btoa(location.origin);
|
|
126
|
+
|
|
127
|
+
return new Promise((resolve, reject) => {
|
|
128
|
+
/* eslint-disable consistent-return */
|
|
129
|
+
this.#buildAuthorizeUrl(state).then((url) => {
|
|
130
|
+
let myWindow = null; // popup or iframe
|
|
131
|
+
let elIframe = null,
|
|
132
|
+
elCloseBtn = null;
|
|
133
|
+
const iframeTimeout = this.config.silentTimeout !== undefined ? this.config.silentTimeout : 5000;
|
|
134
|
+
let bWinIframe = !this.isNode &&
|
|
135
|
+
iframeTimeout > 0 &&
|
|
136
|
+
((!!this.config.userIdentifier && !!this.config.password) ||
|
|
137
|
+
this.config.iframeLoginUI ||
|
|
138
|
+
this.config.authService !== 'pega');
|
|
139
|
+
let tmrAuthComplete = null,
|
|
140
|
+
checkWindowClosed = null;
|
|
141
|
+
const myWinOnLoad = () => {
|
|
142
|
+
try {
|
|
143
|
+
if (bWinIframe) {
|
|
144
|
+
elIframe.contentWindow.postMessage({ type: "PegaAuth" }, redirectOrigin);
|
|
145
|
+
console.log("authjs(login): loaded a page in iFrame");
|
|
146
|
+
} else {
|
|
147
|
+
myWindow.postMessage({ type: "PegaAuth" }, redirectOrigin);
|
|
148
|
+
}
|
|
149
|
+
} catch (e) {
|
|
150
|
+
console.log("authjs(login): Exception trying to postMessage on load");
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
const fnOpenPopup = () => {
|
|
154
|
+
myWindow = (this.isNode ? open : window.open)(url, '_blank', 'width=700,height=500,left=200,top=100');
|
|
155
|
+
if (!myWindow) {
|
|
156
|
+
// Blocked by popup-blocker
|
|
157
|
+
return reject("blocked");
|
|
158
|
+
}
|
|
159
|
+
checkWindowClosed = setInterval(() => {
|
|
160
|
+
if (myWindow.closed) {
|
|
161
|
+
clearInterval(checkWindowClosed);
|
|
162
|
+
reject("closed");
|
|
163
|
+
}
|
|
164
|
+
}, 500);
|
|
165
|
+
if (!this.isNode) {
|
|
166
|
+
try {
|
|
167
|
+
myWindow.addEventListener("load", myWinOnLoad, true);
|
|
168
|
+
} catch (e) {
|
|
169
|
+
console.log("authjs(login): Exception trying to add onload handler to opened window;");
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
const fnCloseIframe = () => {
|
|
174
|
+
elIframe.parentNode.removeChild(elIframe);
|
|
175
|
+
elCloseBtn.parentNode.removeChild(elCloseBtn);
|
|
176
|
+
elIframe = elCloseBtn = null;
|
|
177
|
+
bWinIframe = false;
|
|
178
|
+
};
|
|
179
|
+
const fnCloseAndReject = () => {
|
|
180
|
+
fnCloseIframe();
|
|
181
|
+
reject("closed");
|
|
182
|
+
};
|
|
183
|
+
let authMessageReceiver = null;
|
|
184
|
+
/* Retrieve token(s) and close login window */
|
|
185
|
+
const fnGetTokenAndFinish = (code) => {
|
|
186
|
+
if (!this.isNode) {
|
|
187
|
+
window.removeEventListener("message", authMessageReceiver, false);
|
|
188
|
+
}
|
|
189
|
+
this.getToken(code).then(token => {
|
|
190
|
+
if (bWinIframe) {
|
|
191
|
+
clearTimeout(tmrAuthComplete);
|
|
192
|
+
fnCloseIframe();
|
|
193
|
+
} else {
|
|
194
|
+
if (!this.isNode) {
|
|
195
|
+
clearInterval(checkWindowClosed);
|
|
196
|
+
myWindow.close();
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
resolve(token);
|
|
200
|
+
})
|
|
201
|
+
.catch(e => {
|
|
202
|
+
reject(e);
|
|
203
|
+
});
|
|
204
|
+
};
|
|
205
|
+
// If there is a userIdentifier and password specified or an external SSO auth service,
|
|
206
|
+
// we can try to use this silently in an iFrame first
|
|
207
|
+
if (bWinIframe) {
|
|
208
|
+
const nFrameZLevel = 99999;
|
|
209
|
+
elIframe = document.createElement('iframe');
|
|
210
|
+
elIframe.id = 'pe' + this.config.clientId;
|
|
211
|
+
const loginBoxWidth = 500,
|
|
212
|
+
loginBoxHeight = 700;
|
|
213
|
+
const oStyle = elIframe.style;
|
|
214
|
+
oStyle.position = 'absolute';
|
|
215
|
+
oStyle.display = 'none';
|
|
216
|
+
oStyle.zIndex = nFrameZLevel;
|
|
217
|
+
oStyle.top = `${Math.round(Math.max(window.innerHeight - loginBoxHeight, 0) / 2)}px`;
|
|
218
|
+
oStyle.left = `${Math.round(Math.max(window.innerWidth - loginBoxWidth, 0) / 2)}px`;
|
|
219
|
+
oStyle.width = '500px';
|
|
220
|
+
oStyle.height = '700px';
|
|
221
|
+
// Add Iframe to top of document DOM to have it load
|
|
222
|
+
document.body.insertBefore(elIframe, document.body.firstChild);
|
|
223
|
+
// document.getElementsByTagName('body')[0].appendChild(elIframe);
|
|
224
|
+
elIframe.addEventListener('load', myWinOnLoad, true);
|
|
225
|
+
// Disallow iframe content attempts to navigate main window
|
|
226
|
+
elIframe.setAttribute('sandbox', 'allow-scripts allow-forms allow-same-origin');
|
|
227
|
+
elIframe.setAttribute('src', url);
|
|
228
|
+
|
|
229
|
+
const svgCloseBtn = `<?xml version="1.0" encoding="UTF-8"?>
|
|
230
|
+
<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">
|
|
231
|
+
<title>Dismiss - Black</title>
|
|
232
|
+
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
|
233
|
+
<g transform="translate(1.000000, 1.000000)">
|
|
234
|
+
<circle fill="#252C32" cx="16" cy="16" r="16"></circle>
|
|
235
|
+
<g transform="translate(9.109375, 9.214844)" fill="#FFFFFF" fill-rule="nonzero">
|
|
236
|
+
<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>
|
|
237
|
+
</g>
|
|
238
|
+
</g>
|
|
239
|
+
</g>
|
|
240
|
+
</svg>`;
|
|
241
|
+
const bCloseWithinFrame = false;
|
|
242
|
+
elCloseBtn = document.createElement('img');
|
|
243
|
+
elCloseBtn.onclick = fnCloseAndReject;
|
|
244
|
+
elCloseBtn.src = `data:image/svg+xml;base64,${this.#btoa(svgCloseBtn)}`;
|
|
245
|
+
const oBtnStyle = elCloseBtn.style;
|
|
246
|
+
oBtnStyle.cursor = 'pointer';
|
|
247
|
+
// If svg doesn't set width and height might want to set oBtStyle width and height to something like '2em'
|
|
248
|
+
oBtnStyle.position = 'absolute';
|
|
249
|
+
oBtnStyle.display = 'none';
|
|
250
|
+
oBtnStyle.zIndex = nFrameZLevel + 1;
|
|
251
|
+
const nTopOffset = bCloseWithinFrame ? 5 : -10;
|
|
252
|
+
const nRightOffset = bCloseWithinFrame ? -34 : -20;
|
|
253
|
+
oBtnStyle.top = `${Math.round(Math.max(window.innerHeight - loginBoxHeight, 0) / 2) + nTopOffset}px`;
|
|
254
|
+
oBtnStyle.left = `${Math.round(Math.max(window.innerWidth - loginBoxWidth, 0) / 2) + loginBoxWidth + nRightOffset
|
|
255
|
+
}px`;
|
|
256
|
+
document.body.insertBefore(elCloseBtn, document.body.firstChild);
|
|
257
|
+
// If the password was wrong, then the login screen will be in the iframe
|
|
258
|
+
// ..and with Pega without realization of US-372314 it may replace the top (main portal) window
|
|
259
|
+
// For now set a timer and if the timer expires, remove the iFrame and use same url within
|
|
260
|
+
// visible window
|
|
261
|
+
tmrAuthComplete = setTimeout(() => {
|
|
262
|
+
clearTimeout(tmrAuthComplete);
|
|
263
|
+
// remove password from config
|
|
264
|
+
if (this.config.password) {
|
|
265
|
+
delete this.config.password;
|
|
266
|
+
this.#updateConfig();
|
|
267
|
+
}
|
|
268
|
+
// Display the iframe where the redirects did not succeed (or invoke a popup window)
|
|
269
|
+
if (this.config.iframeLoginUI) {
|
|
270
|
+
elIframe.style.display = 'block';
|
|
271
|
+
elCloseBtn.style.display = 'block';
|
|
272
|
+
} else {
|
|
273
|
+
fnCloseIframe();
|
|
274
|
+
fnOpenPopup();
|
|
275
|
+
}
|
|
276
|
+
}, iframeTimeout);
|
|
277
|
+
} else {
|
|
278
|
+
if (this.isNode) {
|
|
279
|
+
// Determine port to listen to by extracting it from redirect uri
|
|
280
|
+
const { redirectUri } = this.config;
|
|
281
|
+
const nLocalhost = redirectUri.indexOf("localhost:");
|
|
282
|
+
const nSlash = redirectUri.indexOf('/', nLocalhost + 10);
|
|
283
|
+
const nPort = parseInt(redirectUri.substring(nLocalhost + 10, nSlash));
|
|
284
|
+
if (-1 !== nLocalhost) {
|
|
285
|
+
const server = http.createServer((req, res) => {
|
|
286
|
+
const { method, url } = req;
|
|
287
|
+
const { winTitle, winBodyHtml } = this.config;
|
|
288
|
+
// console.log(`HTTP request received: ${url}`);
|
|
289
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
290
|
+
// Auto closing window for now. Can always leave it up and allow authConfig props to set title and bodyHtml
|
|
291
|
+
res.end(`<html><head><title>${winTitle}</title><script>window.close();</script></head><body>${winBodyHtml}</body></html>`);
|
|
292
|
+
const queryString = url.split('?')[1];
|
|
293
|
+
const urlParams = new URLSearchParams(queryString);
|
|
294
|
+
const code = urlParams.get('code');
|
|
295
|
+
if (code) {
|
|
296
|
+
// console.log(`Auth code received: ${code}`);
|
|
297
|
+
// Stop receiving connections and close when all are handled.
|
|
298
|
+
server.close();
|
|
299
|
+
fnGetTokenAndFinish(code);
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
server.listen(nPort);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
fnOpenPopup();
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (!this.isNode) {
|
|
309
|
+
/* Handler to receive the auth code */
|
|
310
|
+
authMessageReceiver = (event) => {
|
|
311
|
+
// Check origin to make sure it is the redirect origin
|
|
312
|
+
if (event.origin !== redirectOrigin) return;
|
|
313
|
+
if (!event.data || !event.data.type || event.data.type !== 'PegaAuth') return;
|
|
314
|
+
console.log('authjs(login): postMessage received with code');
|
|
315
|
+
const code = event.data.code.toString();
|
|
316
|
+
fnGetTokenAndFinish(code);
|
|
317
|
+
};
|
|
318
|
+
window.addEventListener('message', authMessageReceiver, false);
|
|
319
|
+
window.authCodeCallback = function (code) {
|
|
320
|
+
console.log('authjs(login): authCodeCallback used with code');
|
|
321
|
+
fnGetTokenAndFinish(code);
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// For PKCE token endpoint includes code_verifier
|
|
329
|
+
getToken(authCode) {
|
|
330
|
+
// Reload config to pick up the previously stored codeVerifier
|
|
331
|
+
this.#reloadConfig();
|
|
332
|
+
|
|
333
|
+
const { serverType, isolationId, clientId, clientSecret, redirectUri, tokenUri, codeVerifier, grantType,
|
|
334
|
+
sessionIndex, customTokenParams, userIdentifier, password, noPKCE } = this.config;
|
|
335
|
+
|
|
336
|
+
const bAuthCode = !grantType || grantType === 'authCode';
|
|
337
|
+
if (bAuthCode && !authCode && !this.isNode) {
|
|
338
|
+
const queryString = window.location.search;
|
|
339
|
+
const urlParams = new URLSearchParams(queryString);
|
|
340
|
+
authCode = urlParams.get('code');
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const formData = new URLSearchParams();
|
|
344
|
+
formData.append('client_id', clientId);
|
|
345
|
+
if (clientSecret) {
|
|
346
|
+
formData.append('client_secret', clientSecret);
|
|
347
|
+
}
|
|
348
|
+
const fullGTName = {
|
|
349
|
+
"authCode": "authorization_code",
|
|
350
|
+
"clientCreds": "client_credentials",
|
|
351
|
+
"customBearer": "custom-bearer",
|
|
352
|
+
"passwordCreds": "password"
|
|
353
|
+
}[grantType];
|
|
354
|
+
const grant_type = fullGTName ? fullGTName :
|
|
355
|
+
(grantType ? grantType : 'authorization_code');
|
|
356
|
+
formData.append('grant_type', grant_type);
|
|
357
|
+
if (serverType === 'launchpad' && grantType !== 'authCode') {
|
|
358
|
+
formData.append('isolation_ids', isolationId);
|
|
359
|
+
}
|
|
360
|
+
if (bAuthCode) {
|
|
361
|
+
formData.append('code', authCode);
|
|
362
|
+
formData.append('redirect_uri', redirectUri);
|
|
363
|
+
if (!noPKCE) {
|
|
364
|
+
formData.append('code_verifier', codeVerifier);
|
|
365
|
+
}
|
|
366
|
+
} else if (sessionIndex) {
|
|
367
|
+
formData.append('session_index', sessionIndex);
|
|
368
|
+
}
|
|
369
|
+
if (grantType === 'customBearer' && customTokenParams) {
|
|
370
|
+
Object.keys(customTokenParams).forEach((param) => {
|
|
371
|
+
formData.append(param, customTokenParams[param]);
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
if (grantType === 'passwordCreds') {
|
|
375
|
+
formData.append('username', userIdentifier);
|
|
376
|
+
formData.append('password', this.#atob(password));
|
|
377
|
+
}
|
|
378
|
+
// console.log(`getToken form data: ${formData.toString()}`);
|
|
379
|
+
|
|
380
|
+
return fetch(tokenUri, {
|
|
381
|
+
method: 'POST',
|
|
382
|
+
headers: new Headers({
|
|
383
|
+
'content-type': 'application/x-www-form-urlencoded'
|
|
384
|
+
}),
|
|
385
|
+
|
|
386
|
+
body: formData.toString()
|
|
387
|
+
})
|
|
388
|
+
.then((response) => response.json())
|
|
389
|
+
.then((token) => {
|
|
390
|
+
if (token.errors || token.error) {
|
|
391
|
+
// console.log(`formData: ${formData.toString()}`);
|
|
392
|
+
// console.log(`Token endpoint error: ${JSON.stringify(token.errors ? token.errors : token)}`);
|
|
393
|
+
} else {
|
|
394
|
+
// .expires_in contains the # of seconds before access token expires
|
|
395
|
+
// add property to keep track of current time when the token expires
|
|
396
|
+
/* eslint-disable camelcase */
|
|
397
|
+
token.eA = Date.now() + token.expires_in * 1000;
|
|
398
|
+
if (this.config.codeVerifier) {
|
|
399
|
+
delete this.config.codeVerifier;
|
|
400
|
+
}
|
|
401
|
+
// If there is a session_index then move this to the peConfig structure (as used on authorize)
|
|
402
|
+
if (token.session_index) {
|
|
403
|
+
this.config.sessionIndex = token.session_index;
|
|
404
|
+
}
|
|
405
|
+
// If we got a token and have a session index, then reset the sessionIndexAttempts
|
|
406
|
+
if (this.config.sessionIndex) {
|
|
407
|
+
this.config.sessionIndexAttempts = 0;
|
|
408
|
+
}
|
|
409
|
+
this.#updateConfig();
|
|
410
|
+
}
|
|
411
|
+
return token;
|
|
412
|
+
/* eslint-enable camelcase */
|
|
413
|
+
})
|
|
414
|
+
.catch((e) => {
|
|
415
|
+
console.log(e);
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/* eslint-disable camelcase */
|
|
420
|
+
async refreshToken(refresh_token) {
|
|
421
|
+
const { clientId, clientSecret, tokenUri } = this.config;
|
|
422
|
+
|
|
423
|
+
const formData = new URLSearchParams();
|
|
424
|
+
formData.append('client_id', clientId);
|
|
425
|
+
if (clientSecret) {
|
|
426
|
+
formData.append('client_secret', clientSecret);
|
|
427
|
+
}
|
|
428
|
+
formData.append('grant_type', 'refresh_token');
|
|
429
|
+
formData.append('refresh_token', refresh_token);
|
|
430
|
+
|
|
431
|
+
return fetch(tokenUri, {
|
|
432
|
+
method: 'POST',
|
|
433
|
+
headers: new Headers({
|
|
434
|
+
'content-type': 'application/x-www-form-urlencoded'
|
|
435
|
+
}),
|
|
436
|
+
|
|
437
|
+
body: formData.toString()
|
|
438
|
+
})
|
|
439
|
+
.then((response) => {
|
|
440
|
+
if (!response.ok && 401 === response.status) {
|
|
441
|
+
return null;
|
|
442
|
+
}
|
|
443
|
+
return response.json();
|
|
444
|
+
})
|
|
445
|
+
.then((token) => {
|
|
446
|
+
if (token) {
|
|
447
|
+
// .expires_in contains the # of seconds before access token expires
|
|
448
|
+
// add property to keep track of current time when the token expires
|
|
449
|
+
token.eA = Date.now() + token.expires_in * 1000;
|
|
450
|
+
}
|
|
451
|
+
return token;
|
|
452
|
+
})
|
|
453
|
+
.catch((e) => {
|
|
454
|
+
console.log(e);
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
async revokeTokens(access_token, refresh_token = null) {
|
|
459
|
+
if (Object.keys(this.config).length === 0) {
|
|
460
|
+
// Must have a config structure to proceed
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
const { clientId, clientSecret, revokeUri } = this.config;
|
|
464
|
+
|
|
465
|
+
let hdrs = { 'content-type': 'application/x-www-form-urlencoded' };
|
|
466
|
+
if (clientSecret) {
|
|
467
|
+
hdrs['authorization'] = 'Basic ' + this.#btoa(clientId + ':' + clientSecret);
|
|
468
|
+
}
|
|
469
|
+
let aTknProps = ['access_token'];
|
|
470
|
+
if (refresh_token) {
|
|
471
|
+
aTknProps.push('refresh_token');
|
|
472
|
+
}
|
|
473
|
+
aTknProps.forEach((prop) => {
|
|
474
|
+
const formData = new URLSearchParams();
|
|
475
|
+
if (!clientSecret) {
|
|
476
|
+
formData.append('client_id', clientId);
|
|
477
|
+
}
|
|
478
|
+
formData.append('token', prop === 'access_token' ? access_token : refresh_token);
|
|
479
|
+
formData.append('token_type_hint', prop);
|
|
480
|
+
fetch(revokeUri, {
|
|
481
|
+
method: 'POST',
|
|
482
|
+
headers: new Headers(hdrs),
|
|
483
|
+
body: formData.toString()
|
|
484
|
+
})
|
|
485
|
+
.then((response) => {
|
|
486
|
+
if (!response.ok) {
|
|
487
|
+
console.log('Error revoking ' + prop + ':' + response.status);
|
|
488
|
+
}
|
|
489
|
+
})
|
|
490
|
+
.catch((e) => {
|
|
491
|
+
console.log(e);
|
|
492
|
+
});
|
|
493
|
+
});
|
|
494
|
+
// Also clobber any sessionIndex
|
|
495
|
+
if (this.config.sessionIndex) {
|
|
496
|
+
delete this.config.sessionIndex;
|
|
497
|
+
this.#updateConfig();
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
/* eslint-enable camelcase */
|
|
501
|
+
|
|
502
|
+
#sha256Hash(str) {
|
|
503
|
+
// Found that the Node implementation of subtle.digest is yielding incorrect results
|
|
504
|
+
// so using a different set of apis to get expected results.
|
|
505
|
+
if (this.isNode) {
|
|
506
|
+
return new Promise((resolve) => {
|
|
507
|
+
return resolve(this.crypto.createHash("sha256").update(str).digest());
|
|
508
|
+
});
|
|
509
|
+
} else {
|
|
510
|
+
return this.subtle.digest('SHA-256', new TextEncoder().encode(str));
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/* btoa to work with node or browser - create B64 string*/
|
|
515
|
+
#btoa(str) {
|
|
516
|
+
if (this.isNode) {
|
|
517
|
+
return Buffer.from(str, 'binary').toString('base64');
|
|
518
|
+
} else {
|
|
519
|
+
return window.btoa(str);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/* atoa to work with node or browser - convert B64 to binary */
|
|
524
|
+
#atob(str) {
|
|
525
|
+
if (this.isNode) {
|
|
526
|
+
return Buffer.from(str, 'base64').toString('binary');
|
|
527
|
+
} else {
|
|
528
|
+
return window.atob(str);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Base64 encode
|
|
533
|
+
/* eslint-disable-next-line class-methods-use-this */
|
|
534
|
+
#encode64(buff) {
|
|
535
|
+
return this.#btoa(new Uint8Array(buff).reduce((s, b) => s + String.fromCharCode(b), ''));
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/*
|
|
539
|
+
* Base64 url safe encoding of an array
|
|
540
|
+
*/
|
|
541
|
+
#base64UrlSafeEncode(buf) {
|
|
542
|
+
return this.#encode64(buf).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/* Calc code verifier if necessary
|
|
546
|
+
*/
|
|
547
|
+
/* eslint-disable camelcase */
|
|
548
|
+
#getCodeChallenge(code_verifier) {
|
|
549
|
+
return this.#sha256Hash(code_verifier)
|
|
550
|
+
.then((hashed) => {
|
|
551
|
+
return this.#base64UrlSafeEncode(hashed);
|
|
552
|
+
})
|
|
553
|
+
.catch((error) => {
|
|
554
|
+
console.log(error);
|
|
555
|
+
})
|
|
556
|
+
.finally(() => {
|
|
557
|
+
return null;
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
/* eslint-enable camelcase */
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
export default PegaAuth;
|
package/src/constants.js
CHANGED
|
@@ -10,7 +10,37 @@ export const PUBLISH_COMPONENT_SERVICE_REST_ENDPOINT = 'PRRestService/dev/v1/pub
|
|
|
10
10
|
export const LIST_COMPONENT_SERVICE_REST_ENDPOINT = 'PRRestService/dev/v1/list_component';
|
|
11
11
|
export const OOTB_COMPONENT_SERVICE_REST_ENDPOINT = 'PRRestService/dev/v1/ootb_component';
|
|
12
12
|
export const DELETE_COMPONENT_SERVICE_REST_ENDPOINT = 'PRRestService/dev/v1/delete_component';
|
|
13
|
-
export const
|
|
13
|
+
export const INF_TOKEN_URL_V1 = 'PRRestService/oauth2/v1/token';
|
|
14
|
+
export const INF_AUTHORIZE_URL_V1 = 'PRRestService/oauth2/v1/authorize';
|
|
15
|
+
export const LP_TOKEN_URL = 'uas/oauth/token';
|
|
16
|
+
export const LP_AUTHORIZE_URL = 'uas/oauth/authorize';
|
|
17
|
+
|
|
18
|
+
export const AUTHENTICATE_SCHEMA = {
|
|
19
|
+
serverType: [
|
|
20
|
+
{
|
|
21
|
+
name: 'Infinity',
|
|
22
|
+
value: 'infinity'
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: 'Launchpad',
|
|
26
|
+
value: 'launchpad'
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
grantType: [
|
|
30
|
+
{
|
|
31
|
+
name: 'Authorization code',
|
|
32
|
+
value: 'authCode'
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'Password credentials',
|
|
36
|
+
value: 'passwordCreds'
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'Client credentials',
|
|
40
|
+
value: 'clientCreds'
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
};
|
|
14
44
|
|
|
15
45
|
export const COMPONENT_SCHEMA = {
|
|
16
46
|
framework: [
|
|
@@ -108,7 +138,7 @@ export const COMPONENT_SCHEMA = {
|
|
|
108
138
|
value: 'Picklist'
|
|
109
139
|
}
|
|
110
140
|
],
|
|
111
|
-
template: ['DETAILS', 'DETAILS-region', 'FORM', 'FORM-region', 'PAGE'
|
|
141
|
+
template: ['DETAILS', 'DETAILS-region', 'FORM', 'FORM-region', 'PAGE'],
|
|
112
142
|
widget: [
|
|
113
143
|
{
|
|
114
144
|
name: 'CASE',
|
|
@@ -1,24 +1,73 @@
|
|
|
1
1
|
/* eslint-disable import/prefer-default-export */
|
|
2
2
|
import { getPegaServerConfig } from '../../util.js';
|
|
3
3
|
|
|
4
|
+
import { AUTHENTICATE_SCHEMA } from '../../constants.js';
|
|
5
|
+
|
|
4
6
|
export const getAuthQuestions = async () => {
|
|
5
7
|
const defaultPegaConfig = await getPegaServerConfig();
|
|
6
8
|
|
|
7
9
|
return [
|
|
10
|
+
{
|
|
11
|
+
name: 'serverType',
|
|
12
|
+
type: 'rawlist',
|
|
13
|
+
message: 'Enter Pega server type',
|
|
14
|
+
default: answers => {
|
|
15
|
+
return defaultPegaConfig.serverType;
|
|
16
|
+
},
|
|
17
|
+
choices: AUTHENTICATE_SCHEMA.serverType,
|
|
18
|
+
validate: value => {
|
|
19
|
+
if (value.trim()) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
return 'Pega server type cannot be empty';
|
|
23
|
+
},
|
|
24
|
+
when: answers => {
|
|
25
|
+
if (!defaultPegaConfig.serverType) {
|
|
26
|
+
answers.serverType = "infinity";
|
|
27
|
+
}
|
|
28
|
+
return (defaultPegaConfig.askServerType ? true : false);
|
|
29
|
+
}
|
|
30
|
+
},
|
|
8
31
|
{
|
|
9
32
|
name: 'server',
|
|
10
33
|
message: 'Enter pega server url',
|
|
11
34
|
default: defaultPegaConfig.server
|
|
12
35
|
},
|
|
36
|
+
{
|
|
37
|
+
name: 'isolationId',
|
|
38
|
+
message: 'Enter isolation id',
|
|
39
|
+
default: defaultPegaConfig.isolationId,
|
|
40
|
+
when: answers => {
|
|
41
|
+
return answers.serverType === "launchpad";
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'grantType',
|
|
46
|
+
type: 'rawlist',
|
|
47
|
+
message: 'Enter OAuth 2.0 grant type',
|
|
48
|
+
default: defaultPegaConfig.grantType,
|
|
49
|
+
choices: answers => {
|
|
50
|
+
return AUTHENTICATE_SCHEMA.grantType.filter(gt => !(answers.serverType === "launchpad" && gt.value === "passwordCreds"));
|
|
51
|
+
},
|
|
52
|
+
validate: value => {
|
|
53
|
+
if (value.trim()) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
return 'Grant type cannot be empty';
|
|
57
|
+
}
|
|
58
|
+
},
|
|
13
59
|
{
|
|
14
60
|
name: 'user',
|
|
15
61
|
message: 'Enter user id',
|
|
16
62
|
default: defaultPegaConfig.user,
|
|
17
|
-
validate: value => {
|
|
18
|
-
if (value.trim()) {
|
|
63
|
+
validate: (value, answers) => {
|
|
64
|
+
if (value.trim() || answers.grantType === "authCode") {
|
|
19
65
|
return true;
|
|
20
66
|
}
|
|
21
67
|
return 'Username cannot be empty';
|
|
68
|
+
},
|
|
69
|
+
when: answers => {
|
|
70
|
+
return (answers.serverType === "infinity" && answers.grantType !== "clientCreds");
|
|
22
71
|
}
|
|
23
72
|
},
|
|
24
73
|
{
|
|
@@ -26,11 +75,14 @@ export const getAuthQuestions = async () => {
|
|
|
26
75
|
type: 'password',
|
|
27
76
|
message: 'Enter password',
|
|
28
77
|
default: defaultPegaConfig.password,
|
|
29
|
-
validate: value => {
|
|
30
|
-
if (value.trim()) {
|
|
78
|
+
validate: (value, answers) => {
|
|
79
|
+
if (value.trim() || answers.grantType === "authCode") {
|
|
31
80
|
return true;
|
|
32
81
|
}
|
|
33
82
|
return 'Password cannot be empty';
|
|
83
|
+
},
|
|
84
|
+
when: answers => {
|
|
85
|
+
return (answers.serverType === "infinity" && answers.grantType !== "clientCreds");
|
|
34
86
|
}
|
|
35
87
|
},
|
|
36
88
|
{
|
|
@@ -49,8 +101,8 @@ export const getAuthQuestions = async () => {
|
|
|
49
101
|
type: 'password',
|
|
50
102
|
message: 'Enter clientSecret',
|
|
51
103
|
default: defaultPegaConfig.clientSecret,
|
|
52
|
-
validate: value => {
|
|
53
|
-
if (value.trim()) {
|
|
104
|
+
validate: (value, answers) => {
|
|
105
|
+
if (value.trim() || answers.grantType === "authCode") {
|
|
54
106
|
return true;
|
|
55
107
|
}
|
|
56
108
|
return 'clientSecret cannot be empty';
|
|
@@ -1,83 +1,138 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import fs from 'fs';
|
|
3
|
-
import https from 'https';
|
|
4
3
|
|
|
5
4
|
import chalk from 'chalk';
|
|
6
5
|
import inquirer from 'inquirer';
|
|
7
|
-
import fetch from 'node-fetch';
|
|
8
6
|
|
|
9
|
-
import { checkPathAccess,
|
|
10
|
-
import { TOKEN_PATH } from '../../constants.js';
|
|
7
|
+
import { checkPathAccess, showVersion } from '../../util.js';
|
|
8
|
+
import { TOKEN_PATH, INF_TOKEN_URL_V1, INF_AUTHORIZE_URL_V1, LP_TOKEN_URL, LP_AUTHORIZE_URL } from '../../constants.js';
|
|
11
9
|
|
|
12
10
|
import { getAuthQuestions } from './helper.js';
|
|
11
|
+
import PegaAuth from '../../auth.js';
|
|
13
12
|
|
|
14
13
|
export const TASKS_CONFIG_JSON_FILENAME = 'tasks.config.json';
|
|
15
14
|
|
|
16
15
|
const pegaConfigJsonPath = path.join(path.resolve(), TASKS_CONFIG_JSON_FILENAME);
|
|
16
|
+
let server = null;
|
|
17
17
|
|
|
18
18
|
const generateAccessToken = async config => {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
if (data.token_type) {
|
|
48
|
-
fs.writeFile(TOKEN_PATH, JSON.stringify(data), err => {
|
|
49
|
-
if (err) {
|
|
50
|
-
console.error(err);
|
|
51
|
-
}
|
|
52
|
-
console.log(`${chalk.bold.green('Authenticated successfully !!')}`);
|
|
53
|
-
});
|
|
19
|
+
|
|
20
|
+
let authSuccess = false;
|
|
21
|
+
const { serverType, isolationId, grantType, clientId, clientSecret, user, password } = config;
|
|
22
|
+
const server = config.server.endsWith('/') ? config.server : `${config.server}/`;
|
|
23
|
+
|
|
24
|
+
return new Promise((resolve, reject) => {
|
|
25
|
+
const sTokenUri = `${server}${serverType === 'infinity' ? INF_TOKEN_URL_V1 : LP_TOKEN_URL}`;
|
|
26
|
+
const sAuthorizeUri = `${server}${serverType === 'infinity' ? INF_AUTHORIZE_URL_V1 : LP_AUTHORIZE_URL}`;
|
|
27
|
+
// console.log(`grantType: ${config.grantType}`);
|
|
28
|
+
|
|
29
|
+
// Build auth config structure
|
|
30
|
+
const authConfig = {
|
|
31
|
+
serverType: serverType,
|
|
32
|
+
grantType: grantType,
|
|
33
|
+
authorizeUri: sAuthorizeUri,
|
|
34
|
+
tokenUri: sTokenUri,
|
|
35
|
+
clientId: clientId,
|
|
36
|
+
clientSecret: clientSecret,
|
|
37
|
+
winTitle: 'DXCB',
|
|
38
|
+
winBodyHtml: `Authentication is complete. This browser window may be closed.`
|
|
39
|
+
};
|
|
40
|
+
if (serverType === 'infinity') {
|
|
41
|
+
authConfig.authService = 'pega';
|
|
42
|
+
if (grantType !== 'clientCreds' && user && password) {
|
|
43
|
+
authConfig.userIdentifier = user;
|
|
44
|
+
authConfig.password = Buffer.from(password).toString('base64');
|
|
54
45
|
}
|
|
55
|
-
}
|
|
56
|
-
|
|
46
|
+
}
|
|
47
|
+
if (serverType === 'launchpad') {
|
|
48
|
+
authConfig.isolationId = isolationId;
|
|
49
|
+
// authConfig.noPKCE = true;
|
|
50
|
+
}
|
|
51
|
+
if (grantType === 'authCode') {
|
|
52
|
+
authConfig.redirectUri = 'http://localhost:4010/';
|
|
53
|
+
}
|
|
54
|
+
const auth = new PegaAuth(authConfig);
|
|
55
|
+
// console.log(`...about to infoke auth.login\n${JSON.stringify(authConfig)}`);
|
|
56
|
+
|
|
57
|
+
auth.login()
|
|
58
|
+
.then((token) => {
|
|
59
|
+
if (token?.token_type) {
|
|
60
|
+
fs.writeFile(TOKEN_PATH, JSON.stringify(token), err => {
|
|
61
|
+
if (err) {
|
|
62
|
+
console.error(err);
|
|
63
|
+
return reject(authSuccess);
|
|
64
|
+
}
|
|
65
|
+
console.log(`${chalk.bold.green('Authenticated successfully !!')}`);
|
|
66
|
+
authSuccess = true;
|
|
67
|
+
return resolve(authSuccess);
|
|
68
|
+
});
|
|
69
|
+
} else {
|
|
70
|
+
console.log(`${chalk.bold.red('Authentication failed:' + JSON.stringify(token.errors ? token.errors : token))}`);
|
|
71
|
+
return reject(authSuccess);
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
.catch((e) => {
|
|
75
|
+
console.log(`${chalk.bold.red(e)}`);
|
|
76
|
+
return reject(authSuccess);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
57
79
|
};
|
|
58
80
|
|
|
59
81
|
export default async options => {
|
|
82
|
+
|
|
83
|
+
let authOK = false;
|
|
84
|
+
|
|
60
85
|
showVersion();
|
|
61
86
|
|
|
62
87
|
await checkPathAccess(pegaConfigJsonPath);
|
|
63
88
|
|
|
64
|
-
let
|
|
65
|
-
data =
|
|
89
|
+
let cfgData = fs.readFileSync(pegaConfigJsonPath, { encoding: 'utf8' });
|
|
90
|
+
let data = null;
|
|
91
|
+
if (cfgData) {
|
|
92
|
+
try {
|
|
93
|
+
data = JSON.parse(cfgData);
|
|
94
|
+
} catch (e) {
|
|
95
|
+
console.log(`${chalk.red('Invalid JSON within tasks.config.json')}`);
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
66
99
|
|
|
67
100
|
const serverConfig = data['server-config'];
|
|
68
101
|
const params = options.params;
|
|
69
102
|
|
|
70
103
|
const questions = await getAuthQuestions(params);
|
|
71
104
|
const answers = await inquirer.prompt(questions);
|
|
105
|
+
serverConfig.serverType = answers.serverType;
|
|
72
106
|
serverConfig.server = answers.server;
|
|
73
|
-
|
|
74
|
-
|
|
107
|
+
// Isolation id only needed for launchpad
|
|
108
|
+
if (answers.serverType === 'launchpad') {
|
|
109
|
+
serverConfig.isolationId = answers.isolationId;
|
|
110
|
+
}
|
|
111
|
+
serverConfig.grantType = answers.grantType;
|
|
112
|
+
if (answers.serverType === 'infinity' && answers.user && answers.password) {
|
|
113
|
+
serverConfig.user = answers.user;
|
|
114
|
+
serverConfig.password = answers.password;
|
|
115
|
+
}
|
|
75
116
|
serverConfig.clientId = answers.clientId;
|
|
76
117
|
serverConfig.clientSecret = answers.clientSecret;
|
|
77
|
-
await generateAccessToken(serverConfig);
|
|
78
118
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
119
|
+
try {
|
|
120
|
+
authOK = await generateAccessToken(serverConfig);
|
|
121
|
+
} catch (e) {
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (authOK) {
|
|
125
|
+
console.log(`${chalk.green('Writing updated config...')}`);
|
|
126
|
+
let updCfg = null;
|
|
127
|
+
try {
|
|
128
|
+
updCfg = JSON.stringify(data, null, 2);
|
|
129
|
+
fs.writeFileSync(pegaConfigJsonPath, updCfg, err => {
|
|
130
|
+
if (err) throw err;
|
|
131
|
+
});
|
|
132
|
+
} catch (e) {
|
|
133
|
+
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
process.exit(1);
|
|
83
138
|
};
|
|
@@ -3,7 +3,7 @@ import { Input, FieldValueList, Text } from "@pega/cosmos-react-core";
|
|
|
3
3
|
import PropTypes from "prop-types";
|
|
4
4
|
|
|
5
5
|
// includes in bundle
|
|
6
|
-
import {formatExists,textFormatter, urlFormatter
|
|
6
|
+
import {formatExists,textFormatter, urlFormatter} from "./text-url.jsx";
|
|
7
7
|
import handleEvent from "./event-utils";
|
|
8
8
|
import { suggestionsHandler } from './suggestions-handler';
|
|
9
9
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useState, useRef, React} from "react";
|
|
2
|
+
import { EmailDisplay, PhoneDisplay, URLDisplay, Image, Button, Lightbox } from '@pega/cosmos-react-core';
|
|
2
3
|
|
|
3
4
|
export const formatExists = (formatterVal) => {
|
|
4
5
|
const formatterValues = [
|
package/src/tasks/init/index.js
CHANGED
|
@@ -140,7 +140,7 @@ export default async options => {
|
|
|
140
140
|
'\n',
|
|
141
141
|
`Inside that directory, you can run several commands:`,
|
|
142
142
|
'\n\n',
|
|
143
|
-
`${chalk.blueBright('npm start')}`,
|
|
143
|
+
`${chalk.blueBright('npm run start-storybook')}`,
|
|
144
144
|
`\n Start Storybook in development mode`,
|
|
145
145
|
'\n\n',
|
|
146
146
|
`${chalk.blueBright('npm run create')}`,
|
|
@@ -158,7 +158,7 @@ export default async options => {
|
|
|
158
158
|
'\n\n',
|
|
159
159
|
`${chalk.blueBright('cd')} ${chalk.bold(projectName)}`,
|
|
160
160
|
'\n',
|
|
161
|
-
`${chalk.blueBright('npm start')}`,
|
|
161
|
+
`${chalk.blueBright('npm run start-storybook')}`,
|
|
162
162
|
'\n'
|
|
163
163
|
);
|
|
164
164
|
return true;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"organization": "{{organization}}",
|
|
6
6
|
"main": "src/index.js",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"start": "start-storybook --port 6040 --quiet",
|
|
8
|
+
"start-storybook": "start-storybook --port 6040 --quiet",
|
|
9
9
|
"build": "tsc -b tsconfig.build.json",
|
|
10
10
|
"lint": "run-p -cl lint:*",
|
|
11
11
|
"fix": "run-p -cl fix:*",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@pega/configs": "^0.3.4",
|
|
57
|
-
"@pega/custom-dx-components": "^2.0.
|
|
57
|
+
"@pega/custom-dx-components": "^2.0.22",
|
|
58
58
|
"@pega/eslint-config": "^0.3.2",
|
|
59
59
|
"@storybook/addon-actions": "^6.5.12",
|
|
60
60
|
"@storybook/addon-essentials": "^6.5.12",
|
package/src/util.js
CHANGED
|
@@ -16,8 +16,7 @@ import {
|
|
|
16
16
|
TOKEN_PATH,
|
|
17
17
|
OOTB_COMPONENT_SERVICE_REST_ENDPOINT,
|
|
18
18
|
DELETE_COMPONENT_SERVICE_REST_ENDPOINT,
|
|
19
|
-
OOTB_COMPONENTS
|
|
20
|
-
TOKEN_URL_V1
|
|
19
|
+
OOTB_COMPONENTS
|
|
21
20
|
} from './constants.js';
|
|
22
21
|
|
|
23
22
|
const access = promisify(fs.access);
|
|
@@ -226,7 +225,7 @@ export const showVersion = () => {
|
|
|
226
225
|
const pData = require("@pega/custom-dx-components/package.json");
|
|
227
226
|
|
|
228
227
|
console.log(chalk.green("DX Component Builder v" + pData.version));
|
|
229
|
-
}
|
|
228
|
+
};
|
|
230
229
|
|
|
231
230
|
export const getOOTBComponents = async () => {
|
|
232
231
|
return new Promise((resolve, reject) => {
|
|
@@ -281,7 +280,3 @@ export const getOOTBComponents = async () => {
|
|
|
281
280
|
});
|
|
282
281
|
});
|
|
283
282
|
};
|
|
284
|
-
|
|
285
|
-
export const getTokenUrl = async (server) => {
|
|
286
|
-
return server.endsWith('/') ? `${server}${TOKEN_URL_V1}` : `${server}/${TOKEN_URL_V1}`;
|
|
287
|
-
};
|