diff --git a/packages/api/package.json b/packages/api/package.json index ad3271b01..4a1ed436f 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -37,7 +37,7 @@ }, "devDependencies": { "@verdaccio/dev-types": "5.0.0-alpha.0", - "@verdaccio/types": "^9.0.0" + "@verdaccio/types": "^9.3.0" }, "gitHead": "7c246ede52ff717707fcae66dd63fc4abd536982" } diff --git a/packages/auth/src/auth.ts b/packages/auth/src/auth.ts index 682e47d13..13e829440 100644 --- a/packages/auth/src/auth.ts +++ b/packages/auth/src/auth.ts @@ -7,7 +7,6 @@ import { loadPlugin } from '@verdaccio/loaders'; import { aesEncrypt, signPayload } from '@verdaccio/utils'; import { getDefaultPlugins, - getMiddlewareCredentials, verifyJWTPayload, createAnonymousRemoteUser, isAuthHeaderValid, @@ -23,6 +22,7 @@ import { import { getMatchedPackagesSpec } from '@verdaccio/utils'; import { Config, Logger, Callback, IPluginAuth, RemoteUser, JWTSignOptions, Security, AuthPluginPackage, AllowAccess, PackageAccess } from '@verdaccio/types'; import { $RequestExtend, $ResponseExtend, IAuth, AESPayload } from '@verdaccio/dev-types'; +import {getMiddlewareCredentials} from "./utils"; /* eslint-disable @typescript-eslint/no-var-requires */ const LoggerApi = require('@verdaccio/logger'); diff --git a/packages/auth/src/index.ts b/packages/auth/src/index.ts index 7804136f1..3d27f740e 100644 --- a/packages/auth/src/index.ts +++ b/packages/auth/src/index.ts @@ -1 +1,2 @@ export { Auth } from './auth' +export * from './utils'; diff --git a/packages/auth/src/utils.ts b/packages/auth/src/utils.ts new file mode 100644 index 000000000..b5241dee3 --- /dev/null +++ b/packages/auth/src/utils.ts @@ -0,0 +1,32 @@ +import {Security} from "@verdaccio/types"; +import {AuthMiddlewarePayload} from "@verdaccio/dev-types"; +import _ from "lodash"; +import {TOKEN_BEARER} from "@verdaccio/dev-commons"; +import { + isAESLegacy, + parseAESCredentials, + parseAuthTokenHeader, + parseBasicPayload, + verifyJWTPayload +} from "@verdaccio/utils"; + +export function getMiddlewareCredentials(security: Security, secret: string, authorizationHeader: string): AuthMiddlewarePayload { + if (isAESLegacy(security)) { + const credentials = parseAESCredentials(authorizationHeader, secret); + if (!credentials) { + return; + } + + const parsedCredentials = parseBasicPayload(credentials); + if (!parsedCredentials) { + return; + } + + return parsedCredentials; + } + const { scheme, token } = parseAuthTokenHeader(authorizationHeader); + + if (_.isString(token) && scheme.toUpperCase() === TOKEN_BEARER.toUpperCase()) { + return verifyJWTPayload(token, secret); + } +} diff --git a/packages/auth/test/auth-utils.spec.ts b/packages/auth/test/auth-utils.spec.ts index ada60b1e9..b0636b38f 100644 --- a/packages/auth/test/auth-utils.spec.ts +++ b/packages/auth/test/auth-utils.spec.ts @@ -10,12 +10,16 @@ import { buildUserBuffer, getApiToken, getAuthenticatedMessage, - getMiddlewareCredentials, getSecurity, - aesDecrypt, verifyPayload, - buildToken, convertPayloadToBase64, parseConfigFile + aesDecrypt, + verifyPayload, + buildToken, + convertPayloadToBase64, + parseConfigFile } from '@verdaccio/utils'; +import { getMiddlewareCredentials } from '../src' + import { IAuth } from '@verdaccio/dev-types'; import {Config, Security, RemoteUser} from '@verdaccio/types'; import path from "path"; diff --git a/packages/commons/src/helpers/pkg.ts b/packages/commons/src/helpers/pkg.ts new file mode 100644 index 000000000..734ae4217 --- /dev/null +++ b/packages/commons/src/helpers/pkg.ts @@ -0,0 +1,54 @@ +import { Package } from "@verdaccio/types"; + +export function generatePackageMetadata(pkgName: string, version = '1.0.0'): Package { + // @ts-ignore + return { + "_id": pkgName, + "name": pkgName, + "dist-tags": { + "latest": version + }, + "versions": { + [version]: { + "name": pkgName, + "version": version, + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [ + + ], + "author": { + "name": "User NPM", + "email": "user@domain.com" + }, + "license": "ISC", + "dependencies": { + "verdaccio": "^2.7.2" + }, + "readme": "# test", + "readmeFilename": "README.md", + "_id": `${pkgName}@${version}`, + "_npmVersion": "5.5.1", + "_npmUser": { + 'name': 'foo', + }, + "dist": { + "integrity": "sha512-6gHiERpiDgtb3hjqpQH5\/i7zRmvYi9pmCjQf2ZMy3QEa9wVk9RgdZaPWUt7ZOnWUPFjcr9cmE6dUBf+XoPoH4g==", + "shasum": "2c03764f651a9f016ca0b7620421457b619151b9", // pragma: allowlist secret + "tarball": `http:\/\/localhost:5555\/${pkgName}\/-\/${pkgName}-${version}.tgz` + } + } + }, + "readme": "# test", + "_attachments": { + [`${pkgName}-${version}.tgz`]: { + "content_type": "application\/octet-stream", + "data": "H4sIAAAAAAAAE+2W32vbMBDH85y\/QnjQp9qxLEeBMsbGlocNBmN7bFdQ5WuqxJaEpGQdo\/\/79KPeQsnIw5KUDX\/9IOvurLuz\/DHSjK\/YAiY6jcXSKjk6sMqypHWNdtmD6hlBI0wqQmo8nVbVqMR4OsNoVB66kF1aW8eML+Vv10m9oF\/jP6IfY4QyyTrILlD2eqkcm+gVzpdrJrPz4NuAsULJ4MZFWdBkbcByI7R79CRjx0ScCdnAvf+SkjUFWu8IubzBgXUhDPidQlfZ3BhlLpBUKDiQ1cDFrYDmKkNnZwjuhUM4808+xNVW8P2bMk1Y7vJrtLC1u1MmLPjBF40+Cc4ahV6GDmI\/DWygVRpMwVX3KtXUCg7Sxp7ff3nbt6TBFy65gK1iffsN41yoEHtdFbOiisWMH8bPvXUH0SP3k+KG3UBr+DFy7OGfEJr4x5iWVeS\/pLQe+D+FIv\/agIWI6GX66kFuIhT+1gDjrp\/4d7WAvAwEJPh0u14IufWkM0zaW2W6nLfM2lybgJ4LTJ0\/jWiAK8OcMjt8MW3OlfQppcuhhQ6k+2OgkK2Q8DssFPi\/IHpU9fz3\/+xj5NjDf8QFE39VmE4JDfzPCBn4P4X6\/f88f\/Pu47zomiPk2Lv\/dOv8h+P\/34\/D\/p9CL+Kp67mrGDRo0KBBp9ZPsETQegASAAA=", + "length": 512 + } + } + } +} diff --git a/packages/commons/src/index.ts b/packages/commons/src/index.ts index c94f80f84..6a4054b6b 100644 --- a/packages/commons/src/index.ts +++ b/packages/commons/src/index.ts @@ -1 +1,2 @@ export * from './constants'; +export * from './helpers/pkg'; diff --git a/packages/hooks/package.json b/packages/hooks/package.json index 60fa20c9e..7cba3025a 100644 --- a/packages/hooks/package.json +++ b/packages/hooks/package.json @@ -22,7 +22,7 @@ }, "devDependencies": { "@verdaccio/dev-commons": "5.0.0-alpha.0", - "@verdaccio/types": "^8.5.2" + "@verdaccio/types": "^9.3.0" }, "scripts": { "clean": "rimraf ./build", diff --git a/packages/mock/src/config.ts b/packages/mock/src/config.ts index 43be800b0..eeebc2f8a 100644 --- a/packages/mock/src/config.ts +++ b/packages/mock/src/config.ts @@ -7,7 +7,7 @@ import {parseConfigFile} from '@verdaccio/utils'; /** * Override the default.yaml configuration file with any new config provided. */ -function configExample(externalConfig, configFile: string = 'default.yaml', location: string) { +function configExample(externalConfig, configFile: string = 'default.yaml', location: string = '') { const locationFile = location ? path.join(location, configFile) : path.join(__dirname, `./config/yaml/${configFile}`); const config = parseConfigFile(locationFile); diff --git a/packages/node-api/test/node-api.spec.ts b/packages/node-api/test/node-api.spec.ts index 0753d4aa3..822d48323 100644 --- a/packages/node-api/test/node-api.spec.ts +++ b/packages/node-api/test/node-api.spec.ts @@ -1,14 +1,12 @@ import path from 'path'; -import _ from 'lodash'; import selfsigned from 'selfsigned'; import os from 'os'; import fs from 'fs'; import { configExample } from '@verdaccio/mock'; -import {DEFAULT_DOMAIN, DEFAULT_PORT, DEFAULT_PROTOCOL} from '@verdaccio/dev-commons'; +import {DEFAULT_DOMAIN, DEFAULT_PROTOCOL} from '@verdaccio/dev-commons'; import {parseConfigFile} from '@verdaccio/utils'; -import { getListListenAddresses } from '../src/cli-utils'; import { logger } from '@verdaccio/logger'; import { startVerdaccio } from '../src'; @@ -181,66 +179,4 @@ describe('startServer via API', () => { }); }); - - describe('getListListenAddresses test', () => { - - test('should return no address if a single address is wrong', () => { - // @ts-ignore - const addrs = getListListenAddresses("wrong"); - - expect(_.isArray(addrs)).toBeTruthy(); - expect(addrs).toHaveLength(0); - }); - - test('should return no address if a two address are wrong', () => { - // @ts-ignore - const addrs = getListListenAddresses(["wrong", "same-wrong"]); - - expect(_.isArray(addrs)).toBeTruthy(); - expect(addrs).toHaveLength(0); - }); - - test('should return a list of 1 address provided', () => { - // @ts-ignore - const addrs = getListListenAddresses(null, '1000'); - - expect(_.isArray(addrs)).toBeTruthy(); - expect(addrs).toHaveLength(1); - }); - - test('should return a list of 2 address provided', () => { - // @ts-ignore - const addrs = getListListenAddresses(null, ['1000', '2000']); - - expect(_.isArray(addrs)).toBeTruthy(); - expect(addrs).toHaveLength(2); - }); - - test(`should return by default ${DEFAULT_PORT}`, () => { - // @ts-ignore - const [addrs] = getListListenAddresses(); - - // @ts-ignore - expect(addrs.proto).toBe(DEFAULT_PROTOCOL); - // @ts-ignore - expect(addrs.host).toBe(DEFAULT_DOMAIN); - // @ts-ignore - expect(addrs.port).toBe(DEFAULT_PORT); - }); - - test('should return default proto, host and custom port', () => { - const initPort = '1000'; - // @ts-ignore - const [addrs] = getListListenAddresses(null, initPort); - - // @ts-ignore - expect(addrs.proto).toEqual(DEFAULT_PROTOCOL); - // @ts-ignore - expect(addrs.host).toEqual(DEFAULT_DOMAIN); - // @ts-ignore - expect(addrs.port).toEqual(initPort); - }); - - }); - }); diff --git a/packages/node-api/test/node-api.utils.spec.ts b/packages/node-api/test/node-api.utils.spec.ts new file mode 100644 index 000000000..daf7dfd95 --- /dev/null +++ b/packages/node-api/test/node-api.utils.spec.ts @@ -0,0 +1,76 @@ +import {getListListenAddresses} from "../src/cli-utils"; +import _ from "lodash"; +import {DEFAULT_DOMAIN, DEFAULT_PORT, DEFAULT_PROTOCOL} from "@verdaccio/dev-commons"; + +jest.mock('@verdaccio/logger', () => ({ + setup: jest.fn(), + logger: { + child: jest.fn(), + debug: jest.fn(), + trace: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + fatal: jest.fn() + } +})); + +describe('getListListenAddresses test', () => { + + test('should return no address if a single address is wrong', () => { + // @ts-ignore + const addrs = getListListenAddresses("wrong"); + + expect(_.isArray(addrs)).toBeTruthy(); + expect(addrs).toHaveLength(0); + }); + + test('should return no address if a two address are wrong', () => { + // @ts-ignore + const addrs = getListListenAddresses(["wrong", "same-wrong"]); + + expect(_.isArray(addrs)).toBeTruthy(); + expect(addrs).toHaveLength(0); + }); + + test('should return a list of 1 address provided', () => { + // @ts-ignore + const addrs = getListListenAddresses(null, '1000'); + + expect(_.isArray(addrs)).toBeTruthy(); + expect(addrs).toHaveLength(1); + }); + + test('should return a list of 2 address provided', () => { + // @ts-ignore + const addrs = getListListenAddresses(null, ['1000', '2000']); + + expect(_.isArray(addrs)).toBeTruthy(); + expect(addrs).toHaveLength(2); + }); + + test(`should return by default ${DEFAULT_PORT}`, () => { + // @ts-ignore + const [addrs] = getListListenAddresses(); + + // @ts-ignore + expect(addrs.proto).toBe(DEFAULT_PROTOCOL); + // @ts-ignore + expect(addrs.host).toBe(DEFAULT_DOMAIN); + // @ts-ignore + expect(addrs.port).toBe(DEFAULT_PORT); + }); + + test('should return default proto, host and custom port', () => { + const initPort = '1000'; + // @ts-ignore + const [addrs] = getListListenAddresses(null, initPort); + + // @ts-ignore + expect(addrs.proto).toEqual(DEFAULT_PROTOCOL); + // @ts-ignore + expect(addrs.host).toEqual(DEFAULT_DOMAIN); + // @ts-ignore + expect(addrs.port).toEqual(initPort); + }); + +}); diff --git a/packages/proxy/package.json b/packages/proxy/package.json index 153550ad0..aafd55f91 100644 --- a/packages/proxy/package.json +++ b/packages/proxy/package.json @@ -33,7 +33,7 @@ }, "devDependencies": { "@verdaccio/dev-types": "5.0.0-alpha.0", - "@verdaccio/types": "^8.5.2" + "@verdaccio/types": "^9.3.0" }, "gitHead": "7c246ede52ff717707fcae66dd63fc4abd536982" } diff --git a/packages/utils/src/auth-utils.ts b/packages/utils/src/auth-utils.ts index a80aeee7f..86141114d 100644 --- a/packages/utils/src/auth-utils.ts +++ b/packages/utils/src/auth-utils.ts @@ -1,8 +1,9 @@ import _ from 'lodash'; import { API_ERROR, HTTP_STATUS, ROLES, TIME_EXPIRATION_7D, TOKEN_BASIC, TOKEN_BEARER, DEFAULT_MIN_LIMIT_PASSWORD } from '@verdaccio/dev-commons'; -import { CookieSessionToken, IAuthWebUI, AuthMiddlewarePayload, AuthTokenHeader, BasicPayload } from '@verdaccio/dev-types'; -import { RemoteUser, Package, Callback, Config, Security, APITokenOptions, JWTOptions, IPluginAuth } from '@verdaccio/types'; +import { CookieSessionToken, IAuthWebUI, AuthTokenHeader, BasicPayload } from '@verdaccio/dev-types'; +import { RemoteUser, AllowAccess, PackageAccess, Callback, Config, Security, APITokenOptions, JWTOptions, IPluginAuth } from '@verdaccio/types'; +import { VerdaccioError } from '@verdaccio/commons-api'; import { convertPayloadToBase64, ErrorCode } from './utils'; import { aesDecrypt, verifyPayload } from './crypto-utils'; @@ -13,13 +14,28 @@ export function validatePassword(password: string, minLength: number = DEFAULT_M return typeof password === 'string' && password.length >= minLength; } +/** + * All logged users will have by default the group $all and $authenticate + */ +export const defaultLoggedUserRoles = [ROLES.$ALL, ROLES.$AUTH, ROLES.DEPRECATED_ALL, ROLES.DEPRECATED_AUTH, ROLES.ALL]; +/** + * + */ +export const defaultNonLoggedUserRoles = [ + ROLES.$ALL, + ROLES.$ANONYMOUS, + // groups without '$' are going to be deprecated eventually + ROLES.DEPRECATED_ALL, + ROLES.DEPRECATED_ANONYMOUS +]; + /** * Create a RemoteUser object * @return {Object} { name: xx, pluginGroups: [], real_groups: [] } */ export function createRemoteUser(name: string, pluginGroups: string[]): RemoteUser { const isGroupValid: boolean = Array.isArray(pluginGroups); - const groups = (isGroupValid ? pluginGroups : []).concat([ROLES.$ALL, ROLES.$AUTH, ROLES.DEPRECATED_ALL, ROLES.DEPRECATED_AUTH, ROLES.ALL]); + const groups = (isGroupValid ? pluginGroups : []).concat([...defaultLoggedUserRoles]); return { name, @@ -35,17 +51,26 @@ export function createRemoteUser(name: string, pluginGroups: string[]): RemoteUs export function createAnonymousRemoteUser(): RemoteUser { return { name: undefined, - // groups without '$' are going to be deprecated eventually - groups: [ROLES.$ALL, ROLES.$ANONYMOUS, ROLES.DEPRECATED_ALL, ROLES.DEPRECATED_ANONYMOUS], + groups: [...defaultNonLoggedUserRoles], real_groups: [], }; } -export function allow_action(action: string): Function { - return function(user: RemoteUser, pkg: Package, callback: Callback): void { +export type AllowActionCallbackResponse = boolean | undefined; +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): 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]; + 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}`); @@ -66,11 +91,11 @@ export function allow_action(action: string): Function { * */ export function handleSpecialUnpublish(): any { - return function(user: RemoteUser, pkg: Package, callback: Callback): void { + 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].length > 0; + 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) { @@ -88,7 +113,7 @@ export function getDefaultPlugins(): IPluginAuth { cb(ErrorCode.getForbidden(API_ERROR.BAD_USERNAME_PASSWORD)); }, - add_user(user: string, password: string, cb: Callback): void { + adduser(user: string, password: string, cb: Callback): void { return cb(ErrorCode.getConflict(API_ERROR.BAD_USERNAME_PASSWORD)); }, @@ -227,24 +252,3 @@ export function verifyJWTPayload(token: string, secret: string): RemoteUser { export function isAuthHeaderValid(authorization: string): boolean { return authorization.split(' ').length === 2; } - -export function getMiddlewareCredentials(security: Security, secret: string, authorizationHeader: string): AuthMiddlewarePayload { - if (isAESLegacy(security)) { - const credentials = parseAESCredentials(authorizationHeader, secret); - if (!credentials) { - return; - } - - const parsedCredentials = parseBasicPayload(credentials); - if (!parsedCredentials) { - return; - } - - return parsedCredentials; - } - const { scheme, token } = parseAuthTokenHeader(authorizationHeader); - - if (_.isString(token) && scheme.toUpperCase() === TOKEN_BEARER.toUpperCase()) { - return verifyJWTPayload(token, secret); - } -} diff --git a/packages/utils/src/crypto-utils.ts b/packages/utils/src/crypto-utils.ts index 21bd4167f..2630ff922 100644 --- a/packages/utils/src/crypto-utils.ts +++ b/packages/utils/src/crypto-utils.ts @@ -42,25 +42,31 @@ export function createTarballHash(): Hash { * @return {String} */ export function stringToMD5(data: Buffer | string): string { - return createHash('md5').update(data).digest('hex'); + return createHash('md5') + .update(data) + .digest('hex'); } export function generateRandomHexString(length = 8): string { return pseudoRandomBytes(length).toString('hex'); } -export async function signPayload( - payload: RemoteUser, - secretOrPrivateKey: string, - options: JWTSignOptions -): Promise { - return new Promise(function (resolve, reject): Promise { +/** + * 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 + ...options, }, (error, token) => (error ? reject(error) : resolve(token)) ); diff --git a/packages/utils/test/auth-utils.spec.ts b/packages/utils/test/auth-utils.spec.ts new file mode 100644 index 000000000..8a640e641 --- /dev/null +++ b/packages/utils/test/auth-utils.spec.ts @@ -0,0 +1,183 @@ +import { + allow_action, + createAnonymousRemoteUser, + createRemoteUser, + validatePassword, + ActionsAllowed, + AllowActionCallbackResponse, + getDefaultPlugins, + createSessionToken, + getAuthenticatedMessage, + verifyJWTPayload, defaultNonLoggedUserRoles, signPayload +} from "../src"; +import { API_ERROR, ROLES } from "@verdaccio/dev-commons"; +import { VerdaccioError, getForbidden } from "@verdaccio/commons-api"; +import { Config, IPluginAuth } from '@verdaccio/types'; +jest.mock('@verdaccio/logger', () => ({ + logger: { trace: jest.fn() } +})); + +describe('Auth Utilities', () => { + + describe('validatePassword', () => { + test('should validate password according the length', () => { + expect(validatePassword('12345', 1)).toBeTruthy(); + }); + + test('should fails on validate password according the length', () => { + expect(validatePassword('12345', 10)).toBeFalsy(); + }); + + test('should fails on validate password according the length and default config', () => { + expect(validatePassword('12')).toBeFalsy(); + }); + + test('should validate password according the length and default config', () => { + expect(validatePassword('1235678910')).toBeTruthy(); + }); + }); + + describe('createRemoteUser and createAnonymousRemoteUser', () => { + test('should create a remote user with default groups', () => { + expect(createRemoteUser('12345', ['foo', 'bar'])).toEqual( + { + "groups": ["foo", "bar", ROLES.$ALL, ROLES.$AUTH, ROLES.DEPRECATED_ALL, ROLES.DEPRECATED_AUTH, ROLES.ALL], + "name": "12345", + "real_groups": ["foo", "bar"] + } + ); + }); + + test('should create a anonymous remote user with default groups', () => { + expect(createAnonymousRemoteUser()).toEqual( + { + "groups": [ROLES.$ALL, ROLES.$ANONYMOUS, ROLES.DEPRECATED_ALL, ROLES.DEPRECATED_ANONYMOUS], + "name": undefined, + "real_groups": [] + } + ); + }); + }); + + 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)( + 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)( + 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)( + 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)( + 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)( + 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'); + expect(createSessionToken().expires).toBeInstanceOf(Date); + }); + }); + + describe('getDefaultPlugins', () => { + test('authentication should fail by default (default)', () => { + const plugin = getDefaultPlugins(); + 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: IPluginAuth = getDefaultPlugins(); + // @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\''); + }); + }); + + describe('verifyJWTPayload', () => { + test('should fail on verify the token and return anonymous users', () => { + expect(verifyJWTPayload('fakeToken', 'secret')).toEqual(createAnonymousRemoteUser()); + }); + + test('should fail on verify the token and return anonymous users', async () => { + const remoteUser = createRemoteUser('foo', []); + const token = await signPayload(remoteUser, '12345'); + const verifiedToken = verifyJWTPayload(token, '12345'); + expect(verifiedToken.groups).toEqual(remoteUser.groups); + expect(verifiedToken.name).toEqual(remoteUser.name); + }); + }); + + // verifyJWTPayload +}); diff --git a/packages/auth/test/crypto-utils.spec.ts b/packages/utils/test/crypto-utils.spec.ts similarity index 81% rename from packages/auth/test/crypto-utils.spec.ts rename to packages/utils/test/crypto-utils.spec.ts index 0575627da..3dffc23fe 100644 --- a/packages/auth/test/crypto-utils.spec.ts +++ b/packages/utils/test/crypto-utils.spec.ts @@ -1,4 +1,4 @@ -import {aesDecrypt, aesEncrypt, convertPayloadToBase64} from "@verdaccio/utils"; +import {aesDecrypt, aesEncrypt, convertPayloadToBase64} from '../src'; describe('test crypto utils', () => { describe('default encryption', () => { diff --git a/yarn.lock b/yarn.lock index d48fc9180..70a432cb3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2745,16 +2745,11 @@ resolved "https://registry.verdaccio.org/@verdaccio%2fstreams/-/streams-9.3.1.tgz#4e1524382e18d3121d60c32b7d5ee39e21381430" integrity sha512-AO0i8lsu3H1ss694Dtg9KbWpOlSRFNVeT5J2oscAAjQydXjOB63paxiOdUBTaavhT03T+i/AnSgWahsdSG1diA== -"@verdaccio/types@9.3.0", "@verdaccio/types@^9.0.0", "@verdaccio/types@^9.3.0": +"@verdaccio/types@9.3.0", "@verdaccio/types@^9.3.0": version "9.3.0" resolved "https://registry.verdaccio.org/@verdaccio%2ftypes/-/types-9.3.0.tgz#4062183e84bef3a56b4275d111181873f8a082d6" integrity sha512-TzBuWPKxhQILk3Tl8EGvAj6zinwBJw+bEhg5w7HYoE+FpEV6rJ1XW+GF/h/7mRBPhtKiPMMzclRRPysNsT/0ww== -"@verdaccio/types@^8.5.2": - version "8.5.2" - resolved "https://registry.verdaccio.org/@verdaccio%2ftypes/-/types-8.5.2.tgz#4e371aae10e8550b4a19b57f6ba1f1f185147669" - integrity sha512-x/sacqVndl1dXVKPd7pomea6gs9BKC+i83LDn6MEAO+tuhqWRsKC3UwztLA1YSKjNF33//7JCYMDQk6uaJ9ipw== - "@verdaccio/ui-theme@^0.3.12", "@verdaccio/ui-theme@^0.3.13": version "0.3.13" resolved "https://registry.verdaccio.org/@verdaccio%2fui-theme/-/ui-theme-0.3.13.tgz#e6f06907b0940c47883f35861723012437b7b958"