@sap/cds-dk 7.6.0 → 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.
@@ -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
@@ -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
+ const { getMessage } = require('./util/logging');
6
7
 
7
8
  require('../util/axios').pruneErrors();
8
- const { getMessage } = require('./util/logging');
9
9
 
10
10
  const DEBUG = cds.debug('cli');
11
+ const REASONS = { invalid_scope: /\binvalid_scope\b/ };
11
12
 
12
- function addTokenInfo(params, authData) {
13
- params.set('token', authData.access_token);
14
- if (authData.expires_in) {
15
- params.set('tokenExpirationDate', Date.now() + authData.expires_in * 1000);
16
- } else {
17
- params.delete('tokenExpirationDate');
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.refresh_token) {
20
- params.set('refreshToken', authData.refresh_token);
21
- } else {
22
- params.delete('refreshToken');
27
+ if (authData?.passcode_url) {
28
+ params.set('passcodeUrl', authData.passcode_url);
23
29
  }
24
30
  }
25
- function addUrlInfo(params, authData) {
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
- yield {
48
- method: 'post',
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 retry;
65
- let authData;
66
- do {
67
- let response;
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 (error) {
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
- if (response) {
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
- if (authData.passcode_url) {
94
- addUrlInfo(params, authData);
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
- await retrieveTokenOrPasscodeUrl(params);
109
- if (!params.has('token')) {
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 `Failed to login: no valid token or passcode provided. Get a passcode${
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
  }
@@ -105,22 +105,17 @@ async function getTokenBaseUrl(params, renewUrl) {
105
105
  return url.toString();
106
106
  };
107
107
 
108
- let existingPath;
109
- const oauthPaths = [OAUTH_PATH_STREAMLINED_MTX];
110
- for (const path of oauthPaths) {
111
- try {
112
- await axios.head(appUrlWith(path));
113
- existingPath = path;
114
- break;
115
- } catch (error) {
116
- if ([401, 404].includes(error.status)) {
117
- continue;
118
- }
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)) {
119
114
  throw error;
120
115
  }
116
+ DEBUG?.(`Falling back to legacy token URL: ${error.message}`);
121
117
  }
122
- existingPath = existingPath ?? OAUTH_PATH_OLD_MTX; // REVISIT can this just be included in the array?
123
- return appUrlWith(existingPath);
118
+ return appUrlWith(path);
124
119
  }
125
120
 
126
121
  function getKeyringAccountName(params) {
@@ -184,7 +179,7 @@ class SettingsManager {
184
179
  }
185
180
 
186
181
  if (params.get('clearOtherTokenStorage')) {
187
- // Delete token from the other storage.
182
+ // Delete data from the other storage.
188
183
  params.delete('clearOtherTokenStorage');
189
184
  await this.deleteToken(params, { fromOtherStorage: true });
190
185
  }
@@ -195,7 +190,7 @@ class SettingsManager {
195
190
  await this.setKeytar(params);
196
191
  await this._saveAuthToKeyring(params, params.toEntries(Persistence.auth));
197
192
  } else {
198
- console.log('Note: the authentication token is not saved by default. To save the token for later commands, please run `cds login`.');
193
+ console.log('Note: authentication data is not persisted by default. To save tokens for later commands, please run `cds login`.');
199
194
  }
200
195
  }
201
196
 
@@ -586,11 +581,11 @@ class SettingsManager {
586
581
  return emptyData(all);
587
582
  }
588
583
 
589
- let allAuth
584
+ let allAuth;
590
585
  try {
591
586
  allAuth = JSON.parse((await fs.promises.readFile(CONFIG.paths.auth)).toString());
592
587
  } catch (err) {
593
- DEBUG?.('File contains invalid saved auth');
588
+ DEBUG?.(`${capitalize(TOKEN_STORAGE_DESC.plain)} contains invalid saved auth`);
594
589
  return emptyData(all);
595
590
  }
596
591
 
@@ -599,14 +594,14 @@ class SettingsManager {
599
594
  }
600
595
 
601
596
  if (!allAuth[appUrl]) {
602
- DEBUG?.(`File contains no authentication data for app URL ${appUrl}`);
597
+ DEBUG?.(`${capitalize(TOKEN_STORAGE_DESC.plain)} contains no authentication data for app URL ${appUrl}`);
603
598
  return new ParamCollection();
604
599
  }
605
600
 
606
601
  const authForSubdomain = allAuth[appUrl][subdomain];
607
602
 
608
603
  if (!authForSubdomain) {
609
- DEBUG?.(`File contains no authentication data for subdomain ${subdomain} (app URL: ${appUrl})`);
604
+ DEBUG?.(`${capitalize(TOKEN_STORAGE_DESC.plain)} contains no authentication data for subdomain ${subdomain} (app URL: ${appUrl})`);
610
605
  return new ParamCollection();
611
606
  }
612
607
 
@@ -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()}`, error);
27
+ DEBUG?.(`Failed to find module '${packageId}' in ${globalModulesDir()}:`, error);
28
28
  }
29
29
  }
30
30
 
@@ -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
- message += '\n' + (
7
- error.response?.data?.message // response body == error
8
- || error.response?.data?.error?.message // response body == { error }
9
- || error.message // no response attached
10
- );
11
- if (error.stack && require('../../cds').debug('cli')) {
12
- message += '\n' + error.stack;
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
@@ -54,6 +54,11 @@ function pruneErrors() {
54
54
  if (status) {
55
55
  error.status = status;
56
56
  }
57
+ if (data?.passcode_url) {
58
+ error.auth = {
59
+ passcode_url: data.passcode_url
60
+ };
61
+ }
57
62
  }
58
63
 
59
64
  if (require('../').debug('req')) {