@verdaccio/auth 8.0.0-next-8.11 → 8.0.0-next-8.13
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/README.md +30 -24
- package/package.json +16 -12
- package/.babelrc +0 -3
- package/CHANGELOG.md +0 -1755
- package/src/auth.ts +0 -607
- package/src/index.ts +0 -3
- package/src/signature-legacy.ts +0 -66
- package/src/types.ts +0 -46
- package/src/utils.ts +0 -252
- package/test/auth-utils-middleware.spec.ts +0 -260
- package/test/auth-utils.spec.ts +0 -362
- package/test/auth.spec.ts +0 -722
- package/test/helper/plugin.ts +0 -43
- package/test/partials/config/yaml/security/security-basic.yaml +0 -12
- package/test/partials/config/yaml/security/security-empty.yaml +0 -1
- package/test/partials/config/yaml/security/security-jwt-legacy-enabled.yaml +0 -10
- package/test/partials/config/yaml/security/security-jwt.yaml +0 -6
- package/test/partials/config/yaml/security/security-legacy-disabled.yaml +0 -3
- package/test/partials/config/yaml/security/security-legacy.yaml +0 -3
- package/test/partials/config/yaml/security/security-missing.yaml +0 -0
- package/test/partials/config/yaml/security/security-no-legacy.yaml +0 -9
- package/test/partials/plugin/verdaccio-access-ok/access.js +0 -9
- package/test/partials/plugin/verdaccio-access-ok/package.json +0 -5
- package/test/partials/plugin/verdaccio-adduser/adduser.js +0 -25
- package/test/partials/plugin/verdaccio-adduser/package.json +0 -5
- package/test/partials/plugin/verdaccio-adduser-legacy/adduser.js +0 -25
- package/test/partials/plugin/verdaccio-adduser-legacy/package.json +0 -5
- package/test/partials/plugin/verdaccio-change-password/change.js +0 -32
- package/test/partials/plugin/verdaccio-change-password/package.json +0 -5
- package/test/partials/plugin/verdaccio-fail/fail.js +0 -14
- package/test/partials/plugin/verdaccio-fail/package.json +0 -5
- package/test/partials/plugin/verdaccio-fail-invalid-method/fail.js +0 -11
- package/test/partials/plugin/verdaccio-fail-invalid-method/package.json +0 -5
- package/test/partials/plugin/verdaccio-passthroug/package.json +0 -5
- package/test/partials/plugin/verdaccio-passthroug/passthroug.js +0 -9
- package/test/partials/plugin/verdaccio-success/package.json +0 -5
- package/test/partials/plugin/verdaccio-success/success.js +0 -9
- package/tsconfig.build.json +0 -9
- package/tsconfig.json +0 -32
package/test/auth-utils.spec.ts
DELETED
|
@@ -1,362 +0,0 @@
|
|
|
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
|
-
ROLES,
|
|
8
|
-
createAnonymousRemoteUser,
|
|
9
|
-
createRemoteUser,
|
|
10
|
-
parseConfigFile,
|
|
11
|
-
} from '@verdaccio/config';
|
|
12
|
-
import { getDefaultConfig } from '@verdaccio/config';
|
|
13
|
-
import { API_ERROR, CHARACTER_ENCODING, VerdaccioError, errorUtils } from '@verdaccio/core';
|
|
14
|
-
import { logger, setup } from '@verdaccio/logger';
|
|
15
|
-
import { aesDecrypt, verifyPayload } from '@verdaccio/signature';
|
|
16
|
-
import { Config, RemoteUser } from '@verdaccio/types';
|
|
17
|
-
import { getAuthenticatedMessage } from '@verdaccio/utils';
|
|
18
|
-
|
|
19
|
-
import {
|
|
20
|
-
ActionsAllowed,
|
|
21
|
-
AllowActionCallbackResponse,
|
|
22
|
-
Auth,
|
|
23
|
-
allow_action,
|
|
24
|
-
getApiToken,
|
|
25
|
-
getDefaultPlugins,
|
|
26
|
-
} from '../src';
|
|
27
|
-
|
|
28
|
-
setup({});
|
|
29
|
-
|
|
30
|
-
const parseConfigurationFile = (conf) => {
|
|
31
|
-
const { name, ext } = path.parse(conf);
|
|
32
|
-
const format = ext.startsWith('.') ? ext.substring(1) : 'yaml';
|
|
33
|
-
|
|
34
|
-
return path.join(__dirname, `./partials/config/${format}/security/${name}.${format}`);
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
describe('Auth utilities', () => {
|
|
38
|
-
vi.setConfig({ testTimeout: 20000 });
|
|
39
|
-
|
|
40
|
-
const parseConfigurationSecurityFile = (name) => {
|
|
41
|
-
return parseConfigurationFile(`security/${name}`);
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
function getConfig(configFileName: string, secret: string) {
|
|
45
|
-
const conf = parseConfigFile(parseConfigurationSecurityFile(configFileName));
|
|
46
|
-
// @ts-ignore
|
|
47
|
-
const secConf = _.merge(getDefaultConfig(), conf);
|
|
48
|
-
// @ts-expect-error
|
|
49
|
-
secConf.secret = secret;
|
|
50
|
-
const config: Config = new AppConfig(secConf);
|
|
51
|
-
|
|
52
|
-
return config;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
async function getTokenByConfiguration(
|
|
56
|
-
configFileName: string,
|
|
57
|
-
username: string,
|
|
58
|
-
password: string,
|
|
59
|
-
secret = '12345',
|
|
60
|
-
methodToSpy: string,
|
|
61
|
-
methodNotBeenCalled: string
|
|
62
|
-
): Promise<string> {
|
|
63
|
-
const config: Config = getConfig(configFileName, secret);
|
|
64
|
-
const auth: Auth = new Auth(config, logger);
|
|
65
|
-
await auth.init();
|
|
66
|
-
// @ts-ignore
|
|
67
|
-
const spy = vi.spyOn(auth, methodToSpy);
|
|
68
|
-
// @ts-ignore
|
|
69
|
-
const spyNotCalled = vi.spyOn(auth, methodNotBeenCalled);
|
|
70
|
-
const user: RemoteUser = {
|
|
71
|
-
name: username,
|
|
72
|
-
real_groups: ['test', '$all', '$authenticated', '@all', '@authenticated', 'all'],
|
|
73
|
-
groups: ['company-role1', 'company-role2'],
|
|
74
|
-
};
|
|
75
|
-
const token = await getApiToken(auth, config, user, password);
|
|
76
|
-
expect(spy).toHaveBeenCalled();
|
|
77
|
-
expect(spy).toHaveBeenCalledTimes(1);
|
|
78
|
-
expect(spyNotCalled).not.toHaveBeenCalled();
|
|
79
|
-
expect(token).toBeDefined();
|
|
80
|
-
|
|
81
|
-
return token as string;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const verifyJWT = (token: string, user: string, password: string, secret: string) => {
|
|
85
|
-
const payload = verifyPayload(token, secret);
|
|
86
|
-
expect(payload.name).toBe(user);
|
|
87
|
-
expect(payload.groups).toBeDefined();
|
|
88
|
-
expect(payload.groups).toEqual([
|
|
89
|
-
'company-role1',
|
|
90
|
-
'company-role2',
|
|
91
|
-
'test',
|
|
92
|
-
'$all',
|
|
93
|
-
'$authenticated',
|
|
94
|
-
'@all',
|
|
95
|
-
'@authenticated',
|
|
96
|
-
'all',
|
|
97
|
-
]);
|
|
98
|
-
expect(payload.real_groups).toBeDefined();
|
|
99
|
-
expect(payload.real_groups).toEqual([
|
|
100
|
-
'test',
|
|
101
|
-
'$all',
|
|
102
|
-
'$authenticated',
|
|
103
|
-
'@all',
|
|
104
|
-
'@authenticated',
|
|
105
|
-
'all',
|
|
106
|
-
]);
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
const verifyAES = (token: string, user: string, password: string, secret: string) => {
|
|
110
|
-
// @ts-ignore
|
|
111
|
-
const payload = aesDecrypt(token, secret).toString(CHARACTER_ENCODING.UTF8);
|
|
112
|
-
const content = payload.split(':');
|
|
113
|
-
|
|
114
|
-
expect(content[0]).toBe(user);
|
|
115
|
-
expect(content[0]).toBe(password);
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
describe('getDefaultPlugins', () => {
|
|
119
|
-
test('authentication should fail by default (default)', () => {
|
|
120
|
-
const plugin = getDefaultPlugins({ trace: vi.fn() });
|
|
121
|
-
plugin.authenticate('foo', 'bar', (error: any) => {
|
|
122
|
-
expect(error).toEqual(errorUtils.getForbidden(API_ERROR.BAD_USERNAME_PASSWORD));
|
|
123
|
-
});
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
test('add user should not fail by default (default)', () => {
|
|
127
|
-
const plugin = getDefaultPlugins({ trace: vi.fn() });
|
|
128
|
-
// @ts-ignore
|
|
129
|
-
plugin.adduser('foo', 'bar', (error: any, access: any) => {
|
|
130
|
-
expect(error).toEqual(null);
|
|
131
|
-
expect(access).toEqual(true);
|
|
132
|
-
});
|
|
133
|
-
});
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
describe('allow_action', () => {
|
|
137
|
-
describe('access/publish/unpublish and anonymous', () => {
|
|
138
|
-
const packageAccess = {
|
|
139
|
-
name: 'foo',
|
|
140
|
-
version: undefined,
|
|
141
|
-
access: ['foo'],
|
|
142
|
-
unpublish: false,
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
// const type = 'access';
|
|
146
|
-
test.each(['access', 'publish', 'unpublish'])(
|
|
147
|
-
'should restrict %s to anonymous users',
|
|
148
|
-
(type) => {
|
|
149
|
-
allow_action(type as ActionsAllowed, { trace: vi.fn() })(
|
|
150
|
-
createAnonymousRemoteUser(),
|
|
151
|
-
{
|
|
152
|
-
...packageAccess,
|
|
153
|
-
[type]: ['foo'],
|
|
154
|
-
},
|
|
155
|
-
(error: VerdaccioError | null, allowed: AllowActionCallbackResponse) => {
|
|
156
|
-
expect(error).not.toBeNull();
|
|
157
|
-
expect(allowed).toBeUndefined();
|
|
158
|
-
}
|
|
159
|
-
);
|
|
160
|
-
}
|
|
161
|
-
);
|
|
162
|
-
|
|
163
|
-
test.each(['access', 'publish', 'unpublish'])(
|
|
164
|
-
'should allow %s to anonymous users',
|
|
165
|
-
(type) => {
|
|
166
|
-
allow_action(type as ActionsAllowed, { trace: vi.fn() })(
|
|
167
|
-
createAnonymousRemoteUser(),
|
|
168
|
-
{
|
|
169
|
-
...packageAccess,
|
|
170
|
-
[type]: [ROLES.$ANONYMOUS],
|
|
171
|
-
},
|
|
172
|
-
(error: VerdaccioError | null, allowed: AllowActionCallbackResponse) => {
|
|
173
|
-
expect(error).toBeNull();
|
|
174
|
-
expect(allowed).toBe(true);
|
|
175
|
-
}
|
|
176
|
-
);
|
|
177
|
-
}
|
|
178
|
-
);
|
|
179
|
-
|
|
180
|
-
test.each(['access', 'publish', 'unpublish'])(
|
|
181
|
-
'should allow %s only if user is anonymous if the logged user has groups',
|
|
182
|
-
(type) => {
|
|
183
|
-
allow_action(type as ActionsAllowed, { trace: vi.fn() })(
|
|
184
|
-
createRemoteUser('juan', ['maintainer', 'admin']),
|
|
185
|
-
{
|
|
186
|
-
...packageAccess,
|
|
187
|
-
[type]: [ROLES.$ANONYMOUS],
|
|
188
|
-
},
|
|
189
|
-
(error: VerdaccioError | null, allowed: AllowActionCallbackResponse) => {
|
|
190
|
-
expect(error).not.toBeNull();
|
|
191
|
-
expect(allowed).toBeUndefined();
|
|
192
|
-
}
|
|
193
|
-
);
|
|
194
|
-
}
|
|
195
|
-
);
|
|
196
|
-
|
|
197
|
-
test.each(['access', 'publish', 'unpublish'])(
|
|
198
|
-
'should allow %s only if user is anonymous match any other groups',
|
|
199
|
-
(type) => {
|
|
200
|
-
allow_action(type as ActionsAllowed, { trace: vi.fn() })(
|
|
201
|
-
createRemoteUser('juan', ['maintainer', 'admin']),
|
|
202
|
-
{
|
|
203
|
-
...packageAccess,
|
|
204
|
-
[type]: ['admin', 'some-other-group', ROLES.$ANONYMOUS],
|
|
205
|
-
},
|
|
206
|
-
(error: VerdaccioError | null, allowed: AllowActionCallbackResponse) => {
|
|
207
|
-
expect(error).toBeNull();
|
|
208
|
-
expect(allowed).toBe(true);
|
|
209
|
-
}
|
|
210
|
-
);
|
|
211
|
-
}
|
|
212
|
-
);
|
|
213
|
-
|
|
214
|
-
test.each(['access', 'publish', 'unpublish'])(
|
|
215
|
-
'should not allow %s anonymous if other groups are defined and does not match',
|
|
216
|
-
(type) => {
|
|
217
|
-
allow_action(type as ActionsAllowed, { trace: vi.fn() })(
|
|
218
|
-
createRemoteUser('juan', ['maintainer', 'admin']),
|
|
219
|
-
{
|
|
220
|
-
...packageAccess,
|
|
221
|
-
[type]: ['bla-bla-group', 'some-other-group', ROLES.$ANONYMOUS],
|
|
222
|
-
},
|
|
223
|
-
(error: VerdaccioError | null, allowed: AllowActionCallbackResponse) => {
|
|
224
|
-
expect(error).not.toBeNull();
|
|
225
|
-
expect(allowed).toBeUndefined();
|
|
226
|
-
}
|
|
227
|
-
);
|
|
228
|
-
}
|
|
229
|
-
);
|
|
230
|
-
});
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
describe('createRemoteUser', () => {
|
|
234
|
-
test('create remote user', () => {
|
|
235
|
-
expect(createRemoteUser('test', [])).toEqual({
|
|
236
|
-
name: 'test',
|
|
237
|
-
real_groups: [],
|
|
238
|
-
groups: ['$all', '$authenticated', '@all', '@authenticated', 'all'],
|
|
239
|
-
});
|
|
240
|
-
});
|
|
241
|
-
test('create remote user with groups', () => {
|
|
242
|
-
expect(createRemoteUser('test', ['group1', 'group2'])).toEqual({
|
|
243
|
-
name: 'test',
|
|
244
|
-
real_groups: ['group1', 'group2'],
|
|
245
|
-
groups: ['group1', 'group2', '$all', '$authenticated', '@all', '@authenticated', 'all'],
|
|
246
|
-
});
|
|
247
|
-
});
|
|
248
|
-
test('create anonymous remote user', () => {
|
|
249
|
-
expect(createAnonymousRemoteUser()).toEqual({
|
|
250
|
-
name: undefined,
|
|
251
|
-
real_groups: [],
|
|
252
|
-
groups: ['$all', '$anonymous', '@all', '@anonymous'],
|
|
253
|
-
});
|
|
254
|
-
});
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
describe('getApiToken test', () => {
|
|
258
|
-
test('should sign token with aes and security missing', async () => {
|
|
259
|
-
const token = await getTokenByConfiguration(
|
|
260
|
-
'security-missing',
|
|
261
|
-
'test',
|
|
262
|
-
'test',
|
|
263
|
-
'b2df428b9929d3ace7c598bbf4e496b2',
|
|
264
|
-
'aesEncrypt',
|
|
265
|
-
'jwtEncrypt'
|
|
266
|
-
);
|
|
267
|
-
|
|
268
|
-
verifyAES(token, 'test', 'test', 'b2df428b9929d3ace7c598bbf4e496b2');
|
|
269
|
-
expect(_.isString(token)).toBeTruthy();
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
test('should sign token with aes and security empty', async () => {
|
|
273
|
-
const token = await getTokenByConfiguration(
|
|
274
|
-
'security-empty',
|
|
275
|
-
'test',
|
|
276
|
-
'test',
|
|
277
|
-
'b2df428b9929d3ace7c598bbf4e496b2',
|
|
278
|
-
'aesEncrypt',
|
|
279
|
-
'jwtEncrypt'
|
|
280
|
-
);
|
|
281
|
-
|
|
282
|
-
verifyAES(token, 'test', 'test', 'b2df428b9929d3ace7c598bbf4e496b2');
|
|
283
|
-
expect(_.isString(token)).toBeTruthy();
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
test('should sign token with aes', async () => {
|
|
287
|
-
const token = await getTokenByConfiguration(
|
|
288
|
-
'security-basic',
|
|
289
|
-
'test',
|
|
290
|
-
'test',
|
|
291
|
-
'b2df428b9929d3ace7c598bbf4e496b2',
|
|
292
|
-
'aesEncrypt',
|
|
293
|
-
'jwtEncrypt'
|
|
294
|
-
);
|
|
295
|
-
|
|
296
|
-
verifyAES(token, 'test', 'test', 'b2df428b9929d3ace7c598bbf4e496b2');
|
|
297
|
-
expect(_.isString(token)).toBeTruthy();
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
test('should sign token with legacy and jwt disabled', async () => {
|
|
301
|
-
const token = await getTokenByConfiguration(
|
|
302
|
-
'security-no-legacy',
|
|
303
|
-
'test',
|
|
304
|
-
'test',
|
|
305
|
-
'b2df428b9929d3ace7c598bbf4e496b2',
|
|
306
|
-
'aesEncrypt',
|
|
307
|
-
'jwtEncrypt'
|
|
308
|
-
);
|
|
309
|
-
|
|
310
|
-
expect(_.isString(token)).toBeTruthy();
|
|
311
|
-
verifyAES(token, 'test', 'test', 'b2df428b9929d3ace7c598bbf4e496b2');
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
test('should sign token with legacy enabled and jwt enabled', async () => {
|
|
315
|
-
const token = await getTokenByConfiguration(
|
|
316
|
-
'security-jwt-legacy-enabled',
|
|
317
|
-
'test',
|
|
318
|
-
'test',
|
|
319
|
-
'b2df428b9929d3ace7c598bbf4e496b2',
|
|
320
|
-
'jwtEncrypt',
|
|
321
|
-
'aesEncrypt'
|
|
322
|
-
);
|
|
323
|
-
|
|
324
|
-
verifyJWT(token, 'test', 'test', 'b2df428b9929d3ace7c598bbf4e496b2');
|
|
325
|
-
expect(_.isString(token)).toBeTruthy();
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
test('should sign token with jwt enabled', async () => {
|
|
329
|
-
const token = await getTokenByConfiguration(
|
|
330
|
-
'security-jwt',
|
|
331
|
-
'test',
|
|
332
|
-
'test',
|
|
333
|
-
'b2df428b9929d3ace7c598bbf4e496b2',
|
|
334
|
-
'jwtEncrypt',
|
|
335
|
-
'aesEncrypt'
|
|
336
|
-
);
|
|
337
|
-
|
|
338
|
-
expect(_.isString(token)).toBeTruthy();
|
|
339
|
-
verifyJWT(token, 'test', 'test', 'b2df428b9929d3ace7c598bbf4e496b2');
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
test('should sign with jwt whether legacy is disabled', async () => {
|
|
343
|
-
const token = await getTokenByConfiguration(
|
|
344
|
-
'security-legacy-disabled',
|
|
345
|
-
'test',
|
|
346
|
-
'test',
|
|
347
|
-
'b2df428b9929d3ace7c598bbf4e496b2',
|
|
348
|
-
'jwtEncrypt',
|
|
349
|
-
'aesEncrypt'
|
|
350
|
-
);
|
|
351
|
-
|
|
352
|
-
expect(_.isString(token)).toBeTruthy();
|
|
353
|
-
verifyJWT(token, 'test', 'test', 'b2df428b9929d3ace7c598bbf4e496b2');
|
|
354
|
-
});
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
describe('getAuthenticatedMessage test', () => {
|
|
358
|
-
test('should sign token with jwt enabled', () => {
|
|
359
|
-
expect(getAuthenticatedMessage('test')).toBe("you are authenticated as 'test'");
|
|
360
|
-
});
|
|
361
|
-
});
|
|
362
|
-
});
|