diff --git a/packages/auth/package.json b/packages/auth/package.json index bd3eaecb6..9b93144e0 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -29,6 +29,7 @@ "@verdaccio/logger": "workspace:5.0.0-alpha.0", "@verdaccio/utils": "workspace:5.0.0-alpha.0", "@verdaccio/auth": "workspace:5.0.0-alpha.0", + "jsonwebtoken": "8.5.1", "debug": "^4.1.1", "express": "4.17.1", "lodash": "4.17.15" diff --git a/packages/auth/src/auth.ts b/packages/auth/src/auth.ts index 88968673a..afb39c1b6 100644 --- a/packages/auth/src/auth.ts +++ b/packages/auth/src/auth.ts @@ -10,17 +10,6 @@ import { } from '@verdaccio/commons-api'; import { API_ERROR, SUPPORT_ERRORS, TOKEN_BASIC, TOKEN_BEARER } from '@verdaccio/dev-commons'; import { loadPlugin } from '@verdaccio/loaders'; -import { - aesEncrypt, - signPayload, - isNil, - isFunction, - getMatchedPackagesSpec, - getDefaultPlugins, - createAnonymousRemoteUser, - convertPayloadToBase64, - createRemoteUser, -} from '@verdaccio/utils'; import { Config, @@ -35,9 +24,20 @@ import { AllowAccess, PackageAccess, } from '@verdaccio/types'; + +import { + isNil, + isFunction, + getMatchedPackagesSpec, + createAnonymousRemoteUser, + convertPayloadToBase64, + createRemoteUser, +} from '@verdaccio/utils'; + import { getMiddlewareCredentials, getSecurity, + getDefaultPlugins, verifyJWTPayload, parseBasicPayload, parseAuthTokenHeader, @@ -45,6 +45,8 @@ import { isAESLegacy, } from './utils'; +import { aesEncrypt, signPayload } from './crypto-utils'; + /* eslint-disable @typescript-eslint/no-var-requires */ const LoggerApi = require('@verdaccio/logger'); diff --git a/packages/auth/src/crypto-utils.ts b/packages/auth/src/crypto-utils.ts new file mode 100644 index 000000000..99f4f5a7a --- /dev/null +++ b/packages/auth/src/crypto-utils.ts @@ -0,0 +1,60 @@ +import { createDecipher, createCipher } from 'crypto'; +import jwt from 'jsonwebtoken'; + +import { JWTSignOptions, RemoteUser } from '@verdaccio/types'; + +export const defaultAlgorithm = 'aes192'; + +export function aesEncrypt(buf: Buffer, secret: string): Buffer { + // deprecated (it will be migrated in Verdaccio 5), it is a breaking change + // https://nodejs.org/api/crypto.html#crypto_crypto_createcipher_algorithm_password_options + // https://www.grainger.xyz/changing-from-cipher-to-cipheriv/ + const c = createCipher(defaultAlgorithm, secret); + const b1 = c.update(buf); + const b2 = c.final(); + return Buffer.concat([b1, b2]); +} + +export function aesDecrypt(buf: Buffer, secret: string): Buffer { + try { + // deprecated (it will be migrated in Verdaccio 5), it is a breaking change + // https://nodejs.org/api/crypto.html#crypto_crypto_createdecipher_algorithm_password_options + // https://www.grainger.xyz/changing-from-cipher-to-cipheriv/ + const c = createDecipher(defaultAlgorithm, secret); + const b1 = c.update(buf); + const b2 = c.final(); + return Buffer.concat([b1, b2]); + } catch (_) { + return new Buffer(0); + } +} + +/** + * Sign the payload and return JWT + * https://github.com/auth0/node-jsonwebtoken#jwtsignpayload-secretorprivatekey-options-callback + * @param payload + * @param secretOrPrivateKey + * @param options + */ +export async function signPayload( + payload: RemoteUser, + secretOrPrivateKey: string, + options: JWTSignOptions = {} +): Promise { + return new Promise(function (resolve, reject): Promise { + return jwt.sign( + payload, + secretOrPrivateKey, + { + // 1 === 1ms (one millisecond) + notBefore: '1', // Make sure the time will not rollback :) + ...options, + }, + (error, token) => (error ? reject(error) : resolve(token)) + ); + }); +} + +export function verifyPayload(token: string, secretOrPrivateKey: string): RemoteUser { + return jwt.verify(token, secretOrPrivateKey); +} diff --git a/packages/auth/src/index.ts b/packages/auth/src/index.ts index bc1527464..893ec3c4a 100644 --- a/packages/auth/src/index.ts +++ b/packages/auth/src/index.ts @@ -1,2 +1,3 @@ export { Auth, IAuth, IAuthWebUI } from './auth'; export * from './utils'; +export * from './crypto-utils'; diff --git a/packages/auth/src/utils.ts b/packages/auth/src/utils.ts index ac7b510d5..d4d0c37f9 100644 --- a/packages/auth/src/utils.ts +++ b/packages/auth/src/utils.ts @@ -1,17 +1,19 @@ import _ from 'lodash'; -import { Config, RemoteUser, Security } from '@verdaccio/types'; -import { HTTP_STATUS, TOKEN_BASIC, TOKEN_BEARER } from '@verdaccio/dev-commons'; +import { Callback, Config, IPluginAuth, RemoteUser, Security } from '@verdaccio/types'; +import { HTTP_STATUS, TOKEN_BASIC, TOKEN_BEARER, API_ERROR } from '@verdaccio/dev-commons'; +import { getForbidden, getUnauthorized, getConflict, getCode } from '@verdaccio/commons-api'; import { - aesDecrypt, + AllowAction, + AllowActionCallback, + AuthPackageAllow, buildUserBuffer, convertPayloadToBase64, createAnonymousRemoteUser, defaultSecurity, - ErrorCode, - verifyPayload, } from '@verdaccio/utils'; import { IAuthWebUI, AESPayload } from './auth'; +import { aesDecrypt, verifyPayload } from './crypto-utils'; export type BasicPayload = AESPayload | void; export type AuthMiddlewarePayload = RemoteUser | BasicPayload; @@ -127,7 +129,7 @@ export function verifyJWTPayload(token: string, secret: string): RemoteUser { // we return an anonymous user to force log in. return createAnonymousRemoteUser(); } - throw ErrorCode.getCode(HTTP_STATUS.UNAUTHORIZED, error.message); + throw getCode(HTTP_STATUS.UNAUTHORIZED, error.message); } } @@ -146,3 +148,78 @@ export function parseBasicPayload(credentials: string): BasicPayload { return { user, password }; } + +export function getDefaultPlugins(logger: any): IPluginAuth { + return { + authenticate(user: string, password: string, cb: Callback): void { + cb(getForbidden(API_ERROR.BAD_USERNAME_PASSWORD)); + }, + + adduser(user: string, password: string, cb: Callback): void { + return cb(getConflict(API_ERROR.BAD_USERNAME_PASSWORD)); + }, + + // FIXME: allow_action and allow_publish should be in the @verdaccio/types + // @ts-ignore + allow_access: allow_action('access', logger), + // @ts-ignore + allow_publish: allow_action('publish', logger), + allow_unpublish: handleSpecialUnpublish(logger), + }; +} + +export type ActionsAllowed = 'publish' | 'unpublish' | 'access'; + +export function allow_action(action: ActionsAllowed, logger): AllowAction { + return function allowActionCallback( + user: RemoteUser, + pkg: AuthPackageAllow, + callback: AllowActionCallback + ): void { + logger.trace({ remote: user.name }, `[auth/allow_action]: user: @{user.name}`); + const { name, groups } = user; + const groupAccess = pkg[action] as string[]; + const hasPermission = groupAccess.some((group) => name === group || groups.includes(group)); + logger.trace( + { pkgName: pkg.name, hasPermission, remote: user.name, groupAccess }, + `[auth/allow_action]: hasPermission? @{hasPermission} for user: @{user}` + ); + + if (hasPermission) { + logger.trace({ remote: user.name }, `auth/allow_action: access granted to: @{user}`); + return callback(null, true); + } + + if (name) { + callback(getForbidden(`user ${name} is not allowed to ${action} package ${pkg.name}`)); + } else { + callback(getUnauthorized(`authorization required to ${action} package ${pkg.name}`)); + } + }; +} + +/** + * + */ +export function handleSpecialUnpublish(logger): any { + return function (user: RemoteUser, pkg: AuthPackageAllow, callback: AllowActionCallback): void { + const action = 'unpublish'; + // verify whether the unpublish prop has been defined + const isUnpublishMissing: boolean = _.isNil(pkg[action]); + const hasGroups: boolean = isUnpublishMissing ? false : (pkg[action] as string[]).length > 0; + logger.trace( + { user: user.name, name: pkg.name, hasGroups }, + `fallback unpublish for @{name} has groups: @{hasGroups} for @{user}` + ); + + if (isUnpublishMissing || hasGroups === false) { + return callback(null, undefined); + } + + logger.trace( + { user: user.name, name: pkg.name, action, hasGroups }, + `allow_action for @{action} for @{name} has groups: @{hasGroups} for @{user}` + ); + return allow_action(action, logger)(user, pkg, callback); + }; +} diff --git a/packages/auth/test/auth-utils.spec.ts b/packages/auth/test/auth-utils.spec.ts index 582812751..4219dd84b 100644 --- a/packages/auth/test/auth-utils.spec.ts +++ b/packages/auth/test/auth-utils.spec.ts @@ -1,6 +1,6 @@ import path from 'path'; import _ from 'lodash'; -import { CHARACTER_ENCODING, TOKEN_BEARER } from '@verdaccio/dev-commons'; +import { CHARACTER_ENCODING, TOKEN_BEARER, ROLES, API_ERROR } from '@verdaccio/dev-commons'; import { configExample } from '@verdaccio/mock'; import { Config as AppConfig } from '@verdaccio/config'; @@ -9,19 +9,30 @@ import { setup } from '@verdaccio/logger'; import { buildUserBuffer, getAuthenticatedMessage, - aesDecrypt, - verifyPayload, buildToken, convertPayloadToBase64, parseConfigFile, createAnonymousRemoteUser, createRemoteUser, - signPayload, + AllowActionCallbackResponse, } from '@verdaccio/utils'; import { Config, Security, RemoteUser } from '@verdaccio/types'; -import { Auth, IAuth } from '../src'; -import { getMiddlewareCredentials, getApiToken, verifyJWTPayload, getSecurity } from '../src'; +import { VerdaccioError, getForbidden } from '@verdaccio/commons-api'; +import { + IAuth, + Auth, + ActionsAllowed, + allow_action, + getDefaultPlugins, + getMiddlewareCredentials, + getApiToken, + verifyJWTPayload, + getSecurity, + aesDecrypt, + verifyPayload, + signPayload, +} from '../src'; setup([]); @@ -86,6 +97,7 @@ describe('Auth utilities', () => { const verifyAES = (token: string, user: string, password: string, secret: string) => { const payload = aesDecrypt(convertPayloadToBase64(token), secret).toString( + // @ts-ignore CHARACTER_ENCODING.UTF8 ); const content = payload.split(':'); @@ -94,6 +106,120 @@ describe('Auth utilities', () => { expect(content[0]).toBe(password); }; + describe('getDefaultPlugins', () => { + test('authentication should fail by default (default)', () => { + const plugin = getDefaultPlugins({ trace: jest.fn() }); + plugin.authenticate('foo', 'bar', (error: any) => { + expect(error).toEqual(getForbidden(API_ERROR.BAD_USERNAME_PASSWORD)); + }); + }); + + test('add user should fail by default (default)', () => { + const plugin = getDefaultPlugins({ trace: jest.fn() }); + // @ts-ignore + plugin.adduser('foo', 'bar', (error: any) => { + expect(error).toEqual(getForbidden(API_ERROR.BAD_USERNAME_PASSWORD)); + }); + }); + }); + + describe('allow_action', () => { + describe('access/publish/unpublish and anonymous', () => { + const packageAccess = { + name: 'foo', + version: undefined, + access: ['foo'], + unpublish: false, + }; + + // const type = 'access'; + test.each(['access', 'publish', 'unpublish'])( + 'should restrict %s to anonymous users', + (type) => { + allow_action(type as ActionsAllowed, { trace: jest.fn() })( + createAnonymousRemoteUser(), + { + ...packageAccess, + [type]: ['foo'], + }, + (error: VerdaccioError | null, allowed: AllowActionCallbackResponse) => { + expect(error).not.toBeNull(); + expect(allowed).toBeUndefined(); + } + ); + } + ); + + test.each(['access', 'publish', 'unpublish'])( + 'should allow %s to anonymous users', + (type) => { + allow_action(type as ActionsAllowed, { trace: jest.fn() })( + createAnonymousRemoteUser(), + { + ...packageAccess, + [type]: [ROLES.$ANONYMOUS], + }, + (error: VerdaccioError | null, allowed: AllowActionCallbackResponse) => { + expect(error).toBeNull(); + expect(allowed).toBe(true); + } + ); + } + ); + + test.each(['access', 'publish', 'unpublish'])( + 'should allow %s only if user is anonymous if the logged user has groups', + (type) => { + allow_action(type as ActionsAllowed, { trace: jest.fn() })( + createRemoteUser('juan', ['maintainer', 'admin']), + { + ...packageAccess, + [type]: [ROLES.$ANONYMOUS], + }, + (error: VerdaccioError | null, allowed: AllowActionCallbackResponse) => { + expect(error).not.toBeNull(); + expect(allowed).toBeUndefined(); + } + ); + } + ); + + test.each(['access', 'publish', 'unpublish'])( + 'should allow %s only if user is anonymous match any other groups', + (type) => { + allow_action(type as ActionsAllowed, { trace: jest.fn() })( + createRemoteUser('juan', ['maintainer', 'admin']), + { + ...packageAccess, + [type]: ['admin', 'some-other-group', ROLES.$ANONYMOUS], + }, + (error: VerdaccioError | null, allowed: AllowActionCallbackResponse) => { + expect(error).toBeNull(); + expect(allowed).toBe(true); + } + ); + } + ); + + test.each(['access', 'publish', 'unpublish'])( + 'should not allow %s anonymous if other groups are defined and does not match', + (type) => { + allow_action(type as ActionsAllowed, { trace: jest.fn() })( + createRemoteUser('juan', ['maintainer', 'admin']), + { + ...packageAccess, + [type]: ['bla-bla-group', 'some-other-group', ROLES.$ANONYMOUS], + }, + (error: VerdaccioError | null, allowed: AllowActionCallbackResponse) => { + expect(error).not.toBeNull(); + expect(allowed).toBeUndefined(); + } + ); + } + ); + }); + }); + describe('getApiToken test', () => { test('should sign token with aes and security missing', async () => { const token = await signCredentials( diff --git a/packages/utils/test/crypto-utils.spec.ts b/packages/auth/test/crypto-utils.spec.ts similarity index 75% rename from packages/utils/test/crypto-utils.spec.ts rename to packages/auth/test/crypto-utils.spec.ts index a0d5e5f3c..28b4abb96 100644 --- a/packages/utils/test/crypto-utils.spec.ts +++ b/packages/auth/test/crypto-utils.spec.ts @@ -1,4 +1,5 @@ -import { aesDecrypt, aesEncrypt, convertPayloadToBase64 } from '../src'; +import { convertPayloadToBase64 } from '@verdaccio/utils'; +import { aesDecrypt, aesEncrypt } from '../src/crypto-utils'; describe('test crypto utils', () => { describe('default encryption', () => { diff --git a/packages/utils/package.json b/packages/utils/package.json index e309ca70e..47a01d577 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -19,7 +19,6 @@ "@verdaccio/dev-commons": "workspace:5.0.0-alpha.0", "@verdaccio/readme": "workspace:*", "js-yaml": "3.13.1", - "jsonwebtoken": "8.5.1", "minimatch": "3.0.4", "semver": "7.3.2" }, diff --git a/packages/utils/src/auth-utils.ts b/packages/utils/src/auth-utils.ts index 2a45b0248..62c025196 100644 --- a/packages/utils/src/auth-utils.ts +++ b/packages/utils/src/auth-utils.ts @@ -1,26 +1,14 @@ -import _ from 'lodash'; - -import { - API_ERROR, - ROLES, - TIME_EXPIRATION_7D, - DEFAULT_MIN_LIMIT_PASSWORD, -} from '@verdaccio/dev-commons'; +import { ROLES, TIME_EXPIRATION_7D, DEFAULT_MIN_LIMIT_PASSWORD } from '@verdaccio/dev-commons'; import { RemoteUser, AllowAccess, PackageAccess, - Callback, - Config, Security, APITokenOptions, JWTOptions, - IPluginAuth, } from '@verdaccio/types'; import { VerdaccioError } from '@verdaccio/commons-api'; -import { ErrorCode } from './utils'; - export interface CookieSessionToken { expires: Date; } @@ -85,95 +73,18 @@ export type AllowActionCallback = ( error: VerdaccioError | null, allowed?: AllowActionCallbackResponse ) => void; + export type AllowAction = ( user: RemoteUser, pkg: AuthPackageAllow, callback: AllowActionCallback ) => void; + export interface AuthPackageAllow extends PackageAccess, AllowAccess { // TODO: this should be on @verdaccio/types unpublish: boolean | string[]; } -export type ActionsAllowed = 'publish' | 'unpublish' | 'access'; - -export function allow_action(action: ActionsAllowed, logger): AllowAction { - return function allowActionCallback( - user: RemoteUser, - pkg: AuthPackageAllow, - callback: AllowActionCallback - ): void { - logger.trace({ remote: user.name }, `[auth/allow_action]: user: @{user.name}`); - const { name, groups } = user; - const groupAccess = pkg[action] as string[]; - const hasPermission = groupAccess.some((group) => name === group || groups.includes(group)); - logger.trace( - { pkgName: pkg.name, hasPermission, remote: user.name, groupAccess }, - `[auth/allow_action]: hasPermission? @{hasPermission} for user: @{user}` - ); - - if (hasPermission) { - logger.trace({ remote: user.name }, `auth/allow_action: access granted to: @{user}`); - return callback(null, true); - } - - if (name) { - callback( - ErrorCode.getForbidden(`user ${name} is not allowed to ${action} package ${pkg.name}`) - ); - } else { - callback( - ErrorCode.getUnauthorized(`authorization required to ${action} package ${pkg.name}`) - ); - } - }; -} - -/** - * - */ -export function handleSpecialUnpublish(logger): any { - return function (user: RemoteUser, pkg: AuthPackageAllow, callback: AllowActionCallback): void { - const action = 'unpublish'; - // verify whether the unpublish prop has been defined - const isUnpublishMissing: boolean = _.isNil(pkg[action]); - const hasGroups: boolean = isUnpublishMissing ? false : (pkg[action] as string[]).length > 0; - logger.trace( - { user: user.name, name: pkg.name, hasGroups }, - `fallback unpublish for @{name} has groups: @{hasGroups} for @{user}` - ); - - if (isUnpublishMissing || hasGroups === false) { - return callback(null, undefined); - } - - logger.trace( - { user: user.name, name: pkg.name, action, hasGroups }, - `allow_action for @{action} for @{name} has groups: @{hasGroups} for @{user}` - ); - return allow_action(action, logger)(user, pkg, callback); - }; -} - -export function getDefaultPlugins(logger: any): IPluginAuth { - return { - authenticate(user: string, password: string, cb: Callback): void { - cb(ErrorCode.getForbidden(API_ERROR.BAD_USERNAME_PASSWORD)); - }, - - adduser(user: string, password: string, cb: Callback): void { - return cb(ErrorCode.getConflict(API_ERROR.BAD_USERNAME_PASSWORD)); - }, - - // FIXME: allow_action and allow_publish should be in the @verdaccio/types - // @ts-ignore - allow_access: allow_action('access', logger), - // @ts-ignore - allow_publish: allow_action('publish', logger), - allow_unpublish: handleSpecialUnpublish(logger), - }; -} - export function createSessionToken(): CookieSessionToken { const tenHoursTime = 10 * 60 * 60 * 1000; diff --git a/packages/utils/src/crypto-utils.ts b/packages/utils/src/crypto-utils.ts index 9f09c2cfc..561cb1ef4 100644 --- a/packages/utils/src/crypto-utils.ts +++ b/packages/utils/src/crypto-utils.ts @@ -1,35 +1,8 @@ -import { createDecipher, createCipher, createHash, pseudoRandomBytes, Hash } from 'crypto'; -import jwt from 'jsonwebtoken'; +import { createHash, pseudoRandomBytes, Hash } from 'crypto'; -import { JWTSignOptions, RemoteUser } from '@verdaccio/types'; - -export const defaultAlgorithm = 'aes192'; export const defaultTarballHashAlgorithm = 'sha1'; -export function aesEncrypt(buf: Buffer, secret: string): Buffer { - // deprecated (it will be migrated in Verdaccio 5), it is a breaking change - // https://nodejs.org/api/crypto.html#crypto_crypto_createcipher_algorithm_password_options - // https://www.grainger.xyz/changing-from-cipher-to-cipheriv/ - const c = createCipher(defaultAlgorithm, secret); - const b1 = c.update(buf); - const b2 = c.final(); - return Buffer.concat([b1, b2]); -} - -export function aesDecrypt(buf: Buffer, secret: string): Buffer { - try { - // deprecated (it will be migrated in Verdaccio 5), it is a breaking change - // https://nodejs.org/api/crypto.html#crypto_crypto_createdecipher_algorithm_password_options - // https://www.grainger.xyz/changing-from-cipher-to-cipheriv/ - const c = createDecipher(defaultAlgorithm, secret); - const b1 = c.update(buf); - const b2 = c.final(); - return Buffer.concat([b1, b2]); - } catch (_) { - return Buffer.alloc(0); - } -} - +// podria moverse a storage donde se usa export function createTarballHash(): Hash { return createHash(defaultTarballHashAlgorithm); } @@ -41,40 +14,12 @@ export function createTarballHash(): Hash { * @param {Object} data * @return {String} */ +// se usa en api, middleware, web export function stringToMD5(data: Buffer | string): string { return createHash('md5').update(data).digest('hex'); } +// se usa en config export function generateRandomHexString(length = 8): string { return pseudoRandomBytes(length).toString('hex'); } - -/** - * Sign the payload and return JWT - * https://github.com/auth0/node-jsonwebtoken#jwtsignpayload-secretorprivatekey-options-callback - * @param payload - * @param secretOrPrivateKey - * @param options - */ -export async function signPayload( - payload: RemoteUser, - secretOrPrivateKey: string, - options: JWTSignOptions = {} -): Promise { - return new Promise(function (resolve, reject): Promise { - return jwt.sign( - payload, - secretOrPrivateKey, - { - // 1 === 1ms (one millisecond) - notBefore: '1', // Make sure the time will not rollback :) - ...options, - }, - (error, token) => (error ? reject(error) : resolve(token)) - ); - }); -} - -export function verifyPayload(token: string, secretOrPrivateKey: string): RemoteUser { - return jwt.verify(token, secretOrPrivateKey); -} diff --git a/packages/utils/test/auth-utils.spec.ts b/packages/utils/test/auth-utils.spec.ts index 5fc033393..1ad97e6bc 100644 --- a/packages/utils/test/auth-utils.spec.ts +++ b/packages/utils/test/auth-utils.spec.ts @@ -1,16 +1,13 @@ import { API_ERROR, ROLES } from '@verdaccio/dev-commons'; import { VerdaccioError, getForbidden } from '@verdaccio/commons-api'; import { - allow_action, createAnonymousRemoteUser, createRemoteUser, validatePassword, - ActionsAllowed, - AllowActionCallbackResponse, - getDefaultPlugins, createSessionToken, getAuthenticatedMessage, } from '../src'; + jest.mock('@verdaccio/logger', () => ({ logger: { trace: jest.fn() }, })); @@ -60,102 +57,6 @@ describe('Auth Utilities', () => { }); }); - describe('allow_action', () => { - describe('access/publish/unpublish and anonymous', () => { - const packageAccess = { - name: 'foo', - version: undefined, - access: ['foo'], - unpublish: false, - }; - - // const type = 'access'; - test.each(['access', 'publish', 'unpublish'])( - 'should restrict %s to anonymous users', - (type) => { - allow_action(type as ActionsAllowed, { trace: jest.fn() })( - createAnonymousRemoteUser(), - { - ...packageAccess, - [type]: ['foo'], - }, - (error: VerdaccioError | null, allowed: AllowActionCallbackResponse) => { - expect(error).not.toBeNull(); - expect(allowed).toBeUndefined(); - } - ); - } - ); - - test.each(['access', 'publish', 'unpublish'])( - 'should allow %s to anonymous users', - (type) => { - allow_action(type as ActionsAllowed, { trace: jest.fn() })( - createAnonymousRemoteUser(), - { - ...packageAccess, - [type]: [ROLES.$ANONYMOUS], - }, - (error: VerdaccioError | null, allowed: AllowActionCallbackResponse) => { - expect(error).toBeNull(); - expect(allowed).toBe(true); - } - ); - } - ); - - test.each(['access', 'publish', 'unpublish'])( - 'should allow %s only if user is anonymous if the logged user has groups', - (type) => { - allow_action(type as ActionsAllowed, { trace: jest.fn() })( - createRemoteUser('juan', ['maintainer', 'admin']), - { - ...packageAccess, - [type]: [ROLES.$ANONYMOUS], - }, - (error: VerdaccioError | null, allowed: AllowActionCallbackResponse) => { - expect(error).not.toBeNull(); - expect(allowed).toBeUndefined(); - } - ); - } - ); - - test.each(['access', 'publish', 'unpublish'])( - 'should allow %s only if user is anonymous match any other groups', - (type) => { - allow_action(type as ActionsAllowed, { trace: jest.fn() })( - createRemoteUser('juan', ['maintainer', 'admin']), - { - ...packageAccess, - [type]: ['admin', 'some-other-group', ROLES.$ANONYMOUS], - }, - (error: VerdaccioError | null, allowed: AllowActionCallbackResponse) => { - expect(error).toBeNull(); - expect(allowed).toBe(true); - } - ); - } - ); - - test.each(['access', 'publish', 'unpublish'])( - 'should not allow %s anonymous if other groups are defined and does not match', - (type) => { - allow_action(type as ActionsAllowed, { trace: jest.fn() })( - createRemoteUser('juan', ['maintainer', 'admin']), - { - ...packageAccess, - [type]: ['bla-bla-group', 'some-other-group', ROLES.$ANONYMOUS], - }, - (error: VerdaccioError | null, allowed: AllowActionCallbackResponse) => { - expect(error).not.toBeNull(); - expect(allowed).toBeUndefined(); - } - ); - } - ); - }); - }); describe('createSessionToken', () => { test('should generate session token', () => { expect(createSessionToken()).toHaveProperty('expires'); @@ -163,23 +64,6 @@ describe('Auth Utilities', () => { }); }); - describe('getDefaultPlugins', () => { - test('authentication should fail by default (default)', () => { - const plugin = getDefaultPlugins({ trace: jest.fn() }); - plugin.authenticate('foo', 'bar', (error: any) => { - expect(error).toEqual(getForbidden(API_ERROR.BAD_USERNAME_PASSWORD)); - }); - }); - - test('add user should fail by default (default)', () => { - const plugin = getDefaultPlugins({ trace: jest.fn() }); - // @ts-ignore - plugin.adduser('foo', 'bar', (error: any) => { - expect(error).toEqual(getForbidden(API_ERROR.BAD_USERNAME_PASSWORD)); - }); - }); - }); - describe('getAuthenticatedMessage', () => { test('should generate user message token', () => { expect(getAuthenticatedMessage('foo')).toEqual("you are authenticated as 'foo'"); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0c29cac77..2b3bdc1fd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -224,6 +224,7 @@ importers: '@verdaccio/utils': 'link:../utils' debug: 4.1.1 express: 4.17.1 + jsonwebtoken: 8.5.1 lodash: 4.17.15 devDependencies: '@verdaccio/config': 'link:../config' @@ -241,6 +242,7 @@ importers: '@verdaccio/utils': 'workspace:5.0.0-alpha.0' debug: ^4.1.1 express: 4.17.1 + jsonwebtoken: 8.5.1 lodash: 4.17.15 packages/cli: dependencies: @@ -622,7 +624,6 @@ importers: '@verdaccio/dev-commons': 'link:../commons' '@verdaccio/readme': 'link:../core/readme' js-yaml: 3.13.1 - jsonwebtoken: 8.5.1 minimatch: 3.0.4 semver: 7.3.2 devDependencies: @@ -636,7 +637,6 @@ importers: '@verdaccio/logger': 'workspace:5.0.0-alpha.0' '@verdaccio/readme': 'workspace:*' js-yaml: 3.13.1 - jsonwebtoken: 8.5.1 lodash: ^4.17.20 minimatch: 3.0.4 semver: 7.3.2 @@ -940,7 +940,7 @@ packages: /@babel/helper-compilation-targets/7.10.4: dependencies: '@babel/compat-data': 7.11.0 - browserslist: 4.14.0 + browserslist: 4.14.4 invariant: 2.2.4 levenary: 1.1.1 semver: 5.7.1 @@ -993,7 +993,7 @@ packages: dependencies: '@babel/helper-annotate-as-pure': 7.10.4 '@babel/helper-regex': 7.10.5 - regexpu-core: 4.7.0 + regexpu-core: 4.7.1 dev: false peerDependencies: '@babel/core': ^7.0.0 @@ -1339,8 +1339,8 @@ packages: dependencies: '@babel/core': 7.10.5 '@babel/helper-plugin-utils': 7.10.4 - '@babel/plugin-syntax-object-rest-spread': 7.8.3_@babel+core@7.10.5 - '@babel/plugin-transform-parameters': 7.10.5_@babel+core@7.10.5 + '@babel/plugin-syntax-object-rest-spread': 7.8.3 + '@babel/plugin-transform-parameters': 7.10.5 dev: false peerDependencies: '@babel/core': ^7.0.0-0 @@ -1732,6 +1732,7 @@ packages: dependencies: '@babel/core': 7.10.5 '@babel/helper-plugin-utils': 7.10.4 + dev: true peerDependencies: '@babel/core': ^7.0.0-0 resolution: @@ -2251,16 +2252,6 @@ packages: '@babel/core': ^7.0.0-0 resolution: integrity: sha512-xPHwUj5RdFV8l1wuYiu5S9fqWGM2DrYc24TMvUiRrPVm+SM3XeqU9BcokQX/kEUe+p2RBwy+yoiR1w/Blq6ubw== - /@babel/plugin-transform-parameters/7.10.5_@babel+core@7.10.5: - dependencies: - '@babel/core': 7.10.5 - '@babel/helper-get-function-arity': 7.10.4 - '@babel/helper-plugin-utils': 7.10.4 - dev: false - peerDependencies: - '@babel/core': ^7.0.0-0 - resolution: - integrity: sha512-xPHwUj5RdFV8l1wuYiu5S9fqWGM2DrYc24TMvUiRrPVm+SM3XeqU9BcokQX/kEUe+p2RBwy+yoiR1w/Blq6ubw== /@babel/plugin-transform-parameters/7.10.5_@babel+core@7.11.6: dependencies: '@babel/core': 7.11.6 @@ -18839,7 +18830,6 @@ packages: regjsparser: 0.6.4 unicode-match-property-ecmascript: 1.0.4 unicode-match-property-value-ecmascript: 1.2.0 - dev: true engines: node: '>=4' resolution: