@suitegeezus/suitecloud-cli 3.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/CONTRIBUTING.md +7 -0
  2. package/DELTA.md +235 -0
  3. package/README.md +107 -0
  4. package/docs/Command.md +299 -0
  5. package/messages.json +317 -0
  6. package/package.json +51 -0
  7. package/postinstall.js +8 -0
  8. package/resources/FUTC-LICENSE.txt +115 -0
  9. package/src/ApplicationConstants.js +87 -0
  10. package/src/CLI.js +202 -0
  11. package/src/CLIException.js +21 -0
  12. package/src/ExecutionEnvironmentContext.js +39 -0
  13. package/src/SdkExecutionContext.js +90 -0
  14. package/src/SdkExecutor.js +160 -0
  15. package/src/commands/Command.js +158 -0
  16. package/src/commands/account/manageauth/ManageAccountAction.js +126 -0
  17. package/src/commands/account/manageauth/ManageAccountCommand.js +20 -0
  18. package/src/commands/account/manageauth/ManageAccountInputHandler.js +215 -0
  19. package/src/commands/account/manageauth/ManageAccountOutputHandler.js +30 -0
  20. package/src/commands/account/setup/SetupAction.js +39 -0
  21. package/src/commands/account/setup/SetupCommand.js +21 -0
  22. package/src/commands/account/setup/SetupInputHandler.js +169 -0
  23. package/src/commands/account/setup/SetupOutputHandler.js +65 -0
  24. package/src/commands/account/setupci/AccountSetupCiAction.js +58 -0
  25. package/src/commands/account/setupci/AccountSetupCiCommand.js +20 -0
  26. package/src/commands/account/setupci/AccountSetupCiConstants.js +21 -0
  27. package/src/commands/account/setupci/AccountSetupCiOutputHandler.js +40 -0
  28. package/src/commands/account/setupci/AccountSetupCiValidation.js +100 -0
  29. package/src/commands/base/BaseAction.js +37 -0
  30. package/src/commands/base/BaseInputHandler.js +24 -0
  31. package/src/commands/base/BaseOutputHandler.js +30 -0
  32. package/src/commands/custom/hello/Action.js +58 -0
  33. package/src/commands/custom/hello/Command.js +20 -0
  34. package/src/commands/custom/hello/Handler.js +26 -0
  35. package/src/commands/custom/hello/README.md +78 -0
  36. package/src/commands/custom/hook/Action.js +28 -0
  37. package/src/commands/custom/hook/Command.js +22 -0
  38. package/src/commands/custom/hook/InputHandler.js +8 -0
  39. package/src/commands/custom/hook/OutputHandler.js +8 -0
  40. package/src/commands/custom/hook/README.md +32 -0
  41. package/src/commands/file/create/CreateFileAction.js +89 -0
  42. package/src/commands/file/create/CreateFileCommand.js +20 -0
  43. package/src/commands/file/create/CreateFileInputHandler.js +175 -0
  44. package/src/commands/file/create/CreateFileOutputHandler.js +22 -0
  45. package/src/commands/file/import/ImportFilesAction.js +112 -0
  46. package/src/commands/file/import/ImportFilesCommand.js +22 -0
  47. package/src/commands/file/import/ImportFilesInputHandler.js +130 -0
  48. package/src/commands/file/import/ImportFilesOutputHandler.js +53 -0
  49. package/src/commands/file/list/ListFilesAction.js +55 -0
  50. package/src/commands/file/list/ListFilesCommand.js +20 -0
  51. package/src/commands/file/list/ListFilesInputHandler.js +55 -0
  52. package/src/commands/file/list/ListFilesOutputHandler.js +24 -0
  53. package/src/commands/file/upload/UploadFilesAction.js +67 -0
  54. package/src/commands/file/upload/UploadFilesCommand.js +20 -0
  55. package/src/commands/file/upload/UploadFilesInputHandler.js +125 -0
  56. package/src/commands/file/upload/UploadFilesOutputHandler.js +49 -0
  57. package/src/commands/object/create/CreateObjectAction.js +33 -0
  58. package/src/commands/object/create/CreateObjectCommand.js +19 -0
  59. package/src/commands/object/create/CreateObjectInputHandler.js +82 -0
  60. package/src/commands/object/import/ImportObjectsAction.js +225 -0
  61. package/src/commands/object/import/ImportObjectsCommand.js +20 -0
  62. package/src/commands/object/import/ImportObjectsInputHandler.js +310 -0
  63. package/src/commands/object/import/ImportObjectsOutputHandler.js +114 -0
  64. package/src/commands/object/list/ListObjectsAction.js +62 -0
  65. package/src/commands/object/list/ListObjectsCommand.js +20 -0
  66. package/src/commands/object/list/ListObjectsInputHandler.js +148 -0
  67. package/src/commands/object/list/ListObjectsOutputHandler.js +29 -0
  68. package/src/commands/object/update/UpdateAction.js +138 -0
  69. package/src/commands/object/update/UpdateCommand.js +20 -0
  70. package/src/commands/object/update/UpdateInputHandler.js +170 -0
  71. package/src/commands/object/update/UpdateOutputHandler.js +61 -0
  72. package/src/commands/project/adddependencies/AddDependenciesAction.js +55 -0
  73. package/src/commands/project/adddependencies/AddDependenciesCommand.js +19 -0
  74. package/src/commands/project/adddependencies/AddDependenciesOutputHandler.js +114 -0
  75. package/src/commands/project/create/CreateProjectAction.js +370 -0
  76. package/src/commands/project/create/CreateProjectCommand.js +20 -0
  77. package/src/commands/project/create/CreateProjectInputHandler.js +169 -0
  78. package/src/commands/project/create/CreateProjectOutputHandler.js +36 -0
  79. package/src/commands/project/deploy/DeployAction.js +161 -0
  80. package/src/commands/project/deploy/DeployCommand.js +20 -0
  81. package/src/commands/project/deploy/DeployInputHandler.js +100 -0
  82. package/src/commands/project/deploy/DeployOutputHandler.js +49 -0
  83. package/src/commands/project/package/PackageAction.js +59 -0
  84. package/src/commands/project/package/PackageCommand.js +18 -0
  85. package/src/commands/project/package/PackageOutputHandler.js +18 -0
  86. package/src/commands/project/validate/ValidateAction.js +106 -0
  87. package/src/commands/project/validate/ValidateCommand.js +20 -0
  88. package/src/commands/project/validate/ValidateInputHandler.js +92 -0
  89. package/src/commands/project/validate/ValidateOutputHandler.js +74 -0
  90. package/src/core/CommandActionExecutor.js +347 -0
  91. package/src/core/CommandAuthentication.js +13 -0
  92. package/src/core/CommandOptionsValidator.js +42 -0
  93. package/src/core/CommandRegistrationService.js +130 -0
  94. package/src/core/CommandsMetadataService.js +104 -0
  95. package/src/core/extensibility/CLIConfigurationService.js +192 -0
  96. package/src/core/extensibility/CommandUserExtension.js +64 -0
  97. package/src/core/sdksetup/SdkDownloadService.js +109 -0
  98. package/src/core/sdksetup/SdkLicense.js +39 -0
  99. package/src/core/sdksetup/SdkProperties.js +51 -0
  100. package/src/loggers/ConsoleLogger.js +32 -0
  101. package/src/loggers/LoggerFontFormatter.mjs +17 -0
  102. package/src/loggers/LoggerOsConstants.js +12 -0
  103. package/src/loggers/NodeConsoleLogger.js +47 -0
  104. package/src/metadata/CommandGenerators.json +92 -0
  105. package/src/metadata/NodeCommandsMetadata.json +139 -0
  106. package/src/metadata/ObjectTypesMetadata.js +615 -0
  107. package/src/metadata/SdkCommandsMetadata.json +846 -0
  108. package/src/metadata/SdkCommandsMetadataPatch.json +130 -0
  109. package/src/metadata/SuiteScriptModulesMetadata.js +152 -0
  110. package/src/metadata/SuiteScriptTypesMetadata.js +64 -0
  111. package/src/services/AccountFileCabinetService.js +86 -0
  112. package/src/services/EnvironmentInformationService.js +31 -0
  113. package/src/services/ExecutionContextService.js +108 -0
  114. package/src/services/FileCabinetService.js +65 -0
  115. package/src/services/FileSystemService.js +245 -0
  116. package/src/services/NodeTranslationService.js +22 -0
  117. package/src/services/NpmInstallRunner.js +33 -0
  118. package/src/services/ProjectInfoService.js +209 -0
  119. package/src/services/SuiteCloudAuthProxyService.js +469 -0
  120. package/src/services/TranslationKeys.js +506 -0
  121. package/src/services/TranslationService.js +30 -0
  122. package/src/services/actionresult/ActionResult.js +129 -0
  123. package/src/services/actionresult/AuthenticateActionResult.js +85 -0
  124. package/src/services/actionresult/CreateProjectActionResult.js +100 -0
  125. package/src/services/actionresult/DeployActionResult.js +69 -0
  126. package/src/services/actionresult/HelloActionResult.js +13 -0
  127. package/src/services/actionresult/ManageAccountActionResult.js +70 -0
  128. package/src/services/settings/CLISettings.js +46 -0
  129. package/src/services/settings/CLISettingsService.js +132 -0
  130. package/src/suitecloud.js +33 -0
  131. package/src/templates/TemplateKeys.js +25 -0
  132. package/src/templates/objects/commerceextension.xml +9 -0
  133. package/src/templates/projectconfigs/default_gitignore.template +47 -0
  134. package/src/templates/projectconfigs/sdf.config.js +4 -0
  135. package/src/templates/projectconfigs/suitecloud.config.js +4 -0
  136. package/src/templates/scripts/blankscript.js +3 -0
  137. package/src/templates/unittest/jest.config.js.template +7 -0
  138. package/src/templates/unittest/jsconfig.json.template +5 -0
  139. package/src/templates/unittest/package.json.template +12 -0
  140. package/src/templates/unittest/sample-test.js.template +37 -0
  141. package/src/templates/unittest/suitecloud.config.js.template +15 -0
  142. package/src/ui/CliSpinner.js +34 -0
  143. package/src/utils/AccountCredentialsFormatter.js +62 -0
  144. package/src/utils/AccountSpecificValuesUtils.js +55 -0
  145. package/src/utils/ActionResultUtils.js +47 -0
  146. package/src/utils/ApplyInstallationPreferencesUtils.js +41 -0
  147. package/src/utils/AuthenticationUtils.js +262 -0
  148. package/src/utils/CommandUtils.js +50 -0
  149. package/src/utils/CryptoUtils.js +41 -0
  150. package/src/utils/ExceptionUtils.js +33 -0
  151. package/src/utils/FileUtils.js +43 -0
  152. package/src/utils/SdkOperationResult.js +68 -0
  153. package/src/utils/SdkOperationResultUtils.js +20 -0
  154. package/src/utils/ValidationErrorsFormatter.js +23 -0
  155. package/src/utils/http/HttpConstants.js +39 -0
  156. package/src/utils/http/ProxyAgent.js +110 -0
  157. package/src/validation/InteractiveAnswersValidator.js +205 -0
@@ -0,0 +1,469 @@
1
+ /*
2
+ ** Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved.
3
+ ** Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
4
+ */
5
+ 'use strict';
6
+
7
+ /** http libraries */
8
+ const http = require('node:http');
9
+ const https = require('node:https');
10
+ const EventEmitter = require('events');
11
+
12
+ /** Events */
13
+ const EVENTS = {
14
+ PROXY_ERROR: {
15
+ DEFAULT: 'proxyError',
16
+ MANUAL_AUTH_REFRESH_REQUIRED: 'manualAuthRefreshRequired'
17
+ },
18
+ REQUEST_ERROR: {
19
+ PATH_NOT_ALLOWED: 'requestPathNotAllowed',
20
+ UNAUTHORIZED: 'unauthorizedProxyRequest'
21
+ },
22
+ SERVER_ERROR: {
23
+ DEFAULT: 'serverError',
24
+ ON_AUTH_REFRESH: 'serverErrorOnRefresh',
25
+ },
26
+ }
27
+
28
+ /** Authentication methods */
29
+ const {
30
+ getAuthIds,
31
+ checkIfReauthorizationIsNeeded,
32
+ forceRefreshAuthorization,
33
+ } = require('../utils/AuthenticationUtils');
34
+ const {
35
+ AUTHORIZATION_PROPERTIES_KEYS,
36
+ HTTP_RESPONSE_CODE,
37
+ } = require('../ApplicationConstants');
38
+
39
+ /** Message literal service method */
40
+ const NodeTranslationService = require('./NodeTranslationService');
41
+ const {
42
+ SUITECLOUD_AUTH_PROXY_SERVICE,
43
+ } = require('./TranslationKeys');
44
+
45
+ const MAX_RETRY_ATTEMPTS = 1;
46
+ const LOCAL_HOSTNAME = '127.0.0.1';
47
+ const DEVASSIST_CHAT_COMPLETIONS_PATH = '/api/internal/devassist/chat/completions'
48
+
49
+ /** Target server port */
50
+ const TARGET_SERVER_PORT = 443;
51
+
52
+ class SuiteCloudAuthProxyService extends EventEmitter {
53
+
54
+ constructor(sdkPath, executionEnvironmentContext, allowedPathPrefix, apiKey) {
55
+ super();
56
+ this._sdkPath = sdkPath;
57
+ this._executionEnvironmentContext = executionEnvironmentContext;
58
+ this._allowedPathPrefix = allowedPathPrefix;
59
+ this._apiKey = apiKey;
60
+ /** These are the variables we are going to use to store instance data */
61
+ this._accessToken = undefined;
62
+ this._localProxy = undefined;
63
+ this._targetHost = undefined;
64
+ this._authId = undefined;
65
+ }
66
+
67
+ /**
68
+ * starts the listener.
69
+ * It can return an error, for instance when it cannot connect to the auth server or the parameters being incorrect
70
+ * @public
71
+ * @param authId
72
+ * @param proxyPort
73
+ * @returns {Promise<void>}
74
+ */
75
+ async start(authId, proxyPort) {
76
+
77
+ //Parameters validation
78
+ this._evalInputParameters(authId, proxyPort);
79
+ this._authId = authId;
80
+
81
+ //Retrieve from authId accessToken and target host
82
+ const { accessToken, hostName } = await this._retrieveCredentials();
83
+ this._targetHost = hostName;
84
+ this._accessToken = accessToken;
85
+
86
+ await this.stop();
87
+ this._localProxy = http.createServer();
88
+
89
+ this._localProxy.addListener('request', async (request, response) => {
90
+
91
+ // Validate incoming request (Api Key & Request Path)
92
+ if (!this._isValidAndFilterIncomingRequest(request, response)) {
93
+ return
94
+ }
95
+
96
+ const requestOptions = this._buildRequestOptions(request);
97
+
98
+ // Save body
99
+ const bodyChunks = [];
100
+ request.on('data', function (chunk) {
101
+ bodyChunks.push(chunk);
102
+ });
103
+
104
+ request.on('end', async () => {
105
+ const body = Buffer.concat(bodyChunks);
106
+ const proxyRequest = await this._createProxyRequest(requestOptions, body, response, 0);
107
+ proxyRequest.write(body);
108
+ proxyRequest.end();
109
+ });
110
+ });
111
+
112
+ this._localProxy.listen(proxyPort, LOCAL_HOSTNAME, () => {
113
+ const localURL = `http://${LOCAL_HOSTNAME}:${proxyPort}`;
114
+ console.log(`SuiteCloud Proxy server listening on ${localURL}`);
115
+ });
116
+
117
+ this._localProxy.on('error', (error) => {
118
+ const errorMessage = (error.code === 'EADDRINUSE') ?
119
+ NodeTranslationService.getMessage(SUITECLOUD_AUTH_PROXY_SERVICE.ALREADY_USED_PORT, proxyPort, error.message ?? '')
120
+ : NodeTranslationService.getMessage(SUITECLOUD_AUTH_PROXY_SERVICE.INTERNAL_PROXY_SERVER_ERROR, proxyPort, error.message ?? '');
121
+ this._emitEventWithData(EVENTS.PROXY_ERROR.DEFAULT, errorMessage);
122
+ });
123
+ }
124
+
125
+ /**
126
+ * Public method that stops the proxy and returns a Promise resolved when it's fully closed
127
+ * @public
128
+ */
129
+ async stop() {
130
+ // when having a "listen EADDRINUSE: address already in use 127.0.0.1:49285" the server instance exists but is not listening
131
+ // to avoid "Error [ERR_SERVER_NOT_RUNNING]: Server is not running." at close time there is the need to check if server is activelly listening
132
+ if (this._localProxy && this._localProxy.listening) {
133
+ // Wrap the close callback in a Promise
134
+ const closePromise = new Promise((resolve, reject) => {
135
+ this._localProxy.close(error => {
136
+ if (error) {
137
+ console.error('Error occurred while stopping SuiteCloud Auth Proxy server.')
138
+ console.error(error)
139
+ reject(error);
140
+ } else {
141
+ console.log('SuiteCloud Auth Proxy server stopped.');
142
+ resolve();
143
+ }
144
+ });
145
+ });
146
+
147
+ await closePromise;
148
+ } else {
149
+ console.log('No server instance to stop.');
150
+ }
151
+
152
+ this._localProxy = null;
153
+ }
154
+
155
+ /**
156
+ * Updates the stored API key, which is used to authenticate and filter incoming requests to the local server.
157
+ *
158
+ * @public
159
+ * @param {string} newApiKey - The new API key to set for request filtering.
160
+ * @returns {void}
161
+ */
162
+ updateApiKey(newApiKey) {
163
+ this._apiKey = newApiKey;
164
+ }
165
+
166
+
167
+ /**
168
+ * For being used after manual authentication. It refreshes the access token from credentials.
169
+ * @public
170
+ * @returns {Promise<void>}
171
+ */
172
+ async reloadAccessToken() {
173
+ const { accessToken } = await this._retrieveCredentials();
174
+ this._accessToken = accessToken;
175
+ console.log('access token refreshed');
176
+ }
177
+
178
+ /**
179
+ * Emits an event with a structured data object.
180
+ *
181
+ * @param {string} eventName - The name of the event to emit.
182
+ * @param {string} errorMessage - The error message to include in the data object.
183
+ * @param {string} [requestUrl] - (Optional) The URL associated with the event, if applicable.
184
+ * @returns {void}
185
+ * @private
186
+ */
187
+ _emitEventWithData(eventName, errorMessage, requestUrl) {
188
+ const emitData = {
189
+ authId: this._authId,
190
+ message: errorMessage,
191
+ ...(requestUrl && { requestUrl })
192
+ }
193
+ console.error({ eventName, emitData });
194
+ this.emit(eventName, emitData);
195
+ }
196
+
197
+ /**
198
+ * It validates the input parameters
199
+ * @param authId
200
+ * @param proxyPort
201
+ * @private
202
+ */
203
+ _evalInputParameters(authId, proxyPort) {
204
+ if (!authId) {
205
+ throw NodeTranslationService.getMessage(SUITECLOUD_AUTH_PROXY_SERVICE.MISSING_AUTH_ID);
206
+ }
207
+
208
+ if (!proxyPort) {
209
+ throw NodeTranslationService.getMessage(SUITECLOUD_AUTH_PROXY_SERVICE.MISSING_PORT);
210
+ }
211
+
212
+ if (isNaN(proxyPort)) {
213
+ throw NodeTranslationService.getMessage(SUITECLOUD_AUTH_PROXY_SERVICE.PORT_MUST_BE_NUMBER);
214
+ }
215
+ }
216
+
217
+ /**
218
+ * This method retrieves the credentials and returns the hostname and the accessToken
219
+ * @returns {Promise<{hostName: string, accessToken: string}>}
220
+ * @private
221
+ */
222
+ async _retrieveCredentials() {
223
+ const authIDActionResult = await getAuthIds(this._sdkPath);
224
+
225
+ if (!authIDActionResult.isSuccess()) {
226
+ throw authIDActionResult.errorMessages;
227
+ }
228
+
229
+ if (!authIDActionResult.data.hasOwnProperty(this._authId)) {
230
+ throw NodeTranslationService.getMessage(SUITECLOUD_AUTH_PROXY_SERVICE.NOT_EXISTING_AUTH_ID, this._authId);
231
+ }
232
+ return {
233
+ accessToken: authIDActionResult.data[this._authId].token.accessToken,
234
+ hostName: authIDActionResult.data[this._authId].hostInfo.hostName,
235
+ };
236
+ }
237
+
238
+ /**
239
+ * Validates an incoming proxy request for apiKey & allowed path.
240
+ * Responds and emits the correct events on failure.
241
+ * @param {http.IncomingMessage} request
242
+ * @param {http.ServerResponse} response
243
+ * @returns {boolean} true if valid, false if rejected
244
+ */
245
+ _isValidAndFilterIncomingRequest(request, response) {
246
+ const requestValidationFunctions = [
247
+ this._checkAuthenticationHeader.bind(this),
248
+ this._checkRequestPath.bind(this)
249
+ ];
250
+
251
+ // check all validations are true or stops in the first failing one and returns false
252
+ return requestValidationFunctions.every((validateFunction => validateFunction(request, response)));
253
+ }
254
+
255
+ /**
256
+ * Authentication filter: check authorization header if an API key is configured.
257
+ * Manages response and emit event on failure
258
+ */
259
+ _checkAuthenticationHeader(request, response) {
260
+ if (this._apiKey) {
261
+ const authHeader = request.headers['authorization'];
262
+ if (authHeader !== `Bearer ${this._apiKey}`) {
263
+ const unauthorizedMessage = NodeTranslationService.getMessage(SUITECLOUD_AUTH_PROXY_SERVICE.UNAUTHORIZED_PROXY_REQUEST);
264
+ // using 401-Unauthorized http response code as CLINE won't activate the retry mechanism with it
265
+ // not using 407-Proxy Authentication Required as CLINE activates the retry mechanism with it
266
+ this._writeResponseMessage(response, HTTP_RESPONSE_CODE.UNAUTHORIZED, unauthorizedMessage);
267
+ this._emitEventWithData(EVENTS.REQUEST_ERROR.UNAUTHORIZED, unauthorizedMessage, request.url)
268
+ return false;
269
+ }
270
+ }
271
+ return true;
272
+ }
273
+
274
+ /**
275
+ * Allowed path filter: check allowed prefix if configured.
276
+ * Manages response and emit event on failure
277
+ */
278
+ _checkRequestPath(request, response) {
279
+ // Allowed path filter: check allowed prefix if configured
280
+ if (this._allowedPathPrefix && !request.url.startsWith(this._allowedPathPrefix)) {
281
+ const errorMessage = NodeTranslationService.
282
+ getMessage(SUITECLOUD_AUTH_PROXY_SERVICE.REQUEST_PATH_NOT_ALLOWED_ERROR, this._allowedPathPrefix);
283
+ this._writeResponseMessage(response, HTTP_RESPONSE_CODE.FORBIDDEN, errorMessage);
284
+ this._emitEventWithData(EVENTS.REQUEST_ERROR.PATH_NOT_ALLOWED, errorMessage);
285
+ return false;
286
+ }
287
+ return true;
288
+ }
289
+
290
+ /**
291
+ * Builds request options
292
+ * @param request request
293
+ * @returns {{path: *, headers: *&{authorization: string}, hostname: *, method: *, port: number}}
294
+ * @private
295
+ */
296
+ _buildRequestOptions(request) {
297
+ const authorization = 'Bearer ' + this._accessToken;
298
+ const host = this._targetHost;
299
+
300
+ const requestOptions = {
301
+ hostname: this._targetHost,
302
+ port: TARGET_SERVER_PORT,
303
+ path: request.url,
304
+ method: request.method,
305
+ headers: { ...request.headers, host, authorization },
306
+ };
307
+
308
+ // added to get "stream responses" from netsuite devassist backend
309
+ if (request?.url.startsWith(DEVASSIST_CHAT_COMPLETIONS_PATH)) {
310
+ requestOptions.headers = {
311
+ ...requestOptions.headers,
312
+ Accept: 'text/event-stream'
313
+ };
314
+ }
315
+
316
+ // Add agent for insecure connections when connecting to runboxes
317
+ if (this._targetHost && this._targetHost.includes('vm.eng')) {
318
+ requestOptions.agent = new https.Agent({
319
+ rejectUnauthorized: false,
320
+ });
321
+ requestOptions.rejectUnauthorized = false;
322
+ }
323
+ return requestOptions;
324
+ }
325
+
326
+ async _createProxyRequest(requestOptions, body, response, attempts) {
327
+ const proxyRequest = https.request(requestOptions, async (proxyResponse) => {
328
+ if (proxyResponse.statusCode === HTTP_RESPONSE_CODE.UNAUTHORIZED && attempts <= MAX_RETRY_ATTEMPTS) {
329
+ proxyResponse.resume();
330
+ const refreshOperationResult = await this._tryRefreshOperation();
331
+
332
+ if (refreshOperationResult.accessTokenHasBeenUpdated) {
333
+ this._updateRequestAuthorizationHeader(requestOptions);
334
+ const newProxyRequest = await this._createProxyRequest(requestOptions, body, response, attempts + 1);
335
+ newProxyRequest.write(body);
336
+ newProxyRequest.end();
337
+ } else {
338
+ this._emitEventWithData(refreshOperationResult.emitEventName, refreshOperationResult.errorMessage);
339
+ //Message shown to cline
340
+ this._writeResponseMessage(response, refreshOperationResult.responseStatusCode, refreshOperationResult.errorMessage);
341
+ proxyResponse.pipe(response, { end: true });
342
+ }
343
+ } else {
344
+ response.writeHead(proxyResponse.statusCode || HTTP_RESPONSE_CODE.INTERNAL_SERVER_ERROR, proxyResponse.headers);
345
+ proxyResponse.pipe(response, { end: true });
346
+ }
347
+
348
+ });
349
+
350
+ proxyRequest.on('error', (error) => {
351
+ console.error('Proxy request error:', error);
352
+ this._emitEventWithData(EVENTS.SERVER_ERROR.DEFAULT, error.message)
353
+ response.writeHead(HTTP_RESPONSE_CODE.INTERNAL_SERVER_ERROR);
354
+ //TODO Review this message and see confluence error pages and review with the tech writers
355
+ response.end('SuiteCloud Proxy error: ' + error.message);
356
+ });
357
+ return proxyRequest;
358
+ }
359
+
360
+ /**
361
+ * This method refreshes authorization.
362
+ * If successful returns true and updates this._accessToken
363
+ * If not successful returns false and returns the http response and the info to generate the emit message.
364
+ * @returns {Promise<*>}
365
+ * @private
366
+ */
367
+ async _tryRefreshOperation() {
368
+ const refreshInfo = {
369
+ accessTokenHasBeenUpdated: false,//Whether the token has been updated or not.
370
+ emitEventName: undefined, //Event to be thrown.
371
+ responseStatusCode: undefined, //HTTP response status code.
372
+ errorMessage: undefined //Error message, used both for emit data and http response.
373
+ };
374
+
375
+ const inspectAuthOperationResult = await checkIfReauthorizationIsNeeded(this._authId, this._sdkPath, this._executionEnvironmentContext);
376
+
377
+ //Not being able to execute the reauth if needed, it can be vpn disconnected, network problems...
378
+ if (!inspectAuthOperationResult.isSuccess()) {
379
+ const errorMsg = this._cleanText(inspectAuthOperationResult.errorMessages.join('. '));
380
+
381
+ refreshInfo.emitEventName = EVENTS.SERVER_ERROR.ON_AUTH_REFRESH;
382
+ refreshInfo.errorMessage = errorMsg;
383
+ refreshInfo.responseStatusCode = HTTP_RESPONSE_CODE.FORBIDDEN;
384
+
385
+ return Object.freeze(refreshInfo);
386
+ }
387
+
388
+ //Needs manual reauthorization
389
+ const inspectAuthData = inspectAuthOperationResult.data;
390
+ if (inspectAuthData[AUTHORIZATION_PROPERTIES_KEYS.NEEDS_REAUTHORIZATION]) {
391
+ const errorMsg = NodeTranslationService.getMessage(SUITECLOUD_AUTH_PROXY_SERVICE.NEED_TO_REAUTHENTICATE);
392
+
393
+ refreshInfo.emitEventName = EVENTS.PROXY_ERROR.MANUAL_AUTH_REFRESH_REQUIRED;
394
+ refreshInfo.errorMessage = errorMsg;
395
+ refreshInfo.responseStatusCode = HTTP_RESPONSE_CODE.FORBIDDEN;
396
+
397
+ return Object.freeze(refreshInfo);
398
+ }
399
+
400
+ //force refresh
401
+ const forceRefreshOperationResult = await forceRefreshAuthorization(this._authId, this._sdkPath, this._executionEnvironmentContext);
402
+ if (!forceRefreshOperationResult.isSuccess()) {
403
+ //Refresh unsuccessful
404
+ const errorMsg = this._cleanText(forceRefreshOperationResult.errorMessages.join('. '));
405
+
406
+ refreshInfo.emitEventName = EVENTS.PROXY_ERROR.MANUAL_AUTH_REFRESH_REQUIRED;
407
+ refreshInfo.errorMessage = errorMsg;
408
+ refreshInfo.responseStatusCode = HTTP_RESPONSE_CODE.FORBIDDEN;
409
+
410
+ return Object.freeze(refreshInfo);
411
+ } else {
412
+ //If the refresh has been successful
413
+ //Return operation forceRefreshOperationResult as true and update the accessToken
414
+ refreshInfo.accessTokenHasBeenUpdated = true;
415
+ this._accessToken = forceRefreshOperationResult.data.accessToken;
416
+
417
+ return Object.freeze(refreshInfo);
418
+ }
419
+ }
420
+
421
+ /**
422
+ * Method to clear output messages.
423
+ * The reason for this is the output do not show properly \n and \r
424
+ * So they are replaced by . and made some extra adjustments.
425
+ * @param input
426
+ * @returns {*}
427
+ */
428
+ _cleanText(input) {
429
+ let result = input.replace(/\r/g, ''); // Remove \r
430
+ result = result.replace(/\n/g, '. '); // Replace \n with ". "
431
+ result = result.replace(/,\./g, '.'); // Replace ",." with "."
432
+ result = result.replace(/"/g, '\''); //Replace \" by ' since \" also show problems.
433
+ while (result.includes(' ')) { // Replace double spaces with single space
434
+ result = result.replace(/ /g, ' ');
435
+ }
436
+ while (result.includes('..')) { // Replace ".." with "."
437
+ result = result.replace(/\.\./g, '.');
438
+ }
439
+ return result;
440
+ }
441
+
442
+ /**
443
+ * Update requestOptions access token without need to rebuild the requestOptions
444
+ * @param requestOptions
445
+ * @private
446
+ */
447
+ _updateRequestAuthorizationHeader(requestOptions) {
448
+ if (requestOptions.headers && requestOptions.headers.authorization) {
449
+ requestOptions.headers.authorization = 'Bearer ' + this._accessToken;
450
+ }
451
+ }
452
+
453
+
454
+ /**
455
+ * Write JSON response message
456
+ * @param response
457
+ * @param responseCode
458
+ * @param responseMessage
459
+ * @private
460
+ */
461
+ _writeResponseMessage(response, responseCode, responseMessage) {
462
+ response.writeHead(responseCode, { 'Content-Type': 'application/json' });
463
+ const message = { error: responseMessage };
464
+ response.end(JSON.stringify(message));
465
+ }
466
+
467
+ }
468
+
469
+ module.exports = { SuiteCloudAuthProxyService, EVENTS };