@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.
- package/CHANGELOG.md +95 -0
- package/cache/secrets/SecretsStorageImpl.d.ts +2 -0
- package/cache/secrets/SecretsStorageImpl.js +37 -12
- package/cache/secrets/utils.d.ts +2 -0
- package/cache/secrets/utils.js +9 -1
- package/commands/config.command/cache.command/clean.command.js +16 -3
- package/commands/config.command/index.js +25 -2
- package/commands/config.command/secrets.command/index.js +2 -1
- package/commands/config.command/secrets.command/refresh.command.d.ts +3 -0
- package/commands/config.command/secrets.command/refresh.command.js +13 -0
- package/commands/handler/authentication/oauth/tokenProvider/getToken.js +1 -0
- package/commands/handler/authentication/oauth/tokenProvider/utils.js +66 -58
- package/commands/handler/authentication/oauth/utils.js +8 -0
- package/commands/handler/authentication/technicalJWT/index.js +1 -1
- package/commands/handler/authentication/technicalJWT/utils.js +1 -1
- package/commands/handler/fetch/utils.d.ts +6 -1
- package/commands/handler/fetch/utils.js +60 -8
- package/commands/handler/input/file.js +4 -5
- package/commands/handler/input/input.js +5 -7
- package/commands/handler/next.js +1 -1
- package/commands/handler/options/utils.js +1 -1
- package/commands/handler/or.js +1 -1
- package/commands/handler/root/index.d.ts +2 -2
- package/commands/handler/root/index.js +22 -2
- package/commands/login.command.js +1 -1
- package/commands/openAPI.command/index.js +3 -2
- package/commands/openAPI.command/utils.d.ts +2 -1
- package/commands/openAPI.command/utils.js +22 -9
- package/constants.d.ts +2 -1
- package/constants.js +3 -24
- package/discovery/index.d.ts +1 -1
- package/discovery/index.js +7 -3
- package/dwc/dwc.js +5 -5
- package/dwc/run.js +1 -1
- package/dwc/utils.d.ts +1 -1
- package/dwc/utils.js +4 -4
- package/index.d.ts +3 -3
- package/package.json +5 -5
- package/schemas/discovery.json +13 -1
- package/types.d.ts +21 -1
- package/types.js +21 -1
- package/utils/commands.d.ts +4 -4
- package/utils/commands.js +64 -29
- package/utils/http/index.js +5 -5
- package/utils/utils.d.ts +2 -1
- 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
|
|
10
|
+
const utils_2 = require("../../utils/utils");
|
|
9
11
|
const cache_1 = require("../cache");
|
|
10
|
-
const
|
|
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 (
|
|
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,
|
|
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,
|
|
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,
|
|
129
|
-
this.secrets[secretIndex].token_url = (0,
|
|
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) {
|
package/cache/secrets/utils.d.ts
CHANGED
package/cache/secrets/utils.js
CHANGED
|
@@ -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
|
|
7
|
-
|
|
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
|
|
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
|
|
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,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
|
|
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
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
server
|
|
73
|
-
reject(new Error("
|
|
74
|
-
}
|
|
75
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
|
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;
|