1
0
mirror of https://github.com/verdaccio/verdaccio.git synced 2024-11-08 23:25:51 +01:00

refactor: utilities and auth

This commit is contained in:
Juan Picado @jotadeveloper 2020-03-08 09:19:12 +01:00 committed by Juan Picado
parent 7739e6f4a2
commit 463888165d
17 changed files with 411 additions and 114 deletions

@ -37,7 +37,7 @@
}, },
"devDependencies": { "devDependencies": {
"@verdaccio/dev-types": "5.0.0-alpha.0", "@verdaccio/dev-types": "5.0.0-alpha.0",
"@verdaccio/types": "^9.0.0" "@verdaccio/types": "^9.3.0"
}, },
"gitHead": "7c246ede52ff717707fcae66dd63fc4abd536982" "gitHead": "7c246ede52ff717707fcae66dd63fc4abd536982"
} }

@ -7,7 +7,6 @@ import { loadPlugin } from '@verdaccio/loaders';
import { aesEncrypt, signPayload } from '@verdaccio/utils'; import { aesEncrypt, signPayload } from '@verdaccio/utils';
import { import {
getDefaultPlugins, getDefaultPlugins,
getMiddlewareCredentials,
verifyJWTPayload, verifyJWTPayload,
createAnonymousRemoteUser, createAnonymousRemoteUser,
isAuthHeaderValid, isAuthHeaderValid,
@ -23,6 +22,7 @@ import {
import { getMatchedPackagesSpec } from '@verdaccio/utils'; import { getMatchedPackagesSpec } from '@verdaccio/utils';
import { Config, Logger, Callback, IPluginAuth, RemoteUser, JWTSignOptions, Security, AuthPluginPackage, AllowAccess, PackageAccess } from '@verdaccio/types'; import { Config, Logger, Callback, IPluginAuth, RemoteUser, JWTSignOptions, Security, AuthPluginPackage, AllowAccess, PackageAccess } from '@verdaccio/types';
import { $RequestExtend, $ResponseExtend, IAuth, AESPayload } from '@verdaccio/dev-types'; import { $RequestExtend, $ResponseExtend, IAuth, AESPayload } from '@verdaccio/dev-types';
import {getMiddlewareCredentials} from "./utils";
/* eslint-disable @typescript-eslint/no-var-requires */ /* eslint-disable @typescript-eslint/no-var-requires */
const LoggerApi = require('@verdaccio/logger'); const LoggerApi = require('@verdaccio/logger');

@ -1 +1,2 @@
export { Auth } from './auth' export { Auth } from './auth'
export * from './utils';

@ -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);
}
}

@ -10,12 +10,16 @@ import {
buildUserBuffer, buildUserBuffer,
getApiToken, getApiToken,
getAuthenticatedMessage, getAuthenticatedMessage,
getMiddlewareCredentials,
getSecurity, getSecurity,
aesDecrypt, verifyPayload, aesDecrypt,
buildToken, convertPayloadToBase64, parseConfigFile verifyPayload,
buildToken,
convertPayloadToBase64,
parseConfigFile
} from '@verdaccio/utils'; } from '@verdaccio/utils';
import { getMiddlewareCredentials } from '../src'
import { IAuth } from '@verdaccio/dev-types'; import { IAuth } from '@verdaccio/dev-types';
import {Config, Security, RemoteUser} from '@verdaccio/types'; import {Config, Security, RemoteUser} from '@verdaccio/types';
import path from "path"; import path from "path";

@ -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
}
}
}
}

@ -1 +1,2 @@
export * from './constants'; export * from './constants';
export * from './helpers/pkg';

@ -22,7 +22,7 @@
}, },
"devDependencies": { "devDependencies": {
"@verdaccio/dev-commons": "5.0.0-alpha.0", "@verdaccio/dev-commons": "5.0.0-alpha.0",
"@verdaccio/types": "^8.5.2" "@verdaccio/types": "^9.3.0"
}, },
"scripts": { "scripts": {
"clean": "rimraf ./build", "clean": "rimraf ./build",

@ -7,7 +7,7 @@ import {parseConfigFile} from '@verdaccio/utils';
/** /**
* Override the default.yaml configuration file with any new config provided. * 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) : const locationFile = location ? path.join(location, configFile) :
path.join(__dirname, `./config/yaml/${configFile}`); path.join(__dirname, `./config/yaml/${configFile}`);
const config = parseConfigFile(locationFile); const config = parseConfigFile(locationFile);

@ -1,14 +1,12 @@
import path from 'path'; import path from 'path';
import _ from 'lodash';
import selfsigned from 'selfsigned'; import selfsigned from 'selfsigned';
import os from 'os'; import os from 'os';
import fs from 'fs'; import fs from 'fs';
import { configExample } from '@verdaccio/mock'; 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 {parseConfigFile} from '@verdaccio/utils';
import { getListListenAddresses } from '../src/cli-utils';
import { logger } from '@verdaccio/logger'; import { logger } from '@verdaccio/logger';
import { startVerdaccio } from '../src'; 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);
});
});
}); });

@ -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);
});
});

@ -33,7 +33,7 @@
}, },
"devDependencies": { "devDependencies": {
"@verdaccio/dev-types": "5.0.0-alpha.0", "@verdaccio/dev-types": "5.0.0-alpha.0",
"@verdaccio/types": "^8.5.2" "@verdaccio/types": "^9.3.0"
}, },
"gitHead": "7c246ede52ff717707fcae66dd63fc4abd536982" "gitHead": "7c246ede52ff717707fcae66dd63fc4abd536982"
} }

@ -1,8 +1,9 @@
import _ from 'lodash'; 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 { 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 { CookieSessionToken, IAuthWebUI, AuthTokenHeader, BasicPayload } from '@verdaccio/dev-types';
import { RemoteUser, Package, Callback, Config, Security, APITokenOptions, JWTOptions, IPluginAuth } from '@verdaccio/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 { convertPayloadToBase64, ErrorCode } from './utils';
import { aesDecrypt, verifyPayload } from './crypto-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; 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 * Create a RemoteUser object
* @return {Object} { name: xx, pluginGroups: [], real_groups: [] } * @return {Object} { name: xx, pluginGroups: [], real_groups: [] }
*/ */
export function createRemoteUser(name: string, pluginGroups: string[]): RemoteUser { export function createRemoteUser(name: string, pluginGroups: string[]): RemoteUser {
const isGroupValid: boolean = Array.isArray(pluginGroups); 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 { return {
name, name,
@ -35,17 +51,26 @@ export function createRemoteUser(name: string, pluginGroups: string[]): RemoteUs
export function createAnonymousRemoteUser(): RemoteUser { export function createAnonymousRemoteUser(): RemoteUser {
return { return {
name: undefined, name: undefined,
// groups without '$' are going to be deprecated eventually groups: [...defaultNonLoggedUserRoles],
groups: [ROLES.$ALL, ROLES.$ANONYMOUS, ROLES.DEPRECATED_ALL, ROLES.DEPRECATED_ANONYMOUS],
real_groups: [], real_groups: [],
}; };
} }
export function allow_action(action: string): Function { export type AllowActionCallbackResponse = boolean | undefined;
return function(user: RemoteUser, pkg: Package, callback: Callback): void { 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}`); logger.trace({remote: user.name}, `[auth/allow_action]: user: @{user.name}`);
const { name, groups } = user; const { name, groups } = user;
const groupAccess = pkg[action]; const groupAccess = pkg[action] as string[];
const hasPermission = groupAccess.some(group => name === group || groups.includes(group)); 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}`); 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 { 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'; const action = 'unpublish';
// verify whether the unpublish prop has been defined // verify whether the unpublish prop has been defined
const isUnpublishMissing: boolean = _.isNil(pkg[action]); 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}`); logger.trace({user: user.name, name: pkg.name, hasGroups}, `fallback unpublish for @{name} has groups: @{hasGroups} for @{user}`);
if (isUnpublishMissing || hasGroups === false) { if (isUnpublishMissing || hasGroups === false) {
@ -88,7 +113,7 @@ export function getDefaultPlugins(): IPluginAuth<Config> {
cb(ErrorCode.getForbidden(API_ERROR.BAD_USERNAME_PASSWORD)); 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)); 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 { export function isAuthHeaderValid(authorization: string): boolean {
return authorization.split(' ').length === 2; 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);
}
}

@ -42,25 +42,31 @@ export function createTarballHash(): Hash {
* @return {String} * @return {String}
*/ */
export function stringToMD5(data: Buffer | string): 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 { export function generateRandomHexString(length = 8): string {
return pseudoRandomBytes(length).toString('hex'); return pseudoRandomBytes(length).toString('hex');
} }
export async function signPayload( /**
payload: RemoteUser, * Sign the payload and return JWT
secretOrPrivateKey: string, * https://github.com/auth0/node-jsonwebtoken#jwtsignpayload-secretorprivatekey-options-callback
options: JWTSignOptions * @param payload
): Promise<string> { * @param secretOrPrivateKey
* @param options
*/
export async function signPayload(payload: RemoteUser, secretOrPrivateKey: string, options: JWTSignOptions = {}): Promise<string> {
return new Promise(function(resolve, reject): Promise<string> { return new Promise(function(resolve, reject): Promise<string> {
return jwt.sign( return jwt.sign(
payload, payload,
secretOrPrivateKey, secretOrPrivateKey,
{ {
// 1 === 1ms (one millisecond)
notBefore: '1', // Make sure the time will not rollback :) notBefore: '1', // Make sure the time will not rollback :)
...options ...options,
}, },
(error, token) => (error ? reject(error) : resolve(token)) (error, token) => (error ? reject(error) : resolve(token))
); );

@ -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<Config> = 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
});

@ -1,4 +1,4 @@
import {aesDecrypt, aesEncrypt, convertPayloadToBase64} from "@verdaccio/utils"; import {aesDecrypt, aesEncrypt, convertPayloadToBase64} from '../src';
describe('test crypto utils', () => { describe('test crypto utils', () => {
describe('default encryption', () => { describe('default encryption', () => {

BIN
yarn.lock

Binary file not shown.