@sap/cli-core 2024.8.0 → 2024.12.0

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 (46) hide show
  1. package/CHANGELOG.md +95 -0
  2. package/cache/secrets/SecretsStorageImpl.d.ts +2 -0
  3. package/cache/secrets/SecretsStorageImpl.js +37 -12
  4. package/cache/secrets/utils.d.ts +2 -0
  5. package/cache/secrets/utils.js +9 -1
  6. package/commands/config.command/cache.command/clean.command.js +16 -3
  7. package/commands/config.command/index.js +25 -2
  8. package/commands/config.command/secrets.command/index.js +2 -1
  9. package/commands/config.command/secrets.command/refresh.command.d.ts +3 -0
  10. package/commands/config.command/secrets.command/refresh.command.js +13 -0
  11. package/commands/handler/authentication/oauth/tokenProvider/getToken.js +1 -0
  12. package/commands/handler/authentication/oauth/tokenProvider/utils.js +66 -58
  13. package/commands/handler/authentication/oauth/utils.js +8 -0
  14. package/commands/handler/authentication/technicalJWT/index.js +1 -1
  15. package/commands/handler/authentication/technicalJWT/utils.js +1 -1
  16. package/commands/handler/fetch/utils.d.ts +6 -1
  17. package/commands/handler/fetch/utils.js +60 -8
  18. package/commands/handler/input/file.js +4 -5
  19. package/commands/handler/input/input.js +5 -7
  20. package/commands/handler/next.js +1 -1
  21. package/commands/handler/options/utils.js +1 -1
  22. package/commands/handler/or.js +1 -1
  23. package/commands/handler/root/index.d.ts +2 -2
  24. package/commands/handler/root/index.js +22 -2
  25. package/commands/login.command.js +1 -1
  26. package/commands/openAPI.command/index.js +3 -2
  27. package/commands/openAPI.command/utils.d.ts +2 -1
  28. package/commands/openAPI.command/utils.js +22 -9
  29. package/constants.d.ts +2 -1
  30. package/constants.js +3 -24
  31. package/discovery/index.d.ts +1 -1
  32. package/discovery/index.js +7 -3
  33. package/dwc/dwc.js +5 -5
  34. package/dwc/run.js +1 -1
  35. package/dwc/utils.d.ts +1 -1
  36. package/dwc/utils.js +4 -4
  37. package/index.d.ts +3 -3
  38. package/package.json +5 -5
  39. package/schemas/discovery.json +13 -1
  40. package/types.d.ts +21 -1
  41. package/types.js +21 -1
  42. package/utils/commands.d.ts +4 -4
  43. package/utils/commands.js +64 -29
  44. package/utils/http/index.js +5 -5
  45. package/utils/utils.d.ts +2 -1
  46. package/utils/utils.js +2 -2
package/CHANGELOG.md CHANGED
@@ -5,6 +5,101 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## 2024.11.0
9
+
10
+ ### Added
11
+
12
+ - Support for deprecated commands. If a command is to be deprecated, this can be specified in the discovery by setting `deprecated: true` for a given operation object. Also, you can use the following custom properties to provide information about the final version after which the command is removed, a new command to be used, and a link to the respective SAP Help page. Example:
13
+
14
+ ```json
15
+ {
16
+ "paths": {
17
+ "/your/api/path": {
18
+ "get": {
19
+ "operationId": "command",
20
+ "deprecated": true,
21
+ "x-deprecated-with-wave": "2024.11",
22
+ "x-decommissioned-after-wave": "2024.12",
23
+ "x-deprecation-new-command": "new command",
24
+ "x-deprecation-sap-help-url": "https://www.help.sap.com/some/help/page"
25
+ }
26
+ }
27
+ }
28
+ }
29
+ ```
30
+
31
+ When a user runs a deprecated command, a warning is shown:
32
+
33
+ ```bash
34
+ WARNING: The command 'command' is deprecated from wave 2024.11 onwards. It will be removed after version 2024.11. Please start using the new command 'new command'. See https://www.help.sap.com/some/help/page for more information.
35
+ ```
36
+
37
+ - Support for deprecated properties. If a property is deprecated but still used by a client, the server can include the custom response header `x-dsp-api-deprecated-properties` with the following `JSON.stringify`d value:
38
+
39
+ ```json
40
+ [
41
+ {
42
+ "name": "deprecated property name",
43
+ "deprecatedWithWave": "2024.11",
44
+ "decommissionedAfterWave": "2024.12",
45
+ "sapHelpUrl": "https://www.help.sap.com/some/help/page",
46
+ "customMessage": "some custom message."
47
+ },
48
+ {
49
+ ...
50
+ }
51
+ ]
52
+ ```
53
+
54
+ All properties are optional except the `name`. When a user sends a deprecated property to the server and the server includes the header `x-dsp-api-deprecated-properties` in the response, the following warning is shown to the user:
55
+
56
+ ```bash
57
+ WARNING: the following properties are deprecated:
58
+ deprecated property name. Deprecated since version 2024.11. Decommissioned after version 2024.12. See the SAP Help documentation at https://www.help.sap.com/some/help/page for more information. some custom message.
59
+ ```
60
+
61
+ ### Fixed
62
+
63
+ - No short flag could be calculated for an option even though for the command owning the option not all short flags were used.
64
+
65
+ ## 2024.10.0
66
+
67
+ ### Added
68
+
69
+ - Added a new command `<cli> config secrets refresh` which can be used to refresh the access token of the host defined via option `--host` or set as the global host via `<cli> config host set <host>`.
70
+
71
+ - Added option `--purge-all` to command `<cli> config cache clean` to enable clients to remove the whole cache if required.
72
+
73
+ ### Fixed
74
+
75
+ - In case the login failed, the CLI would still store the inconsistent secret information to the CLI cache. This caused issues as following login attempts failed without a good reason. With this version, whenever a login fails, the CLI does not store inconsistent secrets to enable the user to run additional login attempts successfully. Also, the CLI prints a verbose message when the `--verbose` option is added in case an expired access token cannot be refreshed.
76
+
77
+ - The CLI used the same short flag for different options. For example, the short flag `-f` was used twice for the two different options `--force-definition-deployment` and `--file-path`:
78
+
79
+ ```bash
80
+ Options:
81
+ -f, --force-definition-deployment force redeployment of definitions (optional)
82
+ -n, --no-async do not run deployment asynchronously (optional)
83
+ -e, --enforce-database-user-deletion to allow deletion of Database users (optional)
84
+ -f, --file-path <path> specifies the file to use as input for the command (optional)
85
+ -i, --input <input> specifies input as string to use for the command (optional)
86
+ -h, --help display help for command
87
+ ```
88
+
89
+ With this version the CLI ensures that a short flag is only used once.
90
+
91
+ ```bash
92
+ Options:
93
+ -f, --force-definition-deployment force redeployment of definitions (optional)
94
+ -n, --no-async do not run deployment asynchronously (optional)
95
+ -e, --enforce-database-user-deletion to allow deletion of Database users (optional)
96
+ -F, --file-path <path> specifies the file to use as input for the command (optional)
97
+ -i, --input <input> specifies input as string to use for the command (optional)
98
+ -h, --help display help for command
99
+ ```
100
+
101
+ - When using an access token from another tenant, where the user the access token belongs to is not existing in the target tenant, the CLI now prints a warning saying that the used access token is invalid. The login credentials have to be refreshed in this case and a valid access token must be used.
102
+
8
103
  ## 2024.8.0
9
104
 
10
105
  ### Fixed
@@ -17,6 +17,8 @@ export declare class SecretsStorageImpl implements SecretsStorage {
17
17
  private updateUrls;
18
18
  storeSecret(secret: Omit<Secret, "id" | "host" | "customClient">): Promise<void>;
19
19
  deleteSecretById(id: number): void;
20
+ private removeInconsistentSecrets;
21
+ private removeUnknownPropertiesFromSecrets;
20
22
  synchronizeSecretsToStorage(): Promise<void>;
21
23
  getAllSecrets(): ReadonlyArray<Secret>;
22
24
  }
@@ -4,10 +4,12 @@ exports.SecretsStorageImpl = void 0;
4
4
  const config_1 = require("../../config");
5
5
  const constants_1 = require("../../constants");
6
6
  const logger_1 = require("../../logger");
7
+ const utils_1 = require("../../logger/utils");
8
+ const types_1 = require("../../types");
7
9
  const http_1 = require("../../utils/http");
8
- const utils_1 = require("../../utils/utils");
10
+ const utils_2 = require("../../utils/utils");
9
11
  const cache_1 = require("../cache");
10
- const utils_2 = require("./utils");
12
+ const utils_3 = require("./utils");
11
13
  class SecretsStorageImpl {
12
14
  secrets = [];
13
15
  logger;
@@ -21,14 +23,14 @@ class SecretsStorageImpl {
21
23
  async recreateCalculatedValues() {
22
24
  for (let i = 0; i < this.secrets.length; i++) {
23
25
  this.secrets[i].id = i;
24
- if (!this.secrets[i].client_id) {
26
+ if (this.secrets[i].client_id) {
27
+ this.secrets[i].customClient = (0, utils_3.isCustomClient)(this.secrets[i].client_id);
28
+ // eslint-disable-next-line no-await-in-loop
29
+ await this.updateUrls(i);
30
+ }
31
+ else {
25
32
  this.logger.warn(`client id is not set for secret with ID ${i}, skipping update of secret information`);
26
- // eslint-disable-next-line no-continue
27
- continue;
28
33
  }
29
- this.secrets[i].customClient = (0, utils_2.isCustomClient)(this.secrets[i].client_id);
30
- // eslint-disable-next-line no-await-in-loop
31
- await this.updateUrls(i);
32
34
  }
33
35
  }
34
36
  async initializeStorage() {
@@ -38,9 +40,11 @@ class SecretsStorageImpl {
38
40
  this.logger.warn("secrets data is no array, initializing secrets to empty array");
39
41
  this.secrets = [];
40
42
  }
43
+ this.removeInconsistentSecrets();
41
44
  await this.recreateCalculatedValues();
42
45
  }
43
46
  catch (err) {
47
+ (0, utils_1.logVerbose)(this.logger, "The secrets file could not be read.");
44
48
  this.logger.warn("failed to read or parse secrets file", err);
45
49
  }
46
50
  }
@@ -48,7 +52,7 @@ class SecretsStorageImpl {
48
52
  return this.getSecretById(await this.getDefaultSecretId());
49
53
  }
50
54
  async getDefaultSecretId() {
51
- const tenantUrl = (0, utils_2.getTenantUrl)();
55
+ const tenantUrl = (0, utils_3.getTenantUrl)();
52
56
  const { host } = new URL(tenantUrl);
53
57
  const secret = this.secrets.find((s) => new URL(s.tenantUrl).host === host);
54
58
  if (!secret) {
@@ -72,7 +76,7 @@ class SecretsStorageImpl {
72
76
  this.secrets.splice(id, 1);
73
77
  }
74
78
  updateSecret(secret) {
75
- const tenantUrl = (0, utils_2.getTenantUrl)();
79
+ const tenantUrl = (0, utils_3.getTenantUrl)();
76
80
  let prevSecretIx = this.secrets.findIndex((s) => s.tenantUrl === tenantUrl);
77
81
  let newSecret;
78
82
  if (prevSecretIx > -1) {
@@ -125,8 +129,8 @@ class SecretsStorageImpl {
125
129
  throw new Error("invalid token url or authorization url");
126
130
  }
127
131
  this.secrets[secretIndex].authorization_url =
128
- (0, utils_1.removeQueryParametersFromUrl)(authorizationUrl);
129
- this.secrets[secretIndex].token_url = (0, utils_1.removeQueryParametersFromUrl)(tokenUrl);
132
+ (0, utils_2.removeQueryParametersFromUrl)(authorizationUrl);
133
+ this.secrets[secretIndex].token_url = (0, utils_2.removeQueryParametersFromUrl)(tokenUrl);
130
134
  }
131
135
  }
132
136
  async storeSecret(secret) {
@@ -137,8 +141,29 @@ class SecretsStorageImpl {
137
141
  this.throwIfSecretsDoesntExist(id);
138
142
  this.removeSecretsFromArray(id);
139
143
  }
144
+ removeInconsistentSecrets() {
145
+ this.secrets = this.secrets.filter((secret) => {
146
+ if (!(0, utils_3.isSecretConsistent)(secret)) {
147
+ this.logger.warn(`secret ${JSON.stringify(secret)} is not consistent and removed from cache`);
148
+ return false;
149
+ }
150
+ return true;
151
+ });
152
+ }
153
+ removeUnknownPropertiesFromSecrets() {
154
+ this.secrets.forEach((secret) => {
155
+ Object.keys(secret).forEach((key) => {
156
+ if (!types_1.ALLOWED_SECRET_TYPES.includes(key)) {
157
+ // eslint-disable-next-line no-param-reassign
158
+ delete secret[key];
159
+ }
160
+ });
161
+ });
162
+ }
140
163
  async synchronizeSecretsToStorage() {
141
164
  try {
165
+ this.removeInconsistentSecrets();
166
+ this.removeUnknownPropertiesFromSecrets();
142
167
  await (0, cache_1.writeFile)(constants_1.CACHE_SECRETS_FILE, JSON.stringify(this.secrets));
143
168
  }
144
169
  catch (err) {
@@ -1,2 +1,4 @@
1
+ import { Secret } from "../../types";
1
2
  export declare const isCustomClient: (clientId?: string) => boolean;
2
3
  export declare const getTenantUrl: () => string;
4
+ export declare function isSecretConsistent(secret: Secret): boolean;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getTenantUrl = exports.isCustomClient = void 0;
3
+ exports.isSecretConsistent = exports.getTenantUrl = exports.isCustomClient = void 0;
4
4
  const config_1 = require("../../config");
5
5
  const constants_1 = require("../../constants");
6
6
  // Pre-delivered OAuth Client ID: 5a638330-5899-366e-ac00-ab62cc32dcda
@@ -16,3 +16,11 @@ const getTenantUrl = () => {
16
16
  return tenantUrl;
17
17
  };
18
18
  exports.getTenantUrl = getTenantUrl;
19
+ function isSecretConsistent(secret) {
20
+ // all these properties must exist
21
+ return ((!!secret.tenantUrl ||
22
+ (!!secret.authorization_url && !!secret.token_url)) &&
23
+ (!!secret.access_token ||
24
+ (!!secret.refresh_token && !!secret.client_id && !!secret.client_secret)));
25
+ }
26
+ exports.isSecretConsistent = isSecretConsistent;
@@ -2,14 +2,27 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const cache_1 = require("../../../cache");
4
4
  const constants_1 = require("../../../constants");
5
+ const options_1 = require("../../../utils/options");
6
+ const handler_1 = require("../../handler");
7
+ const OPTION_PURGE_ALL = {
8
+ longName: "purge-all",
9
+ description: "clean the whole local CLI cache",
10
+ };
5
11
  const cleanHandler = async () => async () => {
6
- const files = (await (0, cache_1.getFiles)()).filter((file) => file.startsWith(constants_1.DISCOVERY_DOCUMENT_PREFIX));
7
- await Promise.all(files.map(async (file) => (0, cache_1.deleteFile)(file)));
12
+ const purgeAll = (0, options_1.getOptionValueFromConfigGracefully)(OPTION_PURGE_ALL);
13
+ if (purgeAll) {
14
+ await (0, cache_1.cleanCache)();
15
+ }
16
+ else {
17
+ const files = (await (0, cache_1.getFiles)()).filter((file) => file.startsWith(constants_1.DISCOVERY_DOCUMENT_PREFIX));
18
+ await Promise.all(files.map(async (file) => (0, cache_1.deleteFile)(file)));
19
+ }
8
20
  };
9
21
  const cleanCommand = {
10
22
  type: "command",
11
23
  command: "clean",
12
24
  description: "clean the local CLI cache",
13
- handler: cleanHandler,
25
+ handler: (0, handler_1.createNextHandler)("command.config.cache.clean", (0, handler_1.createParseArgumentsHandler)(), cleanHandler),
26
+ options: [OPTION_PURGE_ALL],
14
27
  };
15
28
  exports.default = cleanCommand;
@@ -1,14 +1,37 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
2
25
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
26
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
27
  };
5
28
  Object.defineProperty(exports, "__esModule", { value: true });
6
29
  exports.addCommands = void 0;
7
30
  const path_1 = __importDefault(require("path"));
8
- const commands_1 = require("../../utils/commands");
31
+ const commands = __importStar(require("../../utils/commands"));
9
32
  const utils_1 = require("../../dwc/utils");
10
33
  const addCommands = async (program) => {
11
- const configCommand = await (0, commands_1.buildCommand)(program, {
34
+ const configCommand = await commands.buildCommand(program, {
12
35
  type: "topCommand",
13
36
  command: "config",
14
37
  description: "configure your CLI",
@@ -3,12 +3,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ const refresh_command_1 = __importDefault(require("./refresh.command"));
6
7
  const reset_command_1 = __importDefault(require("./reset.command"));
7
8
  const show_command_1 = __importDefault(require("./show.command"));
8
9
  const secretsCommand = {
9
10
  type: "topCommand",
10
11
  command: "secrets",
11
12
  description: "work with the locally stored secrets",
12
- subCommands: [show_command_1.default, reset_command_1.default],
13
+ subCommands: [show_command_1.default, refresh_command_1.default, reset_command_1.default],
13
14
  };
14
15
  exports.default = secretsCommand;
@@ -0,0 +1,3 @@
1
+ import { Command } from "../../../types";
2
+ declare const refreshCommand: Command;
3
+ export default refreshCommand;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const constants_1 = require("../../../constants");
4
+ const handler_1 = require("../../handler");
5
+ const refreshToken_1 = require("../../handler/authentication/oauth/tokenProvider/refreshToken");
6
+ /* jscpd:ignore-end */
7
+ const refreshCommand = {
8
+ type: "command",
9
+ command: "refresh",
10
+ description: "refresh the access token for interactive OAuth authentication",
11
+ handler: (0, handler_1.createNextHandler)("commands.config.secrets.refresh", (0, handler_1.createParseArgumentsHandler)(), (0, handler_1.createOptionsHandler)([{ ...constants_1.OPTION_HOST, hidden: false }]), (0, handler_1.createResilientHandler)((0, refreshToken_1.create)())),
12
+ };
13
+ exports.default = refreshCommand;
@@ -16,6 +16,7 @@ const handler = async () => async () => {
16
16
  if (!secrets.access_token && !secrets.refresh_token) {
17
17
  debug("access token not available, retrieving token from server");
18
18
  const code = await (0, utils_2.getCode)(secrets.authorization_url, secrets.client_id);
19
+ debug("code received, reading token");
19
20
  await (0, utils_1.readToken)({
20
21
  code,
21
22
  grant_type: "authorization_code",
@@ -10,16 +10,18 @@ const constants_1 = require("../../../../../constants");
10
10
  const options_1 = require("../../../../../utils/options");
11
11
  const SecretsStorageSingleton_1 = require("../../../../../cache/secrets/SecretsStorageSingleton");
12
12
  const openUtils_1 = require("../../../../../utils/openUtils");
13
+ const utils_2 = require("../../../../../logger/utils");
13
14
  const getLogger = () => (0, logger_1.get)("commands.handler.authentication.oauth.tokenProvider.utils.refreshToken");
14
15
  const refreshToken = async (forceRefresh = false) => {
15
- const { info: logInfo, debug } = getLogger();
16
+ const logger = getLogger();
16
17
  const secrets = await SecretsStorageSingleton_1.SecretsStorageSingleton.SINGLETON.getDefaultSecret();
17
18
  if ((!forceRefresh && !secrets.expires_after) || !secrets.refresh_token) {
19
+ (0, utils_2.logVerbose)(logger, "Access token cannot be refreshed. Is the refresh token available?");
18
20
  throw new Error("invalid secrets information");
19
21
  }
20
- logInfo("checking token expiry date");
22
+ logger.info("checking token expiry date");
21
23
  if (forceRefresh || (0, utils_1.isExpired)(secrets.expires_after)) {
22
- debug("access token is expired, refreshing token");
24
+ logger.debug("access token is expired, refreshing token");
23
25
  await (0, utils_1.readToken)({
24
26
  refresh_token: secrets.refresh_token,
25
27
  grant_type: "refresh_token",
@@ -27,58 +29,65 @@ const refreshToken = async (forceRefresh = false) => {
27
29
  await (0, setAuthorization_1.updateAuthorization)();
28
30
  }
29
31
  else {
30
- debug("access token is not expired");
32
+ logger.debug("access token is not expired");
31
33
  }
32
34
  };
33
35
  exports.refreshToken = refreshToken;
34
- const retrieveCode = async () =>
35
- // eslint-disable-next-line no-async-promise-executor
36
- new Promise(async (resolve, reject) => {
37
- const { debug, error } = getLogger();
38
- try {
39
- const secrets = await SecretsStorageSingleton_1.SecretsStorageSingleton.SINGLETON.getDefaultSecret();
40
- const defaultPort = secrets.customClient ? "8080" : "65000";
41
- const PORT = parseInt(process.env.CLI_HTTP_PORT || defaultPort, 10);
42
- let timeout;
43
- const server = (0, http_1.createServer)(async (req, res) => {
44
- clearTimeout(timeout);
45
- const code = new URL(req.url
46
- ? `http://localhost:${PORT}${req.url}`
47
- : "http://no-code.damn").searchParams.get("code");
48
- let file;
49
- try {
50
- file = await (0, fs_extra_1.readFile)(code ? constants_1.PATH_TO_SUCCESS_HTML : constants_1.PATH_TO_ERROR_HTML, "utf8");
51
- }
52
- catch (err) {
53
- debug("failed to read file for code", code, err);
54
- file =
55
- "<html><body>Ops, something went wrong! Please try again.</body></html>";
56
- }
57
- res.writeHead(200, {
58
- "Content-Type": "text/html",
59
- "Content-Security-Policy": "frame-ancestors 'none'",
60
- "X-Frame-Options": "DENY",
36
+ const retrieveCode = async () => new Promise((resolve, reject) => {
37
+ void (async () => {
38
+ const { debug, error, output } = getLogger();
39
+ try {
40
+ const secrets = await SecretsStorageSingleton_1.SecretsStorageSingleton.SINGLETON.getDefaultSecret();
41
+ const defaultPort = secrets.customClient ? "8080" : "65000";
42
+ const PORT = parseInt(process.env.CLI_HTTP_PORT ?? defaultPort, 10);
43
+ let timeout;
44
+ const server = (0, http_1.createServer)((req, res) => {
45
+ void (async () => {
46
+ clearTimeout(timeout);
47
+ const code = new URL(req.url
48
+ ? `http://localhost:${PORT}${req.url}`
49
+ : "http://no-code.damn").searchParams.get("code");
50
+ let file;
51
+ try {
52
+ file = await (0, fs_extra_1.readFile)(code ? constants_1.PATH_TO_SUCCESS_HTML : constants_1.PATH_TO_ERROR_HTML, "utf8");
53
+ }
54
+ catch (err) {
55
+ debug("failed to read file for code", code, err);
56
+ file =
57
+ "<html><body>Ops, something went wrong! Please try again.</body></html>";
58
+ }
59
+ res.writeHead(200, {
60
+ "Content-Type": "text/html",
61
+ "Content-Security-Policy": "frame-ancestors 'none'",
62
+ "X-Frame-Options": "DENY",
63
+ });
64
+ res.end(file, "utf8");
65
+ server.close();
66
+ if (code) {
67
+ debug(`code received: ${code}`);
68
+ resolve(code);
69
+ }
70
+ else {
71
+ const message = "no code found in callback URI";
72
+ error(message);
73
+ reject(new Error(message));
74
+ }
75
+ })();
61
76
  });
62
- res.end(file, "utf8");
63
- server.close();
64
- if (code) {
65
- resolve(code);
66
- }
67
- else {
68
- reject(new Error("no code found in callback URI"));
69
- }
70
- });
71
- timeout = setTimeout(() => {
72
- server.close();
73
- reject(new Error("Did not receive a code within 30 seconds"));
74
- }, 30 * 1000);
75
- server.listen(PORT);
76
- debug(`started http server at localhost:${PORT}`);
77
- }
78
- catch (err) {
79
- error("failed to instantiate server", err);
80
- reject(new Error("failed to instantiate server", { cause: err }));
81
- }
77
+ timeout = setTimeout(() => {
78
+ server.close();
79
+ const message = `Did not receive a code within 30 seconds. Did you maintain the redirect URI for the OAuth client as http://localhost:${PORT} in SAP Datasphere?`;
80
+ output(message);
81
+ reject(new Error(message));
82
+ }, 30 * 1000);
83
+ server.listen(PORT);
84
+ debug(`started http server at localhost:${PORT}`);
85
+ }
86
+ catch (err) {
87
+ error("failed to instantiate server", err);
88
+ reject(new Error("failed to instantiate server", { cause: err }));
89
+ }
90
+ })();
82
91
  });
83
92
  exports.retrieveCode = retrieveCode;
84
93
  const getCode = async (authorizeUrl, clientId) => {
@@ -88,13 +97,12 @@ const getCode = async (authorizeUrl, clientId) => {
88
97
  }
89
98
  catch (err) {
90
99
  debug("failed to retrieve code from options", err);
91
- return (await Promise.all([
92
- (0, exports.retrieveCode)(),
93
- (0, openUtils_1.openUrlInBrowser)(authorizeUrl, {
94
- response_type: "code",
95
- client_id: clientId,
96
- }),
97
- ]))[0];
100
+ const code = (0, exports.retrieveCode)();
101
+ void (0, openUtils_1.openUrlInBrowser)(authorizeUrl, {
102
+ response_type: "code",
103
+ client_id: clientId,
104
+ });
105
+ return code;
98
106
  }
99
107
  };
100
108
  exports.getCode = getCode;
@@ -7,13 +7,19 @@ exports.readToken = exports.isExpired = exports.calculateExpiresAfter = void 0;
7
7
  const qs_1 = __importDefault(require("qs"));
8
8
  const http_1 = require("../../../../utils/http");
9
9
  const SecretsStorageSingleton_1 = require("../../../../cache/secrets/SecretsStorageSingleton");
10
+ const utils_1 = require("../../../../logger/utils");
11
+ const logger_1 = require("../../../../logger");
12
+ const getLogger = () => (0, logger_1.get)("commands.handler.authentication.oauth.utils");
10
13
  const calculateExpiresAfter = (expires_in) => Math.floor(Date.now() / 1000) + expires_in - 10;
11
14
  exports.calculateExpiresAfter = calculateExpiresAfter;
12
15
  const isExpired = (expires_after) => expires_after <= (0, exports.calculateExpiresAfter)(10);
13
16
  exports.isExpired = isExpired;
14
17
  const readToken = async (data) => {
18
+ const logger = getLogger();
15
19
  const secrets = await SecretsStorageSingleton_1.SecretsStorageSingleton.SINGLETON.getDefaultSecret();
16
20
  if (!secrets.token_url || !secrets.client_id || !secrets.client_secret) {
21
+ (0, utils_1.logVerbose)(logger, `The secret is invalid. Either the token URL, client ID, or client secret is missing.` +
22
+ `Please check the secret and login again.`);
17
23
  throw new Error("invalid secrets information");
18
24
  }
19
25
  const body = {
@@ -23,6 +29,7 @@ const readToken = async (data) => {
23
29
  if (!secrets.customClient) {
24
30
  body.client_id = secrets.client_id;
25
31
  }
32
+ logger.debug(`reading token`);
26
33
  const info = (await (0, http_1.fetch)({
27
34
  method: "POST",
28
35
  url: secrets.token_url,
@@ -36,6 +43,7 @@ const readToken = async (data) => {
36
43
  },
37
44
  data: qs_1.default.stringify(body),
38
45
  })).data;
46
+ logger.debug(`token received: ${JSON.stringify(info, null, 2)}`);
39
47
  await SecretsStorageSingleton_1.SecretsStorageSingleton.SINGLETON.storeSecret({
40
48
  ...info,
41
49
  expires_after: (0, exports.calculateExpiresAfter)(info.expires_in),
@@ -6,7 +6,7 @@ const commands_1 = require("../../../../utils/commands");
6
6
  const types_1 = require("./types");
7
7
  const utils_1 = require("./utils");
8
8
  const create = () => async (command) => {
9
- command.addOption(await (0, commands_1.buildOption)(command.name(), types_1.OPTION_SECRET));
9
+ command.addOption(await (0, commands_1.buildOption)(command, types_1.OPTION_SECRET));
10
10
  return async () => {
11
11
  const token = await (0, utils_1.getTechnicalJwt)();
12
12
  (0, config_1.set)({ authorization: { Authorization: `Bearer ${token}` } });
@@ -12,7 +12,7 @@ const logger_1 = require("../../../../logger");
12
12
  const http_1 = require("../../../../utils/http");
13
13
  const types_1 = require("./types");
14
14
  const getLogger = () => (0, logger_1.get)("commands.handler.authentication.technicalJWT.utils");
15
- // eslint-disable-next-line
15
+ // eslint-disable-next-line @typescript-eslint/no-var-requires, import/extensions
16
16
  const cf = require("./cf.js");
17
17
  const APPNAME_GLOBAL = "dwaas-core";
18
18
  const getSecret = async () => {
@@ -1,9 +1,12 @@
1
1
  /// <reference types="node" />
2
2
  import { URL } from "url";
3
3
  import { AxiosResponseHeaders, RawAxiosResponseHeaders } from "axios";
4
- import { HTTPConfig, HTTPMethod, KeyValuePair, ParameterMappings } from "../../../types";
4
+ import { HTTPConfig, HTTPMethod, KeyValuePair, ParameterMapping, ParameterMappings } from "../../../types";
5
5
  export declare const checkConfiguration: (config: KeyValuePair) => void;
6
6
  export declare const removeLeadingPathSegmentForPasscode: (path: string) => string;
7
+ export declare const handleMappingIn: (mapping: ParameterMapping, url: URL, value: any, headers: KeyValuePair, bodyWrapper: any) => void;
8
+ export declare const getValueFromMappping: (mapping: ParameterMapping, config: any) => any;
9
+ export declare const handleParameterMapping: (config: any, url: URL, headers: KeyValuePair, bodyWrapper: any) => (mapping: ParameterMapping) => void;
7
10
  export declare const buildParameters: (path: string, parameterMappings?: ParameterMappings) => {
8
11
  url: URL;
9
12
  headers: KeyValuePair;
@@ -16,7 +19,9 @@ export declare const buildParameters: (path: string, parameterMappings?: Paramet
16
19
  * @returns Path to output file
17
20
  */
18
21
  export declare function getOutputFileName(outputPath?: string): string;
22
+ export declare function checkResponseDataForInvalidLoginData(response: any): boolean;
19
23
  export declare const handleResponseData: (data: any, outputPath?: string) => Promise<void>;
24
+ export declare function handleResponseHeaders(headers: RawAxiosResponseHeaders | AxiosResponseHeaders): void;
20
25
  export declare const handleResponse: (data: any, headers?: RawAxiosResponseHeaders | AxiosResponseHeaders) => Promise<void>;
21
26
  export declare const configRequiresBody: (method: HTTPMethod) => boolean;
22
27
  export declare const buildHttpConfig: (method: HTTPMethod, path: string, parameterMappings?: ParameterMappings) => HTTPConfig;