@sap/cds-dk 7.5.1 → 7.6.1
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/bin/add.js +28 -24
- package/bin/bind.js +59 -32
- package/bin/build.js +1 -1
- package/bin/cds.js +1 -1
- package/bin/env.js +1 -6
- package/bin/import.js +2 -2
- package/bin/init.js +7 -3
- package/bin/logout.js +2 -2
- package/bin/migrate.js +1 -1
- package/bin/version.js +1 -0
- package/bin/watch.js +8 -1
- package/lib/bind/bindingUtil.js +14 -20
- package/lib/bind/cfKeyProvider.js +8 -10
- package/lib/bind/index.js +4 -2
- package/lib/build/buildTaskFactory.js +31 -20
- package/lib/build/constants.js +9 -8
- package/lib/build/provider/featureToggleBuildPlugin.js +1 -7
- package/lib/build/provider/hana/index.js +44 -9
- package/lib/build/provider/internalBuildTaskProvider.js +7 -5
- package/lib/build/provider/java/index.js +0 -6
- package/lib/build/provider/mtx-sidecar/index.js +5 -0
- package/lib/build/provider/nodejs/index.js +32 -47
- package/lib/build/util.js +134 -33
- package/lib/deploy/to-hana/hana.js +47 -77
- package/lib/deploy/to-hana/hdiDeployUtil.js +15 -18
- package/lib/deploy/to-hana/mtaUtil.js +1 -1
- package/lib/env/schema.js +157 -17
- package/lib/env/schemas/cds-rc.js +118 -0
- package/lib/import/common.js +18 -23
- package/lib/import/odata/metadataconverter/MetadataConverter.js +12 -8
- package/lib/import/odata/v4parser.js +67 -12
- package/lib/init/constants.js +2 -4
- package/lib/init/merge.js +2 -1
- package/lib/init/template/application-logging/index.js +1 -2
- package/lib/init/template/approuter/files/mta.yaml.hbs +2 -0
- package/lib/init/template/approuter/files/package.json +1 -1
- package/lib/init/template/approuter/files/values.yaml.hbs +8 -1
- package/lib/init/template/hana/files/undeploy.json +3 -2
- package/lib/init/template/helm/files/chart/templates/NOTES.txt +2 -0
- package/lib/init/template/helm/files/chart/templates/_deployment_helpers.tpl +10 -2
- package/lib/init/template/helm/files/content-deployment/Chart.yaml +1 -1
- package/lib/init/template/helm/files/content-deployment/templates/_helpers.tpl +6 -5
- package/lib/init/template/helm/files/content-deployment/values.yaml +1 -1
- package/lib/init/template/helm/files/service-instance/Chart.yaml +1 -1
- package/lib/init/template/helm/files/service-instance/templates/_helpers.tpl +6 -5
- package/lib/init/template/helm/files/service-instance/values.yaml +1 -1
- package/lib/init/template/helm/files/web-application/Chart.yaml +1 -1
- package/lib/init/template/helm/files/web-application/templates/_api.tpl +6 -5
- package/lib/init/template/helm/files/web-application/values.yaml +1 -1
- package/lib/init/template/helm/index.js +1 -12
- package/lib/init/template/helm-unified-runtime/index.js +3 -2
- package/lib/init/template/html5-repo/files/values.yaml.hbs +9 -0
- package/lib/init/template/multitenancy/files/package.sidecar.json.hbs +1 -2
- package/lib/init/template/multitenancy/index.js +3 -2
- package/lib/init/template/xsuaa/files/package.json.hbs +1 -2
- package/lib/lint/Linter.js +2 -2
- package/lib/lint/checks.js +0 -51
- package/lib/mtx/activate.js +1 -1
- package/lib/mtx/auth_manager.js +50 -57
- package/lib/mtx/download_legacy.js +1 -1
- package/lib/mtx/extend.js +2 -2
- package/lib/mtx/pull.js +1 -1
- package/lib/mtx/push.js +1 -1
- package/lib/mtx/settings_manager.js +90 -44
- package/lib/mtx/tenant_action.js +1 -1
- package/lib/mtx/util/dependencies.js +1 -1
- package/lib/mtx/util/legacy_ext.js +0 -1
- package/lib/mtx/util/logging.js +10 -8
- package/lib/util/axios.js +6 -19
- package/lib/util/cf.js +15 -10
- package/npm-shrinkwrap.json +104 -48
- package/package.json +1 -10
- package/lib/env/schemas/cds-rc.json +0 -101
- package/lib/init/template/helm-unified-runtime/lib/build.js +0 -0
package/lib/lint/checks.js
CHANGED
|
@@ -2,10 +2,8 @@ const fs = require("fs");
|
|
|
2
2
|
const os = require("os");
|
|
3
3
|
const path = require("path");
|
|
4
4
|
const term = require("../util/term");
|
|
5
|
-
const commandUtil = require("../util/command");
|
|
6
5
|
|
|
7
6
|
const IS_WIN = os.platform() === "win32";
|
|
8
|
-
const { URLS } = require("../init/constants");
|
|
9
7
|
|
|
10
8
|
module.exports = {
|
|
11
9
|
/**
|
|
@@ -73,55 +71,6 @@ module.exports = {
|
|
|
73
71
|
return true;
|
|
74
72
|
},
|
|
75
73
|
|
|
76
|
-
/**
|
|
77
|
-
* Checks if VS Code is installed. If not, suggests to install it
|
|
78
|
-
* @param {*} inputPath CDS project root
|
|
79
|
-
* @returns boolean whether VS Code is installed
|
|
80
|
-
*/
|
|
81
|
-
async hasVscode(inputPath, logger = console) {
|
|
82
|
-
try {
|
|
83
|
-
await commandUtil.spawnCommand("code", ["--version"], {
|
|
84
|
-
cwd: inputPath,
|
|
85
|
-
shell: IS_WIN,
|
|
86
|
-
});
|
|
87
|
-
} catch (err) {
|
|
88
|
-
logger.log(`Consider installing VS Code: ${URLS.VSCODE}`);
|
|
89
|
-
return false;
|
|
90
|
-
}
|
|
91
|
-
return true;
|
|
92
|
-
},
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Checks whether VS Code extension <extension> is installed
|
|
96
|
-
* @param {*} extension VS Code extension
|
|
97
|
-
* @param {*} inputPath CDS roject root
|
|
98
|
-
* @returns boolean whether VS Code extension is installed
|
|
99
|
-
*/
|
|
100
|
-
async hasVscodeExtension(extension, inputPath, logger = console) {
|
|
101
|
-
try {
|
|
102
|
-
this.linterExtensions = await commandUtil.spawnCommand(
|
|
103
|
-
"code",
|
|
104
|
-
["--list-extensions", "--category", "linters"],
|
|
105
|
-
{
|
|
106
|
-
cwd: inputPath,
|
|
107
|
-
shell: IS_WIN,
|
|
108
|
-
},
|
|
109
|
-
true
|
|
110
|
-
);
|
|
111
|
-
} catch (err) {
|
|
112
|
-
// Do nothing
|
|
113
|
-
}
|
|
114
|
-
if (this.linterExtensions && this.linterExtensions.includes(extension)) {
|
|
115
|
-
return true;
|
|
116
|
-
} else {
|
|
117
|
-
logger.log(
|
|
118
|
-
"\n\nConsider installing the VS Code extension for ESLint:\n" +
|
|
119
|
-
term.info(`'dbaeumer.vscode-eslint': ${URLS.VSCODE_ESLINT}\n`)
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
return false;
|
|
123
|
-
},
|
|
124
|
-
|
|
125
74
|
/**
|
|
126
75
|
* Checks ESLint config file for proper setup
|
|
127
76
|
* in context of a local ESLint plugin installation
|
package/lib/mtx/activate.js
CHANGED
|
@@ -5,7 +5,7 @@ const axios = require('axios');
|
|
|
5
5
|
const cds = require('../cds');
|
|
6
6
|
const AuthManager = require('./auth_manager');
|
|
7
7
|
|
|
8
|
-
require('../util/axios').pruneErrors()
|
|
8
|
+
require('../util/axios').pruneErrors();
|
|
9
9
|
const { collectFileContent, collectFiles, toUnixPath } = require('./util/fs');
|
|
10
10
|
const { getMessage } = require('./util/logging');
|
|
11
11
|
const { handleHttpError } = require('./util/errors');
|
package/lib/mtx/auth_manager.js
CHANGED
|
@@ -3,29 +3,32 @@ const axios = require('axios');
|
|
|
3
3
|
const cds = require('../cds');
|
|
4
4
|
const { SettingsManager } = require('./settings_manager');
|
|
5
5
|
const { ParamCollection } = require('./params');
|
|
6
|
-
|
|
7
|
-
require('../util/axios').pruneErrors().setReqTimeout();
|
|
8
6
|
const { getMessage } = require('./util/logging');
|
|
9
7
|
|
|
8
|
+
require('../util/axios').pruneErrors();
|
|
9
|
+
|
|
10
10
|
const DEBUG = cds.debug('cli');
|
|
11
|
+
const REASONS = { invalid_scope: /\binvalid_scope\b/ };
|
|
11
12
|
|
|
12
|
-
function
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
function assign(params, authData) {
|
|
14
|
+
if (authData?.access_token) {
|
|
15
|
+
params.set('token', authData.access_token);
|
|
16
|
+
if (authData.expires_in) {
|
|
17
|
+
params.set('tokenExpirationDate', Date.now() + authData.expires_in * 1000);
|
|
18
|
+
} else {
|
|
19
|
+
params.delete('tokenExpirationDate');
|
|
20
|
+
}
|
|
21
|
+
if (authData.refresh_token) {
|
|
22
|
+
params.set('refreshToken', authData.refresh_token);
|
|
23
|
+
} else {
|
|
24
|
+
params.delete('refreshToken');
|
|
25
|
+
}
|
|
18
26
|
}
|
|
19
|
-
if (authData
|
|
20
|
-
params.set('
|
|
21
|
-
} else {
|
|
22
|
-
params.delete('refreshToken');
|
|
27
|
+
if (authData?.passcode_url) {
|
|
28
|
+
params.set('passcodeUrl', authData.passcode_url);
|
|
23
29
|
}
|
|
24
30
|
}
|
|
25
|
-
function
|
|
26
|
-
params.set('passcodeUrl', authData.passcode_url);
|
|
27
|
-
}
|
|
28
|
-
function* reqParams(params) {
|
|
31
|
+
function reqParams(method, params) {
|
|
29
32
|
const url = params.get('tokenUrl');
|
|
30
33
|
const d = {};
|
|
31
34
|
if (params.has('subdomain')) {
|
|
@@ -44,16 +47,9 @@ function* reqParams(params) {
|
|
|
44
47
|
d.key = params.get('key');
|
|
45
48
|
}
|
|
46
49
|
const data = new URLSearchParams(d).toString();
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
url
|
|
50
|
-
data
|
|
51
|
-
};
|
|
52
|
-
yield {
|
|
53
|
-
method: 'get',
|
|
54
|
-
url: `${url}?${data}`,
|
|
55
|
-
data: undefined
|
|
56
|
-
};
|
|
50
|
+
return method === 'post'
|
|
51
|
+
? { url, data }
|
|
52
|
+
: { url: `${url}?${data}`, data: undefined };
|
|
57
53
|
}
|
|
58
54
|
|
|
59
55
|
async function retrieveTokenOrPasscodeUrl(params) {
|
|
@@ -61,41 +57,37 @@ async function retrieveTokenOrPasscodeUrl(params) {
|
|
|
61
57
|
return;
|
|
62
58
|
}
|
|
63
59
|
|
|
64
|
-
let
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
for (const { method, url, data } of reqParams(params)) {
|
|
60
|
+
let response, error;
|
|
61
|
+
reqParams: for (const method of ['post', 'get']) {
|
|
62
|
+
do {
|
|
63
|
+
const { url, data } = reqParams(method, params);
|
|
69
64
|
try {
|
|
70
65
|
DEBUG?.(`Getting authentication token or passcode URL from ${method.toUpperCase()} ${params.obfuscateQueryParams(url)}`);
|
|
71
66
|
response = await axios[method](url, data);
|
|
72
|
-
} catch (
|
|
67
|
+
} catch (e) {
|
|
68
|
+
error = e;
|
|
73
69
|
if (error.status === 404) { // may represent 405 in case of wrong method
|
|
74
|
-
continue;
|
|
70
|
+
continue reqParams;
|
|
71
|
+
} else if (params.has('refreshToken')) {
|
|
72
|
+
DEBUG?.('Discarding invalid refresh token');
|
|
73
|
+
params.delete('refreshToken');
|
|
74
|
+
} else {
|
|
75
|
+
break reqParams;
|
|
75
76
|
}
|
|
76
|
-
throw getMessage('Failed to get authentication token', { error });
|
|
77
77
|
}
|
|
78
|
-
|
|
79
|
-
break;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
if (!response) {
|
|
83
|
-
throw 'Failed to get authentication token';
|
|
84
|
-
}
|
|
85
|
-
authData = response.data;
|
|
86
|
-
|
|
87
|
-
retry = !authData.access_token && params.has('refreshToken') && (params.delete('refreshToken'), true);
|
|
88
|
-
} while (retry);
|
|
89
|
-
|
|
90
|
-
if (authData.access_token) {
|
|
91
|
-
addTokenInfo(params, authData);
|
|
78
|
+
} while (!(response || error));
|
|
92
79
|
}
|
|
93
|
-
|
|
94
|
-
|
|
80
|
+
const data = response?.data ?? error?.auth;
|
|
81
|
+
assign(params, data);
|
|
82
|
+
if (!response) {
|
|
83
|
+
throw new Error(REASONS.invalid_scope.test(error.message)
|
|
84
|
+
? 'Token has invalid scope. Check if your user has the required roles'
|
|
85
|
+
: `No valid passcode or token provided. Get a passcode${
|
|
86
|
+
params.get('passcodeUrl') ? ' from ' + params.get('passcodeUrl') : ''
|
|
87
|
+
} and re-run the command with '-p <passcode>'`);
|
|
95
88
|
}
|
|
96
89
|
}
|
|
97
90
|
|
|
98
|
-
|
|
99
91
|
module.exports = class AuthManager {
|
|
100
92
|
|
|
101
93
|
static async login(paramValues) {
|
|
@@ -105,14 +97,13 @@ module.exports = class AuthManager {
|
|
|
105
97
|
if (params.has('username')) {
|
|
106
98
|
params.set('reqAuth', { auth: { username: params.get('username'), password: params.get('password') } });
|
|
107
99
|
} else if (!params.get('skipToken')) {
|
|
108
|
-
|
|
109
|
-
|
|
100
|
+
try {
|
|
101
|
+
await retrieveTokenOrPasscodeUrl(params);
|
|
102
|
+
} catch (error) {
|
|
110
103
|
if (params.get('saveData')) {
|
|
111
104
|
await SettingsManager.saveSettings(params);
|
|
112
105
|
}
|
|
113
|
-
throw
|
|
114
|
-
params.get('passcodeUrl') ? ' from ' + params.get('passcodeUrl') : ''
|
|
115
|
-
} and supply the code when re-running the command`;
|
|
106
|
+
throw getMessage('Failed to login', { error });
|
|
116
107
|
}
|
|
117
108
|
params.set('reqAuth', { headers: { Authorization: 'Bearer ' + params.get('token') } });
|
|
118
109
|
}
|
|
@@ -127,8 +118,10 @@ module.exports = class AuthManager {
|
|
|
127
118
|
static async logout(paramValues) {
|
|
128
119
|
SettingsManager.init();
|
|
129
120
|
const params = new ParamCollection(paramValues);
|
|
121
|
+
await SettingsManager.setKeytar(params, true);
|
|
130
122
|
if (params.get('clearInvalid')) {
|
|
131
|
-
await SettingsManager.
|
|
123
|
+
await SettingsManager.deleteInvalidSettings();
|
|
124
|
+
await SettingsManager.deleteInvalidTokens();
|
|
132
125
|
} else {
|
|
133
126
|
await SettingsManager.deleteToken(params);
|
|
134
127
|
if (params.get('deleteSettings')) {
|
|
@@ -4,7 +4,7 @@ const axios = require('axios');
|
|
|
4
4
|
|
|
5
5
|
const { login } = require('./auth_manager');
|
|
6
6
|
|
|
7
|
-
require('../util/axios').pruneErrors()
|
|
7
|
+
require('../util/axios').pruneErrors();
|
|
8
8
|
const { handleHttpError } = require('./util/errors');
|
|
9
9
|
|
|
10
10
|
const DOWNLOAD_FILE_NAME = 'migrated_projects.tgz'; // TODO try to add tenant later, if possible
|
package/lib/mtx/extend.js
CHANGED
|
@@ -4,7 +4,7 @@ const axios = require('axios');
|
|
|
4
4
|
|
|
5
5
|
const AuthManager = require('./auth_manager');
|
|
6
6
|
|
|
7
|
-
require('../util/axios').pruneErrors()
|
|
7
|
+
require('../util/axios').pruneErrors();
|
|
8
8
|
const { normalizePath, isParent } = require('./util/fs');
|
|
9
9
|
const Question = require('./util/question');
|
|
10
10
|
const { getMessage } = require('./util/logging');
|
|
@@ -163,7 +163,7 @@ module.exports = class Extend {
|
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
static promptTemplateOverwrite() {
|
|
166
|
-
return Question.askBooleanQuestion(`This will overwrite
|
|
166
|
+
return Question.askBooleanQuestion(`This will overwrite existing templates in folder ${TEMPLATE_FOLDER}. Continue (yN)? `);
|
|
167
167
|
}
|
|
168
168
|
|
|
169
169
|
static _logWrittenFile(file) {
|
package/lib/mtx/pull.js
CHANGED
|
@@ -7,7 +7,7 @@ const axios = require('axios');
|
|
|
7
7
|
|
|
8
8
|
const { login } = require('./auth_manager');
|
|
9
9
|
|
|
10
|
-
require('../util/axios').pruneErrors()
|
|
10
|
+
require('../util/axios').pruneErrors();
|
|
11
11
|
const { getMessage } = require('./util/logging');
|
|
12
12
|
const { handleHttpError } = require('./util/errors');
|
|
13
13
|
|
package/lib/mtx/push.js
CHANGED
|
@@ -6,7 +6,7 @@ const axios = require('axios');
|
|
|
6
6
|
const { login } = require('./auth_manager');
|
|
7
7
|
const buildSystem = require('../build')
|
|
8
8
|
|
|
9
|
-
require('../util/axios').pruneErrors()
|
|
9
|
+
require('../util/axios').pruneErrors();
|
|
10
10
|
const { getMessage } = require('./util/logging');
|
|
11
11
|
const { schemaRegex } = require('./util/urls');
|
|
12
12
|
const { handleHttpError } = require('./util/errors');
|
|
@@ -50,6 +50,7 @@ const CONFIG = {
|
|
|
50
50
|
};
|
|
51
51
|
|
|
52
52
|
let keytar;
|
|
53
|
+
let keytarDisabled;
|
|
53
54
|
|
|
54
55
|
function other(tokenStorage) {
|
|
55
56
|
if (!tokenStorage) {
|
|
@@ -104,28 +105,31 @@ async function getTokenBaseUrl(params, renewUrl) {
|
|
|
104
105
|
return url.toString();
|
|
105
106
|
};
|
|
106
107
|
|
|
107
|
-
let
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
break;
|
|
114
|
-
} catch (error) {
|
|
115
|
-
if ([401, 404].includes(error.status)) {
|
|
116
|
-
continue;
|
|
117
|
-
}
|
|
108
|
+
let path = OAUTH_PATH_OLD_MTX;
|
|
109
|
+
try {
|
|
110
|
+
await axios.head(appUrlWith(OAUTH_PATH_STREAMLINED_MTX));
|
|
111
|
+
path = OAUTH_PATH_STREAMLINED_MTX;
|
|
112
|
+
} catch (error) {
|
|
113
|
+
if (![401, 404].includes(error.status)) {
|
|
118
114
|
throw error;
|
|
119
115
|
}
|
|
116
|
+
DEBUG?.(`Falling back to legacy token URL: ${error.message}`);
|
|
120
117
|
}
|
|
121
|
-
|
|
122
|
-
return appUrlWith(existingPath);
|
|
118
|
+
return appUrlWith(path);
|
|
123
119
|
}
|
|
124
120
|
|
|
125
121
|
function getKeyringAccountName(params) {
|
|
126
122
|
return `${params.get('appUrl')}|${params.get('subdomain')}`;
|
|
127
123
|
}
|
|
128
124
|
|
|
125
|
+
function getAccountUrl(account) {
|
|
126
|
+
return account.includes('|') ? account.split('|')[0] : null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function getAccountSubdomain(account) {
|
|
130
|
+
return account.includes('|') ? account.split('|')[1] : null;
|
|
131
|
+
}
|
|
132
|
+
|
|
129
133
|
function logConfigPaths() {
|
|
130
134
|
DEBUG?.(`Settings are stored in ${CONFIG.paths.settings}`);
|
|
131
135
|
DEBUG?.(`Authentication data is stored in ${CONFIG.paths.auth} (${TOKEN_STORAGE_DESC.plain}) or ${CONFIG.keyringDesignation} (${TOKEN_STORAGE_DESC.keyring})`);
|
|
@@ -145,6 +149,8 @@ class SettingsManager {
|
|
|
145
149
|
|
|
146
150
|
static init() {
|
|
147
151
|
logConfigPaths();
|
|
152
|
+
keytar = undefined;
|
|
153
|
+
keytarDisabled = undefined;
|
|
148
154
|
}
|
|
149
155
|
|
|
150
156
|
static get config() {
|
|
@@ -173,9 +179,9 @@ class SettingsManager {
|
|
|
173
179
|
}
|
|
174
180
|
|
|
175
181
|
if (params.get('clearOtherTokenStorage')) {
|
|
176
|
-
// Delete
|
|
182
|
+
// Delete data from the other storage.
|
|
177
183
|
params.delete('clearOtherTokenStorage');
|
|
178
|
-
await this.deleteToken(params, true);
|
|
184
|
+
await this.deleteToken(params, { fromOtherStorage: true });
|
|
179
185
|
}
|
|
180
186
|
|
|
181
187
|
if (params.get('tokenStorage') === 'plain') {
|
|
@@ -184,7 +190,7 @@ class SettingsManager {
|
|
|
184
190
|
await this.setKeytar(params);
|
|
185
191
|
await this._saveAuthToKeyring(params, params.toEntries(Persistence.auth));
|
|
186
192
|
} else {
|
|
187
|
-
console.log('Note:
|
|
193
|
+
console.log('Note: authentication data is not persisted by default. To save tokens for later commands, please run `cds login`.');
|
|
188
194
|
}
|
|
189
195
|
}
|
|
190
196
|
|
|
@@ -259,7 +265,7 @@ class SettingsManager {
|
|
|
259
265
|
}
|
|
260
266
|
|
|
261
267
|
static async setKeytar(params, logout = false) {
|
|
262
|
-
if (params.get('skipToken')) {
|
|
268
|
+
if (keytar || keytarDisabled || params.get('skipToken')) {
|
|
263
269
|
return;
|
|
264
270
|
}
|
|
265
271
|
if (isBas()) {
|
|
@@ -273,7 +279,7 @@ class SettingsManager {
|
|
|
273
279
|
if (keytar) {
|
|
274
280
|
return;
|
|
275
281
|
}
|
|
276
|
-
|
|
282
|
+
keytarDisabled = true;
|
|
277
283
|
|
|
278
284
|
if (params.get('tokenStorage') === 'keyring' && params.get('saveData')) {
|
|
279
285
|
// Explicit login w/o plain-text storage enabled
|
|
@@ -318,7 +324,19 @@ class SettingsManager {
|
|
|
318
324
|
throw 'Passcode cannot be empty';
|
|
319
325
|
}
|
|
320
326
|
}
|
|
321
|
-
function
|
|
327
|
+
async function addAuthSettings() {
|
|
328
|
+
function removeObsoleteToken() {
|
|
329
|
+
if (params.get('renewLogin') || params.get('tokenExpirationDate') <= Date.now()) {
|
|
330
|
+
DEBUG?.((params.get('renewLogin') ? 'Renewing' : 'Refreshing expired') + ' authentication token');
|
|
331
|
+
params.delete('token');
|
|
332
|
+
params.delete('tokenExpirationDate');
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const auth = {
|
|
337
|
+
plain: await SettingsManager._loadAuthFromFile(params.get('appUrl'), params.get('subdomain')),
|
|
338
|
+
keyring: await SettingsManager._loadAuthFromKeyring(params.get('appUrl'), params.get('subdomain'))
|
|
339
|
+
};
|
|
322
340
|
if (!(auth.keyring.has('tokenStorage') || auth.plain.has('tokenStorage'))) {
|
|
323
341
|
// Saved auth data not present.
|
|
324
342
|
return;
|
|
@@ -348,12 +366,8 @@ class SettingsManager {
|
|
|
348
366
|
|
|
349
367
|
params.merge(auth[storage]);
|
|
350
368
|
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
if (params.get('renewLogin') || params.get('tokenExpirationDate') <= Date.now()) {
|
|
354
|
-
DEBUG?.((params.get('renewLogin') ? 'Renewing' : 'Refreshing expired') + ' authentication token');
|
|
355
|
-
params.delete('token');
|
|
356
|
-
params.delete('tokenExpirationDate');
|
|
369
|
+
if (params.has('token')) {
|
|
370
|
+
removeObsoleteToken();
|
|
357
371
|
}
|
|
358
372
|
}
|
|
359
373
|
|
|
@@ -387,21 +401,13 @@ class SettingsManager {
|
|
|
387
401
|
}
|
|
388
402
|
}
|
|
389
403
|
|
|
390
|
-
|
|
391
|
-
plain: await this._loadAuthFromFile(params.get('appUrl'), params.get('subdomain')),
|
|
392
|
-
keyring: await this._loadAuthFromKeyring(params.get('appUrl'), params.get('subdomain'))
|
|
393
|
-
};
|
|
394
|
-
|
|
395
|
-
setRelevantAuth(auth);
|
|
396
|
-
if (params.has('token')) {
|
|
397
|
-
removeObsoleteToken();
|
|
398
|
-
}
|
|
404
|
+
await addAuthSettings();
|
|
399
405
|
if (!logout && !params.has('token') && !params.has('refreshToken') && !params.has('passcode') && !params.has('clientsecret') && !params.has('key')) {
|
|
400
406
|
await addPasscode();
|
|
401
407
|
}
|
|
402
408
|
}
|
|
403
409
|
|
|
404
|
-
static async deleteToken(params, fromOtherStorage = false) {
|
|
410
|
+
static async deleteToken(params, { fromOtherStorage = false, invalid = false } = {}) {
|
|
405
411
|
const allParams = params.clone();
|
|
406
412
|
await this.loadAndMergeSettings(allParams, true);
|
|
407
413
|
|
|
@@ -409,11 +415,13 @@ class SettingsManager {
|
|
|
409
415
|
let fromStorage = fromOtherStorage && other(params.get('tokenStorage'));
|
|
410
416
|
const deleteBoth = !fromStorage && allParams.get('tokenStorage') && allParams.get('clearOtherTokenStorage');
|
|
411
417
|
|
|
412
|
-
DEBUG?.(`Deleting authentication data${fromOtherStorage ? ' from other storage' : ''} for ${target}`);
|
|
418
|
+
DEBUG?.(`Deleting${invalid ? ' invalid' : ''} authentication data${fromOtherStorage ? ' from other storage' : ''} for ${target}`);
|
|
413
419
|
|
|
414
420
|
fromStorage = fromStorage || allParams.get('tokenStorage');
|
|
415
421
|
if (!fromStorage) {
|
|
416
|
-
|
|
422
|
+
if (!invalid) {
|
|
423
|
+
console.log('Failed to delete authentication data: none found for', target);
|
|
424
|
+
}
|
|
417
425
|
return;
|
|
418
426
|
}
|
|
419
427
|
|
|
@@ -430,7 +438,7 @@ class SettingsManager {
|
|
|
430
438
|
await this._saveToFile(projectFolder, null);
|
|
431
439
|
}
|
|
432
440
|
|
|
433
|
-
static async
|
|
441
|
+
static async deleteInvalidSettings() {
|
|
434
442
|
const settingsByFolder = await this._loadFromFile(undefined);
|
|
435
443
|
const deletionFolders = [];
|
|
436
444
|
const deletionUrlsAndSubdomains = new Set();
|
|
@@ -451,13 +459,31 @@ class SettingsManager {
|
|
|
451
459
|
const subdomain = urlAndSubdomain.replace(/.*\|/, '');
|
|
452
460
|
const urlReference = Object.values(settingsByFolder).find(settings => settings.appUrl === appUrl && settings.subdomain === subdomain);
|
|
453
461
|
if (!urlReference) {
|
|
454
|
-
await this.deleteToken(new ParamCollection({ appUrl, subdomain }));
|
|
462
|
+
await this.deleteToken(new ParamCollection({ appUrl, subdomain }), { invalid: true });
|
|
455
463
|
}
|
|
456
464
|
}
|
|
457
465
|
console.log('Deleted settings for nonexistent project folders:', deletionFolders.map(folder => ' ' + folder));
|
|
458
466
|
} else {
|
|
459
|
-
console.log('All
|
|
467
|
+
console.log('All saved project folders seem valid');
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
static async deleteInvalidTokens() {
|
|
472
|
+
async function deleteFrom(allAuth) {
|
|
473
|
+
for (const [appUrl, authForUrl = {}] of Object.entries(allAuth)) {
|
|
474
|
+
for (const [subdomain, auth] of Object.entries(authForUrl)) {
|
|
475
|
+
if (!auth.token) {
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
if (auth.tokenExpirationDate <= Date.now()) {
|
|
479
|
+
await SettingsManager.deleteToken(new ParamCollection({ appUrl, subdomain }), { invalid: true });
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
460
483
|
}
|
|
484
|
+
|
|
485
|
+
await deleteFrom(await this._loadAuthFromFile(undefined, undefined));
|
|
486
|
+
await deleteFrom(await this._loadAllAuthFromKeyring());
|
|
461
487
|
}
|
|
462
488
|
|
|
463
489
|
static async _saveToFile(projectFolder, paramValues) {
|
|
@@ -555,11 +581,11 @@ class SettingsManager {
|
|
|
555
581
|
return emptyData(all);
|
|
556
582
|
}
|
|
557
583
|
|
|
558
|
-
let allAuth
|
|
584
|
+
let allAuth;
|
|
559
585
|
try {
|
|
560
586
|
allAuth = JSON.parse((await fs.promises.readFile(CONFIG.paths.auth)).toString());
|
|
561
587
|
} catch (err) {
|
|
562
|
-
DEBUG?.(
|
|
588
|
+
DEBUG?.(`${capitalize(TOKEN_STORAGE_DESC.plain)} contains invalid saved auth`);
|
|
563
589
|
return emptyData(all);
|
|
564
590
|
}
|
|
565
591
|
|
|
@@ -568,14 +594,14 @@ class SettingsManager {
|
|
|
568
594
|
}
|
|
569
595
|
|
|
570
596
|
if (!allAuth[appUrl]) {
|
|
571
|
-
DEBUG?.(
|
|
597
|
+
DEBUG?.(`${capitalize(TOKEN_STORAGE_DESC.plain)} contains no authentication data for app URL ${appUrl}`);
|
|
572
598
|
return new ParamCollection();
|
|
573
599
|
}
|
|
574
600
|
|
|
575
601
|
const authForSubdomain = allAuth[appUrl][subdomain];
|
|
576
602
|
|
|
577
603
|
if (!authForSubdomain) {
|
|
578
|
-
DEBUG?.(
|
|
604
|
+
DEBUG?.(`${capitalize(TOKEN_STORAGE_DESC.plain)} contains no authentication data for subdomain ${subdomain} (app URL: ${appUrl})`);
|
|
579
605
|
return new ParamCollection();
|
|
580
606
|
}
|
|
581
607
|
|
|
@@ -613,6 +639,26 @@ class SettingsManager {
|
|
|
613
639
|
return new ParamCollection(auth);
|
|
614
640
|
}
|
|
615
641
|
|
|
642
|
+
static async _loadAllAuthFromKeyring() {
|
|
643
|
+
if (!keytar) {
|
|
644
|
+
return {};
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
return (await keytar.findCredentials(MTX_FULLY_QUALIFIED))
|
|
648
|
+
.reduce((result, { account, password: authString }) => {
|
|
649
|
+
const appUrl = getAccountUrl(account);
|
|
650
|
+
const subdomain = getAccountSubdomain(account);
|
|
651
|
+
if (appUrl && subdomain) {
|
|
652
|
+
try {
|
|
653
|
+
(result[appUrl] ??= {})[subdomain] = JSON.parse(authString);
|
|
654
|
+
} catch (error) {
|
|
655
|
+
throw new Error(`${capitalize(TOKEN_STORAGE_DESC.keyring)} contains invalid saved auth for URL ${appUrl} and subdomain ${subdomain}`);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
return result;
|
|
659
|
+
}, {});
|
|
660
|
+
}
|
|
661
|
+
|
|
616
662
|
}
|
|
617
663
|
|
|
618
664
|
module.exports = {
|
package/lib/mtx/tenant_action.js
CHANGED
|
@@ -2,7 +2,7 @@ const cds = require('../cds');
|
|
|
2
2
|
const { ParamCollection } = require('./params');
|
|
3
3
|
const AuthManager = require('./auth_manager');
|
|
4
4
|
|
|
5
|
-
require('../util/axios').pruneErrors()
|
|
5
|
+
require('../util/axios').pruneErrors();
|
|
6
6
|
const { getMessage } = require('./util/logging');
|
|
7
7
|
const { capitalize } = require('./util/strings');
|
|
8
8
|
const { handleHttpError } = require('./util/errors');
|
|
@@ -24,7 +24,7 @@ function requireGlobal(packageId) {
|
|
|
24
24
|
return require(packagePath);
|
|
25
25
|
} catch (error) {
|
|
26
26
|
// Ignore error.
|
|
27
|
-
DEBUG?.(`Failed to find module '${packageId}' in ${globalModulesDir()}
|
|
27
|
+
DEBUG?.(`Failed to find module '${packageId}' in ${globalModulesDir()}:`, error);
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
|
package/lib/mtx/util/logging.js
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
/* eslint-disable no-console */
|
|
2
2
|
|
|
3
3
|
function getMessage(errorText, { error, command, help = command && getCommandHelp(command) } = {}) {
|
|
4
|
-
let message = errorText ? errorText : '';
|
|
4
|
+
let message = errorText ? errorText.trim() : '';
|
|
5
5
|
if (error) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
const includeStack = error.stack && require('../../cds').debug('cli');
|
|
7
|
+
message = message.replace(/(?<!\p{P})$/u, ':'); // Append colon if no punctuation at the end
|
|
8
|
+
message += includeStack ? '\n' : ' ';
|
|
9
|
+
const oriMsg = error.response?.data?.message // case: response body == error
|
|
10
|
+
|| error.response?.data?.error?.message // case: response body == { error }
|
|
11
|
+
|| (includeStack ? '' : error.message);
|
|
12
|
+
message += oriMsg;
|
|
13
|
+
if (includeStack) {
|
|
14
|
+
message += (oriMsg ? '\n' : '') + error.stack;
|
|
13
15
|
}
|
|
14
16
|
}
|
|
15
17
|
if (typeof help === 'string') {
|
package/lib/util/axios.js
CHANGED
|
@@ -1,25 +1,7 @@
|
|
|
1
1
|
module.exports = {
|
|
2
|
-
pruneErrors
|
|
3
|
-
setReqTimeout
|
|
2
|
+
pruneErrors
|
|
4
3
|
};
|
|
5
4
|
|
|
6
|
-
// Will only count the time until the server starts responding, not the time until the request is finished.
|
|
7
|
-
const GENERIC_REQUEST_TIMEOUT_MS = 1500;
|
|
8
|
-
|
|
9
|
-
function setReqTimeout() {
|
|
10
|
-
require('axios').interceptors.request.use(
|
|
11
|
-
config => {
|
|
12
|
-
config.timeout = GENERIC_REQUEST_TIMEOUT_MS;
|
|
13
|
-
return config;
|
|
14
|
-
},
|
|
15
|
-
error => {
|
|
16
|
-
return Promise.reject(error)
|
|
17
|
-
}
|
|
18
|
-
);
|
|
19
|
-
return module.exports;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
|
|
23
5
|
function pruneErrors() {
|
|
24
6
|
function stacklessError(message) {
|
|
25
7
|
const { stackTraceLimit } = Error;
|
|
@@ -72,6 +54,11 @@ function pruneErrors() {
|
|
|
72
54
|
if (status) {
|
|
73
55
|
error.status = status;
|
|
74
56
|
}
|
|
57
|
+
if (data?.passcode_url) {
|
|
58
|
+
error.auth = {
|
|
59
|
+
passcode_url: data.passcode_url
|
|
60
|
+
};
|
|
61
|
+
}
|
|
75
62
|
}
|
|
76
63
|
|
|
77
64
|
if (require('../').debug('req')) {
|