@verdaccio/auth 7.0.0-next.3 → 7.0.0-next.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/build/auth.d.ts +7 -26
  3. package/build/auth.js +43 -31
  4. package/build/auth.js.map +1 -1
  5. package/build/index.d.ts +1 -0
  6. package/build/index.js +12 -0
  7. package/build/index.js.map +1 -1
  8. package/build/signature-legacy.d.ts +5 -0
  9. package/build/signature-legacy.js +60 -0
  10. package/build/signature-legacy.js.map +1 -0
  11. package/build/signature.d.ts +4 -0
  12. package/build/signature.js +60 -0
  13. package/build/signature.js.map +1 -0
  14. package/build/types.d.ts +34 -0
  15. package/build/types.js +13 -0
  16. package/build/types.js.map +1 -0
  17. package/build/utils.d.ts +5 -15
  18. package/build/utils.js +19 -9
  19. package/build/utils.js.map +1 -1
  20. package/package.json +12 -10
  21. package/src/auth.ts +66 -59
  22. package/src/index.ts +1 -0
  23. package/src/signature-legacy.ts +66 -0
  24. package/src/signature.ts +66 -0
  25. package/src/types.ts +46 -0
  26. package/src/utils.ts +37 -31
  27. package/test/auth.spec.ts +561 -40
  28. package/test/helper/plugin.ts +8 -0
  29. package/test/partials/plugin/verdaccio-access-ok/access.js +9 -0
  30. package/test/partials/plugin/verdaccio-access-ok/package.json +5 -0
  31. package/test/partials/plugin/verdaccio-adduser/adduser.js +25 -0
  32. package/test/partials/plugin/verdaccio-adduser/package.json +5 -0
  33. package/test/partials/plugin/verdaccio-adduser-legacy/adduser.js +25 -0
  34. package/test/partials/plugin/verdaccio-adduser-legacy/package.json +5 -0
  35. package/test/partials/plugin/verdaccio-change-password/change.js +32 -0
  36. package/test/partials/plugin/verdaccio-change-password/package.json +5 -0
  37. 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 { errorUtils } from '@verdaccio/core';
5
- import { setup } from '@verdaccio/logger';
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 { authPluginFailureConf, authPluginPassThrougConf, authProfileConf } from './helper/plugin';
19
+ import { $RequestExtend, Auth } from '../src';
20
+ import {
21
+ authChangePasswordConf,
22
+ authPluginFailureConf,
23
+ authPluginPassThrougConf,
24
+ authProfileConf,
25
+ } from './helper/plugin';
10
26
 
11
- setup({ level: 'debug', type: 'stdout' });
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
- test('should init correctly', async () => {
15
- const config: Config = new AppConfig({ ...authProfileConf });
16
- config.checkSecretKey('12345');
42
+ describe('default', () => {
43
+ test('should init correctly', async () => {
44
+ const config: Config = new AppConfig({ ...authProfileConf });
45
+ config.checkSecretKey('12345');
17
46
 
18
- const auth: Auth = new Auth(config);
19
- await auth.init();
20
- expect(auth).toBeDefined();
21
- });
47
+ const auth: Auth = new Auth(config);
48
+ await auth.init();
49
+ expect(auth).toBeDefined();
50
+ });
22
51
 
23
- test('should load default auth plugin', async () => {
24
- const config: Config = new AppConfig({ ...authProfileConf, auth: undefined });
25
- config.checkSecretKey('12345');
52
+ test('should load default auth plugin', async () => {
53
+ const config: Config = new AppConfig({ ...authProfileConf, auth: undefined });
54
+ config.checkSecretKey('12345');
26
55
 
27
- const auth: Auth = new Auth(config);
28
- await auth.init();
29
- expect(auth).toBeDefined();
56
+ const auth: Auth = new Auth(config);
57
+ await auth.init();
58
+ expect(auth).toBeDefined();
59
+ });
30
60
  });
31
61
 
32
- test('should load custom algorithm', async () => {
33
- const config: Config = new AppConfig({
34
- ...authProfileConf,
35
- auth: { htpasswd: { algorithm: 'sha1', file: './foo' } },
36
- });
37
- config.checkSecretKey('12345');
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
- const auth: Auth = new Auth(config);
40
- await auth.init();
41
- expect(auth).toBeDefined();
70
+ const auth: Auth = new Auth(config);
71
+ await auth.init();
72
+ expect(auth).toBeDefined();
73
+ });
42
74
  });
43
75
 
44
- describe('test authenticate method', () => {
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('test multiple authenticate methods', () => {
169
- test('should skip falsy values', async () => {
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
- success: {},
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
- return new Promise((resolve) => {
183
- auth.authenticate('foo', 'bar', (err, value) => {
184
- expect(value).toEqual({
185
- name: 'foo',
186
- groups: ['test', '$all', '$authenticated', '@all', '@authenticated', 'all'],
187
- real_groups: ['test'],
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
  });
@@ -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,9 @@
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
+ };
9
+ };
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "verdaccio-access-ok",
3
+ "main": "access.js",
4
+ "version": "1.0.0"
5
+ }
@@ -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
+ };