@verdaccio/config 7.0.0-next.5 → 8.0.0-next-8.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 (56) hide show
  1. package/CHANGELOG.md +175 -0
  2. package/LICENSE +1 -1
  3. package/README.md +69 -0
  4. package/build/agent.js +1 -1
  5. package/build/agent.js.map +1 -1
  6. package/build/builder.js +2 -3
  7. package/build/builder.js.map +1 -1
  8. package/build/conf/default.yaml +10 -1
  9. package/build/conf/docker.yaml +10 -1
  10. package/build/conf/index.js.map +1 -1
  11. package/build/config-path.d.ts +2 -3
  12. package/build/config-path.js +51 -22
  13. package/build/config-path.js.map +1 -1
  14. package/build/config-utils.js +14 -3
  15. package/build/config-utils.js.map +1 -1
  16. package/build/config.d.ts +13 -5
  17. package/build/config.js +71 -35
  18. package/build/config.js.map +1 -1
  19. package/build/index.js +1 -1
  20. package/build/index.js.map +1 -1
  21. package/build/package-access.js +5 -2
  22. package/build/package-access.js.map +1 -1
  23. package/build/parse.js +1 -1
  24. package/build/parse.js.map +1 -1
  25. package/build/security.js +2 -1
  26. package/build/security.js.map +1 -1
  27. package/build/serverSettings.js.map +1 -1
  28. package/build/token.d.ts +1 -0
  29. package/build/token.js +2 -0
  30. package/build/token.js.map +1 -1
  31. package/build/uplinks.js +1 -1
  32. package/build/uplinks.js.map +1 -1
  33. package/build/user.js.map +1 -1
  34. package/package.json +8 -10
  35. package/src/builder.ts +1 -2
  36. package/src/conf/default.yaml +10 -1
  37. package/src/conf/docker.yaml +10 -1
  38. package/src/config-path.ts +57 -25
  39. package/src/config-utils.ts +13 -2
  40. package/src/config.ts +79 -35
  41. package/src/package-access.ts +5 -1
  42. package/src/security.ts +1 -0
  43. package/src/token.ts +2 -0
  44. package/test/__snapshots__/builder.spec.ts.snap +2 -2
  45. package/test/__snapshots__/config-parsing.spec.ts.snap +2 -2
  46. package/test/agent.spec.ts +44 -0
  47. package/test/builder.spec.ts +61 -0
  48. package/test/config-parsing.spec.ts +1 -0
  49. package/test/config-utils.spec.ts +1 -0
  50. package/test/config.path.spec.ts +102 -74
  51. package/test/config.spec.ts +92 -4
  52. package/test/package-access.spec.ts +28 -1
  53. package/test/token.spec.ts +2 -0
  54. package/test/uplinks.spec.ts +2 -0
  55. package/test/utils.spec.ts +2 -0
  56. package/jest.config.js +0 -10
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@verdaccio/config",
3
- "version": "7.0.0-next.5",
3
+ "version": "8.0.0-next-8.0",
4
4
  "description": "logger",
5
5
  "main": "./build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -26,20 +26,18 @@
26
26
  "verdaccio"
27
27
  ],
28
28
  "engines": {
29
- "node": ">=12"
29
+ "node": ">=14"
30
30
  },
31
31
  "dependencies": {
32
- "@verdaccio/core": "7.0.0-next.5",
33
- "@verdaccio/utils": "7.0.0-next.5",
34
- "debug": "4.3.4",
32
+ "@verdaccio/core": "8.0.0-next-8.0",
33
+ "@verdaccio/utils": "7.0.1-next-8.0",
34
+ "debug": "4.3.7",
35
35
  "js-yaml": "4.1.0",
36
36
  "lodash": "4.17.21",
37
- "minimatch": "7.4.6",
38
- "yup": "0.32.11"
37
+ "minimatch": "7.4.6"
39
38
  },
40
39
  "devDependencies": {
41
- "@types/minimatch": "5.1.2",
42
- "@types/yup": "0.32.0"
40
+ "@types/minimatch": "5.1.2"
43
41
  },
44
42
  "funding": {
45
43
  "type": "opencollective",
@@ -47,7 +45,7 @@
47
45
  },
48
46
  "scripts": {
49
47
  "clean": "rimraf ./build",
50
- "test": "jest",
48
+ "test": "vitest run",
51
49
  "type-check": "tsc --noEmit -p tsconfig.build.json",
52
50
  "build:types": "tsc --emitDeclarationOnly -p tsconfig.build.json",
53
51
  "build:js": "babel src/ --out-dir build/ --copy-files --extensions \".ts,.tsx\" --source-maps",
package/src/builder.ts CHANGED
@@ -19,8 +19,7 @@ export default class ConfigBuilder {
19
19
  private config: ConfigYaml;
20
20
 
21
21
  public constructor(config?: Partial<ConfigYaml>) {
22
- // @ts-ignore
23
- this.config = config ?? { uplinks: {}, packages: {}, security: {} };
22
+ this.config = merge(config, { uplinks: {}, packages: {}, security: {} });
24
23
  }
25
24
 
26
25
  public static build(config?: Partial<ConfigYaml>): ConfigBuilder {
@@ -18,6 +18,12 @@ storage: ./storage
18
18
  # https://verdaccio.org/docs/webui
19
19
  web:
20
20
  title: Verdaccio
21
+ # custom colors for header background and font
22
+ # primaryColor: "#4b5e40"
23
+ # custom logos and favicon
24
+ # logo: ./path/to/logo.png
25
+ # logoDark: ./path/to/logoDark.png
26
+ # favicon: ./path/to/favicon.ico
21
27
  # comment out to disable gravatar support
22
28
  # gravatar: false
23
29
  # by default packages are ordercer ascendant (asc|desc)
@@ -35,6 +41,7 @@ web:
35
41
  # showSearch: true
36
42
  # showRaw: true
37
43
  # showDownloadTarball: true
44
+ # showUplinks: true
38
45
  # HTML tags injected after manifest <scripts/>
39
46
  # scriptsBodyAfter:
40
47
  # - '<script type="text/javascript" src="https://my.company.com/customJS.min.js"></script>'
@@ -82,7 +89,7 @@ packages:
82
89
  # and three keywords: "$all", "$anonymous", "$authenticated"
83
90
  access: $all
84
91
 
85
- # allow all known users to publish/publish packages
92
+ # allow all known users to publish/unpublish packages
86
93
  # (anyone can register by default, remember?)
87
94
  publish: $authenticated
88
95
  unpublish: $authenticated
@@ -113,6 +120,7 @@ server:
113
120
  # https://verdaccio.org/docs/configuration#offline-publish
114
121
  # publish:
115
122
  # allow_offline: false
123
+ # check_owners: false
116
124
 
117
125
  # https://verdaccio.org/docs/configuration#url-prefix
118
126
  # url_prefix: /verdaccio/
@@ -179,6 +187,7 @@ server:
179
187
  middlewares:
180
188
  audit:
181
189
  enabled: true
190
+ # timeout: 10000
182
191
 
183
192
  # https://verdaccio.org/docs/logger
184
193
  # log settings
@@ -21,6 +21,12 @@ plugins: /verdaccio/plugins
21
21
  # https://verdaccio.org/docs/webui
22
22
  web:
23
23
  title: Verdaccio
24
+ # custom colors for header background and font
25
+ # primaryColor: "#4b5e40"
26
+ # custom logos and favicon
27
+ # logo: ./path/to/logo.png
28
+ # logoDark: ./path/to/logoDark.png
29
+ # favicon: ./path/to/favicon.ico
24
30
  # Comment out to disable gravatar support
25
31
  # gravatar: false
26
32
  # By default packages are ordered ascendant (asc|desc)
@@ -38,6 +44,7 @@ web:
38
44
  # showSearch: true
39
45
  # showRaw: true
40
46
  # showDownloadTarball: true
47
+ # showUplinks: true
41
48
  # HTML tags injected after manifest <scripts/>
42
49
  # scriptsBodyAfter:
43
50
  # - '<script type="text/javascript" src="https://my.company.com/customJS.min.js"></script>'
@@ -86,7 +93,7 @@ packages:
86
93
  # and three keywords: "$all", "$anonymous", "$authenticated"
87
94
  access: $all
88
95
 
89
- # Allow all known users to publish/publish packages
96
+ # Allow all known users to publish/unpublish packages
90
97
  # (anyone can register by default, remember?)
91
98
  publish: $authenticated
92
99
  unpublish: $authenticated
@@ -119,6 +126,7 @@ server:
119
126
  # https://verdaccio.org/docs/configuration#offline-publish
120
127
  # publish:
121
128
  # allow_offline: false
129
+ # check_owners: false
122
130
 
123
131
  # https://verdaccio.org/docs/configuration#url-prefix
124
132
  # url_prefix: /verdaccio/
@@ -185,6 +193,7 @@ server:
185
193
  middlewares:
186
194
  audit:
187
195
  enabled: true
196
+ # timeout: 10000
188
197
 
189
198
  # https://verdaccio.org/docs/logger
190
199
  # log settings
@@ -1,16 +1,13 @@
1
1
  import buildDebug from 'debug';
2
2
  import fs from 'fs';
3
3
  import _ from 'lodash';
4
+ import os from 'os';
4
5
  import path from 'path';
5
6
 
6
- import { CHARACTER_ENCODING } from '@verdaccio/core';
7
-
8
7
  import { fileExists, folderExists } from './config-utils';
9
8
 
10
9
  const CONFIG_FILE = 'config.yaml';
11
10
  const XDG = 'xdg';
12
- const WIN = 'win';
13
- const WIN32 = 'win32';
14
11
  // eslint-disable-next-line
15
12
  const pkgJSON = {
16
13
  name: 'verdaccio',
@@ -18,23 +15,27 @@ const pkgJSON = {
18
15
 
19
16
  export type SetupDirectory = {
20
17
  path: string;
21
- type: string;
18
+ type: 'xdg' | 'win' | 'win32' | 'def' | 'old';
22
19
  };
23
20
 
24
- const debug = buildDebug('verdaccio:config');
21
+ const debug = buildDebug('verdaccio:config:config-path');
25
22
 
26
23
  /**
27
24
  * Find and get the first config file that match.
28
25
  * @return {String} the config file path
29
26
  */
30
27
  function findConfigFile(configPath?: string): string {
28
+ debug('searching for config file %o', configPath);
31
29
  if (typeof configPath !== 'undefined') {
32
- return path.resolve(configPath);
30
+ const configLocation = path.resolve(configPath);
31
+ debug('custom location %s', configLocation);
32
+ return configLocation;
33
33
  }
34
34
 
35
35
  const configPaths: SetupDirectory[] = getConfigPaths();
36
36
  debug('%o posible locations found', configPaths.length);
37
- if (_.isEmpty(configPaths)) {
37
+ if (configPaths.length === 0) {
38
+ debug('no configuration files can be processed');
38
39
  // this should never happens
39
40
  throw new Error('no configuration files can be processed');
40
41
  }
@@ -45,12 +46,12 @@ function findConfigFile(configPath?: string): string {
45
46
  );
46
47
 
47
48
  if (typeof primaryConf !== 'undefined') {
48
- debug('previous location exist already %s', primaryConf?.path);
49
+ debug('at least one primary location detected at %s', primaryConf?.path);
49
50
  return primaryConf.path;
50
51
  }
51
-
52
- // @ts-ignore
53
- return createConfigFile(_.head(configPaths)).path;
52
+ debug('no previous location found, creating a new one');
53
+ debug('generating the first match path location %s', configPaths[0].path);
54
+ return createConfigFile(configPaths[0]).path;
54
55
  }
55
56
 
56
57
  function createConfigFile(configLocation: SetupDirectory): SetupDirectory {
@@ -63,25 +64,37 @@ function createConfigFile(configLocation: SetupDirectory): SetupDirectory {
63
64
  return configLocation;
64
65
  }
65
66
 
66
- export function readDefaultConfig(): Buffer {
67
+ export function readDefaultConfig(): string {
67
68
  const pathDefaultConf: string = path.resolve(__dirname, 'conf/default.yaml');
68
69
  try {
69
- debug('default configuration file %s', pathDefaultConf);
70
+ debug('the path of default config used from %s', pathDefaultConf);
70
71
  fs.accessSync(pathDefaultConf, fs.constants.R_OK);
72
+ debug('configuration file has enough permissions for reading');
71
73
  } catch {
74
+ debug('configuration file does not have enough permissions for reading');
72
75
  throw new TypeError('configuration file does not have enough permissions for reading');
73
76
  }
74
- // @ts-ignore
75
- return fs.readFileSync(pathDefaultConf, CHARACTER_ENCODING.UTF8);
77
+
78
+ return fs.readFileSync(pathDefaultConf, 'utf8');
76
79
  }
77
80
 
78
81
  function createConfigFolder(configLocation): void {
79
- fs.mkdirSync(path.dirname(configLocation.path), { recursive: true });
80
- debug(`Creating default config file in %o`, configLocation?.path);
82
+ const folder = path.dirname(configLocation.path);
83
+ debug(`creating default config file folder at %o`, folder);
84
+ fs.mkdirSync(folder, { recursive: true });
85
+ debug(`folder %o created`, folder);
81
86
  }
82
87
 
83
- function updateStorageLinks(configLocation, defaultConfig): string {
88
+ /**
89
+ * Update the storage links to the new location if it is necessary.
90
+ * @param configLocation
91
+ * @param defaultConfig
92
+ * @returns
93
+ */
94
+ function updateStorageLinks(configLocation: SetupDirectory, defaultConfig: string): string {
95
+ debug('checking storage links for %s and type %s', configLocation.path, configLocation.type);
84
96
  if (configLocation.type !== XDG) {
97
+ debug(`skip storage override for %s`, configLocation.type);
85
98
  return defaultConfig;
86
99
  }
87
100
 
@@ -114,7 +127,11 @@ function getConfigPaths(): SetupDirectory[] {
114
127
 
115
128
  return listPaths.reduce(function (acc, currentValue: SetupDirectory | void): SetupDirectory[] {
116
129
  if (typeof currentValue !== 'undefined') {
117
- debug('directory detected path %s for type %s', currentValue?.path, currentValue.type);
130
+ debug(
131
+ 'posible directory path generated %s for type %s',
132
+ currentValue?.path,
133
+ currentValue.type
134
+ );
118
135
  acc.push(currentValue);
119
136
  }
120
137
  return acc;
@@ -123,11 +140,22 @@ function getConfigPaths(): SetupDirectory[] {
123
140
 
124
141
  /**
125
142
  * Get XDG_CONFIG_HOME or HOME location (usually unix)
143
+ *
144
+ * The XDG_CONFIG_HOME environment variable is also part of the XDG Base Directory Specification,
145
+ * which aims to standardize the locations where applications store configuration files and other
146
+ * user-specific data on Unix-like operating systems.
147
+ *
148
+ *
149
+ *
150
+ * https://specifications.freedesktop.org/basedir-spec/latest/
151
+ *
152
+ *
126
153
  * @returns
127
154
  */
128
155
  const getXDGDirectory = (): SetupDirectory | void => {
129
156
  const xDGConfigPath =
130
157
  process.env.XDG_CONFIG_HOME || (process.env.HOME && path.join(process.env.HOME, '.config'));
158
+ debug('XDGConfig folder path %s', xDGConfigPath);
131
159
  if (xDGConfigPath && folderExists(xDGConfigPath)) {
132
160
  debug('XDGConfig folder path %s', xDGConfigPath);
133
161
  return {
@@ -143,11 +171,11 @@ const getXDGDirectory = (): SetupDirectory | void => {
143
171
  * @returns
144
172
  */
145
173
  const getWindowsDirectory = (): SetupDirectory | void => {
146
- if (process.platform === WIN32 && process.env.APPDATA && folderExists(process.env.APPDATA)) {
147
- debug('is windows appdata: %s', process.env.APPDATA);
174
+ if (os.platform() === 'win32' && process.env.APPDATA && folderExists(process.env.APPDATA)) {
175
+ debug('windows appdata folder path %s', process.env.APPDATA);
148
176
  return {
149
177
  path: path.resolve(path.join(process.env.APPDATA, pkgJSON.name, CONFIG_FILE)),
150
- type: WIN,
178
+ type: 'win',
151
179
  };
152
180
  }
153
181
  };
@@ -158,8 +186,10 @@ const getWindowsDirectory = (): SetupDirectory | void => {
158
186
  * @returns
159
187
  */
160
188
  const getRelativeDefaultDirectory = (): SetupDirectory => {
189
+ const relativePath = path.resolve(path.join('.', pkgJSON.name, CONFIG_FILE));
190
+ debug('relative folder path %s', relativePath);
161
191
  return {
162
- path: path.resolve(path.join('.', pkgJSON.name, CONFIG_FILE)),
192
+ path: relativePath,
163
193
  type: 'def',
164
194
  };
165
195
  };
@@ -169,8 +199,10 @@ const getRelativeDefaultDirectory = (): SetupDirectory => {
169
199
  * @returns
170
200
  */
171
201
  const getOldDirectory = (): SetupDirectory => {
202
+ const oldPath = path.resolve(path.join('.', CONFIG_FILE));
203
+ debug('old folder path %s', oldPath);
172
204
  return {
173
- path: path.resolve(path.join('.', CONFIG_FILE)),
205
+ path: oldPath,
174
206
  type: 'old',
175
207
  };
176
208
  };
@@ -1,5 +1,8 @@
1
+ import buildDebug from 'debug';
1
2
  import fs from 'fs';
2
3
 
4
+ const debug = buildDebug('verdaccio:config:config-utils');
5
+
3
6
  /**
4
7
  * Check whether the path already exist.
5
8
  * @param {String} path
@@ -7,9 +10,13 @@ import fs from 'fs';
7
10
  */
8
11
  export function folderExists(path: string): boolean {
9
12
  try {
13
+ debug('check folder exist', path);
10
14
  const stat = fs.statSync(path);
11
- return stat.isDirectory();
15
+ const isDirectory = stat.isDirectory();
16
+ debug('folder exist', isDirectory);
17
+ return isDirectory;
12
18
  } catch (_: any) {
19
+ debug('folder %s does not exist', path);
13
20
  return false;
14
21
  }
15
22
  }
@@ -21,9 +28,13 @@ export function folderExists(path: string): boolean {
21
28
  */
22
29
  export function fileExists(path: string): boolean {
23
30
  try {
31
+ debug('check file exist', path);
24
32
  const stat = fs.statSync(path);
25
- return stat.isFile();
33
+ const isFile = stat.isFile();
34
+ debug('file exist', isFile);
35
+ return isFile;
26
36
  } catch (_: any) {
37
+ debug('file %s does not exist', path);
27
38
  return false;
28
39
  }
29
40
  }
package/src/config.ts CHANGED
@@ -36,6 +36,13 @@ export const defaultUserRateLimiting = {
36
36
  max: 1000,
37
37
  };
38
38
 
39
+ export function isNodeVersionGreaterThan21() {
40
+ const [major, minor] = process.versions.node.split('.').map(Number);
41
+ return major > 21 || (major === 21 && minor >= 0);
42
+ }
43
+
44
+ const TOKEN_VALID_LENGTH = 32;
45
+
39
46
  /**
40
47
  * Coordinates the application configuration
41
48
  */
@@ -56,21 +63,20 @@ class Config implements AppConfig {
56
63
  public plugins: string | void | null;
57
64
  public security: Security;
58
65
  public serverSettings: ServerSettingsConf;
66
+ private configOverrideOptions: { forceMigrateToSecureLegacySignature: boolean };
59
67
  // @ts-ignore
60
68
  public secret: string;
61
69
  public flags: FlagsConfig;
62
70
  public userRateLimit: RateLimit;
63
- private configOptions: { forceEnhancedLegacySignature: boolean };
64
71
  public constructor(
65
72
  config: ConfigYaml & { config_path: string },
66
73
  // forceEnhancedLegacySignature is a property that
67
74
  // allows switch a new legacy aes signature token signature
68
75
  // for older versions do not want to have this new signature model
69
76
  // this property must be false
70
- configOptions = { forceEnhancedLegacySignature: true }
77
+ configOverrideOptions = { forceMigrateToSecureLegacySignature: true }
71
78
  ) {
72
79
  const self = this;
73
- this.configOptions = configOptions;
74
80
  this.storage = process.env.VERDACCIO_STORAGE_PATH || config.storage;
75
81
  if (!config.configPath) {
76
82
  // backport self_path for previous to version 6
@@ -80,11 +86,21 @@ class Config implements AppConfig {
80
86
  throw new Error('configPath property is required');
81
87
  }
82
88
  }
89
+ this.configOverrideOptions = configOverrideOptions;
83
90
  this.configPath = config.configPath;
84
91
  this.self_path = this.configPath;
85
92
  debug('config path: %s', this.configPath);
86
93
  this.plugins = config.plugins;
87
- this.security = _.merge(defaultSecurity, config.security);
94
+ this.security = _.merge(
95
+ // override the default security configuration via constructor
96
+ _.merge(defaultSecurity, {
97
+ api: {
98
+ migrateToSecureLegacySignature:
99
+ this.configOverrideOptions.forceMigrateToSecureLegacySignature,
100
+ },
101
+ }),
102
+ config.security
103
+ );
88
104
  this.serverSettings = serverSettings;
89
105
  this.flags = {
90
106
  searchRemote: config.flags?.searchRemote ?? true,
@@ -135,14 +151,8 @@ class Config implements AppConfig {
135
151
  }
136
152
  }
137
153
 
138
- public getEnhancedLegacySignature() {
139
- if (typeof this?.security.enhancedLegacySignature !== 'undefined') {
140
- if (this.security.enhancedLegacySignature === true) {
141
- return true;
142
- }
143
- return false;
144
- }
145
- return this.configOptions.forceEnhancedLegacySignature;
154
+ public getMigrateToSecureLegacySignature() {
155
+ return this.security.api.migrateToSecureLegacySignature;
146
156
  }
147
157
 
148
158
  public getConfigPath() {
@@ -158,36 +168,70 @@ class Config implements AppConfig {
158
168
  }
159
169
 
160
170
  /**
161
- * Store or create whether receive a secret key
171
+ * Verify if the secret complies with the required structure
172
+ * - If the secret is not provided, it will generate a new one
173
+ * - For any Node.js version the new secret will be 32 characters long (to allow compatibility with modern Node.js versions)
174
+ * - If the secret is provided:
175
+ * - If Node.js 22 or higher, the secret must be 32 characters long thus the application will fail on startup
176
+ * - If Node.js 21 or lower, the secret will be used as is but will display a deprecation warning
177
+ * - If the property `security.api.migrateToSecureLegacySignature` is provided and set to true, the secret will be
178
+ * generated with the new signature model
162
179
  * @secret external secret key
163
180
  */
164
181
  public checkSecretKey(secret?: string): string {
165
- debug('check secret key');
182
+ debug('checking secret key init');
166
183
  if (typeof secret === 'string' && _.isEmpty(secret) === false) {
184
+ debug('checking secret key length %s', secret.length);
185
+ if (secret.length > TOKEN_VALID_LENGTH) {
186
+ if (isNodeVersionGreaterThan21()) {
187
+ debug('is node version greater than 21');
188
+ if (this.getMigrateToSecureLegacySignature() === true) {
189
+ this.secret = generateRandomSecretKey();
190
+ debug('rewriting secret key with length %s', this.secret.length);
191
+ return this.secret;
192
+ }
193
+ // oops, user needs to generate a new secret key
194
+ debug(
195
+ 'secret does not comply with the required length, current length %d, application will fail on startup',
196
+ secret.length
197
+ );
198
+ throw new Error(
199
+ `Invalid storage secret key length, must be 32 characters long but is ${secret.length}.
200
+ The secret length in Node.js 22 or higher must be 32 characters long. Please consider generate a new one.
201
+ Learn more at https://verdaccio.org/docs/configuration/#.verdaccio-db`
202
+ );
203
+ } else {
204
+ debug('is node version lower than 22');
205
+ if (this.getMigrateToSecureLegacySignature() === true) {
206
+ this.secret = generateRandomSecretKey();
207
+ debug('rewriting secret key with length %s', this.secret.length);
208
+ return this.secret;
209
+ }
210
+ debug('triggering deprecation warning for secret key length %s', secret.length);
211
+ // still using Node.js versions previous to 22, but we need to emit a deprecation warning
212
+ // deprecation warning, secret key is too long and must be 32
213
+ // this will be removed in the next major release and will produce an error
214
+ warningUtils.emit(Codes.VERWAR007);
215
+ this.secret = secret;
216
+ return this.secret;
217
+ }
218
+ } else if (secret.length === TOKEN_VALID_LENGTH) {
219
+ debug('detected valid secret key length %s', secret.length);
220
+ this.secret = secret;
221
+ return this.secret;
222
+ }
223
+ debug('reusing previous key with length %s', secret.length);
167
224
  this.secret = secret;
168
- debug('reusing previous key');
169
- return secret;
170
- }
171
- // generate a new a secret key
172
- // FUTURE: this might be an external secret key, perhaps within config file?
173
- debug('generating a new secret key');
174
-
175
- if (this.getEnhancedLegacySignature()) {
176
- debug('key generated with "enhanced" legacy signature user config');
177
- this.secret = generateRandomSecretKey();
225
+ return this.secret;
178
226
  } else {
179
- debug('key generated with legacy signature user config');
180
- this.secret = generateRandomHexString(32);
181
- }
182
- // set this to false allow use old token signature and is not recommended
183
- // only use for migration reasons, major release will remove this property and
184
- // set it by default
185
- if (this.security?.enhancedLegacySignature === false) {
186
- warningUtils.emit(Codes.VERWAR005);
187
- }
227
+ // generate a new a secret key
228
+ // FUTURE: this might be an external secret key, perhaps within config file?
229
+ debug('generating a new secret key');
230
+ this.secret = generateRandomSecretKey();
231
+ debug('generated a new secret key length %s', this.secret?.length);
188
232
 
189
- debug('generated a new secret key length %s', this.secret?.length);
190
- return this.secret;
233
+ return this.secret;
234
+ }
191
235
  }
192
236
  }
193
237
 
@@ -1,9 +1,12 @@
1
1
  import assert from 'assert';
2
+ import buildDebug from 'debug';
2
3
  import _ from 'lodash';
3
4
 
4
5
  import { errorUtils } from '@verdaccio/core';
5
6
  import { PackageAccess } from '@verdaccio/types';
6
7
 
8
+ const debug = buildDebug('verdaccio:config:utils');
9
+
7
10
  export interface LegacyPackageList {
8
11
  [key: string]: PackageAccess;
9
12
  }
@@ -27,7 +30,7 @@ export const PACKAGE_ACCESS = {
27
30
 
28
31
  export function normalizeUserList(groupsList: any): any {
29
32
  const result: any[] = [];
30
- if (_.isNil(groupsList)) {
33
+ if (_.isNil(groupsList) || _.isEmpty(groupsList)) {
31
34
  return result;
32
35
  }
33
36
 
@@ -61,6 +64,7 @@ export function normalisePackageAccess(packages: LegacyPackageList): LegacyPacka
61
64
  for (const pkg in packages) {
62
65
  if (Object.prototype.hasOwnProperty.call(packages, pkg)) {
63
66
  const packageAccess = packages[pkg];
67
+ debug('package access %s for %s ', packageAccess, pkg);
64
68
  const isInvalid = _.isObject(packageAccess) && _.isArray(packageAccess) === false;
65
69
  assert(isInvalid, `CONFIG: bad "'${pkg}'" package description (object expected)`);
66
70
 
package/src/security.ts CHANGED
@@ -13,6 +13,7 @@ const defaultWebTokenOptions: JWTOptions = {
13
13
 
14
14
  const defaultApiTokenConf: APITokenOptions = {
15
15
  legacy: true,
16
+ migrateToSecureLegacySignature: true,
16
17
  };
17
18
 
18
19
  export const defaultSecurity: Security = {
package/src/token.ts CHANGED
@@ -1,9 +1,11 @@
1
1
  import { randomBytes } from 'crypto';
2
2
 
3
+ // TODO: code duplicated at @verdaccio/signature
3
4
  export const TOKEN_VALID_LENGTH = 32;
4
5
 
5
6
  /**
6
7
  * Secret key must have 32 characters.
8
+ * // TODO: code duplicated at @verdaccio/signature
7
9
  */
8
10
  export function generateRandomSecretKey(): string {
9
11
  return randomBytes(TOKEN_VALID_LENGTH).toString('base64').substring(0, TOKEN_VALID_LENGTH);
@@ -1,6 +1,6 @@
1
- // Jest Snapshot v1, https://goo.gl/fbAQLP
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
2
 
3
- exports[`Config builder should create a configuration file as yaml 1`] = `
3
+ exports[`Config builder > should create a configuration file as yaml 1`] = `
4
4
  "uplinks:
5
5
  upstream:
6
6
  url: https://registry.verdaccio.org
@@ -1,6 +1,6 @@
1
- // Jest Snapshot v1, https://goo.gl/fbAQLP
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
2
 
3
- exports[`parse fromJStoYAML basic conversion roundtrip 1`] = `
3
+ exports[`parse > fromJStoYAML > basic conversion roundtrip 1`] = `
4
4
  "storage: ./storage_default_storage
5
5
  uplinks:
6
6
  npmjs:
@@ -0,0 +1,44 @@
1
+ import { describe, expect, test } from 'vitest';
2
+
3
+ import { getUserAgent } from '../src';
4
+
5
+ describe('getUserAgent', () => {
6
+ test('should return custom user agent when customUserAgent is true', () => {
7
+ const customUserAgent = true;
8
+ const version = '1.0.0';
9
+ const name = 'MyAgent';
10
+
11
+ const result = getUserAgent(customUserAgent, version, name);
12
+
13
+ expect(result).toBe('MyAgent/1.0.0');
14
+ });
15
+
16
+ test('should return custom user agent when customUserAgent is a non-empty string', () => {
17
+ const customUserAgent = 'CustomAgent/1.0.0';
18
+ const version = '1.0.0';
19
+ const name = 'MyAgent';
20
+
21
+ const result = getUserAgent(customUserAgent, version, name);
22
+
23
+ expect(result).toBe('CustomAgent/1.0.0');
24
+ });
25
+
26
+ test('should return "hidden" when customUserAgent is false', () => {
27
+ const customUserAgent = false;
28
+ const version = '1.0.0';
29
+ const name = 'MyAgent';
30
+
31
+ const result = getUserAgent(customUserAgent, version, name);
32
+
33
+ expect(result).toBe('hidden');
34
+ });
35
+
36
+ test('should return default user agent when customUserAgent is undefined', () => {
37
+ const version = '1.0.0';
38
+ const name = 'MyAgent';
39
+
40
+ const result = getUserAgent(undefined, version, name);
41
+
42
+ expect(result).toBe('MyAgent/1.0.0');
43
+ });
44
+ });