@verdaccio/auth 8.0.0-next-8.2 → 8.0.0-next-8.3
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 +14 -0
- package/package.json +9 -9
- package/test/auth-utils-middleware.spec.ts +260 -0
- package/test/auth-utils.spec.ts +16 -208
- package/test/auth.spec.ts +24 -23
- package/jest.config.js +0 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @verdaccio/auth
|
|
2
2
|
|
|
3
|
+
## 8.0.0-next-8.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [124e5f2]
|
|
8
|
+
- Updated dependencies [4afca90]
|
|
9
|
+
- verdaccio-htpasswd@13.0.0-next-8.3
|
|
10
|
+
- @verdaccio/loaders@8.0.0-next-8.3
|
|
11
|
+
- @verdaccio/core@8.0.0-next-8.3
|
|
12
|
+
- @verdaccio/config@8.0.0-next-8.3
|
|
13
|
+
- @verdaccio/utils@8.1.0-next-8.3
|
|
14
|
+
- @verdaccio/signature@8.0.0-next-8.1
|
|
15
|
+
- @verdaccio/logger@8.0.0-next-8.3
|
|
16
|
+
|
|
3
17
|
## 8.0.0-next-8.2
|
|
4
18
|
|
|
5
19
|
### Minor Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@verdaccio/auth",
|
|
3
|
-
"version": "8.0.0-next-8.
|
|
3
|
+
"version": "8.0.0-next-8.3",
|
|
4
4
|
"description": "logger",
|
|
5
5
|
"main": "./build/index.js",
|
|
6
6
|
"types": "./build/index.d.ts",
|
|
@@ -29,18 +29,18 @@
|
|
|
29
29
|
},
|
|
30
30
|
"license": "MIT",
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@verdaccio/config": "8.0.0-next-8.
|
|
33
|
-
"@verdaccio/core": "8.0.0-next-8.
|
|
34
|
-
"@verdaccio/loaders": "8.0.0-next-8.
|
|
35
|
-
"@verdaccio/logger": "8.0.0-next-8.
|
|
32
|
+
"@verdaccio/config": "8.0.0-next-8.3",
|
|
33
|
+
"@verdaccio/core": "8.0.0-next-8.3",
|
|
34
|
+
"@verdaccio/loaders": "8.0.0-next-8.3",
|
|
35
|
+
"@verdaccio/logger": "8.0.0-next-8.3",
|
|
36
36
|
"@verdaccio/signature": "8.0.0-next-8.1",
|
|
37
|
-
"@verdaccio/utils": "
|
|
37
|
+
"@verdaccio/utils": "8.1.0-next-8.3",
|
|
38
38
|
"debug": "4.3.7",
|
|
39
39
|
"lodash": "4.17.21",
|
|
40
|
-
"verdaccio-htpasswd": "13.0.0-next-8.
|
|
40
|
+
"verdaccio-htpasswd": "13.0.0-next-8.3"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
|
-
"@verdaccio/middleware": "8.0.0-next-8.
|
|
43
|
+
"@verdaccio/middleware": "8.0.0-next-8.3",
|
|
44
44
|
"@verdaccio/types": "13.0.0-next-8.1",
|
|
45
45
|
"express": "4.21.0",
|
|
46
46
|
"supertest": "7.0.0"
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
},
|
|
52
52
|
"scripts": {
|
|
53
53
|
"clean": "rimraf ./build",
|
|
54
|
-
"test": "
|
|
54
|
+
"test": "vitest run",
|
|
55
55
|
"type-check": "tsc --noEmit -p tsconfig.build.json",
|
|
56
56
|
"build:types": "tsc --emitDeclarationOnly -p tsconfig.build.json",
|
|
57
57
|
"build:js": "babel src/ --out-dir build/ --copy-files --extensions \".ts,.tsx\" --source-maps",
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { describe, expect, test, vi } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
Config as AppConfig,
|
|
7
|
+
createAnonymousRemoteUser,
|
|
8
|
+
createRemoteUser,
|
|
9
|
+
parseConfigFile,
|
|
10
|
+
} from '@verdaccio/config';
|
|
11
|
+
import { getDefaultConfig } from '@verdaccio/config';
|
|
12
|
+
import { TOKEN_BEARER } from '@verdaccio/core';
|
|
13
|
+
import { setup } from '@verdaccio/logger';
|
|
14
|
+
import { signPayload } from '@verdaccio/signature';
|
|
15
|
+
import { Config, RemoteUser, Security } from '@verdaccio/types';
|
|
16
|
+
import { buildToken, buildUserBuffer } from '@verdaccio/utils';
|
|
17
|
+
|
|
18
|
+
import { Auth, getApiToken, getMiddlewareCredentials, verifyJWTPayload } from '../src';
|
|
19
|
+
|
|
20
|
+
setup({});
|
|
21
|
+
|
|
22
|
+
const parseConfigurationFile = (conf) => {
|
|
23
|
+
const { name, ext } = path.parse(conf);
|
|
24
|
+
const format = ext.startsWith('.') ? ext.substring(1) : 'yaml';
|
|
25
|
+
|
|
26
|
+
return path.join(__dirname, `./partials/config/${format}/security/${name}.${format}`);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
describe('Auth utilities', () => {
|
|
30
|
+
vi.setConfig({ testTimeout: 20000 });
|
|
31
|
+
|
|
32
|
+
const parseConfigurationSecurityFile = (name) => {
|
|
33
|
+
return parseConfigurationFile(`security/${name}`);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
function getConfig(configFileName: string, secret: string) {
|
|
37
|
+
const conf = parseConfigFile(parseConfigurationSecurityFile(configFileName));
|
|
38
|
+
// @ts-ignore
|
|
39
|
+
const secConf = _.merge(getDefaultConfig(), conf);
|
|
40
|
+
secConf.secret = secret;
|
|
41
|
+
const config: Config = new AppConfig(secConf);
|
|
42
|
+
|
|
43
|
+
return config;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function getTokenByConfiguration(
|
|
47
|
+
configFileName: string,
|
|
48
|
+
username: string,
|
|
49
|
+
password: string,
|
|
50
|
+
secret = '12345',
|
|
51
|
+
methodToSpy: string,
|
|
52
|
+
methodNotBeenCalled: string
|
|
53
|
+
): Promise<string> {
|
|
54
|
+
const config: Config = getConfig(configFileName, secret);
|
|
55
|
+
const auth: Auth = new Auth(config);
|
|
56
|
+
await auth.init();
|
|
57
|
+
// @ts-ignore
|
|
58
|
+
const spy = vi.spyOn(auth, methodToSpy);
|
|
59
|
+
// @ts-ignore
|
|
60
|
+
const spyNotCalled = vi.spyOn(auth, methodNotBeenCalled);
|
|
61
|
+
const user: RemoteUser = {
|
|
62
|
+
name: username,
|
|
63
|
+
real_groups: ['test', '$all', '$authenticated', '@all', '@authenticated', 'all'],
|
|
64
|
+
groups: ['company-role1', 'company-role2'],
|
|
65
|
+
};
|
|
66
|
+
const token = await getApiToken(auth, config, user, password);
|
|
67
|
+
expect(spy).toHaveBeenCalled();
|
|
68
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
69
|
+
expect(spyNotCalled).not.toHaveBeenCalled();
|
|
70
|
+
expect(token).toBeDefined();
|
|
71
|
+
|
|
72
|
+
return token as string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
describe('getMiddlewareCredentials test', () => {
|
|
76
|
+
describe('should get AES credentials', () => {
|
|
77
|
+
test.concurrent('should unpack aes token and credentials bearer auth', async () => {
|
|
78
|
+
const secret = 'b2df428b9929d3ace7c598bbf4e496b2';
|
|
79
|
+
const user = 'test';
|
|
80
|
+
const pass = 'test';
|
|
81
|
+
const token = await getTokenByConfiguration(
|
|
82
|
+
'security-legacy',
|
|
83
|
+
user,
|
|
84
|
+
pass,
|
|
85
|
+
secret,
|
|
86
|
+
'aesEncrypt',
|
|
87
|
+
'jwtEncrypt'
|
|
88
|
+
);
|
|
89
|
+
const config: Config = getConfig('security-legacy', secret);
|
|
90
|
+
const security: Security = config.security;
|
|
91
|
+
const credentials = getMiddlewareCredentials(security, secret, `Bearer ${token}`);
|
|
92
|
+
expect(credentials).toBeDefined();
|
|
93
|
+
// @ts-ignore
|
|
94
|
+
expect(credentials.user).toEqual(user);
|
|
95
|
+
// @ts-ignore
|
|
96
|
+
expect(credentials.password).toEqual(pass);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test.concurrent('should unpack aes token and credentials basic auth', async () => {
|
|
100
|
+
const secret = 'b2df428b9929d3ace7c598bbf4e496b2';
|
|
101
|
+
const user = 'test';
|
|
102
|
+
const pass = 'test';
|
|
103
|
+
// basic authentication need send user as base64
|
|
104
|
+
const token = buildUserBuffer(user, pass).toString('base64');
|
|
105
|
+
const config: Config = getConfig('security-legacy', secret);
|
|
106
|
+
const security: Security = config.security;
|
|
107
|
+
const credentials = getMiddlewareCredentials(security, secret, `Basic ${token}`);
|
|
108
|
+
expect(credentials).toBeDefined();
|
|
109
|
+
// @ts-ignore
|
|
110
|
+
expect(credentials.user).toEqual(user);
|
|
111
|
+
// @ts-ignore
|
|
112
|
+
expect(credentials.password).toEqual(pass);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test.concurrent('should return empty credential wrong secret key', async () => {
|
|
116
|
+
const secret = 'b2df428b9929d3ace7c598bbf4e496b2';
|
|
117
|
+
const token = await getTokenByConfiguration(
|
|
118
|
+
'security-legacy',
|
|
119
|
+
'test',
|
|
120
|
+
'test',
|
|
121
|
+
secret,
|
|
122
|
+
'aesEncrypt',
|
|
123
|
+
'jwtEncrypt'
|
|
124
|
+
);
|
|
125
|
+
const config: Config = getConfig('security-legacy', secret);
|
|
126
|
+
const security: Security = config.security;
|
|
127
|
+
const credentials = getMiddlewareCredentials(
|
|
128
|
+
security,
|
|
129
|
+
'b2df428b9929d3ace7c598bbf4e496_BAD_TOKEN',
|
|
130
|
+
buildToken(TOKEN_BEARER, token)
|
|
131
|
+
);
|
|
132
|
+
expect(credentials).not.toBeDefined();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test.concurrent('should return empty credential wrong scheme', async () => {
|
|
136
|
+
const secret = 'b2df428b9929d3ace7c598bbf4e496b2';
|
|
137
|
+
const token = await getTokenByConfiguration(
|
|
138
|
+
'security-legacy',
|
|
139
|
+
'test',
|
|
140
|
+
'test',
|
|
141
|
+
secret,
|
|
142
|
+
'aesEncrypt',
|
|
143
|
+
'jwtEncrypt'
|
|
144
|
+
);
|
|
145
|
+
const config: Config = getConfig('security-legacy', secret);
|
|
146
|
+
const security: Security = config.security;
|
|
147
|
+
const credentials = getMiddlewareCredentials(
|
|
148
|
+
security,
|
|
149
|
+
secret,
|
|
150
|
+
buildToken('BAD_SCHEME', token)
|
|
151
|
+
);
|
|
152
|
+
expect(credentials).not.toBeDefined();
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test.concurrent('should return empty credential corrupted payload', async () => {
|
|
156
|
+
const secret = 'b2df428b9929d3ace7c598bbf4e496b2';
|
|
157
|
+
const config: Config = getConfig('security-legacy', secret);
|
|
158
|
+
const auth: Auth = new Auth(config);
|
|
159
|
+
await auth.init();
|
|
160
|
+
// @ts-expect-error
|
|
161
|
+
const token = auth.aesEncrypt(null);
|
|
162
|
+
const security: Security = config.security;
|
|
163
|
+
const credentials = getMiddlewareCredentials(
|
|
164
|
+
security,
|
|
165
|
+
secret,
|
|
166
|
+
buildToken(TOKEN_BEARER, token as string)
|
|
167
|
+
);
|
|
168
|
+
expect(credentials).not.toBeDefined();
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
describe('verifyJWTPayload', () => {
|
|
173
|
+
test('should fail on verify the token and return anonymous users', () => {
|
|
174
|
+
expect(verifyJWTPayload('fakeToken', 'b2df428b9929d3ace7c598bbf4e496b2')).toEqual(
|
|
175
|
+
createAnonymousRemoteUser()
|
|
176
|
+
);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test('should verify the token and return a remote user', async () => {
|
|
180
|
+
const remoteUser = createRemoteUser('foo', []);
|
|
181
|
+
const token = await signPayload(remoteUser, '12345');
|
|
182
|
+
const verifiedToken = verifyJWTPayload(token, '12345');
|
|
183
|
+
expect(verifiedToken.groups).toEqual(remoteUser.groups);
|
|
184
|
+
expect(verifiedToken.name).toEqual(remoteUser.name);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
describe('should get JWT credentials', () => {
|
|
189
|
+
test('should return anonymous whether token is corrupted', () => {
|
|
190
|
+
const config: Config = getConfig('security-jwt', '12345');
|
|
191
|
+
const security: Security = config.security;
|
|
192
|
+
const credentials = getMiddlewareCredentials(
|
|
193
|
+
security,
|
|
194
|
+
'12345',
|
|
195
|
+
buildToken(TOKEN_BEARER, 'fakeToken')
|
|
196
|
+
) as RemoteUser;
|
|
197
|
+
|
|
198
|
+
expect(credentials).toBeDefined();
|
|
199
|
+
expect(credentials.name).not.toBeDefined();
|
|
200
|
+
expect(credentials.real_groups).toBeDefined();
|
|
201
|
+
|
|
202
|
+
expect(credentials.groups).toEqual(['$all', '$anonymous', '@all', '@anonymous']);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test('should return anonymous whether token and scheme are corrupted', () => {
|
|
206
|
+
const config: Config = getConfig('security-jwt', '12345');
|
|
207
|
+
const security: Security = config.security;
|
|
208
|
+
const credentials = getMiddlewareCredentials(
|
|
209
|
+
security,
|
|
210
|
+
'12345',
|
|
211
|
+
buildToken('FakeScheme', 'fakeToken')
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
expect(credentials).not.toBeDefined();
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
test('should verify successfully a JWT token', async () => {
|
|
218
|
+
const secret = 'b2df428b9929d3ace7c598bbf4e496b2';
|
|
219
|
+
const user = 'test';
|
|
220
|
+
const config: Config = getConfig('security-jwt', secret);
|
|
221
|
+
const token = await getTokenByConfiguration(
|
|
222
|
+
'security-jwt',
|
|
223
|
+
user,
|
|
224
|
+
'secretTest',
|
|
225
|
+
secret,
|
|
226
|
+
'jwtEncrypt',
|
|
227
|
+
'aesEncrypt'
|
|
228
|
+
);
|
|
229
|
+
const security: Security = config.security;
|
|
230
|
+
const credentials = getMiddlewareCredentials(
|
|
231
|
+
security,
|
|
232
|
+
secret,
|
|
233
|
+
buildToken(TOKEN_BEARER, token)
|
|
234
|
+
) as RemoteUser;
|
|
235
|
+
expect(credentials).toBeDefined();
|
|
236
|
+
|
|
237
|
+
expect(credentials.name).toEqual(user);
|
|
238
|
+
expect(credentials.real_groups).toBeDefined();
|
|
239
|
+
expect(credentials.real_groups).toEqual([
|
|
240
|
+
'test',
|
|
241
|
+
'$all',
|
|
242
|
+
'$authenticated',
|
|
243
|
+
'@all',
|
|
244
|
+
'@authenticated',
|
|
245
|
+
'all',
|
|
246
|
+
]);
|
|
247
|
+
expect(credentials.groups).toEqual([
|
|
248
|
+
'company-role1',
|
|
249
|
+
'company-role2',
|
|
250
|
+
'test',
|
|
251
|
+
'$all',
|
|
252
|
+
'$authenticated',
|
|
253
|
+
'@all',
|
|
254
|
+
'@authenticated',
|
|
255
|
+
'all',
|
|
256
|
+
]);
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
});
|
package/test/auth-utils.spec.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import _ from 'lodash';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import { describe, expect, test, vi } from 'vitest';
|
|
3
4
|
|
|
4
5
|
import {
|
|
5
6
|
Config as AppConfig,
|
|
@@ -9,17 +10,11 @@ import {
|
|
|
9
10
|
parseConfigFile,
|
|
10
11
|
} from '@verdaccio/config';
|
|
11
12
|
import { getDefaultConfig } from '@verdaccio/config';
|
|
12
|
-
import {
|
|
13
|
-
API_ERROR,
|
|
14
|
-
CHARACTER_ENCODING,
|
|
15
|
-
TOKEN_BEARER,
|
|
16
|
-
VerdaccioError,
|
|
17
|
-
errorUtils,
|
|
18
|
-
} from '@verdaccio/core';
|
|
13
|
+
import { API_ERROR, CHARACTER_ENCODING, VerdaccioError, errorUtils } from '@verdaccio/core';
|
|
19
14
|
import { setup } from '@verdaccio/logger';
|
|
20
|
-
import { aesDecrypt,
|
|
21
|
-
import { Config, RemoteUser
|
|
22
|
-
import {
|
|
15
|
+
import { aesDecrypt, verifyPayload } from '@verdaccio/signature';
|
|
16
|
+
import { Config, RemoteUser } from '@verdaccio/types';
|
|
17
|
+
import { getAuthenticatedMessage } from '@verdaccio/utils';
|
|
23
18
|
|
|
24
19
|
import {
|
|
25
20
|
ActionsAllowed,
|
|
@@ -28,8 +23,6 @@ import {
|
|
|
28
23
|
allow_action,
|
|
29
24
|
getApiToken,
|
|
30
25
|
getDefaultPlugins,
|
|
31
|
-
getMiddlewareCredentials,
|
|
32
|
-
verifyJWTPayload,
|
|
33
26
|
} from '../src';
|
|
34
27
|
|
|
35
28
|
setup({});
|
|
@@ -42,7 +35,7 @@ const parseConfigurationFile = (conf) => {
|
|
|
42
35
|
};
|
|
43
36
|
|
|
44
37
|
describe('Auth utilities', () => {
|
|
45
|
-
|
|
38
|
+
vi.setConfig({ testTimeout: 20000 });
|
|
46
39
|
|
|
47
40
|
const parseConfigurationSecurityFile = (name) => {
|
|
48
41
|
return parseConfigurationFile(`security/${name}`);
|
|
@@ -52,6 +45,7 @@ describe('Auth utilities', () => {
|
|
|
52
45
|
const conf = parseConfigFile(parseConfigurationSecurityFile(configFileName));
|
|
53
46
|
// @ts-ignore
|
|
54
47
|
const secConf = _.merge(getDefaultConfig(), conf);
|
|
48
|
+
// @ts-expect-error
|
|
55
49
|
secConf.secret = secret;
|
|
56
50
|
const config: Config = new AppConfig(secConf);
|
|
57
51
|
|
|
@@ -70,9 +64,9 @@ describe('Auth utilities', () => {
|
|
|
70
64
|
const auth: Auth = new Auth(config);
|
|
71
65
|
await auth.init();
|
|
72
66
|
// @ts-ignore
|
|
73
|
-
const spy =
|
|
67
|
+
const spy = vi.spyOn(auth, methodToSpy);
|
|
74
68
|
// @ts-ignore
|
|
75
|
-
const spyNotCalled =
|
|
69
|
+
const spyNotCalled = vi.spyOn(auth, methodNotBeenCalled);
|
|
76
70
|
const user: RemoteUser = {
|
|
77
71
|
name: username,
|
|
78
72
|
real_groups: ['test', '$all', '$authenticated', '@all', '@authenticated', 'all'],
|
|
@@ -123,14 +117,14 @@ describe('Auth utilities', () => {
|
|
|
123
117
|
|
|
124
118
|
describe('getDefaultPlugins', () => {
|
|
125
119
|
test('authentication should fail by default (default)', () => {
|
|
126
|
-
const plugin = getDefaultPlugins({ trace:
|
|
120
|
+
const plugin = getDefaultPlugins({ trace: vi.fn() });
|
|
127
121
|
plugin.authenticate('foo', 'bar', (error: any) => {
|
|
128
122
|
expect(error).toEqual(errorUtils.getForbidden(API_ERROR.BAD_USERNAME_PASSWORD));
|
|
129
123
|
});
|
|
130
124
|
});
|
|
131
125
|
|
|
132
126
|
test('add user should fail by default (default)', () => {
|
|
133
|
-
const plugin = getDefaultPlugins({ trace:
|
|
127
|
+
const plugin = getDefaultPlugins({ trace: vi.fn() });
|
|
134
128
|
// @ts-ignore
|
|
135
129
|
plugin.adduser('foo', 'bar', (error: any) => {
|
|
136
130
|
expect(error).toEqual(errorUtils.getForbidden(API_ERROR.BAD_USERNAME_PASSWORD));
|
|
@@ -151,7 +145,7 @@ describe('Auth utilities', () => {
|
|
|
151
145
|
test.each(['access', 'publish', 'unpublish'])(
|
|
152
146
|
'should restrict %s to anonymous users',
|
|
153
147
|
(type) => {
|
|
154
|
-
allow_action(type as ActionsAllowed, { trace:
|
|
148
|
+
allow_action(type as ActionsAllowed, { trace: vi.fn() })(
|
|
155
149
|
createAnonymousRemoteUser(),
|
|
156
150
|
{
|
|
157
151
|
...packageAccess,
|
|
@@ -168,7 +162,7 @@ describe('Auth utilities', () => {
|
|
|
168
162
|
test.each(['access', 'publish', 'unpublish'])(
|
|
169
163
|
'should allow %s to anonymous users',
|
|
170
164
|
(type) => {
|
|
171
|
-
allow_action(type as ActionsAllowed, { trace:
|
|
165
|
+
allow_action(type as ActionsAllowed, { trace: vi.fn() })(
|
|
172
166
|
createAnonymousRemoteUser(),
|
|
173
167
|
{
|
|
174
168
|
...packageAccess,
|
|
@@ -185,7 +179,7 @@ describe('Auth utilities', () => {
|
|
|
185
179
|
test.each(['access', 'publish', 'unpublish'])(
|
|
186
180
|
'should allow %s only if user is anonymous if the logged user has groups',
|
|
187
181
|
(type) => {
|
|
188
|
-
allow_action(type as ActionsAllowed, { trace:
|
|
182
|
+
allow_action(type as ActionsAllowed, { trace: vi.fn() })(
|
|
189
183
|
createRemoteUser('juan', ['maintainer', 'admin']),
|
|
190
184
|
{
|
|
191
185
|
...packageAccess,
|
|
@@ -202,7 +196,7 @@ describe('Auth utilities', () => {
|
|
|
202
196
|
test.each(['access', 'publish', 'unpublish'])(
|
|
203
197
|
'should allow %s only if user is anonymous match any other groups',
|
|
204
198
|
(type) => {
|
|
205
|
-
allow_action(type as ActionsAllowed, { trace:
|
|
199
|
+
allow_action(type as ActionsAllowed, { trace: vi.fn() })(
|
|
206
200
|
createRemoteUser('juan', ['maintainer', 'admin']),
|
|
207
201
|
{
|
|
208
202
|
...packageAccess,
|
|
@@ -219,7 +213,7 @@ describe('Auth utilities', () => {
|
|
|
219
213
|
test.each(['access', 'publish', 'unpublish'])(
|
|
220
214
|
'should not allow %s anonymous if other groups are defined and does not match',
|
|
221
215
|
(type) => {
|
|
222
|
-
allow_action(type as ActionsAllowed, { trace:
|
|
216
|
+
allow_action(type as ActionsAllowed, { trace: vi.fn() })(
|
|
223
217
|
createRemoteUser('juan', ['maintainer', 'admin']),
|
|
224
218
|
{
|
|
225
219
|
...packageAccess,
|
|
@@ -364,190 +358,4 @@ describe('Auth utilities', () => {
|
|
|
364
358
|
expect(getAuthenticatedMessage('test')).toBe("you are authenticated as 'test'");
|
|
365
359
|
});
|
|
366
360
|
});
|
|
367
|
-
|
|
368
|
-
describe('getMiddlewareCredentials test', () => {
|
|
369
|
-
describe('should get AES credentials', () => {
|
|
370
|
-
test.concurrent('should unpack aes token and credentials bearer auth', async () => {
|
|
371
|
-
const secret = 'b2df428b9929d3ace7c598bbf4e496b2';
|
|
372
|
-
const user = 'test';
|
|
373
|
-
const pass = 'test';
|
|
374
|
-
const token = await getTokenByConfiguration(
|
|
375
|
-
'security-legacy',
|
|
376
|
-
user,
|
|
377
|
-
pass,
|
|
378
|
-
secret,
|
|
379
|
-
'aesEncrypt',
|
|
380
|
-
'jwtEncrypt'
|
|
381
|
-
);
|
|
382
|
-
const config: Config = getConfig('security-legacy', secret);
|
|
383
|
-
const security: Security = config.security;
|
|
384
|
-
const credentials = getMiddlewareCredentials(security, secret, `Bearer ${token}`);
|
|
385
|
-
expect(credentials).toBeDefined();
|
|
386
|
-
// @ts-ignore
|
|
387
|
-
expect(credentials.user).toEqual(user);
|
|
388
|
-
// @ts-ignore
|
|
389
|
-
expect(credentials.password).toEqual(pass);
|
|
390
|
-
});
|
|
391
|
-
|
|
392
|
-
test.concurrent('should unpack aes token and credentials basic auth', async () => {
|
|
393
|
-
const secret = 'b2df428b9929d3ace7c598bbf4e496b2';
|
|
394
|
-
const user = 'test';
|
|
395
|
-
const pass = 'test';
|
|
396
|
-
// basic authentication need send user as base64
|
|
397
|
-
const token = buildUserBuffer(user, pass).toString('base64');
|
|
398
|
-
const config: Config = getConfig('security-legacy', secret);
|
|
399
|
-
const security: Security = config.security;
|
|
400
|
-
const credentials = getMiddlewareCredentials(security, secret, `Basic ${token}`);
|
|
401
|
-
expect(credentials).toBeDefined();
|
|
402
|
-
// @ts-ignore
|
|
403
|
-
expect(credentials.user).toEqual(user);
|
|
404
|
-
// @ts-ignore
|
|
405
|
-
expect(credentials.password).toEqual(pass);
|
|
406
|
-
});
|
|
407
|
-
|
|
408
|
-
test.concurrent('should return empty credential wrong secret key', async () => {
|
|
409
|
-
const secret = 'b2df428b9929d3ace7c598bbf4e496b2';
|
|
410
|
-
const token = await getTokenByConfiguration(
|
|
411
|
-
'security-legacy',
|
|
412
|
-
'test',
|
|
413
|
-
'test',
|
|
414
|
-
secret,
|
|
415
|
-
'aesEncrypt',
|
|
416
|
-
'jwtEncrypt'
|
|
417
|
-
);
|
|
418
|
-
const config: Config = getConfig('security-legacy', secret);
|
|
419
|
-
const security: Security = config.security;
|
|
420
|
-
const credentials = getMiddlewareCredentials(
|
|
421
|
-
security,
|
|
422
|
-
'b2df428b9929d3ace7c598bbf4e496_BAD_TOKEN',
|
|
423
|
-
buildToken(TOKEN_BEARER, token)
|
|
424
|
-
);
|
|
425
|
-
expect(credentials).not.toBeDefined();
|
|
426
|
-
});
|
|
427
|
-
|
|
428
|
-
test.concurrent('should return empty credential wrong scheme', async () => {
|
|
429
|
-
const secret = 'b2df428b9929d3ace7c598bbf4e496b2';
|
|
430
|
-
const token = await getTokenByConfiguration(
|
|
431
|
-
'security-legacy',
|
|
432
|
-
'test',
|
|
433
|
-
'test',
|
|
434
|
-
secret,
|
|
435
|
-
'aesEncrypt',
|
|
436
|
-
'jwtEncrypt'
|
|
437
|
-
);
|
|
438
|
-
const config: Config = getConfig('security-legacy', secret);
|
|
439
|
-
const security: Security = config.security;
|
|
440
|
-
const credentials = getMiddlewareCredentials(
|
|
441
|
-
security,
|
|
442
|
-
secret,
|
|
443
|
-
buildToken('BAD_SCHEME', token)
|
|
444
|
-
);
|
|
445
|
-
expect(credentials).not.toBeDefined();
|
|
446
|
-
});
|
|
447
|
-
|
|
448
|
-
test.concurrent('should return empty credential corrupted payload', async () => {
|
|
449
|
-
const secret = 'b2df428b9929d3ace7c598bbf4e496b2';
|
|
450
|
-
const config: Config = getConfig('security-legacy', secret);
|
|
451
|
-
const auth: Auth = new Auth(config);
|
|
452
|
-
await auth.init();
|
|
453
|
-
// @ts-expect-error
|
|
454
|
-
const token = auth.aesEncrypt(null);
|
|
455
|
-
const security: Security = config.security;
|
|
456
|
-
const credentials = getMiddlewareCredentials(
|
|
457
|
-
security,
|
|
458
|
-
secret,
|
|
459
|
-
buildToken(TOKEN_BEARER, token as string)
|
|
460
|
-
);
|
|
461
|
-
expect(credentials).not.toBeDefined();
|
|
462
|
-
});
|
|
463
|
-
});
|
|
464
|
-
|
|
465
|
-
describe('verifyJWTPayload', () => {
|
|
466
|
-
test('should fail on verify the token and return anonymous users', () => {
|
|
467
|
-
expect(verifyJWTPayload('fakeToken', 'b2df428b9929d3ace7c598bbf4e496b2')).toEqual(
|
|
468
|
-
createAnonymousRemoteUser()
|
|
469
|
-
);
|
|
470
|
-
});
|
|
471
|
-
|
|
472
|
-
test('should verify the token and return a remote user', async () => {
|
|
473
|
-
const remoteUser = createRemoteUser('foo', []);
|
|
474
|
-
const token = await signPayload(remoteUser, '12345');
|
|
475
|
-
const verifiedToken = verifyJWTPayload(token, '12345');
|
|
476
|
-
expect(verifiedToken.groups).toEqual(remoteUser.groups);
|
|
477
|
-
expect(verifiedToken.name).toEqual(remoteUser.name);
|
|
478
|
-
});
|
|
479
|
-
});
|
|
480
|
-
|
|
481
|
-
describe('should get JWT credentials', () => {
|
|
482
|
-
test('should return anonymous whether token is corrupted', () => {
|
|
483
|
-
const config: Config = getConfig('security-jwt', '12345');
|
|
484
|
-
const security: Security = config.security;
|
|
485
|
-
const credentials = getMiddlewareCredentials(
|
|
486
|
-
security,
|
|
487
|
-
'12345',
|
|
488
|
-
buildToken(TOKEN_BEARER, 'fakeToken')
|
|
489
|
-
) as RemoteUser;
|
|
490
|
-
|
|
491
|
-
expect(credentials).toBeDefined();
|
|
492
|
-
expect(credentials.name).not.toBeDefined();
|
|
493
|
-
expect(credentials.real_groups).toBeDefined();
|
|
494
|
-
|
|
495
|
-
expect(credentials.groups).toEqual(['$all', '$anonymous', '@all', '@anonymous']);
|
|
496
|
-
});
|
|
497
|
-
|
|
498
|
-
test('should return anonymous whether token and scheme are corrupted', () => {
|
|
499
|
-
const config: Config = getConfig('security-jwt', '12345');
|
|
500
|
-
const security: Security = config.security;
|
|
501
|
-
const credentials = getMiddlewareCredentials(
|
|
502
|
-
security,
|
|
503
|
-
'12345',
|
|
504
|
-
buildToken('FakeScheme', 'fakeToken')
|
|
505
|
-
);
|
|
506
|
-
|
|
507
|
-
expect(credentials).not.toBeDefined();
|
|
508
|
-
});
|
|
509
|
-
|
|
510
|
-
test('should verify successfully a JWT token', async () => {
|
|
511
|
-
const secret = 'b2df428b9929d3ace7c598bbf4e496b2';
|
|
512
|
-
const user = 'test';
|
|
513
|
-
const config: Config = getConfig('security-jwt', secret);
|
|
514
|
-
const token = await getTokenByConfiguration(
|
|
515
|
-
'security-jwt',
|
|
516
|
-
user,
|
|
517
|
-
'secretTest',
|
|
518
|
-
secret,
|
|
519
|
-
'jwtEncrypt',
|
|
520
|
-
'aesEncrypt'
|
|
521
|
-
);
|
|
522
|
-
const security: Security = config.security;
|
|
523
|
-
const credentials = getMiddlewareCredentials(
|
|
524
|
-
security,
|
|
525
|
-
secret,
|
|
526
|
-
buildToken(TOKEN_BEARER, token)
|
|
527
|
-
) as RemoteUser;
|
|
528
|
-
expect(credentials).toBeDefined();
|
|
529
|
-
|
|
530
|
-
expect(credentials.name).toEqual(user);
|
|
531
|
-
expect(credentials.real_groups).toBeDefined();
|
|
532
|
-
expect(credentials.real_groups).toEqual([
|
|
533
|
-
'test',
|
|
534
|
-
'$all',
|
|
535
|
-
'$authenticated',
|
|
536
|
-
'@all',
|
|
537
|
-
'@authenticated',
|
|
538
|
-
'all',
|
|
539
|
-
]);
|
|
540
|
-
expect(credentials.groups).toEqual([
|
|
541
|
-
'company-role1',
|
|
542
|
-
'company-role2',
|
|
543
|
-
'test',
|
|
544
|
-
'$all',
|
|
545
|
-
'$authenticated',
|
|
546
|
-
'@all',
|
|
547
|
-
'@authenticated',
|
|
548
|
-
'all',
|
|
549
|
-
]);
|
|
550
|
-
});
|
|
551
|
-
});
|
|
552
|
-
});
|
|
553
361
|
});
|
package/test/auth.spec.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import express from 'express';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import supertest from 'supertest';
|
|
4
|
+
import { describe, expect, test, vi } from 'vitest';
|
|
4
5
|
|
|
5
6
|
import { Config as AppConfig, ROLES, createRemoteUser, getDefaultConfig } from '@verdaccio/config';
|
|
6
7
|
import {
|
|
@@ -27,9 +28,9 @@ import {
|
|
|
27
28
|
setup({});
|
|
28
29
|
|
|
29
30
|
// to avoid flaky test generate same ramdom key
|
|
30
|
-
|
|
31
|
+
vi.mock('@verdaccio/utils', async (importOriginal) => {
|
|
31
32
|
return {
|
|
32
|
-
...
|
|
33
|
+
...(await importOriginal<typeof import('@verdaccio/utils')>()),
|
|
33
34
|
// used by enhanced legacy aes signature (minimum 32 characters)
|
|
34
35
|
generateRandomSecretKey: () => 'GCYW/3IJzQI6GvPmy9sbMkFoiL7QLVw',
|
|
35
36
|
// used by legacy aes signature
|
|
@@ -82,7 +83,7 @@ describe('AuthTest', () => {
|
|
|
82
83
|
await auth.init();
|
|
83
84
|
expect(auth).toBeDefined();
|
|
84
85
|
|
|
85
|
-
const callback =
|
|
86
|
+
const callback = vi.fn();
|
|
86
87
|
const groups = ['test'];
|
|
87
88
|
|
|
88
89
|
auth.authenticate('foo', 'bar', callback);
|
|
@@ -109,7 +110,7 @@ describe('AuthTest', () => {
|
|
|
109
110
|
await auth.init();
|
|
110
111
|
expect(auth).toBeDefined();
|
|
111
112
|
|
|
112
|
-
const callback =
|
|
113
|
+
const callback = vi.fn();
|
|
113
114
|
|
|
114
115
|
auth.authenticate('foo', 'bar', callback);
|
|
115
116
|
expect(callback).toHaveBeenCalledTimes(1);
|
|
@@ -128,7 +129,7 @@ describe('AuthTest', () => {
|
|
|
128
129
|
await auth.init();
|
|
129
130
|
expect(auth).toBeDefined();
|
|
130
131
|
|
|
131
|
-
const callback =
|
|
132
|
+
const callback = vi.fn();
|
|
132
133
|
let index = 0;
|
|
133
134
|
|
|
134
135
|
// as defined by https://developer.mozilla.org/en-US/docs/Glossary/Falsy
|
|
@@ -148,7 +149,7 @@ describe('AuthTest', () => {
|
|
|
148
149
|
await auth.init();
|
|
149
150
|
expect(auth).toBeDefined();
|
|
150
151
|
|
|
151
|
-
const callback =
|
|
152
|
+
const callback = vi.fn();
|
|
152
153
|
|
|
153
154
|
for (const value of [true, 1, 'test', {}]) {
|
|
154
155
|
expect(function () {
|
|
@@ -166,7 +167,7 @@ describe('AuthTest', () => {
|
|
|
166
167
|
await auth.init();
|
|
167
168
|
expect(auth).toBeDefined();
|
|
168
169
|
|
|
169
|
-
const callback =
|
|
170
|
+
const callback = vi.fn();
|
|
170
171
|
const value = [];
|
|
171
172
|
|
|
172
173
|
// @ts-ignore
|
|
@@ -183,7 +184,7 @@ describe('AuthTest', () => {
|
|
|
183
184
|
await auth.init();
|
|
184
185
|
expect(auth).toBeDefined();
|
|
185
186
|
|
|
186
|
-
const callback =
|
|
187
|
+
const callback = vi.fn();
|
|
187
188
|
let index = 0;
|
|
188
189
|
|
|
189
190
|
for (const value of [[''], ['1'], ['0'], ['000']]) {
|
|
@@ -231,7 +232,7 @@ describe('AuthTest', () => {
|
|
|
231
232
|
const auth: Auth = new Auth(config);
|
|
232
233
|
await auth.init();
|
|
233
234
|
expect(auth).toBeDefined();
|
|
234
|
-
const callback =
|
|
235
|
+
const callback = vi.fn();
|
|
235
236
|
|
|
236
237
|
auth.changePassword('foo', 'bar', 'newFoo', callback);
|
|
237
238
|
|
|
@@ -246,8 +247,8 @@ describe('AuthTest', () => {
|
|
|
246
247
|
const auth: Auth = new Auth(config);
|
|
247
248
|
await auth.init();
|
|
248
249
|
expect(auth).toBeDefined();
|
|
249
|
-
const callback =
|
|
250
|
-
auth.add_user('foo', 'bar',
|
|
250
|
+
const callback = vi.fn();
|
|
251
|
+
auth.add_user('foo', 'bar', vi.fn());
|
|
251
252
|
auth.changePassword('foo', 'bar', 'newFoo', callback);
|
|
252
253
|
expect(callback).toHaveBeenCalledTimes(1);
|
|
253
254
|
expect(callback).toHaveBeenCalledWith(null, true);
|
|
@@ -265,7 +266,7 @@ describe('AuthTest', () => {
|
|
|
265
266
|
await auth.init();
|
|
266
267
|
expect(auth).toBeDefined();
|
|
267
268
|
|
|
268
|
-
const callback =
|
|
269
|
+
const callback = vi.fn();
|
|
269
270
|
const groups = ['test'];
|
|
270
271
|
|
|
271
272
|
auth.allow_access(
|
|
@@ -287,7 +288,7 @@ describe('AuthTest', () => {
|
|
|
287
288
|
await auth.init();
|
|
288
289
|
expect(auth).toBeDefined();
|
|
289
290
|
|
|
290
|
-
const callback =
|
|
291
|
+
const callback = vi.fn();
|
|
291
292
|
// $all comes from configuration file
|
|
292
293
|
const groups = [ROLES.$ALL];
|
|
293
294
|
|
|
@@ -314,7 +315,7 @@ describe('AuthTest', () => {
|
|
|
314
315
|
await auth.init();
|
|
315
316
|
expect(auth).toBeDefined();
|
|
316
317
|
|
|
317
|
-
const callback =
|
|
318
|
+
const callback = vi.fn();
|
|
318
319
|
const groups = ['test'];
|
|
319
320
|
|
|
320
321
|
auth.allow_publish(
|
|
@@ -336,7 +337,7 @@ describe('AuthTest', () => {
|
|
|
336
337
|
await auth.init();
|
|
337
338
|
expect(auth).toBeDefined();
|
|
338
339
|
|
|
339
|
-
const callback =
|
|
340
|
+
const callback = vi.fn();
|
|
340
341
|
// $all comes from configuration file
|
|
341
342
|
const groups = [ROLES.$AUTH];
|
|
342
343
|
|
|
@@ -361,7 +362,7 @@ describe('AuthTest', () => {
|
|
|
361
362
|
await auth.init();
|
|
362
363
|
expect(auth).toBeDefined();
|
|
363
364
|
|
|
364
|
-
const callback =
|
|
365
|
+
const callback = vi.fn();
|
|
365
366
|
const groups = ['test'];
|
|
366
367
|
|
|
367
368
|
auth.allow_unpublish(
|
|
@@ -395,7 +396,7 @@ describe('AuthTest', () => {
|
|
|
395
396
|
await auth.init();
|
|
396
397
|
expect(auth).toBeDefined();
|
|
397
398
|
|
|
398
|
-
const callback =
|
|
399
|
+
const callback = vi.fn();
|
|
399
400
|
const groups = ['test'];
|
|
400
401
|
|
|
401
402
|
auth.allow_unpublish(
|
|
@@ -418,7 +419,7 @@ describe('AuthTest', () => {
|
|
|
418
419
|
await auth.init();
|
|
419
420
|
expect(auth).toBeDefined();
|
|
420
421
|
|
|
421
|
-
const callback =
|
|
422
|
+
const callback = vi.fn();
|
|
422
423
|
// $all comes from configuration file
|
|
423
424
|
const groups = [ROLES.$AUTH];
|
|
424
425
|
|
|
@@ -445,7 +446,7 @@ describe('AuthTest', () => {
|
|
|
445
446
|
await auth.init();
|
|
446
447
|
expect(auth).toBeDefined();
|
|
447
448
|
|
|
448
|
-
const callback =
|
|
449
|
+
const callback = vi.fn();
|
|
449
450
|
|
|
450
451
|
auth.add_user('juan', 'password', callback);
|
|
451
452
|
|
|
@@ -468,7 +469,7 @@ describe('AuthTest', () => {
|
|
|
468
469
|
await auth.init();
|
|
469
470
|
expect(auth).toBeDefined();
|
|
470
471
|
|
|
471
|
-
const callback =
|
|
472
|
+
const callback = vi.fn();
|
|
472
473
|
|
|
473
474
|
// note: fail uas username make plugin fails
|
|
474
475
|
auth.add_user('fail', 'password', callback);
|
|
@@ -492,7 +493,7 @@ describe('AuthTest', () => {
|
|
|
492
493
|
await auth.init();
|
|
493
494
|
expect(auth).toBeDefined();
|
|
494
495
|
|
|
495
|
-
const callback =
|
|
496
|
+
const callback = vi.fn();
|
|
496
497
|
|
|
497
498
|
// note: fail uas username make plugin fails
|
|
498
499
|
auth.add_user('skip', 'password', callback);
|
|
@@ -516,7 +517,7 @@ describe('AuthTest', () => {
|
|
|
516
517
|
await auth.init();
|
|
517
518
|
expect(auth).toBeDefined();
|
|
518
519
|
|
|
519
|
-
const callback =
|
|
520
|
+
const callback = vi.fn();
|
|
520
521
|
|
|
521
522
|
auth.add_user('something', 'password', callback);
|
|
522
523
|
|
|
@@ -540,7 +541,7 @@ describe('AuthTest', () => {
|
|
|
540
541
|
await auth.init();
|
|
541
542
|
expect(auth).toBeDefined();
|
|
542
543
|
|
|
543
|
-
const callback =
|
|
544
|
+
const callback = vi.fn();
|
|
544
545
|
|
|
545
546
|
auth.add_user('something', 'password', callback);
|
|
546
547
|
|