@verdaccio/auth 7.0.0-next.4 → 7.0.0-next.6
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 +30 -0
- package/LICENSE +1 -1
- package/build/auth.d.ts +7 -26
- package/build/auth.js +38 -23
- package/build/auth.js.map +1 -1
- package/build/index.d.ts +1 -0
- package/build/index.js +12 -0
- package/build/index.js.map +1 -1
- package/build/signature-legacy.d.ts +5 -0
- package/build/signature-legacy.js +60 -0
- package/build/signature-legacy.js.map +1 -0
- package/build/signature.d.ts +4 -0
- package/build/signature.js +60 -0
- package/build/signature.js.map +1 -0
- package/build/types.d.ts +34 -0
- package/build/types.js +13 -0
- package/build/types.js.map +1 -0
- package/build/utils.d.ts +5 -15
- package/build/utils.js +18 -7
- package/build/utils.js.map +1 -1
- package/package.json +12 -10
- package/src/auth.ts +62 -50
- package/src/index.ts +1 -0
- package/src/signature-legacy.ts +66 -0
- package/src/signature.ts +66 -0
- package/src/types.ts +46 -0
- package/src/utils.ts +37 -31
- package/test/auth.spec.ts +561 -40
- package/test/helper/plugin.ts +8 -0
- package/test/partials/plugin/verdaccio-access-ok/access.js +9 -0
- package/test/partials/plugin/verdaccio-access-ok/package.json +5 -0
- package/test/partials/plugin/verdaccio-adduser/adduser.js +25 -0
- package/test/partials/plugin/verdaccio-adduser/package.json +5 -0
- package/test/partials/plugin/verdaccio-adduser-legacy/adduser.js +25 -0
- package/test/partials/plugin/verdaccio-adduser-legacy/package.json +5 -0
- package/test/partials/plugin/verdaccio-change-password/change.js +32 -0
- package/test/partials/plugin/verdaccio-change-password/package.json +5 -0
- package/test/partials/plugin/verdaccio-fail/fail.js +3 -0
package/test/auth.spec.ts
CHANGED
|
@@ -1,47 +1,79 @@
|
|
|
1
|
+
import express from 'express';
|
|
1
2
|
import path from 'path';
|
|
3
|
+
import supertest from 'supertest';
|
|
2
4
|
|
|
3
|
-
import { Config as AppConfig, ROLES, getDefaultConfig } from '@verdaccio/config';
|
|
4
|
-
import {
|
|
5
|
-
|
|
5
|
+
import { Config as AppConfig, ROLES, createRemoteUser, getDefaultConfig } from '@verdaccio/config';
|
|
6
|
+
import {
|
|
7
|
+
API_ERROR,
|
|
8
|
+
HEADERS,
|
|
9
|
+
HTTP_STATUS,
|
|
10
|
+
SUPPORT_ERRORS,
|
|
11
|
+
TOKEN_BEARER,
|
|
12
|
+
errorUtils,
|
|
13
|
+
} from '@verdaccio/core';
|
|
14
|
+
import { logger, setup } from '@verdaccio/logger';
|
|
15
|
+
import { errorReportingMiddleware, final, handleError } from '@verdaccio/middleware';
|
|
6
16
|
import { Config } from '@verdaccio/types';
|
|
17
|
+
import { buildToken } from '@verdaccio/utils';
|
|
7
18
|
|
|
8
|
-
import { Auth } from '../src';
|
|
9
|
-
import {
|
|
19
|
+
import { $RequestExtend, Auth } from '../src';
|
|
20
|
+
import {
|
|
21
|
+
authChangePasswordConf,
|
|
22
|
+
authPluginFailureConf,
|
|
23
|
+
authPluginPassThrougConf,
|
|
24
|
+
authProfileConf,
|
|
25
|
+
} from './helper/plugin';
|
|
10
26
|
|
|
11
|
-
setup({
|
|
27
|
+
setup({});
|
|
28
|
+
|
|
29
|
+
// to avoid flaky test generate same ramdom key
|
|
30
|
+
jest.mock('@verdaccio/utils', () => {
|
|
31
|
+
return {
|
|
32
|
+
...jest.requireActual('@verdaccio/utils'),
|
|
33
|
+
// used by enhanced legacy aes signature (minimum 32 characters)
|
|
34
|
+
generateRandomSecretKey: () => 'GCYW/3IJzQI6GvPmy9sbMkFoiL7QLVw',
|
|
35
|
+
// used by legacy aes signature
|
|
36
|
+
generateRandomHexString: () =>
|
|
37
|
+
'ff065fcf7a8330ae37d3ea116328852f387ad7aa6defbe47fb68b1ea25f97446',
|
|
38
|
+
};
|
|
39
|
+
});
|
|
12
40
|
|
|
13
41
|
describe('AuthTest', () => {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
42
|
+
describe('default', () => {
|
|
43
|
+
test('should init correctly', async () => {
|
|
44
|
+
const config: Config = new AppConfig({ ...authProfileConf });
|
|
45
|
+
config.checkSecretKey('12345');
|
|
17
46
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
47
|
+
const auth: Auth = new Auth(config);
|
|
48
|
+
await auth.init();
|
|
49
|
+
expect(auth).toBeDefined();
|
|
50
|
+
});
|
|
22
51
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
52
|
+
test('should load default auth plugin', async () => {
|
|
53
|
+
const config: Config = new AppConfig({ ...authProfileConf, auth: undefined });
|
|
54
|
+
config.checkSecretKey('12345');
|
|
26
55
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
56
|
+
const auth: Auth = new Auth(config);
|
|
57
|
+
await auth.init();
|
|
58
|
+
expect(auth).toBeDefined();
|
|
59
|
+
});
|
|
30
60
|
});
|
|
31
61
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
62
|
+
describe('utils', () => {
|
|
63
|
+
test('should load custom algorithm', async () => {
|
|
64
|
+
const config: Config = new AppConfig({
|
|
65
|
+
...authProfileConf,
|
|
66
|
+
auth: { htpasswd: { algorithm: 'sha1', file: './foo' } },
|
|
67
|
+
});
|
|
68
|
+
config.checkSecretKey('12345');
|
|
38
69
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
70
|
+
const auth: Auth = new Auth(config);
|
|
71
|
+
await auth.init();
|
|
72
|
+
expect(auth).toBeDefined();
|
|
73
|
+
});
|
|
42
74
|
});
|
|
43
75
|
|
|
44
|
-
describe('
|
|
76
|
+
describe('authenticate', () => {
|
|
45
77
|
describe('test authenticate states', () => {
|
|
46
78
|
test('should be a success login', async () => {
|
|
47
79
|
const config: Config = new AppConfig({ ...authProfileConf });
|
|
@@ -163,30 +195,519 @@ describe('AuthTest', () => {
|
|
|
163
195
|
}
|
|
164
196
|
});
|
|
165
197
|
});
|
|
198
|
+
|
|
199
|
+
describe('test multiple authenticate methods', () => {
|
|
200
|
+
test('should skip falsy values', async () => {
|
|
201
|
+
const config: Config = new AppConfig({
|
|
202
|
+
...getDefaultConfig(),
|
|
203
|
+
plugins: path.join(__dirname, './partials/plugin'),
|
|
204
|
+
auth: {
|
|
205
|
+
success: {},
|
|
206
|
+
'fail-invalid-method': {},
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
config.checkSecretKey('12345');
|
|
210
|
+
const auth: Auth = new Auth(config);
|
|
211
|
+
await auth.init();
|
|
212
|
+
|
|
213
|
+
return new Promise((resolve) => {
|
|
214
|
+
auth.authenticate('foo', 'bar', (err, value) => {
|
|
215
|
+
expect(value).toEqual({
|
|
216
|
+
name: 'foo',
|
|
217
|
+
groups: ['test', ROLES.$ALL, '$authenticated', '@all', '@authenticated', 'all'],
|
|
218
|
+
real_groups: ['test'],
|
|
219
|
+
});
|
|
220
|
+
resolve(value);
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
describe('changePassword', () => {
|
|
228
|
+
test('should fail if the plugin does not provide implementation', async () => {
|
|
229
|
+
const config: Config = new AppConfig({ ...authProfileConf });
|
|
230
|
+
config.checkSecretKey('12345');
|
|
231
|
+
const auth: Auth = new Auth(config);
|
|
232
|
+
await auth.init();
|
|
233
|
+
expect(auth).toBeDefined();
|
|
234
|
+
const callback = jest.fn();
|
|
235
|
+
|
|
236
|
+
auth.changePassword('foo', 'bar', 'newFoo', callback);
|
|
237
|
+
|
|
238
|
+
expect(callback).toHaveBeenCalledTimes(1);
|
|
239
|
+
expect(callback).toHaveBeenCalledWith(
|
|
240
|
+
errorUtils.getInternalError(SUPPORT_ERRORS.PLUGIN_MISSING_INTERFACE)
|
|
241
|
+
);
|
|
242
|
+
});
|
|
243
|
+
test('should handle plugin does provide implementation', async () => {
|
|
244
|
+
const config: Config = new AppConfig({ ...authChangePasswordConf });
|
|
245
|
+
config.checkSecretKey('12345');
|
|
246
|
+
const auth: Auth = new Auth(config);
|
|
247
|
+
await auth.init();
|
|
248
|
+
expect(auth).toBeDefined();
|
|
249
|
+
const callback = jest.fn();
|
|
250
|
+
auth.add_user('foo', 'bar', jest.fn());
|
|
251
|
+
auth.changePassword('foo', 'bar', 'newFoo', callback);
|
|
252
|
+
expect(callback).toHaveBeenCalledTimes(1);
|
|
253
|
+
expect(callback).toHaveBeenCalledWith(null, true);
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
describe('allow_access', () => {
|
|
258
|
+
describe('no custom allow_access implementation provided', () => {
|
|
259
|
+
// when allow_access is not implemented, the groups must match
|
|
260
|
+
// exactly with the packages access group
|
|
261
|
+
test('should fails if groups do not match exactly', async () => {
|
|
262
|
+
const config: Config = new AppConfig({ ...authProfileConf });
|
|
263
|
+
config.checkSecretKey('12345');
|
|
264
|
+
const auth: Auth = new Auth(config);
|
|
265
|
+
await auth.init();
|
|
266
|
+
expect(auth).toBeDefined();
|
|
267
|
+
|
|
268
|
+
const callback = jest.fn();
|
|
269
|
+
const groups = ['test'];
|
|
270
|
+
|
|
271
|
+
auth.allow_access(
|
|
272
|
+
{ packageName: 'foo' },
|
|
273
|
+
{ name: 'foo', groups, real_groups: groups },
|
|
274
|
+
callback
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
expect(callback).toHaveBeenCalledTimes(1);
|
|
278
|
+
expect(callback).toHaveBeenCalledWith(
|
|
279
|
+
errorUtils.getForbidden('user foo is not allowed to access package foo')
|
|
280
|
+
);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test('should success if groups do not match exactly', async () => {
|
|
284
|
+
const config: Config = new AppConfig({ ...authProfileConf });
|
|
285
|
+
config.checkSecretKey('12345');
|
|
286
|
+
const auth: Auth = new Auth(config);
|
|
287
|
+
await auth.init();
|
|
288
|
+
expect(auth).toBeDefined();
|
|
289
|
+
|
|
290
|
+
const callback = jest.fn();
|
|
291
|
+
// $all comes from configuration file
|
|
292
|
+
const groups = [ROLES.$ALL];
|
|
293
|
+
|
|
294
|
+
auth.allow_access(
|
|
295
|
+
{ packageName: 'foo' },
|
|
296
|
+
{ name: 'foo', groups, real_groups: groups },
|
|
297
|
+
callback
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
expect(callback).toHaveBeenCalledTimes(1);
|
|
301
|
+
expect(callback).toHaveBeenCalledWith(null, true);
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
describe('allow_publish', () => {
|
|
307
|
+
describe('no custom allow_publish implementation provided', () => {
|
|
308
|
+
// when allow_access is not implemented, the groups must match
|
|
309
|
+
// exactly with the packages access group
|
|
310
|
+
test('should fails if groups do not match exactly', async () => {
|
|
311
|
+
const config: Config = new AppConfig({ ...authProfileConf });
|
|
312
|
+
config.checkSecretKey('12345');
|
|
313
|
+
const auth: Auth = new Auth(config);
|
|
314
|
+
await auth.init();
|
|
315
|
+
expect(auth).toBeDefined();
|
|
316
|
+
|
|
317
|
+
const callback = jest.fn();
|
|
318
|
+
const groups = ['test'];
|
|
319
|
+
|
|
320
|
+
auth.allow_publish(
|
|
321
|
+
{ packageName: 'foo' },
|
|
322
|
+
{ name: 'foo', groups, real_groups: groups },
|
|
323
|
+
callback
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
expect(callback).toHaveBeenCalledTimes(1);
|
|
327
|
+
expect(callback).toHaveBeenCalledWith(
|
|
328
|
+
errorUtils.getForbidden('user foo is not allowed to publish package foo')
|
|
329
|
+
);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
test('should success if groups do match exactly', async () => {
|
|
333
|
+
const config: Config = new AppConfig({ ...authProfileConf });
|
|
334
|
+
config.checkSecretKey('12345');
|
|
335
|
+
const auth: Auth = new Auth(config);
|
|
336
|
+
await auth.init();
|
|
337
|
+
expect(auth).toBeDefined();
|
|
338
|
+
|
|
339
|
+
const callback = jest.fn();
|
|
340
|
+
// $all comes from configuration file
|
|
341
|
+
const groups = [ROLES.$AUTH];
|
|
342
|
+
|
|
343
|
+
auth.allow_publish(
|
|
344
|
+
{ packageName: 'foo' },
|
|
345
|
+
{ name: 'foo', groups, real_groups: groups },
|
|
346
|
+
callback
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
expect(callback).toHaveBeenCalledTimes(1);
|
|
350
|
+
expect(callback).toHaveBeenCalledWith(null, true);
|
|
351
|
+
});
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
describe('allow_unpublish', () => {
|
|
355
|
+
describe('no custom allow_unpublish implementation provided', () => {
|
|
356
|
+
test('should fails if groups do not match exactly', async () => {
|
|
357
|
+
const config: Config = new AppConfig({ ...authProfileConf });
|
|
358
|
+
config.checkSecretKey('12345');
|
|
359
|
+
|
|
360
|
+
const auth: Auth = new Auth(config);
|
|
361
|
+
await auth.init();
|
|
362
|
+
expect(auth).toBeDefined();
|
|
363
|
+
|
|
364
|
+
const callback = jest.fn();
|
|
365
|
+
const groups = ['test'];
|
|
366
|
+
|
|
367
|
+
auth.allow_unpublish(
|
|
368
|
+
{ packageName: 'foo' },
|
|
369
|
+
{ name: 'foo', groups, real_groups: groups },
|
|
370
|
+
callback
|
|
371
|
+
);
|
|
372
|
+
|
|
373
|
+
expect(callback).toHaveBeenCalledTimes(1);
|
|
374
|
+
expect(callback).toHaveBeenCalledWith(
|
|
375
|
+
errorUtils.getForbidden('user foo is not allowed to unpublish package foo')
|
|
376
|
+
);
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
test('should handle missing unpublish method (special case to handle legacy configurations)', async () => {
|
|
380
|
+
const config: Config = new AppConfig({
|
|
381
|
+
...authProfileConf,
|
|
382
|
+
packages: {
|
|
383
|
+
...authProfileConf.packages,
|
|
384
|
+
'**': {
|
|
385
|
+
access: ['$all'],
|
|
386
|
+
publish: ['$authenticated'],
|
|
387
|
+
// it forces publish handle the access
|
|
388
|
+
unpublish: undefined,
|
|
389
|
+
proxy: ['npmjs'],
|
|
390
|
+
},
|
|
391
|
+
},
|
|
392
|
+
});
|
|
393
|
+
config.checkSecretKey('12345');
|
|
394
|
+
const auth: Auth = new Auth(config);
|
|
395
|
+
await auth.init();
|
|
396
|
+
expect(auth).toBeDefined();
|
|
397
|
+
|
|
398
|
+
const callback = jest.fn();
|
|
399
|
+
const groups = ['test'];
|
|
400
|
+
|
|
401
|
+
auth.allow_unpublish(
|
|
402
|
+
{ packageName: 'foo' },
|
|
403
|
+
{ name: 'foo', groups, real_groups: groups },
|
|
404
|
+
callback
|
|
405
|
+
);
|
|
406
|
+
|
|
407
|
+
expect(callback).toHaveBeenCalledTimes(1);
|
|
408
|
+
expect(callback).toHaveBeenCalledWith(
|
|
409
|
+
errorUtils.getForbidden('user foo is not allowed to publish package foo')
|
|
410
|
+
);
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
test('should success if groups do match exactly', async () => {
|
|
414
|
+
const config: Config = new AppConfig({ ...authProfileConf });
|
|
415
|
+
|
|
416
|
+
config.checkSecretKey('12345');
|
|
417
|
+
const auth: Auth = new Auth(config);
|
|
418
|
+
await auth.init();
|
|
419
|
+
expect(auth).toBeDefined();
|
|
420
|
+
|
|
421
|
+
const callback = jest.fn();
|
|
422
|
+
// $all comes from configuration file
|
|
423
|
+
const groups = [ROLES.$AUTH];
|
|
424
|
+
|
|
425
|
+
auth.allow_unpublish(
|
|
426
|
+
{ packageName: 'foo' },
|
|
427
|
+
{ name: 'foo', groups, real_groups: groups },
|
|
428
|
+
callback
|
|
429
|
+
);
|
|
430
|
+
|
|
431
|
+
expect(callback).toHaveBeenCalledTimes(1);
|
|
432
|
+
expect(callback).toHaveBeenCalledWith(null, true);
|
|
433
|
+
});
|
|
434
|
+
});
|
|
166
435
|
});
|
|
167
436
|
|
|
168
|
-
describe('
|
|
169
|
-
|
|
437
|
+
describe('add_user', () => {
|
|
438
|
+
describe('error handling', () => {
|
|
439
|
+
// when allow_access is not implemented, the groups must match
|
|
440
|
+
// exactly with the packages access group
|
|
441
|
+
test('should fails with bad password if adduser is not implemented', async () => {
|
|
442
|
+
const config: Config = new AppConfig({ ...authProfileConf });
|
|
443
|
+
config.checkSecretKey('12345');
|
|
444
|
+
const auth: Auth = new Auth(config);
|
|
445
|
+
await auth.init();
|
|
446
|
+
expect(auth).toBeDefined();
|
|
447
|
+
|
|
448
|
+
const callback = jest.fn();
|
|
449
|
+
|
|
450
|
+
auth.add_user('juan', 'password', callback);
|
|
451
|
+
|
|
452
|
+
expect(callback).toHaveBeenCalledTimes(1);
|
|
453
|
+
expect(callback).toHaveBeenCalledWith(
|
|
454
|
+
errorUtils.getConflict(API_ERROR.BAD_USERNAME_PASSWORD)
|
|
455
|
+
);
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
test('should fails if adduser fails internally (exception)', async () => {
|
|
459
|
+
const config: Config = new AppConfig({
|
|
460
|
+
...getDefaultConfig(),
|
|
461
|
+
plugins: path.join(__dirname, './partials/plugin'),
|
|
462
|
+
auth: {
|
|
463
|
+
adduser: {},
|
|
464
|
+
},
|
|
465
|
+
});
|
|
466
|
+
config.checkSecretKey('12345');
|
|
467
|
+
const auth: Auth = new Auth(config);
|
|
468
|
+
await auth.init();
|
|
469
|
+
expect(auth).toBeDefined();
|
|
470
|
+
|
|
471
|
+
const callback = jest.fn();
|
|
472
|
+
|
|
473
|
+
// note: fail uas username make plugin fails
|
|
474
|
+
auth.add_user('fail', 'password', callback);
|
|
475
|
+
|
|
476
|
+
expect(callback).toHaveBeenCalledTimes(1);
|
|
477
|
+
expect(callback).toHaveBeenCalledWith(new Error('bad username'));
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
test('should skip to the next plugin and fails', async () => {
|
|
481
|
+
const config: Config = new AppConfig({
|
|
482
|
+
...getDefaultConfig(),
|
|
483
|
+
plugins: path.join(__dirname, './partials/plugin'),
|
|
484
|
+
auth: {
|
|
485
|
+
adduser: {},
|
|
486
|
+
// plugin implement adduser with fail auth
|
|
487
|
+
fail: {},
|
|
488
|
+
},
|
|
489
|
+
});
|
|
490
|
+
config.checkSecretKey('12345');
|
|
491
|
+
const auth: Auth = new Auth(config);
|
|
492
|
+
await auth.init();
|
|
493
|
+
expect(auth).toBeDefined();
|
|
494
|
+
|
|
495
|
+
const callback = jest.fn();
|
|
496
|
+
|
|
497
|
+
// note: fail uas username make plugin fails
|
|
498
|
+
auth.add_user('skip', 'password', callback);
|
|
499
|
+
|
|
500
|
+
expect(callback).toHaveBeenCalledTimes(1);
|
|
501
|
+
expect(callback).toHaveBeenCalledWith(
|
|
502
|
+
errorUtils.getConflict(API_ERROR.BAD_USERNAME_PASSWORD)
|
|
503
|
+
);
|
|
504
|
+
});
|
|
505
|
+
});
|
|
506
|
+
test('should success if adduser', async () => {
|
|
170
507
|
const config: Config = new AppConfig({
|
|
171
508
|
...getDefaultConfig(),
|
|
172
509
|
plugins: path.join(__dirname, './partials/plugin'),
|
|
173
510
|
auth: {
|
|
174
|
-
|
|
175
|
-
'fail-invalid-method': {},
|
|
511
|
+
adduser: {},
|
|
176
512
|
},
|
|
177
513
|
});
|
|
178
514
|
config.checkSecretKey('12345');
|
|
179
515
|
const auth: Auth = new Auth(config);
|
|
180
516
|
await auth.init();
|
|
517
|
+
expect(auth).toBeDefined();
|
|
181
518
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
519
|
+
const callback = jest.fn();
|
|
520
|
+
|
|
521
|
+
auth.add_user('something', 'password', callback);
|
|
522
|
+
|
|
523
|
+
expect(callback).toHaveBeenCalledTimes(1);
|
|
524
|
+
expect(callback).toHaveBeenCalledWith(null, {
|
|
525
|
+
groups: ['test', '$all', '$authenticated', '@all', '@authenticated', 'all'],
|
|
526
|
+
name: 'something',
|
|
527
|
+
real_groups: ['test'],
|
|
528
|
+
});
|
|
529
|
+
});
|
|
530
|
+
test('should handle legacy add_user method', async () => {
|
|
531
|
+
const config: Config = new AppConfig({
|
|
532
|
+
...getDefaultConfig(),
|
|
533
|
+
plugins: path.join(__dirname, './partials/plugin'),
|
|
534
|
+
auth: {
|
|
535
|
+
'adduser-legacy': {},
|
|
536
|
+
},
|
|
537
|
+
});
|
|
538
|
+
config.checkSecretKey('12345');
|
|
539
|
+
const auth: Auth = new Auth(config);
|
|
540
|
+
await auth.init();
|
|
541
|
+
expect(auth).toBeDefined();
|
|
542
|
+
|
|
543
|
+
const callback = jest.fn();
|
|
544
|
+
|
|
545
|
+
auth.add_user('something', 'password', callback);
|
|
546
|
+
|
|
547
|
+
expect(callback).toHaveBeenCalledTimes(1);
|
|
548
|
+
expect(callback).toHaveBeenCalledWith(null, {
|
|
549
|
+
groups: ['test', '$all', '$authenticated', '@all', '@authenticated', 'all'],
|
|
550
|
+
name: 'something',
|
|
551
|
+
real_groups: ['test'],
|
|
552
|
+
});
|
|
553
|
+
});
|
|
554
|
+
});
|
|
555
|
+
describe('middlewares', () => {
|
|
556
|
+
describe('apiJWTmiddleware', () => {
|
|
557
|
+
const secret = '12345';
|
|
558
|
+
const getServer = async function (auth) {
|
|
559
|
+
const app = express();
|
|
560
|
+
app.use(express.json({ strict: false, limit: '10mb' }));
|
|
561
|
+
// @ts-expect-error
|
|
562
|
+
app.use(errorReportingMiddleware(logger));
|
|
563
|
+
app.use(auth.apiJWTmiddleware());
|
|
564
|
+
app.get('/*', (req, res, next) => {
|
|
565
|
+
if ((req as $RequestExtend).remote_user.error) {
|
|
566
|
+
next(new Error((req as $RequestExtend).remote_user.error));
|
|
567
|
+
} else {
|
|
568
|
+
// @ts-expect-error
|
|
569
|
+
next({ user: req?.remote_user });
|
|
570
|
+
}
|
|
571
|
+
});
|
|
572
|
+
// @ts-expect-error
|
|
573
|
+
app.use(handleError(logger));
|
|
574
|
+
// @ts-expect-error
|
|
575
|
+
app.use(final);
|
|
576
|
+
return app;
|
|
577
|
+
};
|
|
578
|
+
describe('legacy signature', () => {
|
|
579
|
+
describe('error cases', () => {
|
|
580
|
+
test('should handle invalid auth token', async () => {
|
|
581
|
+
const config: Config = new AppConfig({ ...authProfileConf });
|
|
582
|
+
config.checkSecretKey(secret);
|
|
583
|
+
const auth = new Auth(config);
|
|
584
|
+
await auth.init();
|
|
585
|
+
const app = await getServer(auth);
|
|
586
|
+
return supertest(app)
|
|
587
|
+
.get(`/`)
|
|
588
|
+
.set(HEADERS.AUTHORIZATION, 'Bearer foo')
|
|
589
|
+
.expect(HTTP_STATUS.INTERNAL_ERROR);
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
test('should handle missing auth header', async () => {
|
|
593
|
+
const config: Config = new AppConfig({ ...authProfileConf });
|
|
594
|
+
config.checkSecretKey(secret);
|
|
595
|
+
const auth = new Auth(config);
|
|
596
|
+
await auth.init();
|
|
597
|
+
const app = await getServer(auth);
|
|
598
|
+
return supertest(app).get(`/`).expect(HTTP_STATUS.OK);
|
|
599
|
+
});
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
describe('deprecated legacy handling forceEnhancedLegacySignature=false', () => {
|
|
603
|
+
test('should handle valid auth token', async () => {
|
|
604
|
+
const payload = 'juan:password';
|
|
605
|
+
// const token = await signPayload(remoteUser, '12345');
|
|
606
|
+
const config: Config = new AppConfig(
|
|
607
|
+
{ ...authProfileConf },
|
|
608
|
+
{ forceEnhancedLegacySignature: false }
|
|
609
|
+
);
|
|
610
|
+
// intended to force key generator (associated with mocks above)
|
|
611
|
+
config.checkSecretKey(undefined);
|
|
612
|
+
const auth = new Auth(config);
|
|
613
|
+
await auth.init();
|
|
614
|
+
const token = auth.aesEncrypt(payload) as string;
|
|
615
|
+
const app = await getServer(auth);
|
|
616
|
+
const res = await supertest(app)
|
|
617
|
+
.get(`/`)
|
|
618
|
+
.set(HEADERS.AUTHORIZATION, buildToken(TOKEN_BEARER, token))
|
|
619
|
+
.expect(HTTP_STATUS.OK);
|
|
620
|
+
expect(res.body.user.name).toEqual('juan');
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
test('should handle invalid auth token', async () => {
|
|
624
|
+
const payload = 'juan:password';
|
|
625
|
+
const config: Config = new AppConfig(
|
|
626
|
+
{ ...authPluginFailureConf },
|
|
627
|
+
{ forceEnhancedLegacySignature: false }
|
|
628
|
+
);
|
|
629
|
+
// intended to force key generator (associated with mocks above)
|
|
630
|
+
config.checkSecretKey(undefined);
|
|
631
|
+
const auth = new Auth(config);
|
|
632
|
+
await auth.init();
|
|
633
|
+
const token = auth.aesEncrypt(payload) as string;
|
|
634
|
+
const app = await getServer(auth);
|
|
635
|
+
return await supertest(app)
|
|
636
|
+
.get(`/`)
|
|
637
|
+
.set(HEADERS.AUTHORIZATION, buildToken(TOKEN_BEARER, token))
|
|
638
|
+
.expect(HTTP_STATUS.INTERNAL_ERROR);
|
|
639
|
+
});
|
|
640
|
+
});
|
|
641
|
+
});
|
|
642
|
+
describe('jwt signature', () => {
|
|
643
|
+
describe('error cases', () => {
|
|
644
|
+
test('should handle invalid auth token and return anonymous', async () => {
|
|
645
|
+
// @ts-expect-error
|
|
646
|
+
const config: Config = new AppConfig({
|
|
647
|
+
...authProfileConf,
|
|
648
|
+
...{ security: { api: { jwt: { sign: { expiresIn: '29d' } } } } },
|
|
649
|
+
});
|
|
650
|
+
config.checkSecretKey(secret);
|
|
651
|
+
const auth = new Auth(config);
|
|
652
|
+
await auth.init();
|
|
653
|
+
const app = await getServer(auth);
|
|
654
|
+
const res = await supertest(app)
|
|
655
|
+
.get(`/`)
|
|
656
|
+
.set(HEADERS.AUTHORIZATION, 'Bearer foo')
|
|
657
|
+
.expect(HTTP_STATUS.OK);
|
|
658
|
+
expect(res.body.user.groups).toEqual([
|
|
659
|
+
ROLES.$ALL,
|
|
660
|
+
ROLES.$ANONYMOUS,
|
|
661
|
+
ROLES.DEPRECATED_ALL,
|
|
662
|
+
ROLES.DEPRECATED_ANONYMOUS,
|
|
663
|
+
]);
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
test('should handle missing auth header', async () => {
|
|
667
|
+
// @ts-expect-error
|
|
668
|
+
const config: Config = new AppConfig({
|
|
669
|
+
...authProfileConf,
|
|
670
|
+
...{ security: { api: { jwt: { sign: { expiresIn: '29d' } } } } },
|
|
671
|
+
});
|
|
672
|
+
config.checkSecretKey(secret);
|
|
673
|
+
const auth = new Auth(config);
|
|
674
|
+
await auth.init();
|
|
675
|
+
const app = await getServer(auth);
|
|
676
|
+
const res = await supertest(app).get(`/`).expect(HTTP_STATUS.OK);
|
|
677
|
+
expect(res.body.user.groups).toEqual([
|
|
678
|
+
ROLES.$ALL,
|
|
679
|
+
ROLES.$ANONYMOUS,
|
|
680
|
+
ROLES.DEPRECATED_ALL,
|
|
681
|
+
ROLES.DEPRECATED_ANONYMOUS,
|
|
682
|
+
]);
|
|
683
|
+
});
|
|
684
|
+
});
|
|
685
|
+
describe('valid signature handlers', () => {
|
|
686
|
+
test('should handle valid auth token', async () => {
|
|
687
|
+
const config: Config = new AppConfig(
|
|
688
|
+
// @ts-expect-error
|
|
689
|
+
{
|
|
690
|
+
...authProfileConf,
|
|
691
|
+
...{ security: { api: { jwt: { sign: { expiresIn: '29d' } } } } },
|
|
692
|
+
},
|
|
693
|
+
{ forceEnhancedLegacySignature: false }
|
|
694
|
+
);
|
|
695
|
+
// intended to force key generator (associated with mocks above)
|
|
696
|
+
config.checkSecretKey(undefined);
|
|
697
|
+
const auth = new Auth(config);
|
|
698
|
+
await auth.init();
|
|
699
|
+
const token = (await auth.jwtEncrypt(
|
|
700
|
+
createRemoteUser('jwt_user', [ROLES.ALL]),
|
|
701
|
+
// @ts-expect-error
|
|
702
|
+
config.security.api.jwt.sign
|
|
703
|
+
)) as string;
|
|
704
|
+
const app = await getServer(auth);
|
|
705
|
+
const res = await supertest(app)
|
|
706
|
+
.get(`/`)
|
|
707
|
+
.set(HEADERS.AUTHORIZATION, buildToken(TOKEN_BEARER, token))
|
|
708
|
+
.expect(HTTP_STATUS.OK);
|
|
709
|
+
expect(res.body.user.name).toEqual('jwt_user');
|
|
188
710
|
});
|
|
189
|
-
resolve(value);
|
|
190
711
|
});
|
|
191
712
|
});
|
|
192
713
|
});
|
package/test/helper/plugin.ts
CHANGED
|
@@ -10,6 +10,14 @@ export const authProfileConf = {
|
|
|
10
10
|
},
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
+
export const authChangePasswordConf = {
|
|
14
|
+
...getDefaultConfig(),
|
|
15
|
+
plugins: path.join(__dirname, '../partials/plugin'),
|
|
16
|
+
auth: {
|
|
17
|
+
'change-password': {},
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
|
|
13
21
|
export const authPluginFailureConf = {
|
|
14
22
|
...getDefaultConfig(),
|
|
15
23
|
plugins: path.join(__dirname, '../partials/plugin'),
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module.exports = function () {
|
|
2
|
+
return {
|
|
3
|
+
authenticate(user, pass, callback) {
|
|
4
|
+
// https://verdaccio.org/docs/en/dev-plugins#onsuccess
|
|
5
|
+
// this is a successful login and return a simple group
|
|
6
|
+
callback(null, ['test']);
|
|
7
|
+
},
|
|
8
|
+
adduser(user, password, cb) {
|
|
9
|
+
if (user === 'fail') {
|
|
10
|
+
return cb(Error('bad username'));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (user === 'password') {
|
|
14
|
+
return cb(Error('bad password'));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (user === 'skip') {
|
|
18
|
+
// if wants to the next plugin
|
|
19
|
+
return cb(null, false);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
cb(null, true);
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
};
|