@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.
- package/CHANGELOG.md +175 -0
- package/LICENSE +1 -1
- package/README.md +69 -0
- package/build/agent.js +1 -1
- package/build/agent.js.map +1 -1
- package/build/builder.js +2 -3
- package/build/builder.js.map +1 -1
- package/build/conf/default.yaml +10 -1
- package/build/conf/docker.yaml +10 -1
- package/build/conf/index.js.map +1 -1
- package/build/config-path.d.ts +2 -3
- package/build/config-path.js +51 -22
- package/build/config-path.js.map +1 -1
- package/build/config-utils.js +14 -3
- package/build/config-utils.js.map +1 -1
- package/build/config.d.ts +13 -5
- package/build/config.js +71 -35
- package/build/config.js.map +1 -1
- package/build/index.js +1 -1
- package/build/index.js.map +1 -1
- package/build/package-access.js +5 -2
- package/build/package-access.js.map +1 -1
- package/build/parse.js +1 -1
- package/build/parse.js.map +1 -1
- package/build/security.js +2 -1
- package/build/security.js.map +1 -1
- package/build/serverSettings.js.map +1 -1
- package/build/token.d.ts +1 -0
- package/build/token.js +2 -0
- package/build/token.js.map +1 -1
- package/build/uplinks.js +1 -1
- package/build/uplinks.js.map +1 -1
- package/build/user.js.map +1 -1
- package/package.json +8 -10
- package/src/builder.ts +1 -2
- package/src/conf/default.yaml +10 -1
- package/src/conf/docker.yaml +10 -1
- package/src/config-path.ts +57 -25
- package/src/config-utils.ts +13 -2
- package/src/config.ts +79 -35
- package/src/package-access.ts +5 -1
- package/src/security.ts +1 -0
- package/src/token.ts +2 -0
- package/test/__snapshots__/builder.spec.ts.snap +2 -2
- package/test/__snapshots__/config-parsing.spec.ts.snap +2 -2
- package/test/agent.spec.ts +44 -0
- package/test/builder.spec.ts +61 -0
- package/test/config-parsing.spec.ts +1 -0
- package/test/config-utils.spec.ts +1 -0
- package/test/config.path.spec.ts +102 -74
- package/test/config.spec.ts +92 -4
- package/test/package-access.spec.ts +28 -1
- package/test/token.spec.ts +2 -0
- package/test/uplinks.spec.ts +2 -0
- package/test/utils.spec.ts +2 -0
- package/jest.config.js +0 -10
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@verdaccio/config",
|
|
3
|
-
"version": "
|
|
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": ">=
|
|
29
|
+
"node": ">=14"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@verdaccio/core": "
|
|
33
|
-
"@verdaccio/utils": "7.0.
|
|
34
|
-
"debug": "4.3.
|
|
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": "
|
|
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
|
-
|
|
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 {
|
package/src/conf/default.yaml
CHANGED
|
@@ -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/
|
|
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
|
package/src/conf/docker.yaml
CHANGED
|
@@ -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/
|
|
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
|
package/src/config-path.ts
CHANGED
|
@@ -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:
|
|
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
|
-
|
|
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 (
|
|
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('
|
|
49
|
+
debug('at least one primary location detected at %s', primaryConf?.path);
|
|
49
50
|
return primaryConf.path;
|
|
50
51
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
return createConfigFile(
|
|
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():
|
|
67
|
+
export function readDefaultConfig(): string {
|
|
67
68
|
const pathDefaultConf: string = path.resolve(__dirname, 'conf/default.yaml');
|
|
68
69
|
try {
|
|
69
|
-
debug('default
|
|
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
|
-
|
|
75
|
-
return fs.readFileSync(pathDefaultConf,
|
|
77
|
+
|
|
78
|
+
return fs.readFileSync(pathDefaultConf, 'utf8');
|
|
76
79
|
}
|
|
77
80
|
|
|
78
81
|
function createConfigFolder(configLocation): void {
|
|
79
|
-
|
|
80
|
-
debug(`
|
|
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
|
-
|
|
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(
|
|
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 (
|
|
147
|
-
debug('
|
|
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:
|
|
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:
|
|
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:
|
|
205
|
+
path: oldPath,
|
|
174
206
|
type: 'old',
|
|
175
207
|
};
|
|
176
208
|
};
|
package/src/config-utils.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
139
|
-
|
|
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
|
-
*
|
|
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('
|
|
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
|
-
|
|
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
|
-
|
|
180
|
-
this
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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
|
-
|
|
190
|
-
|
|
233
|
+
return this.secret;
|
|
234
|
+
}
|
|
191
235
|
}
|
|
192
236
|
}
|
|
193
237
|
|
package/src/package-access.ts
CHANGED
|
@@ -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
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
|
-
//
|
|
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
|
-
//
|
|
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
|
+
});
|