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

chore: lint prettier update (#1904)

* chore: update prettier@2.x

* chore: prettier auto fix

* chore: fake circleci

we don't need it but I want make dissapear the error
This commit is contained in:
Juan Picado 2020-08-13 23:27:00 +02:00
parent a13e57586c
commit e61bd6c78f
153 changed files with 4313 additions and 4598 deletions

8
.circleci/config.yml Normal file

@ -0,0 +1,8 @@
version: 2.1
jobs:
build:
docker:
- image: circleci/node:12 # the primary container, where your job's commands are run
steps:
- checkout # check out the code in the project directory
- run: echo "hello world" # run the `echo` command

@ -2,21 +2,23 @@
"extends": [
"eslint:recommended",
"google",
"plugin:prettier/recommended",
"plugin:react/recommended",
"plugin:jest/recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"plugin:import/typescript",
"plugin:prettier/recommended",
"plugin:jsx-a11y/recommended",
"prettier",
"prettier/react",
"prettier/@typescript-eslint"
],
"plugins": [
"import",
"jest",
"prettier",
"jsx-a11y",
"react-hooks"
"react-hooks",
"prettier"
],
"env": {
"es6": true,
@ -30,7 +32,7 @@
"parserOptions": {
"allowImportExportEverywhere": true,
"sourceType": "module",
"ecmaVersion": 2019,
"ecmaVersion": 11,
"ecmaFeatures": {
"impliedStrict": true,
"jsx": true
@ -52,18 +54,25 @@
"error", "all"
],
"prettier/prettier": [
"error",
2,
{
"endOfLine": "auto",
"printWidth": 120,
"tabWidth": 2,
"useTabs": false,
"semi": true,
"trailingComma": "es5",
"printWidth": 160,
"tabWidth": 2,
"singleQuote": true,
"bracketSpacing": true
"bracketSpacing": true,
"jsxBracketSameLine": true,
"trailingComma": "es5",
"semi": true,
"parser": "typescript"
}
],
"react/prop-types": 0,
"jest/no-export": 0,
"jest/no-test-callback": 0,
"jest/expect-expect": 0,
"jest/no-try-expect": 0,
"jest/no-identical-title": 1,
"keyword-spacing": "off",
"no-tabs": "off",
"no-useless-escape": "off",
@ -71,16 +80,10 @@
"require-jsdoc": "off",
"valid-jsdoc": "off",
"import/order": ["warn"],
"react/prop-types": 0,
"jest/no-export": 0,
"jest/no-test-callback": 0,
"jest/no-try-expect": 1,
"jest/no-identical-title": 1,
"eol-last": "warn",
"no-irregular-whitespace": "warn",
"no-mixed-spaces-and-tabs": ["warn", "smart-tabs"],
"no-trailing-spaces": "warn",
"camelcase": "off",
"guard-for-in": "error",
"new-cap": "error",
@ -99,7 +102,9 @@
"one-var": "error",
"prefer-rest-params": "warn",
"prefer-spread": "warn",
"semi": 0,
"handle-callback-err": 0,
"prefer-const": 0,
"prefer-promise-reject-errors": 1,
"@typescript-eslint/camelcase": 0,
"@typescript-eslint/ban-ts-ignore": 0,
"@typescript-eslint/no-var-requires": 0,
@ -115,9 +120,6 @@
"@typescript-eslint/ban-types": 0,
"@typescript-eslint/explicit-module-boundary-types": 0,
"@typescript-eslint/explicit-member-accessibility": ["warn"],
"@typescript-eslint/prefer-optional-chain": ["warn"],
"handle-callback-err": 0,
"prefer-const": 0,
"prefer-promise-reject-errors": 1
"@typescript-eslint/prefer-optional-chain": ["warn"]
}
}

@ -85,7 +85,7 @@
"kleur": "3.0.3",
"lint-staged": "8.2.1",
"nock": "12.0.3",
"prettier": "1.19.1",
"prettier": "2.0.5",
"rimraf": "3.0.2",
"selfsigned": "1.10.7",
"standard-version": "8.0.0",

@ -1,6 +1,6 @@
import mime from 'mime';
import _ from 'lodash';
import{ Router } from 'express';
import { Router } from 'express';
import { media, allow } from '@verdaccio/middleware';
import { API_MESSAGE, HTTP_STATUS, DIST_TAGS } from '@verdaccio/dev-commons';
@ -8,18 +8,18 @@ import { VerdaccioError } from '@verdaccio/commons-api';
import { Package } from '@verdaccio/types';
// @ts-ignore
import{ IAuth, $ResponseExtend, $RequestExtend, $NextFunctionVer, IStorageHandler } from '@verdaccio/dev-types';
import { IAuth, $ResponseExtend, $RequestExtend, $NextFunctionVer, IStorageHandler } from '@verdaccio/dev-types';
export default function(route: Router, auth: IAuth, storage: IStorageHandler): void {
export default function (route: Router, auth: IAuth, storage: IStorageHandler): void {
const can = allow(auth);
const tag_package_version = function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): $NextFunctionVer {
const tag_package_version = function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): $NextFunctionVer {
if (_.isString(req.body) === false) {
return next('route');
}
const tags = {};
tags[req.params.tag] = req.body;
storage.mergeTags(req.params.package, tags, function(err: Error): $NextFunctionVer {
storage.mergeTags(req.params.package, tags, function (err: Error): $NextFunctionVer {
if (err) {
return next(err);
}
@ -35,10 +35,10 @@ export default function(route: Router, auth: IAuth, storage: IStorageHandler): v
route.put('/-/package/:package/dist-tags/:tag', can('publish'), media(mime.getType('json')), tag_package_version);
route.delete('/-/package/:package/dist-tags/:tag', can('publish'), function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
route.delete('/-/package/:package/dist-tags/:tag', can('publish'), function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
const tags = {};
tags[req.params.tag] = null;
storage.mergeTags(req.params.package, tags, function(err: VerdaccioError): $NextFunctionVer {
storage.mergeTags(req.params.package, tags, function (err: VerdaccioError): $NextFunctionVer {
if (err) {
return next(err);
}
@ -49,12 +49,12 @@ export default function(route: Router, auth: IAuth, storage: IStorageHandler): v
});
});
route.get('/-/package/:package/dist-tags', can('access'), function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
route.get('/-/package/:package/dist-tags', can('access'), function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
storage.getPackage({
name: req.params.package,
uplinksLook: true,
req,
callback: function(err: VerdaccioError, info: Package): $NextFunctionVer {
callback: function (err: VerdaccioError, info: Package): $NextFunctionVer {
if (err) {
return next(err);
}
@ -64,8 +64,8 @@ export default function(route: Router, auth: IAuth, storage: IStorageHandler): v
});
});
route.post('/-/package/:package/dist-tags', can('publish'), function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
storage.mergeTags(req.params.package, req.body, function(err: VerdaccioError): $NextFunctionVer {
route.post('/-/package/:package/dist-tags', can('publish'), function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
storage.mergeTags(req.params.package, req.body, function (err: VerdaccioError): $NextFunctionVer {
if (err) {
return next(err);
}

@ -1,5 +1,5 @@
import _ from 'lodash';
import express, {Express} from 'express';
import express, { Express } from 'express';
import { match, validateName, validatePackage, encodeScopePackage, antiLoop } from '@verdaccio/middleware';
import { IAuth, IStorageHandler } from '@verdaccio/dev-types';
@ -16,9 +16,9 @@ import pkg from './package';
import stars from './stars';
import profile from './v1/profile';
import token from './v1/token';
import v1Search from './v1/search'
import v1Search from './v1/search';
export default function(config: Config, auth: IAuth, storage: IStorageHandler): Express.Application {
export default function (config: Config, auth: IAuth, storage: IStorageHandler): Express.Application {
/* eslint new-cap:off */
const app = express.Router();
/* eslint new-cap:off */
@ -55,7 +55,7 @@ export default function(config: Config, auth: IAuth, storage: IStorageHandler):
stars(app, storage);
if (_.get(config, 'experiments.search') === true) {
v1Search(app, auth, storage)
v1Search(app, auth, storage);
}
if (_.get(config, 'experiments.token') === true) {

@ -10,11 +10,11 @@ import { IAuth, $ResponseExtend, $RequestExtend, $NextFunctionVer, IStorageHandl
const downloadStream = (packageName: string, filename: string, storage: any, req: $RequestExtend, res: $ResponseExtend): void => {
const stream = storage.getTarball(packageName, filename);
stream.on('content-length', function(content): void {
stream.on('content-length', function (content): void {
res.header('Content-Length', content);
});
stream.on('error', function(err): void {
stream.on('error', function (err): void {
return res.locals.report_error(err);
});
@ -22,11 +22,11 @@ const downloadStream = (packageName: string, filename: string, storage: any, req
stream.pipe(res);
};
export default function(route: Router, auth: IAuth, storage: IStorageHandler, config: Config): void {
export default function (route: Router, auth: IAuth, storage: IStorageHandler, config: Config): void {
const can = allow(auth);
// TODO: anonymous user?
route.get('/:package/:version?', can('access'), function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
const getPackageMetaCallback = function(err, metadata: Package): void {
route.get('/:package/:version?', can('access'), function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
const getPackageMetaCallback = function (err, metadata: Package): void {
if (err) {
return next(err);
}
@ -62,13 +62,13 @@ export default function(route: Router, auth: IAuth, storage: IStorageHandler, co
});
});
route.get('/:scopedPackage/-/:scope/:filename', can('access'), function(req: $RequestExtend, res: $ResponseExtend): void {
route.get('/:scopedPackage/-/:scope/:filename', can('access'), function (req: $RequestExtend, res: $ResponseExtend): void {
const { scopedPackage, filename } = req.params;
downloadStream(scopedPackage, filename, storage, req, res);
});
route.get('/:package/-/:filename', can('access'), function(req: $RequestExtend, res: $ResponseExtend): void {
route.get('/:package/-/:filename', can('access'), function (req: $RequestExtend, res: $ResponseExtend): void {
downloadStream(req.params.package, req.params.filename, storage, req, res);
});
}

@ -2,10 +2,7 @@ import { Router } from 'express';
import { $RequestExtend, $ResponseExtend, $NextFunctionVer } from '@verdaccio/dev-types';
export default function (route: Router): void {
route.get(
'/-/ping',
function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer) {
next({});
}
);
route.get('/-/ping', function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer) {
next({});
});
}

@ -5,14 +5,14 @@ import { Router } from 'express';
import { IAuth, $ResponseExtend, $RequestExtend, $NextFunctionVer, IStorageHandler } from '@verdaccio/dev-types';
import { API_MESSAGE, HEADERS, DIST_TAGS, API_ERROR, HTTP_STATUS } from '@verdaccio/dev-commons';
import {validateMetadata, isObject, ErrorCode, hasDiffOneKey, isRelatedToDeprecation} from '@verdaccio/utils';
import { validateMetadata, isObject, ErrorCode, hasDiffOneKey, isRelatedToDeprecation } from '@verdaccio/utils';
import { media, expectJson, allow } from '@verdaccio/middleware';
import { notify } from '@verdaccio/hooks';
import { Config, Callback, MergeTags, Version, Package } from '@verdaccio/types';
import { logger } from '@verdaccio/logger';
import star from './star';
import {isPublishablePackage} from "./utils";
import { isPublishablePackage } from './utils';
export default function publish(router: Router, auth: IAuth, storage: IStorageHandler, config: Config): void {
const can = allow(auth);
@ -103,43 +103,43 @@ export default function publish(router: Router, auth: IAuth, storage: IStorageHa
*/
export function publishPackage(storage: IStorageHandler, config: Config, auth: IAuth): any {
const starApi = star(storage);
return function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
return function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
const packageName = req.params.package;
logger.debug({packageName} , `publishing or updating a new version for @{packageName}`);
logger.debug({ packageName }, `publishing or updating a new version for @{packageName}`);
/**
* Write tarball of stream data from package clients.
*/
const createTarball = function(filename: string, data, cb: Callback): void {
const createTarball = function (filename: string, data, cb: Callback): void {
const stream = storage.addTarball(packageName, filename);
stream.on('error', function(err) {
stream.on('error', function (err) {
cb(err);
});
stream.on('success', function() {
stream.on('success', function () {
cb();
});
// this is dumb and memory-consuming, but what choices do we have?
// flow: we need first refactor this file before decides which type use here
stream.end(Buffer.from(data.data, 'base64'));
stream.end(new Buffer(data.data, 'base64'));
stream.done();
};
/**
* Add new package version in storage
*/
const createVersion = function(version: string, metadata: Version, cb: Callback): void {
const createVersion = function (version: string, metadata: Version, cb: Callback): void {
storage.addVersion(packageName, version, metadata, null, cb);
};
/**
* Add new tags in storage
*/
const addTags = function(tags: MergeTags, cb: Callback): void {
const addTags = function (tags: MergeTags, cb: Callback): void {
storage.mergeTags(packageName, tags, cb);
};
const afterChange = function(error, okMessage, metadata: Package): void {
const afterChange = function (error, okMessage, metadata: Package): void {
const metadataCopy: Package = { ...metadata };
const { _attachments, versions } = metadataCopy;
@ -160,8 +160,7 @@ export function publishPackage(storage: IStorageHandler, config: Config, auth: I
// npm-registry-client 0.3+ embeds tarball into the json upload
// https://github.com/isaacs/npm-registry-client/commit/e9fbeb8b67f249394f735c74ef11fe4720d46ca0
// issue https://github.com/rlidwka/sinopia/issues/31, dealing with it here:
const isInvalidBodyFormat = isObject(_attachments) === false || hasDiffOneKey(_attachments) ||
isObject(versions) === false || hasDiffOneKey(versions);
const isInvalidBodyFormat = isObject(_attachments) === false || hasDiffOneKey(_attachments) || isObject(versions) === false || hasDiffOneKey(versions);
if (isInvalidBodyFormat) {
// npm is doing something strange again
@ -177,22 +176,21 @@ export function publishPackage(storage: IStorageHandler, config: Config, auth: I
// at this point document is either created or existed before
const [firstAttachmentKey] = Object.keys(_attachments);
createTarball(Path.basename(firstAttachmentKey), _attachments[firstAttachmentKey], function(error) {
createTarball(Path.basename(firstAttachmentKey), _attachments[firstAttachmentKey], function (error) {
if (error) {
return next(error);
}
const versionToPublish = Object.keys(versions)[0];
const versionMetadataToPublish = versions[versionToPublish];
versionMetadataToPublish.readme = _.isNil(versionMetadataToPublish.readme) === false ? String(versionMetadataToPublish.readme) : '';
versions[versionToPublish].readme = _.isNil(metadataCopy.readme) === false ? String(metadataCopy.readme) : '';
createVersion(versionToPublish, versionMetadataToPublish, function(error) {
createVersion(versionToPublish, versions[versionToPublish], function (error) {
if (error) {
return next(error);
}
addTags(metadataCopy[DIST_TAGS], async function(error) {
addTags(metadataCopy[DIST_TAGS], async function (error) {
if (error) {
return next(error);
}
@ -218,27 +216,27 @@ export function publishPackage(storage: IStorageHandler, config: Config, auth: I
const metadata = validateMetadata(req.body, packageName);
// treating deprecation as updating a package
if (req.params._rev || isRelatedToDeprecation(req.body)) {
logger.debug({packageName} , `updating a new version for @{packageName}`);
logger.debug({ packageName }, `updating a new version for @{packageName}`);
// we check unpublish permissions, an update is basically remove versions
const remote = req.remote_user;
auth.allow_unpublish({packageName}, remote, (error) => {
auth.allow_unpublish({ packageName }, remote, (error) => {
if (error) {
logger.debug({packageName} , `not allowed to unpublish a version for @{packageName}`);
logger.debug({ packageName }, `not allowed to unpublish a version for @{packageName}`);
return next(error);
}
storage.changePackage(packageName, metadata, req.params.revision, function(error) {
storage.changePackage(packageName, metadata, req.params.revision, function (error) {
afterChange(error, API_MESSAGE.PKG_CHANGED, metadata);
});
});
} else {
logger.debug({packageName} , `adding a new version for @{packageName}`);
storage.addPackage(packageName, metadata, function(error) {
logger.debug({ packageName }, `adding a new version for @{packageName}`);
storage.addPackage(packageName, metadata, function (error) {
afterChange(error, API_MESSAGE.PKG_CREATED, metadata);
});
}
} catch (error) {
logger.error({packageName}, 'error on publish, bad package data for @{packageName}');
logger.error({ packageName }, 'error on publish, bad package data for @{packageName}');
return next(ErrorCode.getBadData(API_ERROR.BAD_PACKAGE_DATA));
}
};
@ -248,11 +246,11 @@ export function publishPackage(storage: IStorageHandler, config: Config, auth: I
* un-publish a package
*/
export function unPublishPackage(storage: IStorageHandler) {
return function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
return function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
const packageName = req.params.package;
logger.debug({packageName} , `unpublishing @{packageName}`);
storage.removePackage(packageName, function(err) {
logger.debug({ packageName }, `unpublishing @{packageName}`);
storage.removePackage(packageName, function (err) {
if (err) {
return next(err);
}
@ -266,18 +264,18 @@ export function unPublishPackage(storage: IStorageHandler) {
* Delete tarball
*/
export function removeTarball(storage: IStorageHandler) {
return function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
return function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
const packageName = req.params.package;
const {filename, revision} = req.params;
const { filename, revision } = req.params;
logger.debug({packageName, filename, revision} , `removing a tarball for @{packageName}-@{tarballName}-@{revision}`);
storage.removeTarball(packageName, filename, revision, function(err) {
logger.debug({ packageName, filename, revision }, `removing a tarball for @{packageName}-@{tarballName}-@{revision}`);
storage.removeTarball(packageName, filename, revision, function (err) {
if (err) {
return next(err);
}
res.status(HTTP_STATUS.CREATED);
logger.debug({packageName, filename, revision} , `success remove tarball for @{packageName}-@{tarballName}-@{revision}`);
logger.debug({ packageName, filename, revision }, `success remove tarball for @{packageName}-@{tarballName}-@{revision}`);
return next({ ok: API_MESSAGE.TARBALL_REMOVED });
});
};
@ -286,11 +284,11 @@ export function removeTarball(storage: IStorageHandler) {
* Adds a new version
*/
export function addVersion(storage: IStorageHandler) {
return function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
return function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
const { version, tag } = req.params;
const packageName = req.params.package;
storage.addVersion(packageName, version, req.body, tag, function(error) {
storage.addVersion(packageName, version, req.body, tag, function (error) {
if (error) {
return next(error);
}
@ -307,29 +305,29 @@ export function addVersion(storage: IStorageHandler) {
* uploadPackageTarball
*/
export function uploadPackageTarball(storage: IStorageHandler) {
return function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
return function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
const packageName = req.params.package;
const stream = storage.addTarball(packageName, req.params.filename);
req.pipe(stream);
// checking if end event came before closing
let complete = false;
req.on('end', function() {
req.on('end', function () {
complete = true;
stream.done();
});
req.on('close', function() {
req.on('close', function () {
if (!complete) {
stream.abort();
}
});
stream.on('error', function(err) {
stream.on('error', function (err) {
return res.locals.report_error(err);
});
stream.on('success', function() {
stream.on('success', function () {
res.status(HTTP_STATUS.CREATED);
return next({
ok: API_MESSAGE.TARBALL_UPLOADED,

@ -1,15 +1,15 @@
import { HEADERS } from '@verdaccio/dev-commons';
export default function(route, auth, storage): void {
export default function (route, auth, storage): void {
// searching packages
route.get('/-/all(/since)?', function(req, res) {
route.get('/-/all(/since)?', function (req, res) {
let received_end = false;
let response_finished = false;
let processing_pkgs = 0;
let firstPackage = true;
res.status(200);
res.set(HEADERS.CONTENT_TYPE, HEADERS.JSON_CHARSET)
res.set(HEADERS.CONTENT_TYPE, HEADERS.JSON_CHARSET);
/*
* Offical NPM registry (registry.npmjs.org) no longer return whole database,
@ -33,7 +33,7 @@ export default function(route, auth, storage): void {
if (!respShouldBeArray) {
res.set('Date', 'Mon, 10 Oct 1983 00:12:48 GMT');
}
const check_finish = function(): void {
const check_finish = function (): void {
if (!received_end) {
return;
}
@ -62,7 +62,7 @@ export default function(route, auth, storage): void {
stream.on('data', function each(pkg) {
processing_pkgs++;
auth.allow_access({ packageName: pkg.name }, req.remote_user, function(err, allowed) {
auth.allow_access({ packageName: pkg.name }, req.remote_user, function (err, allowed) {
processing_pkgs--;
if (err) {
@ -90,11 +90,11 @@ export default function(route, auth, storage): void {
});
});
stream.on('error', function() {
stream.on('error', function () {
res.socket.destroy();
});
stream.on('end', function() {
stream.on('end', function () {
received_end = true;
check_finish();
});

@ -1,11 +1,11 @@
import { USERS, HTTP_STATUS } from '@verdaccio/dev-commons';
import {Response} from 'express';
import { Response } from 'express';
import _ from 'lodash';
import { logger } from '@verdaccio/logger';
import {$RequestExtend, $NextFunctionVer, IStorageHandler} from '@verdaccio/dev-types';
import { $RequestExtend, $NextFunctionVer, IStorageHandler } from '@verdaccio/dev-types';
export default function(storage: IStorageHandler): (req: $RequestExtend, res: Response, next: $NextFunctionVer) => void {
export default function (storage: IStorageHandler): (req: $RequestExtend, res: Response, next: $NextFunctionVer) => void {
const validateInputs = (newUsers, localUsers, username, isStar): boolean => {
const isExistlocalUsers = _.isNil(localUsers[username]) === false;
if (isStar && isExistlocalUsers && localUsers[username]) {
@ -20,8 +20,8 @@ export default function(storage: IStorageHandler): (req: $RequestExtend, res: Re
return (req: $RequestExtend, res: Response, next: $NextFunctionVer): void => {
const name = req.params.package;
logger.debug({name}, 'starring a package for @{name}');
const afterChangePackage = function(err?: Error) {
logger.debug({ name }, 'starring a package for @{name}');
const afterChangePackage = function (err?: Error) {
if (err) {
return next(err);
}
@ -34,7 +34,7 @@ export default function(storage: IStorageHandler): (req: $RequestExtend, res: Re
storage.getPackage({
name,
req,
callback: function(err, info) {
callback: function (err, info) {
if (err) {
return next(err);
}
@ -46,16 +46,22 @@ export default function(storage: IStorageHandler): (req: $RequestExtend, res: Re
if (_.isNil(localStarUsers) === false && validateInputs(newStarUser, localStarUsers, remoteUsername, isStar)) {
return afterChangePackage();
}
const users = isStar ? {
...localStarUsers,
[remoteUsername]: true,
} : _.reduce(localStarUsers, (users, value, key) => {
if (key !== remoteUsername) {
users[key] = value;
}
return users;
}, {});
storage.changePackage(name, { ...info, users}, req.body._rev, function(err) {
const users = isStar
? {
...localStarUsers,
[remoteUsername]: true,
}
: _.reduce(
localStarUsers,
(users, value, key) => {
if (key !== remoteUsername) {
users[key] = value;
}
return users;
},
{}
);
storage.changePackage(name, { ...info, users }, req.body._rev, function (err) {
afterChangePackage(err);
});
},

@ -8,7 +8,7 @@ import { $RequestExtend, $NextFunctionVer, IStorageHandler } from '@verdaccio/de
type Packages = Package[];
export default function(route: Router, storage: IStorageHandler): void {
export default function (route: Router, storage: IStorageHandler): void {
route.get('/-/_view/starredByUser', (req: $RequestExtend, res: Response, next: $NextFunctionVer): void => {
const remoteUsername = req.remote_user.name;
@ -17,9 +17,7 @@ export default function(route: Router, storage: IStorageHandler): void {
return next(err);
}
const filteredPackages: Packages = localPackages.filter((localPackage: Package) =>
_.keys(localPackage[USERS]).includes(remoteUsername)
);
const filteredPackages: Packages = localPackages.filter((localPackage: Package) => _.keys(localPackage[USERS]).includes(remoteUsername));
res.status(HTTP_STATUS.OK);
next({

@ -10,15 +10,15 @@ import { Config, RemoteUser } from '@verdaccio/types';
import { $RequestExtend, $ResponseExtend, $NextFunctionVer, IAuth } from '@verdaccio/dev-types';
import { API_ERROR, API_MESSAGE, HTTP_STATUS } from '@verdaccio/dev-commons';
export default function(route: Router, auth: IAuth, config: Config): void {
route.get('/-/user/:org_couchdb_user', function(req: $RequestExtend, res: Response, next: $NextFunctionVer): void {
export default function (route: Router, auth: IAuth, config: Config): void {
route.get('/-/user/:org_couchdb_user', function (req: $RequestExtend, res: Response, next: $NextFunctionVer): void {
res.status(HTTP_STATUS.OK);
next({
ok: getAuthenticatedMessage(req.remote_user.name),
});
});
route.put('/-/user/:org_couchdb_user/:_rev?/:revision?', function(req: $RequestExtend, res: Response, next: $NextFunctionVer): void {
route.put('/-/user/:org_couchdb_user/:_rev?/:revision?', function (req: $RequestExtend, res: Response, next: $NextFunctionVer): void {
const { name, password } = req.body;
const remoteName = req.remote_user.name;
@ -45,7 +45,7 @@ export default function(route: Router, auth: IAuth, config: Config): void {
return next(ErrorCode.getCode(HTTP_STATUS.BAD_REQUEST, API_ERROR.PASSWORD_SHORT()));
}
auth.add_user(name, password, async function(err, user): Promise<void> {
auth.add_user(name, password, async function (err, user): Promise<void> {
if (err) {
if (err.status >= HTTP_STATUS.BAD_REQUEST && err.status < HTTP_STATUS.INTERNAL_ERROR) {
// With npm registering is the same as logging in,
@ -68,7 +68,7 @@ export default function(route: Router, auth: IAuth, config: Config): void {
}
});
route.delete('/-/user/token/*', function(req: $RequestExtend, res: Response, next: $NextFunctionVer): void {
route.delete('/-/user/token/*', function (req: $RequestExtend, res: Response, next: $NextFunctionVer): void {
res.status(HTTP_STATUS.OK);
next({
ok: API_MESSAGE.LOGGED_OUT,

@ -1,4 +1,4 @@
import {Package} from '@verdaccio/types';
import { Package } from '@verdaccio/types';
import _ from 'lodash';
/**
@ -7,7 +7,7 @@ import _ from 'lodash';
*/
export function isPublishablePackage(pkg: Package): boolean {
const keys: string[] = Object.keys(pkg);
const keys: string[] = Object.keys(pkg);
return _.includes(keys, 'versions');
return _.includes(keys, 'versions');
}

@ -16,7 +16,7 @@ export interface Profile {
fullname: string;
}
export default function(route: Router, auth: IAuth): void {
export default function (route: Router, auth: IAuth): void {
function buildProfile(name: string): Profile {
return {
tfa: false,
@ -30,7 +30,7 @@ export default function(route: Router, auth: IAuth): void {
};
}
route.get('/-/npm/v1/user', function(req: $RequestExtend, res: Response, next: $NextFunctionVer): void {
route.get('/-/npm/v1/user', function (req: $RequestExtend, res: Response, next: $NextFunctionVer): void {
if (_.isNil(req.remote_user.name) === false) {
return next(buildProfile(req.remote_user.name));
}
@ -41,7 +41,7 @@ export default function(route: Router, auth: IAuth): void {
});
});
route.post('/-/npm/v1/user', function(req: $RequestExtend, res: Response, next: $NextFunctionVer): void {
route.post('/-/npm/v1/user', function (req: $RequestExtend, res: Response, next: $NextFunctionVer): void {
if (_.isNil(req.remote_user.name)) {
res.status(HTTP_STATUS.UNAUTHORIZED);
return next({

@ -1,98 +1,101 @@
import semver from 'semver'
import semver from 'semver';
import { Package } from '@verdaccio/types';
function compileTextSearch(textSearch: string): ((pkg: Package) => boolean) {
const personMatch = (person, search) => {
if(typeof person === 'string')
{return person.includes(search);}
function compileTextSearch(textSearch: string): (pkg: Package) => boolean {
const personMatch = (person, search) => {
if (typeof person === 'string') {
return person.includes(search);
}
if(typeof person === 'object')
{for(const field of Object.values(person))
{if(typeof field === 'string' && field.includes(search))
{return true;}}}
return false;
if (typeof person === 'object') {
for (const field of Object.values(person)) {
if (typeof field === 'string' && field.includes(search)) {
return true;
}
const matcher = function(q) {
const match = q.match(/author:(.*)/)
if(match !== null)
{return (pkg) => personMatch(pkg.author, match[1])}
}
}
// TODO: maintainer, keywords, not/is unstable insecure, boost-exact
// TODO implement some scoring system for freetext
return (pkg) => {
return ['name', 'displayName', 'description']
.map(k => pkg[k])
.filter(x => x !== undefined)
.some(txt => txt.includes(q))
};
}
return false;
};
const matcher = function (q) {
const match = q.match(/author:(.*)/);
if (match !== null) {
return (pkg) => personMatch(pkg.author, match[1]);
}
const textMatchers = (textSearch || '').split(' ').map(matcher);
return (pkg) => textMatchers.every(m => m(pkg));
// TODO: maintainer, keywords, not/is unstable insecure, boost-exact
// TODO implement some scoring system for freetext
return (pkg) => {
return ['name', 'displayName', 'description']
.map((k) => pkg[k])
.filter((x) => x !== undefined)
.some((txt) => txt.includes(q));
};
};
const textMatchers = (textSearch || '').split(' ').map(matcher);
return (pkg) => textMatchers.every((m) => m(pkg));
}
export default function(route, auth, storage): void {
route.get('/-/v1/search', (req, res)=>{
// TODO: implement proper result scoring weighted by quality, popularity and maintenance query parameters
let [text, size, from /* , quality, popularity, maintenance */] =
['text', 'size', 'from' /* , 'quality', 'popularity', 'maintenance' */]
.map(k => req.query[k])
export default function (route, auth, storage): void {
route.get('/-/v1/search', (req, res) => {
// TODO: implement proper result scoring weighted by quality, popularity and maintenance query parameters
let [text, size, from /* , quality, popularity, maintenance */] = ['text', 'size', 'from' /* , 'quality', 'popularity', 'maintenance' */].map(
(k) => req.query[k]
);
size = parseInt(size) || 20;
from = parseInt(from) || 0;
size = parseInt(size) || 20;
from = parseInt(from) || 0;
const isInteresting = compileTextSearch(text);
const isInteresting = compileTextSearch(text);
const resultStream = storage.search(0, {req: {query: {local: true}}});
const resultBuf = [] as any;
let completed = false;
const resultStream = storage.search(0, { req: { query: { local: true } } });
const resultBuf = [] as any;
let completed = false;
const sendResponse = (): void => {
completed = true;
resultStream.destroy()
const sendResponse = (): void => {
completed = true;
resultStream.destroy();
const final = resultBuf.slice(from, size).map(pkg => {
return {
package: pkg,
flags: {
unstable:
Object.keys(pkg.versions)
.some(v => semver.satisfies(v, '^1.0.0'))
? undefined
: true
},
score: {
final: 1,
detail: {
quality: 1,
popularity: 1,
maintenance: 0
}
},
searchScore: 100000
}
})
const response = {
objects: final,
total: final.length,
time: new Date().toUTCString()
}
const final = resultBuf.slice(from, size).map((pkg) => {
return {
package: pkg,
flags: {
unstable: Object.keys(pkg.versions).some((v) => semver.satisfies(v, '^1.0.0')) ? undefined : true,
},
score: {
final: 1,
detail: {
quality: 1,
popularity: 1,
maintenance: 0,
},
},
searchScore: 100000,
};
});
const response = {
objects: final,
total: final.length,
time: new Date().toUTCString(),
};
res.status(200)
.json(response)
}
res.status(200).json(response);
};
resultStream.on('data', (pkg)=>{
if(!isInteresting(pkg))
{return;}
resultBuf.push(pkg)
if(!completed && resultBuf.length >= size + from)
{sendResponse();}
})
resultStream.on('end', ()=>{
if(!completed)
{sendResponse()}
})
})
resultStream.on('data', (pkg) => {
if (!isInteresting(pkg)) {
return;
}
resultBuf.push(pkg);
if (!completed && resultBuf.length >= size + from) {
sendResponse();
}
});
resultStream.on('end', () => {
if (!completed) {
sendResponse();
}
});
});
}

@ -1,122 +1,126 @@
import _ from 'lodash';
import { HTTP_STATUS, SUPPORT_ERRORS } from '@verdaccio/dev-commons';
import {ErrorCode, stringToMD5, mask } from '@verdaccio/utils';
import { ErrorCode, stringToMD5, mask } from '@verdaccio/utils';
import { getApiToken } from '@verdaccio/auth';
import { logger } from '@verdaccio/logger';
import { Response, Router } from 'express';
import { Config, RemoteUser, Token } from '@verdaccio/types';
import {$NextFunctionVer, $RequestExtend, IAuth, IStorageHandler} from '../../../types';
import { $NextFunctionVer, $RequestExtend, IAuth, IStorageHandler } from '../../../types';
export type NormalizeToken = Token & {
created: string;
created: string;
};
function normalizeToken(token: Token): NormalizeToken {
return {
...token,
created: new Date(token.created).toISOString(),
};
};
return {
...token,
created: new Date(token.created).toISOString(),
};
}
// https://github.com/npm/npm-profile/blob/latest/lib/index.js
export default function(route: Router, auth: IAuth, storage: IStorageHandler, config: Config): void {
route.get('/-/npm/v1/tokens', async function(req: $RequestExtend, res: Response, next: $NextFunctionVer) {
const { name } = req.remote_user;
export default function (route: Router, auth: IAuth, storage: IStorageHandler, config: Config): void {
route.get('/-/npm/v1/tokens', async function (req: $RequestExtend, res: Response, next: $NextFunctionVer) {
const { name } = req.remote_user;
if (_.isNil(name) === false) {
try {
const tokens = await storage.readTokens({user: name});
const totalTokens = tokens.length;
logger.debug({totalTokens}, 'token list retrieved: @{totalTokens}');
if (_.isNil(name) === false) {
try {
const tokens = await storage.readTokens({ user: name });
const totalTokens = tokens.length;
logger.debug({ totalTokens }, 'token list retrieved: @{totalTokens}');
res.status(HTTP_STATUS.OK);
return next({
objects: tokens.map(normalizeToken),
urls: {
next: '', // TODO: pagination?
},
});
} catch (error) {
logger.error({ error: error.msg }, 'token list has failed: @{error}');
return next(ErrorCode.getCode(HTTP_STATUS.INTERNAL_ERROR, error.message));
}
}
return next(ErrorCode.getUnauthorized());
});
res.status(HTTP_STATUS.OK);
return next({
objects: tokens.map(normalizeToken),
urls: {
next: '', // TODO: pagination?
},
});
} catch (error) {
logger.error({ error: error.msg }, 'token list has failed: @{error}');
return next(ErrorCode.getCode(HTTP_STATUS.INTERNAL_ERROR, error.message));
}
}
return next(ErrorCode.getUnauthorized());
});
route.post('/-/npm/v1/tokens', function(req: $RequestExtend, res: Response, next: $NextFunctionVer) {
const { password, readonly, cidr_whitelist } = req.body;
const { name } = req.remote_user;
route.post('/-/npm/v1/tokens', function (req: $RequestExtend, res: Response, next: $NextFunctionVer) {
const { password, readonly, cidr_whitelist } = req.body;
const { name } = req.remote_user;
if (!_.isBoolean(readonly) || !_.isArray(cidr_whitelist)) {
return next(ErrorCode.getCode(HTTP_STATUS.BAD_DATA, SUPPORT_ERRORS.PARAMETERS_NOT_VALID));
}
if (!_.isBoolean(readonly) || !_.isArray(cidr_whitelist)) {
return next(ErrorCode.getCode(HTTP_STATUS.BAD_DATA, SUPPORT_ERRORS.PARAMETERS_NOT_VALID));
}
auth.authenticate(name, password, async (err, user: RemoteUser) => {
if (err) {
const errorCode = err.message ? HTTP_STATUS.UNAUTHORIZED : HTTP_STATUS.INTERNAL_ERROR;
return next(ErrorCode.getCode(errorCode, err.message));
}
auth.authenticate(name, password, async (err, user: RemoteUser) => {
if (err) {
const errorCode = err.message ? HTTP_STATUS.UNAUTHORIZED : HTTP_STATUS.INTERNAL_ERROR;
return next(ErrorCode.getCode(errorCode, err.message));
}
req.remote_user = user;
req.remote_user = user;
if (!_.isFunction(storage.saveToken)) {
return next(ErrorCode.getCode(HTTP_STATUS.NOT_IMPLEMENTED, SUPPORT_ERRORS.STORAGE_NOT_IMPLEMENT));
}
if (!_.isFunction(storage.saveToken)) {
return next(ErrorCode.getCode(HTTP_STATUS.NOT_IMPLEMENTED, SUPPORT_ERRORS.STORAGE_NOT_IMPLEMENT));
}
try {
const token = await getApiToken(auth, config, user, password);
const key = stringToMD5(token);
// TODO: use a utility here
const maskedToken = mask(token, 5);
const created = new Date().getTime();
try {
const token = await getApiToken(auth, config, user, password);
const key = stringToMD5(token);
// TODO: use a utility here
const maskedToken = mask(token, 5);
const created = new Date().getTime();
/**
* cidr_whitelist: is not being used, we pass it through
* token: we do not store the real token (it is generated once and retrieved to the user), just a mask of it.
*/
const saveToken: Token = {
user: name,
token: maskedToken,
key,
cidr: cidr_whitelist,
readonly,
created,
};
/**
* cidr_whitelist: is not being used, we pass it through
* token: we do not store the real token (it is generated once and retrieved to the user), just a mask of it.
*/
const saveToken: Token = {
user: name,
token: maskedToken,
key,
cidr: cidr_whitelist,
readonly,
created,
};
await storage.saveToken(saveToken);
logger.debug({ key, name }, 'token @{key} was created for user @{name}');
return next(normalizeToken({
token,
user: name,
key: saveToken.key,
cidr: cidr_whitelist,
readonly,
created: saveToken.created,
}));
} catch (error) {
logger.error({ error: error.msg }, 'token creation has failed: @{error}');
return next(ErrorCode.getCode(HTTP_STATUS.INTERNAL_ERROR, error.message));
}
});
});
await storage.saveToken(saveToken);
logger.debug({ key, name }, 'token @{key} was created for user @{name}');
return next(
normalizeToken({
token,
user: name,
key: saveToken.key,
cidr: cidr_whitelist,
readonly,
created: saveToken.created,
})
);
} catch (error) {
logger.error({ error: error.msg }, 'token creation has failed: @{error}');
return next(ErrorCode.getCode(HTTP_STATUS.INTERNAL_ERROR, error.message));
}
});
});
route.delete('/-/npm/v1/tokens/token/:tokenKey', async (req: $RequestExtend, res: Response, next: $NextFunctionVer) => {
const { params: { tokenKey }} = req;
const { name } = req.remote_user;
route.delete('/-/npm/v1/tokens/token/:tokenKey', async (req: $RequestExtend, res: Response, next: $NextFunctionVer) => {
const {
params: { tokenKey },
} = req;
const { name } = req.remote_user;
if (_.isNil(name) === false) {
logger.debug({name}, '@{name} has requested remove a token');
try {
await storage.deleteToken(name, tokenKey);
logger.info({ tokenKey, name }, 'token id @{tokenKey} was revoked for user @{name}');
return next({});
} catch(error) {
logger.error({ error: error.msg }, 'token creation has failed: @{error}');
return next(ErrorCode.getCode(HTTP_STATUS.INTERNAL_ERROR, error.message));
}
}
return next(ErrorCode.getUnauthorized());
});
if (_.isNil(name) === false) {
logger.debug({ name }, '@{name} has requested remove a token');
try {
await storage.deleteToken(name, tokenKey);
logger.info({ tokenKey, name }, 'token id @{tokenKey} was revoked for user @{name}');
return next({});
} catch (error) {
logger.error({ error: error.msg }, 'token creation has failed: @{error}');
return next(ErrorCode.getCode(HTTP_STATUS.INTERNAL_ERROR, error.message));
}
}
return next(ErrorCode.getUnauthorized());
});
}

@ -1,72 +1,72 @@
import path from "path";
import express, {Application} from 'express';
import path from 'path';
import express, { Application } from 'express';
import supertest from 'supertest';
import bodyParser from 'body-parser';
import {parseConfigFile} from '@verdaccio/utils';
import { parseConfigFile } from '@verdaccio/utils';
import { Config } from '@verdaccio/config';
import { Storage } from '@verdaccio/store';
import { final, handleError, errorReportingMiddleware } from '@verdaccio/middleware';
import { Auth } from '@verdaccio/auth';
import {IAuth} from "@verdaccio/dev-types";
import {HEADER_TYPE, HTTP_STATUS, generatePackageMetadata} from "@verdaccio/dev-commons";
import {HEADERS} from "@verdaccio/commons-api";
import { IAuth } from '@verdaccio/dev-types';
import { HEADER_TYPE, HTTP_STATUS, generatePackageMetadata } from '@verdaccio/dev-commons';
import { HEADERS } from '@verdaccio/commons-api';
import apiEndpoints from '../../src';
const getConf = (conf) => {
const configPath = path.join(__dirname, 'config', conf);
const configPath = path.join(__dirname, 'config', conf);
return parseConfigFile(configPath);
return parseConfigFile(configPath);
};
export async function initializeServer(configName): Promise<Application> {
const app = express();
const config = new Config(getConf(configName));
const storage = new Storage(config);
await storage.init(config, []);
const auth: IAuth = new Auth(config);
app.use(bodyParser.json({ strict: false, limit: '10mb' }));
// @ts-ignore
app.use(errorReportingMiddleware);
// @ts-ignore
app.use(apiEndpoints(config, auth, storage));
// @ts-ignore
app.use(handleError);
// @ts-ignore
app.use(final);
const app = express();
const config = new Config(getConf(configName));
const storage = new Storage(config);
await storage.init(config, []);
const auth: IAuth = new Auth(config);
app.use(bodyParser.json({ strict: false, limit: '10mb' }));
// @ts-ignore
app.use(errorReportingMiddleware);
// @ts-ignore
app.use(apiEndpoints(config, auth, storage));
// @ts-ignore
app.use(handleError);
// @ts-ignore
app.use(final);
app.use(function(request, response) {
response.status(590);
console.log('respo', response);
response.json({error: 'cannot handle this'});
});
app.use(function (request, response) {
response.status(590);
console.log('respo', response);
response.json({ error: 'cannot handle this' });
});
return app;
return app;
}
export function publishVersion(app, configFile, pkgName, version): supertest.Test {
const pkgMetadata = generatePackageMetadata(pkgName, version);
const pkgMetadata = generatePackageMetadata(pkgName, version);
return supertest(app)
.put(`/${encodeURIComponent(pkgName)}`)
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.send(JSON.stringify(pkgMetadata))
.set('accept', HEADERS.GZIP)
.set(HEADER_TYPE.ACCEPT_ENCODING, HEADERS.JSON)
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON);
return supertest(app)
.put(`/${encodeURIComponent(pkgName)}`)
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.send(JSON.stringify(pkgMetadata))
.set('accept', HEADERS.GZIP)
.set(HEADER_TYPE.ACCEPT_ENCODING, HEADERS.JSON)
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON);
}
export async function publishTaggedVersion(app, configFile, pkgName: string, version: string, tag: string) {
const pkgMetadata = generatePackageMetadata(pkgName, version, {
[tag]: version
});
const pkgMetadata = generatePackageMetadata(pkgName, version, {
[tag]: version,
});
return supertest(app)
.put(`/${encodeURIComponent(pkgName)}/${encodeURIComponent(version)}/-tag/${encodeURIComponent(tag)}`)
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.send(JSON.stringify(pkgMetadata))
.expect(HTTP_STATUS.CREATED)
.set('accept', HEADERS.GZIP)
.set(HEADER_TYPE.ACCEPT_ENCODING, HEADERS.JSON)
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON);
return supertest(app)
.put(`/${encodeURIComponent(pkgName)}/${encodeURIComponent(version)}/-tag/${encodeURIComponent(tag)}`)
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.send(JSON.stringify(pkgMetadata))
.expect(HTTP_STATUS.CREATED)
.set('accept', HEADERS.GZIP)
.set(HEADER_TYPE.ACCEPT_ENCODING, HEADERS.JSON)
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON);
}

@ -1,83 +1,80 @@
import supertest from 'supertest';
import { HTTP_STATUS } from '@verdaccio/commons-api';
import {HEADER_TYPE, HEADERS} from '@verdaccio/dev-commons';
import {$RequestExtend, $ResponseExtend} from "@verdaccio/dev-types";
import {initializeServer, publishTaggedVersion, publishVersion} from './_helper';
import { HEADER_TYPE, HEADERS } from '@verdaccio/dev-commons';
import { $RequestExtend, $ResponseExtend } from '@verdaccio/dev-types';
import { initializeServer, publishTaggedVersion, publishVersion } from './_helper';
const mockApiJWTmiddleware = jest.fn(() =>
(req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: 'foo', groups: [], real_groups: []}
_next();
}
);
const mockApiJWTmiddleware = jest.fn(() => (req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: 'foo', groups: [], real_groups: [] };
_next();
});
jest.mock('@verdaccio/auth', () => ({
Auth: class {
apiJWTmiddleware() {
return mockApiJWTmiddleware();
}
allow_access (_d, f_, cb) {
cb(null, true)
}
allow_publish (_d, f_, cb) {
cb(null, true)
}
}
Auth: class {
apiJWTmiddleware() {
return mockApiJWTmiddleware();
}
allow_access(_d, f_, cb) {
cb(null, true);
}
allow_publish(_d, f_, cb) {
cb(null, true);
}
},
}));
describe('package', () => {
let app;
beforeAll(async () => {
app = await initializeServer('package.yaml');
await publishVersion(app, 'package.yaml', 'foo', '1.0.0');
});
let app;
beforeAll(async () => {
app = await initializeServer('package.yaml');
await publishVersion(app, 'package.yaml', 'foo', '1.0.0');
});
test('should return a package', async (done) => {
test('should return a package', async (done) => {
return supertest(app)
.get('/foo')
.set('Accept', HEADERS.JSON)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.OK)
.then((response) => {
expect(response.body.name).toEqual('foo');
done();
});
});
return supertest(app)
.get('/foo')
.set('Accept', HEADERS.JSON)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.OK)
.then(response => {
expect(response.body.name).toEqual('foo');
done();
});
});
test('should return a package by version', async (done) => {
return supertest(app)
.get('/foo/1.0.0')
.set('Accept', HEADERS.JSON)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.OK)
.then((response) => {
expect(response.body.name).toEqual('foo');
done();
});
});
test('should return a package by version', async (done) => {
return supertest(app)
.get('/foo/1.0.0')
.set('Accept', HEADERS.JSON)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.OK)
.then(response => {
expect(response.body.name).toEqual('foo');
done();
});
});
// TODO: investigate the 404
test.skip('should return a package by dist-tag', async (done) => {
await publishVersion(app, 'package.yaml', 'foo-tagged', '1.0.0');
await publishTaggedVersion(app, 'package.yaml', 'foo-tagged', '1.0.1', 'test');
return supertest(app)
.get('/foo-tagged/1.0.0')
.set('Accept', HEADERS.JSON)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.CREATED)
.then((response) => {
expect(response.body.name).toEqual('foo');
done();
});
});
// TODO: investigate the 404
test.skip('should return a package by dist-tag', async (done) => {
await publishVersion(app, 'package.yaml', 'foo-tagged', '1.0.0');
await publishTaggedVersion(app, 'package.yaml', 'foo-tagged', '1.0.1', 'test');
return supertest(app)
.get('/foo-tagged/1.0.0')
.set('Accept', HEADERS.JSON)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.CREATED)
.then(response => {
expect(response.body.name).toEqual('foo');
done();
});
});
test('should return 404', async () => {
return supertest(app)
.get('/404-not-found')
.set('Accept', HEADERS.JSON)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.NOT_FOUND);
});
test('should return 404', async () => {
return supertest(app)
.get('/404-not-found')
.set('Accept', HEADERS.JSON)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.NOT_FOUND);
});
});

@ -1,16 +1,16 @@
import supertest from 'supertest';
import { HTTP_STATUS } from '@verdaccio/commons-api';
import {HEADER_TYPE, HEADERS} from '@verdaccio/dev-commons';
import {initializeServer } from './_helper';
import { HEADER_TYPE, HEADERS } from '@verdaccio/dev-commons';
import { initializeServer } from './_helper';
describe('ping', () => {
test('should return the reply the ping', async () => {
return supertest(await initializeServer('ping.yaml'))
.get('/-/ping')
.set('Accept', HEADERS.JSON)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.OK)
.then(response => expect(response.body).toEqual({}));
});
test('should return the reply the ping', async () => {
return supertest(await initializeServer('ping.yaml'))
.get('/-/ping')
.set('Accept', HEADERS.JSON)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.OK)
.then((response) => expect(response.body).toEqual({}));
});
});

@ -1,34 +1,32 @@
import { HTTP_STATUS } from '@verdaccio/commons-api';
import {API_ERROR, API_MESSAGE, generatePackageMetadata, HEADER_TYPE, HEADERS} from '@verdaccio/dev-commons';
import {$RequestExtend, $ResponseExtend} from '@verdaccio/dev-types';
import supertest from "supertest";
import {initializeServer, publishVersion} from './_helper';
import { API_ERROR, API_MESSAGE, generatePackageMetadata, HEADER_TYPE, HEADERS } from '@verdaccio/dev-commons';
import { $RequestExtend, $ResponseExtend } from '@verdaccio/dev-types';
import supertest from 'supertest';
import { initializeServer, publishVersion } from './_helper';
const mockApiJWTmiddleware = jest.fn(() =>
(req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: 'foo', groups: [], real_groups: []}
_next();
}
);
const mockApiJWTmiddleware = jest.fn(() => (req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: 'foo', groups: [], real_groups: [] };
_next();
});
jest.setTimeout(50000000);
jest.mock('@verdaccio/auth', () => ({
Auth: class {
apiJWTmiddleware() {
return mockApiJWTmiddleware();
}
allow_access (_d, f_, cb) {
cb(null, true)
}
allow_publish (_d, f_, cb) {
cb(null, true)
}
Auth: class {
apiJWTmiddleware() {
return mockApiJWTmiddleware();
}
allow_access(_d, f_, cb) {
cb(null, true);
}
allow_publish(_d, f_, cb) {
cb(null, true);
}
allow_unpublish (_d, f_, cb) {
cb(null, true)
}
}
allow_unpublish(_d, f_, cb) {
cb(null, true);
}
},
}));
// const mockStorage = jest.fn(() => {
@ -55,125 +53,134 @@ jest.mock('@verdaccio/auth', () => ({
// });
describe('publish', () => {
describe('handle invalid publish formats', () => {
const pkgName = 'test';
const pkgMetadata = generatePackageMetadata(pkgName, '1.0.0');
test.skip('should fail on publish a bad _attachments package', async (done) => {
const app = await initializeServer('publish.yaml');
return supertest(app)
.put(`/${encodeURIComponent(pkgName)}`)
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.send(JSON.stringify(Object.assign({}, pkgMetadata, {
_attachments: {}
})))
.set('accept', HEADERS.GZIP)
.expect(HTTP_STATUS.BAD_REQUEST)
.then(response => {
console.log("response.body", response.body);
expect(response.body.error).toEqual(API_ERROR.UNSUPORTED_REGISTRY_CALL);
done();
});
});
describe('handle invalid publish formats', () => {
const pkgName = 'test';
const pkgMetadata = generatePackageMetadata(pkgName, '1.0.0');
test.skip('should fail on publish a bad _attachments package', async (done) => {
const app = await initializeServer('publish.yaml');
return supertest(app)
.put(`/${encodeURIComponent(pkgName)}`)
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.send(
JSON.stringify(
Object.assign({}, pkgMetadata, {
_attachments: {},
})
)
)
.set('accept', HEADERS.GZIP)
.expect(HTTP_STATUS.BAD_REQUEST)
.then((response) => {
console.log('response.body', response.body);
expect(response.body.error).toEqual(API_ERROR.UNSUPORTED_REGISTRY_CALL);
done();
});
});
test('should fail on publish a bad versions package', async (done) => {
const app = await initializeServer('publish.yaml');
return supertest(app)
.put(`/${encodeURIComponent(pkgName)}`)
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.send(JSON.stringify(Object.assign({}, pkgMetadata, {
versions: ''
})))
.set('accept', HEADERS.GZIP)
.expect(HTTP_STATUS.BAD_REQUEST)
.then(response => {
console.log("response.body", response.body);
expect(response.body.error).toEqual(API_ERROR.UNSUPORTED_REGISTRY_CALL);
done();
});
});
});
test('should fail on publish a bad versions package', async (done) => {
const app = await initializeServer('publish.yaml');
return supertest(app)
.put(`/${encodeURIComponent(pkgName)}`)
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.send(
JSON.stringify(
Object.assign({}, pkgMetadata, {
versions: '',
})
)
)
.set('accept', HEADERS.GZIP)
.expect(HTTP_STATUS.BAD_REQUEST)
.then((response) => {
console.log('response.body', response.body);
expect(response.body.error).toEqual(API_ERROR.UNSUPORTED_REGISTRY_CALL);
done();
});
});
});
describe('publish a package', () => {
test('should publish a package', async (done) => {
const app = await initializeServer('publish.yaml');
return publishVersion(app, 'publish.yaml', 'foo', '1.0.0')
.expect(HTTP_STATUS.CREATED)
.then(response => {
expect(response.body.ok).toEqual(API_MESSAGE.PKG_CREATED);
done();
});
});
describe('publish a package', () => {
test('should publish a package', async (done) => {
const app = await initializeServer('publish.yaml');
return publishVersion(app, 'publish.yaml', 'foo', '1.0.0')
.expect(HTTP_STATUS.CREATED)
.then((response) => {
expect(response.body.ok).toEqual(API_MESSAGE.PKG_CREATED);
done();
});
});
test('should publish a new package', async (done) => {
const pkgName = 'test';
const pkgMetadata = generatePackageMetadata(pkgName, '1.0.0');
const app = await initializeServer('publish.yaml');
return supertest(app)
.put(`/${encodeURIComponent(pkgName)}`)
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.send(JSON.stringify(Object.assign({}, pkgMetadata, {
_attachments: null
})))
.set('accept', HEADERS.GZIP)
.expect(HTTP_STATUS.CREATED)
.then(response => {
expect(response.body.ok).toEqual(API_MESSAGE.PKG_CREATED);
done();
});
});
test('should publish a new package', async (done) => {
const pkgName = 'test';
const pkgMetadata = generatePackageMetadata(pkgName, '1.0.0');
const app = await initializeServer('publish.yaml');
return supertest(app)
.put(`/${encodeURIComponent(pkgName)}`)
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.send(
JSON.stringify(
Object.assign({}, pkgMetadata, {
_attachments: null,
})
)
)
.set('accept', HEADERS.GZIP)
.expect(HTTP_STATUS.CREATED)
.then((response) => {
expect(response.body.ok).toEqual(API_MESSAGE.PKG_CREATED);
done();
});
});
test('should publish a new package with no readme', async (done) => {
const pkgName = 'test';
const pkgMetadata = generatePackageMetadata(pkgName, '1.0.0');
const app = await initializeServer('publish.yaml');
return supertest(app)
.put(`/${encodeURIComponent(pkgName)}`)
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.send(JSON.stringify(Object.assign({}, pkgMetadata, {
versions: {
['1.0.0'] : {
readme: null
}
}
})))
.set('accept', HEADERS.GZIP)
.expect(HTTP_STATUS.CREATED)
.then(response => {
expect(response.body.ok).toEqual(API_MESSAGE.PKG_CREATED);
done();
});
});
test('should publish a new package with no readme', async (done) => {
const pkgName = 'test';
const pkgMetadata = generatePackageMetadata(pkgName, '1.0.0');
const app = await initializeServer('publish.yaml');
return supertest(app)
.put(`/${encodeURIComponent(pkgName)}`)
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.send(
JSON.stringify(
Object.assign({}, pkgMetadata, {
versions: {
['1.0.0']: {
readme: null,
},
},
})
)
)
.set('accept', HEADERS.GZIP)
.expect(HTTP_STATUS.CREATED)
.then((response) => {
expect(response.body.ok).toEqual(API_MESSAGE.PKG_CREATED);
done();
});
});
});
});
test('should fails on publish a duplicated package', async (done) => {
const app = await initializeServer('publish.yaml');
await publishVersion(app, 'publish.yaml', 'foo', '1.0.0');
return publishVersion(app, 'publish.yaml', 'foo', '1.0.0')
.expect(HTTP_STATUS.CONFLICT)
.then((response) => {
console.log('response.body', response.body);
expect(response.body.error).toEqual(API_ERROR.PACKAGE_EXIST);
done();
});
});
describe('unpublish a package', () => {
let app;
test('should fails on publish a duplicated package', async (done) => {
const app = await initializeServer('publish.yaml');
await publishVersion(app, 'publish.yaml', 'foo', '1.0.0');
return publishVersion(app, 'publish.yaml', 'foo', '1.0.0')
.expect(HTTP_STATUS.CONFLICT)
.then(response => {
console.log("response.body", response.body);
expect(response.body.error).toEqual(API_ERROR.PACKAGE_EXIST);
done();
});
});
beforeEach(async () => {
app = await initializeServer('publish.yaml');
await publishVersion(app, 'publish.yaml', 'foo', '1.0.0');
});
describe('unpublish a package', () => {
let app;
beforeEach(async () => {
app = await initializeServer('publish.yaml');
await publishVersion(app, 'publish.yaml', 'foo', '1.0.0');
})
test('should unpublish a package', () => {
})
});
describe('star a package', () => {
});
test('should unpublish a package', () => {});
});
describe('star a package', () => {});
});

@ -1,237 +1,216 @@
import supertest from 'supertest';
import { HTTP_STATUS, API_ERROR } from '@verdaccio/commons-api';
import {HEADERS, HEADER_TYPE, API_MESSAGE} from '@verdaccio/dev-commons';
import {$RequestExtend, $ResponseExtend} from "@verdaccio/dev-types";
import {getBadRequest, getConflict, getUnauthorized} from "@verdaccio/commons-api/lib";
import _ from "lodash";
import { HEADERS, HEADER_TYPE, API_MESSAGE } from '@verdaccio/dev-commons';
import { $RequestExtend, $ResponseExtend } from '@verdaccio/dev-types';
import { getBadRequest, getConflict, getUnauthorized } from '@verdaccio/commons-api/lib';
import _ from 'lodash';
import { initializeServer } from './_helper';
const mockApiJWTmiddleware = jest.fn(() =>
(req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: 'test', groups: [], real_groups: []};
_next();
}
);
const mockApiJWTmiddleware = jest.fn(() => (req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: 'test', groups: [], real_groups: [] };
_next();
});
const mockAuthenticate = jest.fn(() => (_name, _password, callback): void => {
return callback(null, ['all']);
}
);
return callback(null, ['all']);
});
const mockAddUser = jest.fn(() => (_name, _password, callback): void => {
return callback(getConflict(API_ERROR.USERNAME_ALREADY_REGISTERED));
}
);
return callback(getConflict(API_ERROR.USERNAME_ALREADY_REGISTERED));
});
jest.mock('@verdaccio/auth', () => ({
getApiToken: () => 'token',
Auth: class {
apiJWTmiddleware() {
return mockApiJWTmiddleware();
}
allow_access (_d, f_, cb) {
cb(null, true);
}
add_user (name, password, callback) {
mockAddUser()(name, password, callback);
}
authenticate (_name, _password, callback) {
mockAuthenticate()(_name, _password, callback);
}
}
getApiToken: () => 'token',
Auth: class {
apiJWTmiddleware() {
return mockApiJWTmiddleware();
}
allow_access(_d, f_, cb) {
cb(null, true);
}
add_user(name, password, callback) {
mockAddUser()(name, password, callback);
}
authenticate(_name, _password, callback) {
mockAuthenticate()(_name, _password, callback);
}
},
}));
describe('user', () => {
const credentials = { name: 'test', password: 'test' };
const credentials = { name: 'test', password: 'test' };
test('should test add a new user', async (done) => {
mockApiJWTmiddleware.mockImplementationOnce(() =>
(req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: undefined};
_next();
}
);
test('should test add a new user', async (done) => {
mockApiJWTmiddleware.mockImplementationOnce(() => (req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: undefined };
_next();
});
mockAddUser.mockImplementationOnce(() => (_name, _password, callback): void => {
return callback(null, true);
}
);
supertest(await initializeServer('user.yaml'))
.put(`/-/user/org.couchdb.user:newUser`)
.send({
name: 'newUser',
password: 'newUser'
})
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.CREATED)
.end(function(err, res) {
if (err) {
return done(err);
}
expect(res.body.ok).toBeDefined();
expect(res.body.token).toBeDefined();
const token = res.body.token;
expect(typeof token).toBe('string');
expect(res.body.ok).toMatch(`user 'newUser' created`);
done();
});
});
mockAddUser.mockImplementationOnce(() => (_name, _password, callback): void => {
return callback(null, true);
});
supertest(await initializeServer('user.yaml'))
.put(`/-/user/org.couchdb.user:newUser`)
.send({
name: 'newUser',
password: 'newUser',
})
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.CREATED)
.end(function (err, res) {
if (err) {
return done(err);
}
expect(res.body.ok).toBeDefined();
expect(res.body.token).toBeDefined();
const token = res.body.token;
expect(typeof token).toBe('string');
expect(res.body.ok).toMatch(`user 'newUser' created`);
done();
});
});
test('should test fails on add a existing user with login', async (done) => {
mockApiJWTmiddleware.mockImplementationOnce(() =>
(req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: undefined};
_next();
}
);
supertest(await initializeServer('user.yaml'))
.put('/-/user/org.couchdb.user:jotaNew')
.send(credentials)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.CONFLICT)
.end(function(err, res) {
if (err) {
return done(err);
}
expect(res.body.error).toBeDefined();
expect(res.body.error).toMatch(API_ERROR.USERNAME_ALREADY_REGISTERED);
done();
});
});
test('should test fails on add a existing user with login', async (done) => {
mockApiJWTmiddleware.mockImplementationOnce(() => (req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: undefined };
_next();
});
supertest(await initializeServer('user.yaml'))
.put('/-/user/org.couchdb.user:jotaNew')
.send(credentials)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.CONFLICT)
.end(function (err, res) {
if (err) {
return done(err);
}
expect(res.body.error).toBeDefined();
expect(res.body.error).toMatch(API_ERROR.USERNAME_ALREADY_REGISTERED);
done();
});
});
test('should log in as existing user', async (done) => {
supertest(await initializeServer('user.yaml'))
.put(`/-/user/org.couchdb.user:${credentials.name}`)
.send(credentials)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.CREATED)
.end((err, res) => {
if (err) {
return done(err);
}
test('should log in as existing user', async (done) => {
supertest(await initializeServer('user.yaml'))
.put(`/-/user/org.couchdb.user:${credentials.name}`)
.send(credentials)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.CREATED)
.end((err, res) => {
if (err) {
return done(err);
}
expect(res.body).toBeTruthy();
expect(res.body.ok).toMatch(`you are authenticated as \'${credentials.name}\'`);
done();
});
});
expect(res.body).toBeTruthy();
expect(res.body.ok).toMatch(`you are authenticated as \'${credentials.name}\'`);
done();
});
});
test('should test fails add a new user with missing name', async (done) => {
mockApiJWTmiddleware.mockImplementationOnce(() =>
(req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: undefined };
_next();
}
);
mockAddUser.mockImplementationOnce(() => (_name, _password, callback): void => {
return callback(getBadRequest(API_ERROR.USERNAME_PASSWORD_REQUIRED));
}
);
const credentialsShort = _.cloneDeep(credentials);
delete credentialsShort.name;
test('should test fails add a new user with missing name', async (done) => {
mockApiJWTmiddleware.mockImplementationOnce(() => (req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: undefined };
_next();
});
mockAddUser.mockImplementationOnce(() => (_name, _password, callback): void => {
return callback(getBadRequest(API_ERROR.USERNAME_PASSWORD_REQUIRED));
});
const credentialsShort = _.cloneDeep(credentials);
delete credentialsShort.name;
supertest(await initializeServer('user.yaml'))
.put(`/-/user/org.couchdb.user:${credentials.name}`)
.send(credentialsShort)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.BAD_REQUEST)
.end(function(err, res) {
if (err) {
return done(err);
}
supertest(await initializeServer('user.yaml'))
.put(`/-/user/org.couchdb.user:${credentials.name}`)
.send(credentialsShort)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.BAD_REQUEST)
.end(function (err, res) {
if (err) {
return done(err);
}
expect(res.body.error).toBeDefined();
expect(res.body.error).toMatch(API_ERROR.USERNAME_PASSWORD_REQUIRED);
done();
});
});
expect(res.body.error).toBeDefined();
expect(res.body.error).toMatch(API_ERROR.USERNAME_PASSWORD_REQUIRED);
done();
});
});
test('should test fails add a new user with missing password', async (done) => {
mockApiJWTmiddleware.mockImplementationOnce(() =>
(req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: undefined};
_next();
}
);
const credentialsShort = _.cloneDeep(credentials);
delete credentialsShort.password;
test('should test fails add a new user with missing password', async (done) => {
mockApiJWTmiddleware.mockImplementationOnce(() => (req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: undefined };
_next();
});
const credentialsShort = _.cloneDeep(credentials);
delete credentialsShort.password;
supertest(await initializeServer('user.yaml'))
.put(`/-/user/org.couchdb.user:${credentials.name}`)
.send(credentialsShort)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.BAD_REQUEST)
.end(function(err, res) {
if (err) {
return done(err);
}
supertest(await initializeServer('user.yaml'))
.put(`/-/user/org.couchdb.user:${credentials.name}`)
.send(credentialsShort)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.BAD_REQUEST)
.end(function (err, res) {
if (err) {
return done(err);
}
expect(res.body.error).toBeDefined();
// FIXME: message is not 100% accurate
// eslint-disable-next-line new-cap
expect(res.body.error).toMatch(API_ERROR.PASSWORD_SHORT());
done();
});
});
expect(res.body.error).toBeDefined();
// FIXME: message is not 100% accurate
// eslint-disable-next-line new-cap
expect(res.body.error).toMatch(API_ERROR.PASSWORD_SHORT());
done();
});
});
test('should test fails add a new user with wrong password', async (done) => {
mockApiJWTmiddleware.mockImplementationOnce(() =>
(req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: 'test'};
_next();
}
);
mockAuthenticate.mockImplementationOnce(() => (_name, _password, callback): void => {
return callback(getUnauthorized(API_ERROR.BAD_USERNAME_PASSWORD));
}
);
const credentialsShort = _.cloneDeep(credentials);
credentialsShort.password = 'failPassword';
test('should test fails add a new user with wrong password', async (done) => {
mockApiJWTmiddleware.mockImplementationOnce(() => (req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: 'test' };
_next();
});
mockAuthenticate.mockImplementationOnce(() => (_name, _password, callback): void => {
return callback(getUnauthorized(API_ERROR.BAD_USERNAME_PASSWORD));
});
const credentialsShort = _.cloneDeep(credentials);
credentialsShort.password = 'failPassword';
supertest(await initializeServer('user.yaml'))
.put('/-/user/org.couchdb.user:test')
.send(credentialsShort)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.UNAUTHORIZED)
.end(function(err, res) {
if (err) {
return done(err);
}
supertest(await initializeServer('user.yaml'))
.put('/-/user/org.couchdb.user:test')
.send(credentialsShort)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.UNAUTHORIZED)
.end(function (err, res) {
if (err) {
return done(err);
}
expect(res.body.error).toBeDefined();
expect(res.body.error).toMatch(API_ERROR.BAD_USERNAME_PASSWORD);
done();
});
});
expect(res.body.error).toBeDefined();
expect(res.body.error).toMatch(API_ERROR.BAD_USERNAME_PASSWORD);
done();
});
});
test('should be able to logout an user', async (done) => {
mockApiJWTmiddleware.mockImplementationOnce(() =>
(req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: 'test'};
_next();
}
);
mockAuthenticate.mockImplementationOnce(() => (_name, _password, callback): void => {
return callback(getUnauthorized(API_ERROR.BAD_USERNAME_PASSWORD));
}
);
const credentialsShort = _.cloneDeep(credentials);
credentialsShort.password = 'failPassword';
test('should be able to logout an user', async (done) => {
mockApiJWTmiddleware.mockImplementationOnce(() => (req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: 'test' };
_next();
});
mockAuthenticate.mockImplementationOnce(() => (_name, _password, callback): void => {
return callback(getUnauthorized(API_ERROR.BAD_USERNAME_PASSWORD));
});
const credentialsShort = _.cloneDeep(credentials);
credentialsShort.password = 'failPassword';
supertest(await initializeServer('user.yaml'))
.delete('/-/user/token/someSecretToken')
.send(credentialsShort)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.OK)
.end(function(err, res) {
if (err) {
return done(err);
}
expect(res.body.ok).toMatch(API_MESSAGE.LOGGED_OUT);
done();
});
});
supertest(await initializeServer('user.yaml'))
.delete('/-/user/token/someSecretToken')
.send(credentialsShort)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.OK)
.end(function (err, res) {
if (err) {
return done(err);
}
expect(res.body.ok).toMatch(API_MESSAGE.LOGGED_OUT);
done();
});
});
});

@ -1,53 +1,50 @@
import supertest from 'supertest';
import { HTTP_STATUS } from '@verdaccio/commons-api';
import { HEADERS} from '@verdaccio/dev-commons';
import {$RequestExtend, $ResponseExtend} from "@verdaccio/dev-types";
import {initializeServer } from './_helper';
import { HEADERS } from '@verdaccio/dev-commons';
import { $RequestExtend, $ResponseExtend } from '@verdaccio/dev-types';
import { initializeServer } from './_helper';
const mockApiJWTmiddleware = jest.fn(() =>
(req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: 'foo', groups: [], real_groups: []}
_next();
}
);
const mockApiJWTmiddleware = jest.fn(() => (req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: 'foo', groups: [], real_groups: [] };
_next();
});
jest.mock('@verdaccio/auth', () => ({
Auth: class {
apiJWTmiddleware() {
return mockApiJWTmiddleware();
}
allow_access (_d, f_, cb) {
cb(null, true)
}
}
Auth: class {
apiJWTmiddleware() {
return mockApiJWTmiddleware();
}
allow_access(_d, f_, cb) {
cb(null, true);
}
},
}));
describe('whoami', () => {
test.skip('should test referer /whoami endpoint', async (done) => {
return supertest(await initializeServer('whoami.yaml'))
.get('/whoami')
.set('referer', 'whoami')
.expect(HTTP_STATUS.OK)
.end(done);
});
test.skip('should test referer /whoami endpoint', async (done) => {
return supertest(await initializeServer('whoami.yaml'))
.get('/whoami')
.set('referer', 'whoami')
.expect(HTTP_STATUS.OK)
.end(done);
});
test.skip('should test no referer /whoami endpoint', async (done) => {
return supertest(await initializeServer('whoami.yaml'))
.get('/whoami')
.expect(HTTP_STATUS.NOT_FOUND)
.end(done);
});
test.skip('should test no referer /whoami endpoint', async (done) => {
return supertest(await initializeServer('whoami.yaml'))
.get('/whoami')
.expect(HTTP_STATUS.NOT_FOUND)
.end(done);
});
test('should return the logged username', async () => {
return supertest(await initializeServer('whoami.yaml'))
.get('/-/whoami')
.set('Accept', HEADERS.JSON)
.expect('Content-Type', HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.OK)
.then(response => {
expect(response.body.username).toEqual('foo');
});
});
test('should return the logged username', async () => {
return supertest(await initializeServer('whoami.yaml'))
.get('/-/whoami')
.set('Accept', HEADERS.JSON)
.expect('Content-Type', HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.OK)
.then((response) => {
expect(response.body.username).toEqual('foo');
});
});
});

@ -3,9 +3,7 @@ import { addVersion, uploadPackageTarball, removeTarball, unPublishPackage, publ
const REVISION_MOCK = '15-e53a77096b0ee33e';
require('@verdaccio/logger').setup([
{ type: 'stdout', format: 'pretty', level: 'info' }
]);
require('@verdaccio/logger').setup([{ type: 'stdout', format: 'pretty', level: 'info' }]);
describe('Publish endpoints - add a tag', () => {
let req;
@ -28,7 +26,7 @@ describe('Publish endpoints - add a tag', () => {
next = jest.fn();
});
test('should add a version', done => {
test('should add a version', (done) => {
const storage = {
addVersion: (packageName, version, body, tag, cb) => {
expect(packageName).toEqual(req.params.package);
@ -47,7 +45,7 @@ describe('Publish endpoints - add a tag', () => {
expect(next).toHaveBeenLastCalledWith({ ok: 'package published' });
});
test('when failed to add a version', done => {
test('when failed to add a version', (done) => {
const storage = {
addVersion: (packageName, version, body, tag, cb) => {
const error = {
@ -82,7 +80,7 @@ describe('Publish endpoints - upload package tarball', () => {
pipe: jest.fn(),
on: jest.fn(),
};
res = { status: jest.fn(), locals: { report_error: jest.fn() }};
res = { status: jest.fn(), locals: { report_error: jest.fn() } };
next = jest.fn();
});
@ -127,7 +125,7 @@ describe('Publish endpoints - delete tarball', () => {
next = jest.fn();
});
test('should delete tarball successfully', done => {
test('should delete tarball successfully', (done) => {
const storage = {
removeTarball(packageName, filename, revision, cb) {
expect(packageName).toEqual(req.params.package);
@ -144,7 +142,7 @@ describe('Publish endpoints - delete tarball', () => {
expect(next).toHaveBeenCalledWith({ ok: 'tarball removed' });
});
test('failed while deleting the tarball', done => {
test('failed while deleting the tarball', (done) => {
const error = {
message: 'deletion failed',
};
@ -179,7 +177,7 @@ describe('Publish endpoints - un-publish package', () => {
next = jest.fn();
});
test('should un-publish package successfully', done => {
test('should un-publish package successfully', (done) => {
const storage = {
removePackage(packageName, cb) {
expect(packageName).toEqual(req.params.package);
@ -194,7 +192,7 @@ describe('Publish endpoints - un-publish package', () => {
expect(next).toHaveBeenCalledWith({ ok: 'package removed' });
});
test('un-publish failed', done => {
test('un-publish failed', (done) => {
const error = {
message: 'un-publish failed',
};

@ -2,27 +2,15 @@ import _ from 'lodash';
import { NextFunction } from 'express';
import { VerdaccioError, getBadRequest, getInternalError, getForbidden } from '@verdaccio/commons-api';
import {API_ERROR, SUPPORT_ERRORS, TOKEN_BASIC, TOKEN_BEARER} from '@verdaccio/dev-commons';
import { API_ERROR, SUPPORT_ERRORS, TOKEN_BASIC, TOKEN_BEARER } from '@verdaccio/dev-commons';
import { loadPlugin } from '@verdaccio/loaders';
import { aesEncrypt, signPayload } from '@verdaccio/utils';
import {
getDefaultPlugins,
createAnonymousRemoteUser,
convertPayloadToBase64,
createRemoteUser,
} from '@verdaccio/utils';
import { getDefaultPlugins, createAnonymousRemoteUser, convertPayloadToBase64, createRemoteUser } from '@verdaccio/utils';
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,
getSecurity,
verifyJWTPayload,
parseBasicPayload,
parseAuthTokenHeader,
isAuthHeaderValid,
isAESLegacy } from "./utils";
import { getMiddlewareCredentials, getSecurity, verifyJWTPayload, parseBasicPayload, parseAuthTokenHeader, isAuthHeaderValid, isAESLegacy } from './utils';
/* eslint-disable @typescript-eslint/no-var-requires */
const LoggerApi = require('@verdaccio/logger');
@ -47,17 +35,12 @@ class Auth implements IAuth {
logger: this.logger,
};
return loadPlugin<IPluginAuth<Config>>(
config,
config.auth,
pluginOptions,
(plugin: IPluginAuth<Config>): boolean => {
const { authenticate, allow_access, allow_publish } = plugin;
return loadPlugin<IPluginAuth<Config>>(config, config.auth, pluginOptions, (plugin: IPluginAuth<Config>): boolean => {
const { authenticate, allow_access, allow_publish } = plugin;
// @ts-ignore
return authenticate || allow_access || allow_publish;
}
);
// @ts-ignore
return authenticate || allow_access || allow_publish;
});
}
private _applyDefaultPlugins(): void {
@ -65,39 +48,33 @@ class Auth implements IAuth {
}
public changePassword(username: string, password: string, newPassword: string, cb: Callback): void {
const validPlugins = _.filter(this.plugins, plugin => _.isFunction(plugin.changePassword));
const validPlugins = _.filter(this.plugins, (plugin) => _.isFunction(plugin.changePassword));
if (_.isEmpty(validPlugins)) {
return cb(
getInternalError(SUPPORT_ERRORS.PLUGIN_MISSING_INTERFACE));
}
if (_.isEmpty(validPlugins)) {
return cb(getInternalError(SUPPORT_ERRORS.PLUGIN_MISSING_INTERFACE));
}
for (const plugin of validPlugins) {
if (_.isNil(plugin) || _.isFunction(plugin.changePassword) === false) {
this.logger.trace('auth plugin does not implement changePassword, trying next one');
continue;
} else {
this.logger.trace({username}, 'updating password for @{username}');
plugin.changePassword!(
username,
password,
newPassword,
(err, profile): void => {
if (err) {
this.logger.error(
{username, err},
`An error has been produced
for (const plugin of validPlugins) {
if (_.isNil(plugin) || _.isFunction(plugin.changePassword) === false) {
this.logger.trace('auth plugin does not implement changePassword, trying next one');
continue;
} else {
this.logger.trace({ username }, 'updating password for @{username}');
plugin.changePassword!(username, password, newPassword, (err, profile): void => {
if (err) {
this.logger.error(
{ username, err },
`An error has been produced
updating the password for @{username}. Error: @{err.message}`
);
return cb(err);
}
);
return cb(err);
}
this.logger.trace({username}, 'updated password for @{username} was successful');
return cb(null, profile);
}
);
}
}
this.logger.trace({ username }, 'updated password for @{username} was successful');
return cb(null, profile);
});
}
}
}
public authenticate(username: string, password: string, cb: Callback): void {
@ -111,7 +88,7 @@ class Auth implements IAuth {
}
self.logger.trace({ username }, 'authenticating @{username}');
plugin.authenticate(username, password, function(err, groups): void {
plugin.authenticate(username, password, function (err, groups): void {
if (err) {
self.logger.trace({ username, err }, 'authenticating for user @{username} failed. Error: @{err.message}');
return cb(err);
@ -159,7 +136,7 @@ class Auth implements IAuth {
next();
} else {
// p.add_user() execution
plugin[method](user, password, function(err, ok): void {
plugin[method](user, password, function (err, ok): void {
if (err) {
self.logger.trace({ user, err: err.message }, 'the user @{user} could not being added. Error: @{err}');
return cb(err);
@ -191,7 +168,7 @@ class Auth implements IAuth {
return next();
}
plugin.allow_access!(user, pkg, function(err, ok: boolean): void {
plugin.allow_access!(user, pkg, function (err, ok: boolean): void {
if (err) {
self.logger.trace({ packageName, err }, 'forbidden access for @{packageName}. Error: @{err.message}');
return callback(err);
@ -216,28 +193,24 @@ class Auth implements IAuth {
this.logger.trace({ packageName }, 'allow unpublish for @{packageName} plugin does not implement allow_unpublish');
continue;
} else {
plugin.allow_unpublish!(
user,
pkg,
(err, ok: boolean): void => {
if (err) {
this.logger.trace({ packageName }, 'forbidden publish for @{packageName}, it will fallback on unpublish permissions');
return callback(err);
}
if (_.isNil(ok) === true) {
this.logger.trace({ packageName }, 'we bypass unpublish for @{packageName}, publish will handle the access');
// @ts-ignore
// eslint-disable-next-line
return this.allow_publish(...arguments);
}
if (ok) {
this.logger.trace({ packageName }, 'allowed unpublish for @{packageName}');
return callback(null, ok);
}
plugin.allow_unpublish!(user, pkg, (err, ok: boolean): void => {
if (err) {
this.logger.trace({ packageName }, 'forbidden publish for @{packageName}, it will fallback on unpublish permissions');
return callback(err);
}
);
if (_.isNil(ok) === true) {
this.logger.trace({ packageName }, 'we bypass unpublish for @{packageName}, publish will handle the access');
// @ts-ignore
// eslint-disable-next-line
return this.allow_publish(...arguments);
}
if (ok) {
this.logger.trace({ packageName }, 'allowed unpublish for @{packageName}');
return callback(null, ok);
}
});
}
}
}
@ -294,7 +267,7 @@ class Auth implements IAuth {
return (req: $RequestExtend, res: $ResponseExtend, _next: NextFunction): void => {
req.pause();
const next = function(err: VerdaccioError | void): void {
const next = function (err: VerdaccioError | void): void {
req.resume();
// uncomment this to reject users with bad auth headers
// return _next.apply(null, arguments)
@ -342,19 +315,15 @@ class Auth implements IAuth {
// this should happen when client tries to login with an existing user
const credentials = convertPayloadToBase64(token).toString();
const { user, password } = parseBasicPayload(credentials) as AESPayload;
this.authenticate(
user,
password,
(err, user): void => {
if (!err) {
req.remote_user = user;
next();
} else {
req.remote_user = createAnonymousRemoteUser();
next(err);
}
this.authenticate(user, password, (err, user): void => {
if (!err) {
req.remote_user = user;
next();
} else {
req.remote_user = createAnonymousRemoteUser();
next(err);
}
);
});
} else {
// jwt handler
const credentials: any = getMiddlewareCredentials(security, secret, authorization);
@ -373,19 +342,15 @@ class Auth implements IAuth {
const credentials: any = getMiddlewareCredentials(security, secret, authorization);
if (credentials) {
const { user, password } = credentials;
this.authenticate(
user,
password,
(err, user): void => {
if (!err) {
req.remote_user = user;
next();
} else {
req.remote_user = createAnonymousRemoteUser();
next(err);
}
this.authenticate(user, password, (err, user): void => {
if (!err) {
req.remote_user = user;
next();
} else {
req.remote_user = createAnonymousRemoteUser();
next(err);
}
);
});
} else {
// we force npm client to ask again with basic authentication
return next(getBadRequest(API_ERROR.BAD_AUTH_HEADER));

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

@ -1,127 +1,118 @@
import {Config, RemoteUser, Security} from "@verdaccio/types";
import {AuthMiddlewarePayload, AuthTokenHeader, BasicPayload, IAuthWebUI} from "@verdaccio/dev-types";
import _ from "lodash";
import {HTTP_STATUS, TOKEN_BASIC, TOKEN_BEARER} from "@verdaccio/dev-commons";
import {
aesDecrypt,
buildUserBuffer,
convertPayloadToBase64,
createAnonymousRemoteUser,
defaultSecurity,
ErrorCode,
verifyPayload
} from "@verdaccio/utils";
import { Config, RemoteUser, Security } from '@verdaccio/types';
import { AuthMiddlewarePayload, AuthTokenHeader, BasicPayload, IAuthWebUI } from '@verdaccio/dev-types';
import _ from 'lodash';
import { HTTP_STATUS, TOKEN_BASIC, TOKEN_BEARER } from '@verdaccio/dev-commons';
import { aesDecrypt, buildUserBuffer, convertPayloadToBase64, createAnonymousRemoteUser, defaultSecurity, ErrorCode, verifyPayload } from '@verdaccio/utils';
export function parseAuthTokenHeader(authorizationHeader: string): AuthTokenHeader {
const parts = authorizationHeader.split(' ');
const [scheme, token] = parts;
const parts = authorizationHeader.split(' ');
const [scheme, token] = parts;
return { scheme, token };
return { scheme, token };
}
export function parseAESCredentials(authorizationHeader: string, secret: string) {
const { scheme, token } = parseAuthTokenHeader(authorizationHeader);
const { scheme, token } = parseAuthTokenHeader(authorizationHeader);
// basic is deprecated and should not be enforced
if (scheme.toUpperCase() === TOKEN_BASIC.toUpperCase()) {
const credentials = convertPayloadToBase64(token).toString();
// basic is deprecated and should not be enforced
if (scheme.toUpperCase() === TOKEN_BASIC.toUpperCase()) {
const credentials = convertPayloadToBase64(token).toString();
return credentials;
} else if (scheme.toUpperCase() === TOKEN_BEARER.toUpperCase()) {
const tokenAsBuffer = convertPayloadToBase64(token);
const credentials = aesDecrypt(tokenAsBuffer, secret).toString('utf8');
return credentials;
} else if (scheme.toUpperCase() === TOKEN_BEARER.toUpperCase()) {
const tokenAsBuffer = convertPayloadToBase64(token);
const credentials = aesDecrypt(tokenAsBuffer, secret).toString('utf8');
return credentials;
}
return credentials;
}
}
export function getMiddlewareCredentials(security: Security, secret: string, authorizationHeader: string): AuthMiddlewarePayload {
if (isAESLegacy(security)) {
const credentials = parseAESCredentials(authorizationHeader, secret);
if (!credentials) {
return;
}
if (isAESLegacy(security)) {
const credentials = parseAESCredentials(authorizationHeader, secret);
if (!credentials) {
return;
}
const parsedCredentials = parseBasicPayload(credentials);
if (!parsedCredentials) {
return;
}
const parsedCredentials = parseBasicPayload(credentials);
if (!parsedCredentials) {
return;
}
return parsedCredentials;
}
const { scheme, token } = parseAuthTokenHeader(authorizationHeader);
return parsedCredentials;
}
const { scheme, token } = parseAuthTokenHeader(authorizationHeader);
if (_.isString(token) && scheme.toUpperCase() === TOKEN_BEARER.toUpperCase()) {
return verifyJWTPayload(token, secret);
}
if (_.isString(token) && scheme.toUpperCase() === TOKEN_BEARER.toUpperCase()) {
return verifyJWTPayload(token, secret);
}
}
export function isAESLegacy(security: Security): boolean {
const { legacy, jwt } = security.api;
const { legacy, jwt } = security.api;
return _.isNil(legacy) === false && _.isNil(jwt) && legacy === true;
return _.isNil(legacy) === false && _.isNil(jwt) && legacy === true;
}
export async function getApiToken(auth: IAuthWebUI, config: Config, remoteUser: RemoteUser, aesPassword: string): Promise<string> {
const security: Security = getSecurity(config);
const security: Security = getSecurity(config);
if (isAESLegacy(security)) {
// fallback all goes to AES encryption
return await new Promise((resolve): void => {
resolve(auth.aesEncrypt(buildUserBuffer(remoteUser.name as string, aesPassword)).toString('base64'));
});
}
// i am wiling to use here _.isNil but flow does not like it yet.
const { jwt } = security.api;
if (isAESLegacy(security)) {
// fallback all goes to AES encryption
return await new Promise((resolve): void => {
resolve(auth.aesEncrypt(buildUserBuffer(remoteUser.name as string, aesPassword)).toString('base64'));
});
}
// i am wiling to use here _.isNil but flow does not like it yet.
const { jwt } = security.api;
if (jwt?.sign) {
return await auth.jwtEncrypt(remoteUser, jwt.sign);
}
return await new Promise((resolve): void => {
resolve(auth.aesEncrypt(buildUserBuffer(remoteUser.name as string, aesPassword)).toString('base64'));
});
if (jwt?.sign) {
return await auth.jwtEncrypt(remoteUser, jwt.sign);
}
return await new Promise((resolve): void => {
resolve(auth.aesEncrypt(buildUserBuffer(remoteUser.name as string, aesPassword)).toString('base64'));
});
}
export function getSecurity(config: Config): Security {
if (_.isNil(config.security) === false) {
return _.merge(defaultSecurity, config.security);
}
if (_.isNil(config.security) === false) {
return _.merge(defaultSecurity, config.security);
}
return defaultSecurity;
return defaultSecurity;
}
export const expireReasons: string[] = ['JsonWebTokenError', 'TokenExpiredError'];
export function verifyJWTPayload(token: string, secret: string): RemoteUser {
try {
const payload: RemoteUser = verifyPayload(token, secret);
try {
const payload: RemoteUser = verifyPayload(token, secret);
return payload;
} catch (error) {
// #168 this check should be removed as soon AES encrypt is removed.
if (expireReasons.includes(error.name)) {
// it might be possible the jwt configuration is enabled and
// old tokens fails still remains in usage, thus
// we return an anonymous user to force log in.
return createAnonymousRemoteUser();
}
throw ErrorCode.getCode(HTTP_STATUS.UNAUTHORIZED, error.message);
}
return payload;
} catch (error) {
// #168 this check should be removed as soon AES encrypt is removed.
if (expireReasons.includes(error.name)) {
// it might be possible the jwt configuration is enabled and
// old tokens fails still remains in usage, thus
// we return an anonymous user to force log in.
return createAnonymousRemoteUser();
}
throw ErrorCode.getCode(HTTP_STATUS.UNAUTHORIZED, error.message);
}
}
export function isAuthHeaderValid(authorization: string): boolean {
return authorization.split(' ').length === 2;
return authorization.split(' ').length === 2;
}
export function parseBasicPayload(credentials: string): BasicPayload {
const index = credentials.indexOf(':');
if (index < 0) {
return;
}
const index = credentials.indexOf(':');
if (index < 0) {
return;
}
const user: string = credentials.slice(0, index);
const password: string = credentials.slice(index + 1);
const user: string = credentials.slice(0, index);
const password: string = credentials.slice(index + 1);
return { user, password };
return { user, password };
}

@ -1,11 +1,10 @@
import path from "path";
import path from 'path';
import _ from 'lodash';
import { Auth } from '../src';
import {CHARACTER_ENCODING, TOKEN_BEARER} from '@verdaccio/dev-commons';
import { CHARACTER_ENCODING, TOKEN_BEARER } from '@verdaccio/dev-commons';
import { configExample } from '@verdaccio/mock';
import {Config as AppConfig } from '@verdaccio/config';
import {setup} from '@verdaccio/logger';
import { Config as AppConfig } from '@verdaccio/config';
import { setup } from '@verdaccio/logger';
import {
buildUserBuffer,
@ -17,17 +16,13 @@ import {
parseConfigFile,
createAnonymousRemoteUser,
createRemoteUser,
signPayload
signPayload,
} from '@verdaccio/utils';
import { IAuth } from '@verdaccio/dev-types';
import {Config, Security, RemoteUser} from '@verdaccio/types';
import {
getMiddlewareCredentials,
getApiToken,
verifyJWTPayload,
getSecurity } from '../src'
import { Config, Security, RemoteUser } from '@verdaccio/types';
import { Auth } from '../src';
import { getMiddlewareCredentials, getApiToken, verifyJWTPayload, getSecurity } from '../src';
setup([]);
@ -38,7 +33,6 @@ const parseConfigurationFile = (conf) => {
return path.join(__dirname, `./partials/config/${format}/security/${name}.${format}`);
};
describe('Auth utilities', () => {
jest.setTimeout(20000);
@ -49,7 +43,7 @@ describe('Auth utilities', () => {
function getConfig(configFileName: string, secret: string) {
const conf = parseConfigFile(parseConfigurationSecurityFile(configFileName));
// @ts-ignore
const secConf= _.merge(configExample(), conf);
const secConf = _.merge(configExample(), conf);
secConf.secret = secret;
const config: Config = new AppConfig(secConf);
@ -62,7 +56,8 @@ describe('Auth utilities', () => {
password: string,
secret = '12345',
methodToSpy: string,
methodNotBeenCalled: string): Promise<string> {
methodNotBeenCalled: string
): Promise<string> {
const config: Config = getConfig(configFileName, secret);
const auth: IAuth = new Auth(config);
// @ts-ignore
@ -72,7 +67,7 @@ describe('Auth utilities', () => {
const user: RemoteUser = {
name: username,
real_groups: [],
groups: []
groups: [],
};
const token = await getApiToken(auth, config, user, password);
expect(spy).toHaveBeenCalled();
@ -100,56 +95,49 @@ describe('Auth utilities', () => {
describe('getApiToken test', () => {
test('should sign token with aes and security missing', async () => {
const token = await signCredentials('security-missing',
'test', 'test', '1234567', 'aesEncrypt', 'jwtEncrypt');
const token = await signCredentials('security-missing', 'test', 'test', '1234567', 'aesEncrypt', 'jwtEncrypt');
verifyAES(token, 'test', 'test', '1234567');
expect(_.isString(token)).toBeTruthy();
});
test('should sign token with aes and security empty', async () => {
const token = await signCredentials('security-empty',
'test', 'test', '123456', 'aesEncrypt', 'jwtEncrypt');
const token = await signCredentials('security-empty', 'test', 'test', '123456', 'aesEncrypt', 'jwtEncrypt');
verifyAES(token, 'test', 'test', '123456');
expect(_.isString(token)).toBeTruthy();
});
test('should sign token with aes', async () => {
const token = await signCredentials('security-basic',
'test', 'test', '123456', 'aesEncrypt', 'jwtEncrypt');
const token = await signCredentials('security-basic', 'test', 'test', '123456', 'aesEncrypt', 'jwtEncrypt');
verifyAES(token, 'test', 'test', '123456');
expect(_.isString(token)).toBeTruthy();
});
test('should sign token with legacy and jwt disabled', async () => {
const token = await signCredentials('security-no-legacy',
'test', 'test', 'x8T#ZCx=2t', 'aesEncrypt', 'jwtEncrypt');
const token = await signCredentials('security-no-legacy', 'test', 'test', 'x8T#ZCx=2t', 'aesEncrypt', 'jwtEncrypt');
expect(_.isString(token)).toBeTruthy();
verifyAES(token, 'test', 'test', 'x8T#ZCx=2t');
});
test('should sign token with legacy enabled and jwt enabled', async () => {
const token = await signCredentials('security-jwt-legacy-enabled',
'test', 'test', 'secret', 'jwtEncrypt', 'aesEncrypt');
const token = await signCredentials('security-jwt-legacy-enabled', 'test', 'test', 'secret', 'jwtEncrypt', 'aesEncrypt');
verifyJWT(token, 'test', 'test', 'secret');
expect(_.isString(token)).toBeTruthy();
});
test('should sign token with jwt enabled', async () => {
const token = await signCredentials('security-jwt',
'test', 'test', 'secret', 'jwtEncrypt', 'aesEncrypt');
const token = await signCredentials('security-jwt', 'test', 'test', 'secret', 'jwtEncrypt', 'aesEncrypt');
expect(_.isString(token)).toBeTruthy();
verifyJWT(token, 'test', 'test', 'secret');
});
test('should sign with jwt whether legacy is disabled', async () => {
const token = await signCredentials('security-legacy-disabled',
'test', 'test', 'secret', 'jwtEncrypt', 'aesEncrypt');
const token = await signCredentials('security-legacy-disabled', 'test', 'test', 'secret', 'jwtEncrypt', 'aesEncrypt');
expect(_.isString(token)).toBeTruthy();
verifyJWT(token, 'test', 'test', 'secret');
@ -158,7 +146,7 @@ describe('Auth utilities', () => {
describe('getAuthenticatedMessage test', () => {
test('should sign token with jwt enabled', () => {
expect(getAuthenticatedMessage('test')).toBe('you are authenticated as \'test\'');
expect(getAuthenticatedMessage('test')).toBe("you are authenticated as 'test'");
});
});
@ -168,8 +156,7 @@ describe('Auth utilities', () => {
const secret = 'secret';
const user = 'test';
const pass = 'test';
const token = await signCredentials('security-legacy',
user, pass, secret, 'aesEncrypt', 'jwtEncrypt');
const token = await signCredentials('security-legacy', user, pass, secret, 'aesEncrypt', 'jwtEncrypt');
const config: Config = getConfig('security-legacy', secret);
const security: Security = getSecurity(config);
const credentials = getMiddlewareCredentials(security, secret, `Bearer ${token}`);
@ -197,8 +184,7 @@ describe('Auth utilities', () => {
test.concurrent('should return empty credential wrong secret key', async () => {
const secret = 'secret';
const token = await signCredentials('security-legacy',
'test', 'test', secret, 'aesEncrypt', 'jwtEncrypt');
const token = await signCredentials('security-legacy', 'test', 'test', secret, 'aesEncrypt', 'jwtEncrypt');
const config: Config = getConfig('security-legacy', secret);
const security: Security = getSecurity(config);
const credentials = getMiddlewareCredentials(security, 'BAD_SECRET', buildToken(TOKEN_BEARER, token));
@ -207,8 +193,7 @@ describe('Auth utilities', () => {
test.concurrent('should return empty credential wrong scheme', async () => {
const secret = 'secret';
const token = await signCredentials('security-legacy',
'test', 'test', secret, 'aesEncrypt', 'jwtEncrypt');
const token = await signCredentials('security-legacy', 'test', 'test', secret, 'aesEncrypt', 'jwtEncrypt');
const config: Config = getConfig('security-legacy', secret);
const security: Security = getSecurity(config);
const credentials = getMiddlewareCredentials(security, secret, buildToken('BAD_SCHEME', token));
@ -267,8 +252,7 @@ describe('Auth utilities', () => {
const secret = 'secret';
const user = 'test';
const config: Config = getConfig('security-jwt', secret);
const token = await signCredentials('security-jwt',
user, 'secretTest', secret, 'jwtEncrypt', 'aesEncrypt');
const token = await signCredentials('security-jwt', user, 'secretTest', secret, 'jwtEncrypt', 'aesEncrypt');
const security: Security = getSecurity(config);
const credentials = getMiddlewareCredentials(security, secret, buildToken(TOKEN_BEARER, token));
expect(credentials).toBeDefined();

@ -29,18 +29,16 @@ describe('AuthTest', () => {
expect(auth).toBeDefined();
const callback = jest.fn();
const groups = [ "test" ];
const groups = ['test'];
auth.authenticate('foo', 'bar', callback);
expect(callback).toHaveBeenCalledTimes(1);
expect(callback).toHaveBeenCalledWith(null,
{"groups":
[
"test", ROLES.$ALL, ROLES.$AUTH, ROLES.DEPRECATED_ALL, ROLES.DEPRECATED_AUTH, ROLES.ALL],
"name": 'foo',
"real_groups": groups
});
expect(callback).toHaveBeenCalledWith(null, {
groups: ['test', ROLES.$ALL, ROLES.$AUTH, ROLES.DEPRECATED_ALL, ROLES.DEPRECATED_AUTH, ROLES.ALL],
name: 'foo',
real_groups: groups,
});
});
test('should be a fail on login', () => {
@ -71,7 +69,7 @@ describe('AuthTest', () => {
let index = 0;
// as defined by https://developer.mozilla.org/en-US/docs/Glossary/Falsy
for (const value of [ false, 0, "", null, undefined, NaN ]) {
for (const value of [false, 0, '', null, undefined, NaN]) {
// @ts-ignore
auth.authenticate(null, value, callback);
const call = callback.mock.calls[index++];
@ -88,8 +86,8 @@ describe('AuthTest', () => {
const callback = jest.fn();
for (const value of [ true, 1, "test", { } ]) {
expect(function ( ) {
for (const value of [true, 1, 'test', {}]) {
expect(function () {
// @ts-ignore
auth.authenticate(null, value, callback);
}).toThrow(TypeError);
@ -104,7 +102,7 @@ describe('AuthTest', () => {
expect(auth).toBeDefined();
const callback = jest.fn();
const value = [ ];
const value = [];
// @ts-ignore
auth.authenticate(null, value, callback);
@ -122,7 +120,7 @@ describe('AuthTest', () => {
const callback = jest.fn();
let index = 0;
for (const value of [ [ "" ], [ "1" ], [ "0" ], ["000"] ]) {
for (const value of [[''], ['1'], ['0'], ['000']]) {
// @ts-ignore
auth.authenticate(null, value, callback);
const call = callback.mock.calls[index++];
@ -131,5 +129,5 @@ describe('AuthTest', () => {
}
});
});
})
});
});

@ -1,20 +1,20 @@
import path from 'path';
import { configExample as config }from '@verdaccio/mock';
import { configExample as config } from '@verdaccio/mock';
export const authProfileConf = config({
auth: {
[`${path.join(__dirname, '../partials/plugin/authenticate.success')}`]: { }
}
[`${path.join(__dirname, '../partials/plugin/authenticate.success')}`]: {},
},
});
export const authPluginFailureConf = config({
auth: {
[`${path.join(__dirname, '../partials/plugin/authenticate.fail')}`]: { }
}
[`${path.join(__dirname, '../partials/plugin/authenticate.fail')}`]: {},
},
});
export const authPluginPassThrougConf = config({
auth: {
[`${path.join(__dirname, '../partials/plugin/authenticate.passthroug')}`]: { }
}
[`${path.join(__dirname, '../partials/plugin/authenticate.passthroug')}`]: {},
},
});

@ -6,14 +6,14 @@ import { bgYellow, bgRed } from 'kleur';
import { setup, logger } from '@verdaccio/logger';
import infoCommand from "./commands/info";
import initProgram from "./commands/init";
import {isVersionValid, MIN_NODE_VERSION} from "./utils";
import infoCommand from './commands/info';
import initProgram from './commands/init';
import { isVersionValid, MIN_NODE_VERSION } from './utils';
const isRootUser = process.getuid && process.getuid() === 0;
if (isRootUser) {
global.console.warn(bgYellow().red('*** WARNING: Verdaccio doesn\'t need superuser privileges. Don\'t run it under root! ***'));
global.console.warn(bgYellow().red("*** WARNING: Verdaccio doesn't need superuser privileges. Don't run it under root! ***"));
}
if (isVersionValid()) {
@ -38,20 +38,22 @@ const isHelp = commander.args.length !== 0;
if (commander.info) {
infoCommand();
} else if (fallbackConfig) {
} else if (fallbackConfig) {
// handling "verdaccio [config]" case if "-c" is missing in command line
commander.config = commander.args.pop();
initProgram(commander, pkgVersion, pkgName);
initProgram(commander, pkgVersion, pkgName);
} else if (isHelp) {
commander.help();
} else {
initProgram(commander, pkgVersion, pkgName);
initProgram(commander, pkgVersion, pkgName);
}
process.on('uncaughtException', function(err) {
logger.fatal( {
err: err,
},
'uncaught exception, please report (https://github.com/verdaccio/verdaccio/issues) this: \n@{err.stack}' );
process.on('uncaughtException', function (err) {
logger.fatal(
{
err: err,
},
'uncaught exception, please report (https://github.com/verdaccio/verdaccio/issues) this: \n@{err.stack}'
);
process.exit(255);
});

@ -1,18 +1,18 @@
import envinfo from 'envinfo';
export default function infoCommand() {
// eslint-disable-next-line no-console
console.log('\nEnvironment Info:');
(async () => {
const data = await envinfo.run({
System: ['OS', 'CPU'],
Binaries: ['Node', 'Yarn', 'npm'],
Virtualization: ['Docker'],
Browsers: ['Chrome', 'Edge', 'Firefox', 'Safari'],
npmGlobalPackages: ['verdaccio'],
});
// eslint-disable-next-line no-console
console.log(data);
process.exit(0);
})();
// eslint-disable-next-line no-console
console.log('\nEnvironment Info:');
(async () => {
const data = await envinfo.run({
System: ['OS', 'CPU'],
Binaries: ['Node', 'Yarn', 'npm'],
Virtualization: ['Docker'],
Browsers: ['Chrome', 'Edge', 'Firefox', 'Safari'],
npmGlobalPackages: ['verdaccio'],
});
// eslint-disable-next-line no-console
console.log(data);
process.exit(0);
})();
}

@ -1,44 +1,44 @@
import path from "path";
import path from 'path';
import _ from 'lodash';
import { parseConfigFile} from "@verdaccio/utils";
import { findConfigFile } from "@verdaccio/config";
import { parseConfigFile } from '@verdaccio/utils';
import { findConfigFile } from '@verdaccio/config';
import { logger, createLogger } from '@verdaccio/logger';
import {startVerdaccio, listenDefaultCallback} from "@verdaccio/node-api";
import { startVerdaccio, listenDefaultCallback } from '@verdaccio/node-api';
export const DEFAULT_PROCESS_NAME: string = 'verdaccio';
export default function initProgram(commander, pkgVersion, pkgName) {
// FIXME: we need to log before the setup is being applied
// const initLogger = createLogger();
const cliListener = commander.listen;
let configPathLocation;
let verdaccioConfiguration;
try {
configPathLocation = findConfigFile(commander.config);
verdaccioConfiguration = parseConfigFile(configPathLocation);
const { web, https, self_path } = verdaccioConfiguration;
export default function initProgram(commander, pkgVersion, pkgName) {
// FIXME: we need to log before the setup is being applied
// const initLogger = createLogger();
const cliListener = commander.listen;
let configPathLocation;
let verdaccioConfiguration;
try {
configPathLocation = findConfigFile(commander.config);
verdaccioConfiguration = parseConfigFile(configPathLocation);
const { web, https, self_path } = verdaccioConfiguration;
process.title = web?.title || DEFAULT_PROCESS_NAME;
process.title = web?.title || DEFAULT_PROCESS_NAME;
// note: self_path is only being used by @verdaccio/storage , not really useful and migth be removed soon
if (!self_path) {
verdaccioConfiguration = _.assign({}, verdaccioConfiguration, {
self_path: path.resolve(configPathLocation)
});
}
// note: self_path is only being used by @verdaccio/storage , not really useful and migth be removed soon
if (!self_path) {
verdaccioConfiguration = _.assign({}, verdaccioConfiguration, {
self_path: path.resolve(configPathLocation),
});
}
if (!https) {
verdaccioConfiguration = _.assign({}, verdaccioConfiguration, {
https: {enable: false}
});
}
if (!https) {
verdaccioConfiguration = _.assign({}, verdaccioConfiguration, {
https: { enable: false },
});
}
// initLogger.warn({file: configPathLocation}, 'config file - @{file}');
// initLogger.warn({file: configPathLocation}, 'config file - @{file}');
startVerdaccio(verdaccioConfiguration, cliListener, configPathLocation, pkgVersion, pkgName, listenDefaultCallback);
} catch (err) {
// initLogger.fatal({file: configPathLocation, err: err}, 'cannot open config file @{file}: @{!err.message}');
process.exit(1);
}
startVerdaccio(verdaccioConfiguration, cliListener, configPathLocation, pkgVersion, pkgName, listenDefaultCallback);
} catch (err) {
// initLogger.fatal({file: configPathLocation, err: err}, 'cannot open config file @{file}: @{!err.message}');
process.exit(1);
}
}

@ -1,5 +1,5 @@
import semver from "semver";
import semver from 'semver';
export const MIN_NODE_VERSION = '6.9.0';
export const isVersionValid = () => semver.satisfies(process.version, `>=${MIN_NODE_VERSION}`) === false
export const isVersionValid = () => semver.satisfies(process.version, `>=${MIN_NODE_VERSION}`) === false;

@ -1,3 +1,3 @@
describe('cli test', () => {
test.todo('write some test for this module');
test.todo('write some test for this module');
});

@ -1,58 +1,57 @@
import { Package } from "@verdaccio/types";
import { Package } from '@verdaccio/types';
export interface DistTags {
[key: string]: string;
[key: string]: string;
}
export function generatePackageMetadata(pkgName: string, version = '1.0.0', distTags: DistTags = {['latest']: version}): Package {
// @ts-ignore
return {
"_id": pkgName,
"name": pkgName,
"dist-tags": {
...distTags
},
"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
}
}
}
export function generatePackageMetadata(pkgName: string, version = '1.0.0', distTags: DistTags = { ['latest']: version }): Package {
// @ts-ignore
return {
_id: pkgName,
name: pkgName,
'dist-tags': {
...distTags,
},
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,
},
},
};
}

@ -14,12 +14,12 @@ const WIN = 'win';
const WIN32 = 'win32';
// eslint-disable-next-line
const pkgJSON = {
name: 'verdaccio'
name: 'verdaccio',
};
export type SetupDirectory = {
path: string;
type: string
type: string;
};
/**
@ -84,13 +84,16 @@ function updateStorageLinks(configLocation, defaultConfig): string {
}
function getConfigPaths(): SetupDirectory[] {
const listPaths: SetupDirectory[] = [getXDGDirectory(), getWindowsDirectory(), getRelativeDefaultDirectory(), getOldDirectory()].reduce(
function(acc, currentValue: any): SetupDirectory[] {
if (_.isUndefined(currentValue) === false) {
acc.push(currentValue);
}
return acc;
}, [] as SetupDirectory[]);
const listPaths: SetupDirectory[] = [getXDGDirectory(), getWindowsDirectory(), getRelativeDefaultDirectory(), getOldDirectory()].reduce(function (
acc,
currentValue: any
): SetupDirectory[] {
if (_.isUndefined(currentValue) === false) {
acc.push(currentValue);
}
return acc;
},
[] as SetupDirectory[]);
return listPaths;
}

@ -8,7 +8,7 @@ import {
uplinkSanityCheck,
generateRandomHexString,
getUserAgent,
isObject
isObject,
} from '@verdaccio/utils';
import { APP_ERROR } from '@verdaccio/dev-commons';
@ -61,7 +61,7 @@ class Config implements AppConfig {
assert(_.isObject(config), APP_ERROR.CONFIG_NOT_VALID);
// sanity check for strategic config properties
strategicConfigProps.forEach(function(x): void {
strategicConfigProps.forEach(function (x): void {
if (self[x] == null) {
self[x] = {};
}
@ -79,13 +79,11 @@ class Config implements AppConfig {
this.packages = normalisePackageAccess(self.packages);
// loading these from ENV if aren't in config
allowedEnvConfig.forEach(
(envConf): void => {
if (!(envConf in self)) {
self[envConf] = process.env[envConf] || process.env[envConf.toUpperCase()];
}
allowedEnvConfig.forEach((envConf): void => {
if (!(envConf in self)) {
self[envConf] = process.env[envConf] || process.env[envConf.toUpperCase()];
}
);
});
// unique identifier of self server (or a cluster), used to avoid loops
// @ts-ignore

@ -1,2 +1,2 @@
export * from './config'
export * from './config-path'
export * from './config';
export * from './config-path';

@ -1,11 +1,11 @@
import path from 'path';
import _ from 'lodash';
import { Config, readDefaultConfig } from '../src';
import { setup } from '@verdaccio/logger';
import {DEFAULT_REGISTRY, DEFAULT_UPLINK, ROLES, WEB_TITLE} from '@verdaccio/dev-commons';
import { DEFAULT_REGISTRY, DEFAULT_UPLINK, ROLES, WEB_TITLE } from '@verdaccio/dev-commons';
import {parseConfigFile} from '@verdaccio/utils';
import { parseConfigFile } from '@verdaccio/utils';
import { Config, readDefaultConfig } from '../src';
setup([]);
@ -45,7 +45,7 @@ const checkDefaultConfPackages = (config) => {
expect(config.packages['**'].publish).toBeDefined();
expect(config.packages['**'].publish).toContainEqual(ROLES.$AUTH);
expect(config.packages['**'].proxy).toBeDefined();
expect(config.packages['**'].proxy,).toContainEqual(DEFAULT_UPLINK);
expect(config.packages['**'].proxy).toContainEqual(DEFAULT_UPLINK);
// uplinks
expect(config.uplinks[DEFAULT_UPLINK]).toBeDefined();
expect(config.uplinks[DEFAULT_UPLINK].url).toEqual(DEFAULT_REGISTRY);
@ -70,7 +70,7 @@ const checkDefaultConfPackages = (config) => {
};
describe('Config file', () => {
beforeAll(function() {
beforeAll(function () {
/* eslint no-invalid-this: 0 */
// @ts-ignore
this.config = new Config(parseConfigFile(resolveConf('default')));
@ -96,9 +96,5 @@ describe('Config file', () => {
});
});
describe('Config file', () => {
});
describe('Config file', () => {});
});

@ -1 +1 @@
export {handleNotify, notify, sendNotification } from './notify';
export { handleNotify, notify, sendNotification } from './notify';

@ -5,21 +5,19 @@ import { logger } from '@verdaccio/logger';
import { HTTP_STATUS } from '@verdaccio/commons-api';
export function notifyRequest(options: RequiredUriUrl, content): Promise<any | Error> {
return new Promise(
(resolve, reject): void => {
request(options, function(err, response, body): void {
if (err || response.statusCode >= HTTP_STATUS.BAD_REQUEST) {
const errorMessage = isNil(err) ? response.body : err.message;
logger.error({ errorMessage }, 'notify service has thrown an error: @{errorMessage}');
reject(errorMessage);
}
logger.info({ content }, 'A notification has been shipped: @{content}');
if (isNil(body) === false) {
logger.debug({ body }, ' body: @{body}');
resolve(body);
}
reject(Error('body is missing'));
});
}
);
return new Promise((resolve, reject): void => {
request(options, function (err, response, body): void {
if (err || response.statusCode >= HTTP_STATUS.BAD_REQUEST) {
const errorMessage = isNil(err) ? response.body : err.message;
logger.error({ errorMessage }, 'notify service has thrown an error: @{errorMessage}');
reject(errorMessage);
}
logger.info({ content }, 'A notification has been shipped: @{content}');
if (isNil(body) === false) {
logger.debug({ body }, ' body: @{body}');
resolve(body);
}
reject(Error('body is missing'));
});
});
}

@ -7,12 +7,7 @@ import { notifyRequest } from './notify-request';
type TemplateMetadata = Package & { publishedPackage: string };
export function handleNotify(
metadata: Package,
notifyEntry,
remoteUser: RemoteUser,
publishedPackage: string
): Promise<any> | void {
export function handleNotify(metadata: Package, notifyEntry, remoteUser: RemoteUser, publishedPackage: string): Promise<any> | void {
let regex;
if (metadata.name && notifyEntry.packagePattern) {
regex = new RegExp(notifyEntry.packagePattern, notifyEntry.packagePatternFlags || '');
@ -21,7 +16,7 @@ export function handleNotify(
}
}
const template = Handlebars.compile(notifyEntry.content);
const template: HandlebarsTemplateDelegate = Handlebars.compile(notifyEntry.content);
// don't override 'publisher' if package.json already has that
/* eslint no-unused-vars: 0 */
/* eslint @typescript-eslint/no-unused-vars: 0 */
@ -35,7 +30,7 @@ export function handleNotify(
const options: OptionsWithUrl = {
body: content,
url: ''
url: '',
};
// provides fallback support, it's accept an Object {} and Array of {}
@ -65,34 +60,17 @@ export function handleNotify(
return notifyRequest(options, content);
}
export function sendNotification(
metadata: Package,
notify: any,
remoteUser: RemoteUser,
publishedPackage: string
): Promise<any> {
export function sendNotification(metadata: Package, notify: Notification, remoteUser: RemoteUser, publishedPackage: string): Promise<any> {
return handleNotify(metadata, notify, remoteUser, publishedPackage) as Promise<any>;
}
export function notify(
metadata: Package,
config: Config,
remoteUser: RemoteUser,
publishedPackage: string
): Promise<any> | void {
export function notify(metadata: Package, config: Config, remoteUser: RemoteUser, publishedPackage: string): Promise<any> | void {
if (config.notify) {
if (config.notify.content) {
return sendNotification(
metadata,
(config.notify as unknown) as any,
remoteUser,
publishedPackage
);
return sendNotification(metadata, (config.notify as unknown) as Notification, remoteUser, publishedPackage);
}
// multiple notifications endpoints PR #108
return Promise.all(
_.map(config.notify, (key) => sendNotification(metadata, key, remoteUser, publishedPackage))
);
return Promise.all(_.map(config.notify, (key) => sendNotification(metadata, key, remoteUser, publishedPackage)));
}
return Promise.resolve();

@ -1,14 +1,14 @@
import {parseConfigFile} from '@verdaccio/utils';
import { parseConfigFile } from '@verdaccio/utils';
import { setup } from '@verdaccio/logger';
import {notify} from '../src';
import {notifyRequest} from '../src/notify-request';
import {parseConfigurationFile} from './__helper';
import { notify } from '../src';
import { notifyRequest } from '../src/notify-request';
import { parseConfigurationFile } from './__helper';
setup([]);
jest.mock('../src/notify-request', () => ({
notifyRequest: jest.fn((options, content) => Promise.resolve([options, content]))
notifyRequest: jest.fn((options, content) => Promise.resolve([options, content])),
}));
const parseConfigurationNotifyFile = (name) => {
@ -20,24 +20,23 @@ const packagePatternNotificationConfig = parseConfigFile(parseConfigurationNotif
const multiNotificationConfig = parseConfigFile(parseConfigurationNotifyFile('multiple.notify'));
describe('Notifications:: Notify', () => {
beforeEach(() => {
jest.clearAllMocks();
});
// FUTURE: we should add some sort of health check of all props, (not implemented yet)
test("should not fails if config is not provided", async () => {
test('should not fails if config is not provided', async () => {
// @ts-ignore
await notify({}, {});
expect(notifyRequest).toHaveBeenCalledTimes(0);
});
test("should send notification", async () => {
test('should send notification', async () => {
const name = 'package';
// @ts-ignore
const response = await notify({name}, singleNotificationConfig, { name: 'foo'}, 'bar');
const response = await notify({ name }, singleNotificationConfig, { name: 'foo' }, 'bar');
const [options, content] = response;
expect(options.headers).toBeDefined();
@ -48,39 +47,36 @@ describe('Notifications:: Notify', () => {
expect(notifyRequest).toHaveBeenCalledTimes(1);
});
test("should send single header notification", async () => {
test('should send single header notification', async () => {
// @ts-ignore
await notify({}, singleHeaderNotificationConfig, { name: 'foo'}, 'bar');
await notify({}, singleHeaderNotificationConfig, { name: 'foo' }, 'bar');
expect(notifyRequest).toHaveBeenCalledTimes(1);
});
test("should send multiple notification", async () => {
test('should send multiple notification', async () => {
// @ts-ignore
await notify({name}, multiNotificationConfig, { name: 'foo'}, 'bar');
await notify({ name }, multiNotificationConfig, { name: 'foo' }, 'bar');
expect(notifyRequest).toHaveBeenCalled();
expect(notifyRequest).toHaveBeenCalledTimes(3);
});
describe('packagePatternFlags', () => {
test("should send single notification with packagePatternFlags", async () => {
test('should send single notification with packagePatternFlags', async () => {
const name = 'package';
// @ts-ignore
await notify({name}, packagePatternNotificationConfig, { name: 'foo'}, 'bar');
await notify({ name }, packagePatternNotificationConfig, { name: 'foo' }, 'bar');
expect(notifyRequest).toHaveBeenCalledTimes(1);
});
test("should not match on send single notification with packagePatternFlags", async () => {
test('should not match on send single notification with packagePatternFlags', async () => {
const name = 'no-mach-name';
// @ts-ignore
await notify({name}, packagePatternNotificationConfig, { name: 'foo'}, 'bar');
await notify({ name }, packagePatternNotificationConfig, { name: 'foo' }, 'bar');
expect(notifyRequest).toHaveBeenCalledTimes(0);
});
})
});
});

@ -46,13 +46,7 @@ function isES6(plugin): boolean {
* @param {*} sanityCheck callback that check the shape that should fulfill the plugin
* @return {Array} list of plugins
*/
export function loadPlugin<T extends IPlugin<T>>(
config: Config,
pluginConfigs: any = {},
params: any,
sanityCheck: any,
prefix: string = 'verdaccio'
): any[] {
export function loadPlugin<T extends IPlugin<T>>(config: Config, pluginConfigs: any = {}, params: any, sanityCheck: any, prefix: string = 'verdaccio'): any[] {
return Object.keys(pluginConfigs).map(
(pluginId: string): IPlugin<T> => {
let plugin;
@ -102,17 +96,17 @@ export function loadPlugin<T extends IPlugin<T>>(
}
if (!isValid(plugin)) {
logger.error({ content: pluginId }, "@{prefix}-@{content} plugin does not have the right code structure");
logger.error({ content: pluginId }, '@{prefix}-@{content} plugin does not have the right code structure');
throw Error(`"${pluginId}" plugin does not have the right code structure`);
}
/* eslint new-cap:off */
try {
plugin = isES6(plugin) ? new plugin.default(mergeConfig(config, pluginConfigs[pluginId]), params) : plugin(pluginConfigs[pluginId], params);
} catch (error) {
plugin = null;
logger.error({ error, pluginId }, "error loading a plugin @{pluginId}: @{error}");
}
try {
plugin = isES6(plugin) ? new plugin.default(mergeConfig(config, pluginConfigs[pluginId]), params) : plugin(pluginConfigs[pluginId], params);
} catch (error) {
plugin = null;
logger.error({ error, pluginId }, 'error loading a plugin @{pluginId}: @{error}');
}
/* eslint new-cap:off */
if (plugin === null || !sanityCheck(plugin)) {

@ -1,5 +1,5 @@
import path from 'path';
import { setup } from '@verdaccio/logger';
import { setup } from '@verdaccio/logger';
import { loadPlugin } from '../src/plugin-loader';
@ -7,13 +7,13 @@ setup([]);
describe('plugin loader', () => {
const relativePath = path.join(__dirname, './partials/test-plugin-storage');
const buildConf = name => {
const buildConf = (name) => {
return {
self_path: path.join(__dirname, './'),
max_users: 0,
auth: {
[`${relativePath}/${name}`]: {}
}
[`${relativePath}/${name}`]: {},
},
};
};
@ -45,7 +45,7 @@ describe('plugin loader', () => {
loadPlugin(_config, _config.auth, {}, function (p) {
return p.authenticate || p.allow_access || p.allow_publish;
});
} catch(e) {
} catch (e) {
expect(e.message).toEqual(`"${relativePath}/invalid-plugin" plugin does not have the right code structure`);
}
});
@ -57,7 +57,7 @@ describe('plugin loader', () => {
loadPlugin(_config, _config.auth, {}, function (plugin) {
return plugin.authenticate || plugin.allow_access || plugin.allow_publish;
});
} catch(err) {
} catch (err) {
expect(err.message).toEqual(`sanity check has failed, "${relativePath}/invalid-plugin-sanity" is not a valid plugin`);
}
});
@ -69,7 +69,7 @@ describe('plugin loader', () => {
loadPlugin(_config, _config.auth, {}, function (plugin) {
return plugin.authenticate || plugin.allow_access || plugin.allow_publish;
});
} catch(e) {
} catch (e) {
expect(e.message).toMatch('plugin not found');
expect(e.message).toMatch('/partials/test-plugin-storage/invalid-package');
}
@ -78,5 +78,4 @@ describe('plugin loader', () => {
test.todo('test middleware plugins');
test.todo('test storage plugins');
});
});

@ -3,9 +3,9 @@ import { inspect } from 'util';
import { white, red, green } from 'kleur';
import padLeft from 'pad-left';
import {calculateLevel, LevelCode, levelsColors, subSystemLevels} from "./levels";
import {formatLoggingDate, isObject, pad} from './utils';
import {PrettyOptionsExtended} from "./types";
import { calculateLevel, LevelCode, levelsColors, subSystemLevels } from './levels';
import { formatLoggingDate, isObject, pad } from './utils';
import { PrettyOptionsExtended } from './types';
let LEVEL_VALUE_MAX = 0;
for (const l in levelsColors) {
@ -19,13 +19,12 @@ export interface ObjectTemplate {
msg: string;
sub?: string;
[key: string]: string | number | object | null | void;
};
}
export function fillInMsgTemplate(msg, templateOptions: ObjectTemplate, colors): string {
const templateRegex = /@{(!?[$A-Za-z_][$0-9A-Za-z\._]*)}/g;
return msg.replace(templateRegex, (_, name): string => {
let str = templateOptions;
let isError;
if (name[0] === ERROR_FLAG) {
@ -50,7 +49,7 @@ export function fillInMsgTemplate(msg, templateOptions: ObjectTemplate, colors):
}
return green(str);
}
// object, showHidden, depth, colors
return inspect(str, undefined, null, colors);
});
@ -65,17 +64,14 @@ function getMessage(debugLevel, msg, sub, templateObjects, hasColors) {
if (hasColors) {
const logString = `${levelsColors[debugLevel](pad(debugLevel, LEVEL_VALUE_MAX))}${white(`${subSystemType} ${finalMessage}`)}`;
return padLeft(logString, logString.length + CUSTOM_PAD_LENGTH , ' ');
return padLeft(logString, logString.length + CUSTOM_PAD_LENGTH, ' ');
}
const logString = `${pad(debugLevel, LEVEL_VALUE_MAX)}${subSystemType} ${finalMessage}`;
return padLeft(logString, logString.length + CUSTOM_PAD_LENGTH , ' ');
return padLeft(logString, logString.length + CUSTOM_PAD_LENGTH, ' ');
}
export function printMessage(
templateObjects: ObjectTemplate,
options: PrettyOptionsExtended,
hasColors = true): string {
export function printMessage(templateObjects: ObjectTemplate, options: PrettyOptionsExtended, hasColors = true): string {
const { prettyStamp } = options;
const { level, msg, sub } = templateObjects;
const debugLevel = calculateLevel(level);

@ -1,5 +1,5 @@
import { printMessage } from "./formatter";
import {PrettyOptionsExtended} from "./types";
import { printMessage } from './formatter';
import { PrettyOptionsExtended } from './types';
export type PrettyFactory = (param) => string;
@ -8,11 +8,11 @@ export type PrettyFactory = (param) => string;
{ messageKey: 'msg', levelFirst: true, prettyStamp: false }
*/
module.exports = function prettyFactory (options: PrettyOptionsExtended): PrettyFactory {
// the break line must happens in the prettify component
const breakLike = '\n';
return (inputData): string => {
// FIXME: review colors by default is true
return printMessage(inputData, options, true) + breakLike;
};
module.exports = function prettyFactory(options: PrettyOptionsExtended): PrettyFactory {
// the break line must happens in the prettify component
const breakLike = '\n';
return (inputData): string => {
// FIXME: review colors by default is true
return printMessage(inputData, options, true) + breakLike;
};
};

@ -5,52 +5,52 @@ export type LogLevel = 'trace' | 'debug' | 'info' | 'http' | 'warn' | 'error' |
export type LevelCode = number;
export function calculateLevel(levelCode: LevelCode): LogLevel {
switch (true) {
case levelCode < 15:
return 'trace';
case levelCode < 25:
return 'debug';
case levelCode < 35:
return 'info';
case levelCode == 35:
return 'http';
case levelCode < 45:
return 'warn';
case levelCode < 55:
return 'error';
default:
return 'fatal';
}
switch (true) {
case levelCode < 15:
return 'trace';
case levelCode < 25:
return 'debug';
case levelCode < 35:
return 'info';
case levelCode == 35:
return 'http';
case levelCode < 45:
return 'warn';
case levelCode < 55:
return 'error';
default:
return 'fatal';
}
}
export const levelsColors = {
fatal: red,
error: red,
warn: yellow,
http: magenta,
info: cyan,
debug: green,
trace: white,
fatal: red,
error: red,
warn: yellow,
http: magenta,
info: cyan,
debug: green,
trace: white,
};
enum ARROWS {
LEFT = '<--',
RIGHT = '-->',
EQUAL = '-=-',
NEUTRAL = '---'
LEFT = '<--',
RIGHT = '-->',
EQUAL = '-=-',
NEUTRAL = '---',
}
export const subSystemLevels = {
color: {
in: green(ARROWS.LEFT),
out: yellow(ARROWS.RIGHT),
fs: black(ARROWS.EQUAL),
default: blue(ARROWS.NEUTRAL),
},
white: {
in: ARROWS.LEFT,
out: ARROWS.RIGHT,
fs: ARROWS.EQUAL,
default: ARROWS.NEUTRAL,
},
color: {
in: green(ARROWS.LEFT),
out: yellow(ARROWS.RIGHT),
fs: black(ARROWS.EQUAL),
default: blue(ARROWS.NEUTRAL),
},
white: {
in: ARROWS.LEFT,
out: ARROWS.RIGHT,
fs: ARROWS.EQUAL,
default: ARROWS.NEUTRAL,
},
};

@ -1,5 +1,5 @@
import {PrettyOptions} from "pino";
import { PrettyOptions } from 'pino';
export interface PrettyOptionsExtended extends PrettyOptions {
prettyStamp: boolean;
export interface PrettyOptionsExtended extends PrettyOptions {
prettyStamp: boolean;
}

@ -5,15 +5,15 @@ import dayjs from 'dayjs';
export const FORMAT_DATE = 'YYYY-MM-DD HH:mm:ss';
export function isObject(obj: unknown): boolean {
return _.isObject(obj) && _.isNull(obj) === false && _.isArray(obj) === false;
return _.isObject(obj) && _.isNull(obj) === false && _.isArray(obj) === false;
}
export function pad(str: string, max: number): string {
return padRight(str, max, ' ');
return padRight(str, max, ' ');
}
export function formatLoggingDate(time: number, message): string {
const timeFormatted = dayjs(time).format(FORMAT_DATE);
const timeFormatted = dayjs(time).format(FORMAT_DATE);
return `[${timeFormatted}]${message}`;
return `[${timeFormatted}]${message}`;
}

@ -1,213 +1,217 @@
import {printMessage} from "../src/formatter";
import {LevelCode} from "../src/levels";
import { printMessage } from '../src/formatter';
import { LevelCode } from '../src/levels';
jest.mock('dayjs', () => ({
__esModule: true,
default: () => ({
format: () => 'formatted-date'
})
__esModule: true,
default: () => ({
format: () => 'formatted-date',
}),
}));
describe('formatter', () => {
const prettyfierOptions = {messageKey: 'msg', levelFirst: true, prettyStamp: false};
describe('printMessage', () => {
test('should display config file', () => {
const log = {
level: 40,
time: 1585410824129,
v: 1,
pid: 27029,
hostname: 'localhost',
file: '/Users/user/.config/verdaccio/config/config.yaml',
msg: 'config file - @{file}'
};
const prettyfierOptions = { messageKey: 'msg', levelFirst: true, prettyStamp: false };
describe('printMessage', () => {
test('should display config file', () => {
const log = {
level: 40,
time: 1585410824129,
v: 1,
pid: 27029,
hostname: 'localhost',
file: '/Users/user/.config/verdaccio/config/config.yaml',
msg: 'config file - @{file}',
};
expect(printMessage(log, prettyfierOptions)).toMatchSnapshot();
});
expect(printMessage(log, prettyfierOptions)).toMatchSnapshot();
});
test('should display trace level', () => {
const log = {
level: 10,
foo: 'foo',
msg: '[trace] - @{foo}'
};
test('should display trace level', () => {
const log = {
level: 10,
foo: 'foo',
msg: '[trace] - @{foo}',
};
expect(printMessage(log, prettyfierOptions)).toMatchSnapshot();
});
expect(printMessage(log, prettyfierOptions)).toMatchSnapshot();
});
test('should display trace level with pretty stamp', () => {
const log = {
level: 10,
foo: 'foo',
time: 1585411248203,
msg: '[trace] - @{foo}'
};
test('should display trace level with pretty stamp', () => {
const log = {
level: 10,
foo: 'foo',
time: 1585411248203,
msg: '[trace] - @{foo}',
};
expect(printMessage(log, Object.assign({}, prettyfierOptions, {
prettyStamp: true
}))).toMatchSnapshot();
});
expect(
printMessage(
log,
Object.assign({}, prettyfierOptions, {
prettyStamp: true,
})
)
).toMatchSnapshot();
});
test('should display a bytes request', () => {
const log = {
level: 35,
time: 1585411248203,
v: 1,
pid: 27029,
hostname: 'macbook-touch',
sub: 'in',
request: {method: 'GET', url: '/verdaccio'},
user: null,
remoteIP: '127.0.0.1',
status: 200,
error: undefined,
bytes: {in: 0, out: 150186},
msg: "@{status}, user: @{user}(@{remoteIP}), req: '@{request.method} @{request.url}', bytes: @{bytes.in}/@{bytes.out}"
};
test('should display a bytes request', () => {
const log = {
level: 35,
time: 1585411248203,
v: 1,
pid: 27029,
hostname: 'macbook-touch',
sub: 'in',
request: { method: 'GET', url: '/verdaccio' },
user: null,
remoteIP: '127.0.0.1',
status: 200,
error: undefined,
bytes: { in: 0, out: 150186 },
msg: "@{status}, user: @{user}(@{remoteIP}), req: '@{request.method} @{request.url}', bytes: @{bytes.in}/@{bytes.out}",
};
expect(printMessage(log, prettyfierOptions)).toMatchSnapshot();
});
expect(printMessage(log, prettyfierOptions)).toMatchSnapshot();
});
test('should display an error request', () => {
const log = {
level: 54,
time: 1585416029123,
v: 1,
pid: 30032,
hostname: 'macbook-touch',
sub: 'out',
err: {
type: 'Error',
message: 'getaddrinfo ENOTFOUND registry.fake.org',
stack: 'Error: getaddrinfo ENOTFOUND registry.fake.org\n' +
' at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:60:26)',
errno: -3008,
code: 'ENOTFOUND',
syscall: 'getaddrinfo',
hostname: 'registry.fake.org'
},
request: {method: 'GET', url: 'https://registry.fake.org/aaa'},
status: 'ERR',
error: 'getaddrinfo ENOTFOUND registry.fake.org',
bytes: {in: 0, out: 0},
msg: "@{!status}, req: '@{request.method} @{request.url}', error: @{!error}"
};
test('should display an error request', () => {
const log = {
level: 54,
time: 1585416029123,
v: 1,
pid: 30032,
hostname: 'macbook-touch',
sub: 'out',
err: {
type: 'Error',
message: 'getaddrinfo ENOTFOUND registry.fake.org',
stack: 'Error: getaddrinfo ENOTFOUND registry.fake.org\n' + ' at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:60:26)',
errno: -3008,
code: 'ENOTFOUND',
syscall: 'getaddrinfo',
hostname: 'registry.fake.org',
},
request: { method: 'GET', url: 'https://registry.fake.org/aaa' },
status: 'ERR',
error: 'getaddrinfo ENOTFOUND registry.fake.org',
bytes: { in: 0, out: 0 },
msg: "@{!status}, req: '@{request.method} @{request.url}', error: @{!error}",
};
expect(printMessage(log, prettyfierOptions)).toMatchSnapshot();
});
expect(printMessage(log, prettyfierOptions)).toMatchSnapshot();
});
test('should display an fatal request', () => {
const log = {
level: 60,
time: 1585416029123,
v: 1,
pid: 30032,
hostname: 'macbook-touch',
sub: 'out',
err: {
type: 'Error',
message: 'fatal error',
stack: '....',
errno: -3008,
code: 'ENOTFOUND',
},
request: {method: 'GET', url: 'https://registry.fake.org/aaa'},
status: 'ERR',
error: 'fatal error',
bytes: {in: 0, out: 0},
msg: "@{!status}, req: '@{request.method} @{request.url}', error: @{!error}"
};
test('should display an fatal request', () => {
const log = {
level: 60,
time: 1585416029123,
v: 1,
pid: 30032,
hostname: 'macbook-touch',
sub: 'out',
err: {
type: 'Error',
message: 'fatal error',
stack: '....',
errno: -3008,
code: 'ENOTFOUND',
},
request: { method: 'GET', url: 'https://registry.fake.org/aaa' },
status: 'ERR',
error: 'fatal error',
bytes: { in: 0, out: 0 },
msg: "@{!status}, req: '@{request.method} @{request.url}', error: @{!error}",
};
expect(printMessage(log, prettyfierOptions)).toMatchSnapshot();
});
expect(printMessage(log, prettyfierOptions)).toMatchSnapshot();
});
test('should display a streaming request', () => {
const log = {
level: 35,
time: 1585411247920,
v: 1,
pid: 27029,
hostname: 'macbook-touch',
sub: 'out',
request: {method: 'GET', url: 'https://registry.npmjs.org/verdaccio'},
status: 304,
msg: "@{!status}, req: '@{request.method} @{request.url}' (streaming)"
};
test('should display a streaming request', () => {
const log = {
level: 35,
time: 1585411247920,
v: 1,
pid: 27029,
hostname: 'macbook-touch',
sub: 'out',
request: { method: 'GET', url: 'https://registry.npmjs.org/verdaccio' },
status: 304,
msg: "@{!status}, req: '@{request.method} @{request.url}' (streaming)",
};
expect(printMessage(log, prettyfierOptions)).toMatchSnapshot();
});
expect(printMessage(log, prettyfierOptions)).toMatchSnapshot();
});
test('should display version and http address', () => {
const log = {
level: 40,
time: 1585410824322,
v: 1,
pid: 27029,
hostname: 'macbook-touch',
addr: 'http://localhost:4873/',
version: 'verdaccio/5.0.0',
msg: 'http address - @{addr} - @{version}'
};
test('should display version and http address', () => {
const log = {
level: 40,
time: 1585410824322,
v: 1,
pid: 27029,
hostname: 'macbook-touch',
addr: 'http://localhost:4873/',
version: 'verdaccio/5.0.0',
msg: 'http address - @{addr} - @{version}',
};
expect(printMessage(log, prettyfierOptions)).toMatchSnapshot();
});
expect(printMessage(log, prettyfierOptions)).toMatchSnapshot();
});
test('should display custom log message', () => {
const level: LevelCode = 15;
const log = {
level,
something: 'foo',
msg: 'custom - @{something} - @{missingParam}'
};
test('should display custom log message', () => {
const level: LevelCode = 15;
const log = {
level,
something: 'foo',
msg: 'custom - @{something} - @{missingParam}',
};
expect(printMessage(log, prettyfierOptions)).toMatchSnapshot();
});
expect(printMessage(log, prettyfierOptions)).toMatchSnapshot();
});
// test('should handle undefined object', () => {
// const log = {
// level: 15,
// something: 'foo',
// msg: 'custom - @{something} - @{missingParam}'
// };
//
// expect(printMessage(undefined, undefined, undefined)).toMatchSnapshot();
// });
// test('should handle undefined object', () => {
// const log = {
// level: 15,
// something: 'foo',
// msg: 'custom - @{something} - @{missingParam}'
// };
//
// expect(printMessage(undefined, undefined, undefined)).toMatchSnapshot();
// });
test('should display a resource request', () => {
const log = {
level: 30,
time: 1585411247622,
v: 1,
pid: 27029,
hostname: 'macbook-touch',
sub: 'in',
req: {
id: undefined,
method: 'GET',
url: '/verdaccio',
headers: {
connection: 'keep-alive',
'user-agent': 'npm/6.13.2 node/v13.1.0 darwin x64',
'npm-in-ci': 'false',
'npm-scope': '',
'npm-session': 'afebb4748178bd4b',
referer: 'view verdaccio',
'pacote-req-type': 'packument',
'pacote-pkg-id': 'registry:verdaccio',
accept: 'application/json',
authorization: '<Classified>',
'if-none-match': '"fd6440ba2ad24681077664d8f969e5c3"',
'accept-encoding': 'gzip,deflate',
host: 'localhost:4873'
},
remoteAddress: '127.0.0.1',
remotePort: 57968,
},
ip: '127.0.0.1',
msg: "@{ip} requested '@{req.method} @{req.url}'"
};
test('should display a resource request', () => {
const log = {
level: 30,
time: 1585411247622,
v: 1,
pid: 27029,
hostname: 'macbook-touch',
sub: 'in',
req: {
id: undefined,
method: 'GET',
url: '/verdaccio',
headers: {
connection: 'keep-alive',
'user-agent': 'npm/6.13.2 node/v13.1.0 darwin x64',
'npm-in-ci': 'false',
'npm-scope': '',
'npm-session': 'afebb4748178bd4b',
referer: 'view verdaccio',
'pacote-req-type': 'packument',
'pacote-pkg-id': 'registry:verdaccio',
accept: 'application/json',
authorization: '<Classified>',
'if-none-match': '"fd6440ba2ad24681077664d8f969e5c3"',
'accept-encoding': 'gzip,deflate',
host: 'localhost:4873',
},
remoteAddress: '127.0.0.1',
remotePort: 57968,
},
ip: '127.0.0.1',
msg: "@{ip} requested '@{req.method} @{req.url}'",
};
expect(printMessage(log, prettyfierOptions, false)).toMatchSnapshot();
});
});
expect(printMessage(log, prettyfierOptions, false)).toMatchSnapshot();
});
});
});

@ -1,18 +1,18 @@
import * as factory from '../src';
describe('prettyFactory', () => {
const prettyfierOptions = { messageKey: 'msg', levelFirst: true, prettyStamp: false };
test('should return a function', () => {
expect(typeof factory['default']({})).toEqual('function')
});
const prettyfierOptions = { messageKey: 'msg', levelFirst: true, prettyStamp: false };
test('should return a function', () => {
expect(typeof factory['default']({})).toEqual('function');
});
test('should return a function', () => {
const log = {
level: 10,
foo: 'foo',
msg: '[trace] - @{foo}'
};
test('should return a function', () => {
const log = {
level: 10,
foo: 'foo',
msg: '[trace] - @{foo}',
};
expect(factory['default'](prettyfierOptions)(log)).toMatchSnapshot();
});
expect(factory['default'](prettyfierOptions)(log)).toMatchSnapshot();
});
});

@ -1,2 +1 @@
export { setup, createLogger, logger } from './logger';

@ -6,112 +6,112 @@ const DEFAULT_LOG_FORMAT = 'pretty';
export let logger;
function getPrettifier() {
// TODO: this module can be loaded dynamically and allow custom formatting
return require('@verdaccio/logger-prettify');
// TODO: this module can be loaded dynamically and allow custom formatting
return require('@verdaccio/logger-prettify');
}
export type LogPlugin = {
dest: string;
options?: any[];
dest: string;
options?: any[];
};
export type LogType = 'file' | 'stdout';
export type LogFormat = 'json' | 'pretty-timestamped' | 'pretty';
export function createLogger(options = {},
destination = pino.destination(1),
format: LogFormat = DEFAULT_LOG_FORMAT,
prettyPrintOptions = {}) {
if (_.isNil(format)) {
format = DEFAULT_LOG_FORMAT;
}
export function createLogger(options = {}, destination = pino.destination(1), format: LogFormat = DEFAULT_LOG_FORMAT, prettyPrintOptions = {}) {
if (_.isNil(format)) {
format = DEFAULT_LOG_FORMAT;
}
let pinoConfig = {
...options,
customLevels: {
http: 35
},
serializers: {
err: pino.stdSerializers.err,
req: pino.stdSerializers.req,
res: pino.stdSerializers.res
},
};
let pinoConfig = {
...options,
customLevels: {
http: 35,
},
serializers: {
err: pino.stdSerializers.err,
req: pino.stdSerializers.req,
res: pino.stdSerializers.res,
},
};
if (format === DEFAULT_LOG_FORMAT || format !== 'json') {
pinoConfig = Object.assign({}, pinoConfig, {
prettyPrint: {
levelFirst: true,
prettyStamp: format === 'pretty-timestamped',
...prettyPrintOptions
},
prettifier: getPrettifier(),
}
)
}
if (format === DEFAULT_LOG_FORMAT || format !== 'json') {
pinoConfig = Object.assign({}, pinoConfig, {
prettyPrint: {
levelFirst: true,
prettyStamp: format === 'pretty-timestamped',
...prettyPrintOptions,
},
prettifier: getPrettifier(),
});
}
return pino(pinoConfig, destination);
return pino(pinoConfig, destination);
}
export function getLogger() {
if (_.isNil(logger)) {
console.warn('logger is not defined');
return;
}
if (_.isNil(logger)) {
console.warn('logger is not defined');
return;
}
return logger;
return logger;
}
const DEFAULT_LOGGER_CONF: LoggerConfigItem = {
type: 'stdout',
format: 'pretty',
level: 'http'
type: 'stdout',
format: 'pretty',
level: 'http',
};
export type LoggerConfigItem = {
type?: LogType;
plugin?: LogPlugin;
format?: LogFormat;
path?: string;
level?: string;
}
type?: LogType;
plugin?: LogPlugin;
format?: LogFormat;
path?: string;
level?: string;
};
export type LoggerConfig = LoggerConfigItem[];
export function setup(options: LoggerConfig | LoggerConfigItem = [DEFAULT_LOGGER_CONF]) {
const isLegacyConf = _.isArray(options);
if (isLegacyConf) {
// FIXME: re-enable later
// console.warn("DEPRECATE: logs does not have multi-stream support anymore, please upgrade your logger configuration");
}
const isLegacyConf = _.isArray(options);
if (isLegacyConf) {
// FIXME: re-enable later
// console.warn("DEPRECATE: logs does not have multi-stream support anymore, please upgrade your logger configuration");
}
// backward compatible, pick only the first option
let loggerConfig = isLegacyConf ? options[0] : options;
if (!loggerConfig?.level) {
loggerConfig = Object.assign({}, loggerConfig, {
level: 'http'
})
}
// backward compatible, pick only the first option
let loggerConfig = isLegacyConf ? options[0] : options;
if (!loggerConfig?.level) {
loggerConfig = Object.assign({}, loggerConfig, {
level: 'http',
});
}
const pinoConfig = { level: loggerConfig.level };
if (loggerConfig.type === 'file') {
logger = createLogger(pinoConfig,
pino.destination(loggerConfig.path),
loggerConfig.format);
} else if(loggerConfig.type === 'rotating-file') {
throw new Error('rotating-file type is not longer supported, consider use [logrotate] instead');
} else {
logger = createLogger(pinoConfig, pino.destination(1), loggerConfig.format);
}
const pinoConfig = { level: loggerConfig.level };
if (loggerConfig.type === 'file') {
logger = createLogger(pinoConfig, pino.destination(loggerConfig.path), loggerConfig.format);
} else if (loggerConfig.type === 'rotating-file') {
throw new Error('rotating-file type is not longer supported, consider use [logrotate] instead');
} else {
logger = createLogger(pinoConfig, pino.destination(1), loggerConfig.format);
}
process.on('uncaughtException', pino.final(logger, (err, finalLogger) => {
finalLogger.fatal(err, 'uncaughtException');
process.exit(1);
}));
process.on(
'uncaughtException',
pino.final(logger, (err, finalLogger) => {
finalLogger.fatal(err, 'uncaughtException');
process.exit(1);
})
);
// @ts-ignore
process.on('unhandledRejection', pino.final(logger, (err, finalLogger) => {
finalLogger.fatal(err, 'uncaughtException');
process.exit(1);
}));
// @ts-ignore
process.on(
'unhandledRejection',
pino.final(logger, (err, finalLogger) => {
finalLogger.fatal(err, 'uncaughtException');
process.exit(1);
})
);
}

@ -1,15 +1,16 @@
import { logger, setup } from "../src";
import { logger, setup } from '../src';
describe('logger', () => {
test('dsadasd', () => {
const spyOn = jest.spyOn( process.stdout, 'write');
setup([{
level: 'info'
}]);
test('dsadasd', () => {
const spyOn = jest.spyOn(process.stdout, 'write');
setup([
{
level: 'info',
},
]);
logger.info({packageName: 'test'} , `publishing or updating a new version for @{packageName}`);
logger.info({ packageName: 'test' }, `publishing or updating a new version for @{packageName}`);
// expect(spyOn).toHaveBeenCalledTimes(2);
});
// expect(spyOn).toHaveBeenCalledTimes(2);
});
});

@ -1,4 +1,3 @@
import _ from 'lodash';
import {
@ -7,17 +6,17 @@ import {
getVersionFromTarball,
isObject,
stringToMD5,
ErrorCode
ErrorCode,
} from '@verdaccio/utils';
import { API_ERROR, HEADER_TYPE, HEADERS, HTTP_STATUS, TOKEN_BASIC, TOKEN_BEARER } from '@verdaccio/dev-commons';
import { $ResponseExtend, $RequestExtend, $NextFunctionVer, IAuth } from '@verdaccio/dev-types';
import { Config, Package, RemoteUser } from '@verdaccio/types';
import { logger } from '@verdaccio/logger';
import { VerdaccioError } from '@verdaccio/commons-api';
import {HttpError} from "http-errors";
import { HttpError } from 'http-errors';
export function match(regexp: RegExp): any {
return function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer, value: string): void {
return function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer, value: string): void {
if (regexp.exec(value)) {
next();
} else {
@ -65,7 +64,7 @@ export function validatePackage(req: $RequestExtend, res: $ResponseExtend, next:
}
export function media(expect: string | null): any {
return function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
return function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
if (req.headers[HEADER_TYPE.CONTENT_TYPE] !== expect) {
next(ErrorCode.getCode(HTTP_STATUS.UNSUPPORTED_MEDIA, 'wrong content-type, expect: ' + expect + ', got: ' + req.headers[HEADER_TYPE.CONTENT_TYPE]));
} else {
@ -90,7 +89,7 @@ export function expectJson(req: $RequestExtend, res: $ResponseExtend, next: $Nex
}
export function antiLoop(config: Config): Function {
return function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
return function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
if (req.headers.via != null) {
const arr = req.headers.via.split(',');
@ -106,14 +105,14 @@ export function antiLoop(config: Config): Function {
}
export function allow(auth: IAuth): Function {
return function(action: string): Function {
return function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
return function (action: string): Function {
return function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
req.pause();
const packageName = req.params.scope ? `@${req.params.scope}/${req.params.package}` : req.params.package;
const packageVersion = req.params.filename ? getVersionFromTarball(req.params.filename) : undefined;
const remote: RemoteUser = req.remote_user;
logger.trace({ action, user: remote?.name }, `[middleware/allow][@{action}] allow for @{user}`);
auth['allow_' + action]({ packageName, packageVersion }, remote, function(error, allowed): void {
auth['allow_' + action]({ packageName, packageVersion }, remote, function (error, allowed): void {
req.resume();
if (error) {
next(error);
@ -210,7 +209,7 @@ export function log(req: $RequestExtend, res: $ResponseExtend, next: $NextFuncti
}
let bytesin = 0;
req.on('data', function(chunk): void {
req.on('data', function (chunk): void {
bytesin += chunk.length;
});
@ -218,14 +217,14 @@ export function log(req: $RequestExtend, res: $ResponseExtend, next: $NextFuncti
const _write = res.write;
// FIXME: res.write should return boolean
// @ts-ignore
res.write = function(buf): boolean {
res.write = function (buf): boolean {
bytesout += buf.length;
/* eslint prefer-rest-params: "off" */
// @ts-ignore
_write.apply(res, arguments);
};
const log = function(): void {
const log = function (): void {
const forwardedFor = req.headers['x-forwarded-for'];
const remoteAddress = req.connection.remoteAddress;
const remoteIP = forwardedFor ? `${forwardedFor} via ${remoteAddress}` : remoteAddress;
@ -258,12 +257,12 @@ export function log(req: $RequestExtend, res: $ResponseExtend, next: $NextFuncti
req.originalUrl = req.url;
};
req.on('close', function(): void {
req.on('close', function (): void {
log();
});
const _end = res.end;
res.end = function(buf): void {
res.end = function (buf): void {
if (buf) {
bytesout += buf.length;
}
@ -296,7 +295,7 @@ export function handleError(err: HttpError, req: $RequestExtend, res: $ResponseE
export function errorReportingMiddleware(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
res.locals.report_error =
res.locals.report_error ||
function(err: VerdaccioError): void {
function (err: VerdaccioError): void {
if (err.status && err.status >= HTTP_STATUS.BAD_REQUEST && err.status < 600) {
if (_.isNil(res.headersSent) === false) {
res.status(err.status);

@ -1,21 +1,16 @@
import path from 'path';
import _ from 'lodash';
import {parseConfigFile} from '@verdaccio/utils';
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 = '') {
const locationFile = location ? path.join(location, configFile) :
path.join(__dirname, `./config/yaml/${configFile}`);
const locationFile = location ? path.join(location, configFile) : path.join(__dirname, `./config/yaml/${configFile}`);
const config = parseConfigFile(locationFile);
return _.assign({}, _.cloneDeep(config), externalConfig);
}
export {
configExample
}
export { configExample };

@ -1,7 +1,7 @@
export const DOMAIN_SERVERS = '0.0.0.0';
export const CREDENTIALS = {
user: 'test',
password: 'test'
user: 'test',
password: 'test',
};
export const TARBALL = 'tarball-blahblah-file.name';

@ -1,9 +1,9 @@
import _ from 'lodash';
import request from 'supertest';
import {DIST_TAGS, LATEST, HEADER_TYPE, HEADERS, HTTP_STATUS, TOKEN_BEARER} from '@verdaccio/dev-commons';
import {buildToken, encodeScopedUri} from '@verdaccio/utils';
import {generateRandomHexString} from "@verdaccio/utils";
import { DIST_TAGS, LATEST, HEADER_TYPE, HEADERS, HTTP_STATUS, TOKEN_BEARER } from '@verdaccio/dev-commons';
import { buildToken, encodeScopedUri } from '@verdaccio/utils';
import { generateRandomHexString } from '@verdaccio/utils';
import { Package } from '@verdaccio/types';
// API Helpers
@ -29,57 +29,43 @@ export function getTaggedVersionFromPackage(pkg, pkgName, tag: string = LATEST,
return latestPkg;
}
export function putPackage(
request: any,
pkgName: string,
publishMetadata: Package,
token?: string): Promise<any[]> {
export function putPackage(request: any, pkgName: string, publishMetadata: Package, token?: string): Promise<any[]> {
return new Promise((resolve) => {
const put = request.put(pkgName)
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.send(JSON.stringify(publishMetadata));
const put = request.put(pkgName).set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON).send(JSON.stringify(publishMetadata));
if (_.isEmpty(token) === false ) {
expect(token).toBeDefined();
put.set(HEADERS.AUTHORIZATION, buildToken(TOKEN_BEARER, token as string))
if (_.isEmpty(token) === false) {
expect(token).toBeDefined();
put.set(HEADERS.AUTHORIZATION, buildToken(TOKEN_BEARER, token as string));
}
put.set('accept', 'gzip')
.set('accept-encoding', HEADERS.JSON)
.expect(HTTP_STATUS.CREATED)
.end(function(err, res) {
resolve([err, res]);
});
});
}
export function deletePackage(
request: any,
pkgName: string,
token?: string
): Promise<any[]> {
return new Promise((resolve) => {
const del = request.put(`/${encodeScopedUri(pkgName)}/-rev/${generateRandomHexString(8)}`)
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON);
if (_.isNil(token) === false ) {
del.set(HEADERS.AUTHORIZATION, buildToken(TOKEN_BEARER, token as string))
}
del.set('accept-encoding', HEADERS.JSON)
put
.set('accept', 'gzip')
.set('accept-encoding', HEADERS.JSON)
.expect(HTTP_STATUS.CREATED)
.end(function(err, res) {
.end(function (err, res) {
resolve([err, res]);
});
});
}
export function getPackage(
request: any,
token: string,
pkgName: string,
statusCode: number = HTTP_STATUS.OK): Promise<any[]> {
export function deletePackage(request: any, pkgName: string, token?: string): Promise<any[]> {
return new Promise((resolve) => {
const del = request.put(`/${encodeScopedUri(pkgName)}/-rev/${generateRandomHexString(8)}`).set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON);
if (_.isNil(token) === false) {
del.set(HEADERS.AUTHORIZATION, buildToken(TOKEN_BEARER, token as string));
}
del
.set('accept-encoding', HEADERS.JSON)
.expect(HTTP_STATUS.CREATED)
.end(function (err, res) {
resolve([err, res]);
});
});
}
export function getPackage(request: any, token: string, pkgName: string, statusCode: number = HTTP_STATUS.OK): Promise<any[]> {
return new Promise((resolve) => {
const getRequest = request.get(`/${pkgName}`);
@ -90,39 +76,36 @@ export function getPackage(
getRequest
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(statusCode)
.end(function(err, res) {
.end(function (err, res) {
resolve([err, res]);
});
});
}
export function loginUserToken(request: any,
user: string,
credentials: any,
token: string,
statusCode: number = HTTP_STATUS.CREATED): Promise<any[]> {
export function loginUserToken(request: any, user: string, credentials: any, token: string, statusCode: number = HTTP_STATUS.CREATED): Promise<any[]> {
// $FlowFixMe
return new Promise((resolve) => {
request.put(`/-/user/org.couchdb.user:${user}`)
request
.put(`/-/user/org.couchdb.user:${user}`)
.send(credentials)
.set('authorization', buildToken(TOKEN_BEARER, token))
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(statusCode)
.end(function(err, res) {
.end(function (err, res) {
return resolve([err, res]);
});
});
}
export function addUser(request: any, user: string, credentials: any,
statusCode: number = HTTP_STATUS.CREATED): Promise<any[]> {
export function addUser(request: any, user: string, credentials: any, statusCode: number = HTTP_STATUS.CREATED): Promise<any[]> {
// $FlowFixMe
return new Promise((resolve) => {
request.put(`/-/user/org.couchdb.user:${user}`)
request
.put(`/-/user/org.couchdb.user:${user}`)
.send(credentials)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(statusCode)
.end(function(err, res) {
.end(function (err, res) {
return resolve([err, res]);
});
});
@ -133,7 +116,7 @@ export async function getNewToken(request: any, credentials: any): Promise<strin
return new Promise(async (resolve) => {
const [err, res] = await addUser(request, credentials.name, credentials);
expect(err).toBeNull();
const {token, ok} = res.body;
const { token, ok } = res.body;
expect(ok).toBeDefined();
expect(token).toBeDefined();
expect(typeof token).toBe('string');
@ -141,28 +124,30 @@ export async function getNewToken(request: any, credentials: any): Promise<strin
});
}
export function getProfile(request: any, token: string, statusCode: number = HTTP_STATUS.OK): Promise<any[]> {
export function getProfile(request: any, token: string, statusCode: number = HTTP_STATUS.OK): Promise<any[]> {
// $FlowFixMe
return new Promise((resolve) => {
request.get(`/-/npm/v1/user`)
request
.get(`/-/npm/v1/user`)
.set('authorization', buildToken(TOKEN_BEARER, token))
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(statusCode)
.end(function(err, res) {
.end(function (err, res) {
return resolve([err, res]);
});
});
}
export function postProfile(request: any, body: any, token: string, statusCode: number = HTTP_STATUS.OK): Promise<any[]> {
export function postProfile(request: any, body: any, token: string, statusCode: number = HTTP_STATUS.OK): Promise<any[]> {
// $FlowFixMe
return new Promise((resolve) => {
request.post(`/-/npm/v1/user`)
request
.post(`/-/npm/v1/user`)
.send(body)
.set(HEADERS.AUTHORIZATION, `Bearer ${token}`)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(statusCode)
.end(function(err, res) {
.end(function (err, res) {
return resolve([err, res]);
});
});
@ -170,7 +155,7 @@ export function postProfile(request: any, body: any, token: string, statusCode:
export async function fetchPackageByVersionAndTag(app, encodedPkgName, pkgName, version, tag = 'latest') {
// we retrieve the package to verify
const [err, resp]= await getPackage(request(app), '', encodedPkgName);
const [err, resp] = await getPackage(request(app), '', encodedPkgName);
expect(err).toBeNull();
@ -179,13 +164,13 @@ export async function fetchPackageByVersionAndTag(app, encodedPkgName, pkgName,
}
export async function isExistPackage(app, packageName) {
const [err]= await getPackage(request(app), '', encodeScopedUri(packageName), HTTP_STATUS.OK);
const [err] = await getPackage(request(app), '', encodeScopedUri(packageName), HTTP_STATUS.OK);
return _.isNull(err);
}
export async function verifyPackageVersionDoesExist(app, packageName, version, token?: string) {
const [, res]= await getPackage(request(app), token as string, encodeScopedUri(packageName), HTTP_STATUS.OK);
const [, res] = await getPackage(request(app), token as string, encodeScopedUri(packageName), HTTP_STATUS.OK);
const { versions } = res.body;
const versionsKeys = Object.keys(versions);

@ -2,12 +2,11 @@ import path from 'path';
import fs from 'fs';
import os from 'os';
import * as fsExtra from 'fs-extra';
import {DOMAIN_SERVERS} from './constants';
import { DOMAIN_SERVERS } from './constants';
import VerdaccioProcess from './server_process';
import {VerdaccioConfig} from './verdaccio-server';
import { VerdaccioConfig } from './verdaccio-server';
import Server from './server';
import {IServerBridge} from './types';
import { IServerBridge } from './types';
/**
* Fork a Verdaccio process with a custom configuration.
@ -57,7 +56,7 @@ export function mockServer(port: number, options: MockRegistryOptions = {}) {
// console.log("-->tempRoot", tempRoot);
// default locations
const configPath = path.join(__dirname, './config/yaml', '/mock-server-test.yaml');
const configPath = path.join(__dirname, './config/yaml', '/mock-server-test.yaml');
const mockStorePath = path.join(__dirname, '/fixtures/mock-store');
// default options
@ -67,7 +66,7 @@ export function mockServer(port: number, options: MockRegistryOptions = {}) {
storePath: mockStorePath,
rootFolder: tempRoot,
silence: true,
debug: false
debug: false,
};
// mix external options
@ -93,6 +92,6 @@ export interface MockRegistryOptions {
configPath?: string;
port?: number;
storePath?: string;
silence?: boolean
silence?: boolean;
debug?: boolean;
}

@ -6,7 +6,6 @@ import { IRequestPromise } from './types';
const requestData = Symbol('smart_request_data');
export class PromiseAssert extends Promise<any> implements IRequestPromise {
public constructor(options: any) {
super(options);
}
@ -14,56 +13,63 @@ export class PromiseAssert extends Promise<any> implements IRequestPromise {
public status(expected: number) {
const selfData = this[requestData];
return injectResponse(this, this.then(function(body) {
try {
assert.equal(selfData.response.statusCode, expected);
} catch(err) {
selfData.error.message = err.message;
throw selfData.error;
}
return body;
}));
return injectResponse(
this,
this.then(function (body) {
try {
assert.equal(selfData.response.statusCode, expected);
} catch (err) {
selfData.error.message = err.message;
throw selfData.error;
}
return body;
})
);
}
public body_ok(expected: any) {
const selfData = this[requestData];
return injectResponse(this, this.then(function(body) {
try {
if (_.isRegExp(expected)) {
assert(body.ok.match(expected), '\'' + body.ok + '\' doesn\'t match ' + expected);
} else {
assert.equal(body.ok, expected);
return injectResponse(
this,
this.then(function (body) {
try {
if (_.isRegExp(expected)) {
assert(body.ok.match(expected), "'" + body.ok + "' doesn't match " + expected);
} else {
assert.equal(body.ok, expected);
}
assert.equal(body.error, null);
} catch (err) {
selfData.error.message = err.message;
throw selfData.error;
}
assert.equal(body.error, null);
} catch(err) {
selfData.error.message = err.message;
throw selfData.error;
}
return body;
}));
return body;
})
);
}
public body_error(expected: any) {
const selfData = this[requestData];
return injectResponse(this, this.then(function(body) {
try {
if (_.isRegExp(expected)) {
assert(body.error.match(expected), body.error + ' doesn\'t match ' + expected);
} else {
assert.equal(body.error, expected);
return injectResponse(
this,
this.then(function (body) {
try {
if (_.isRegExp(expected)) {
assert(body.error.match(expected), body.error + " doesn't match " + expected);
} else {
assert.equal(body.error, expected);
}
assert.equal(body.ok, null);
} catch (err) {
selfData.error.message = err.message;
throw selfData.error;
}
assert.equal(body.ok, null);
} catch(err) {
selfData.error.message = err.message;
throw selfData.error;
}
return body;
}));
return body;
})
);
}
public request(callback: any) {
@ -74,26 +80,26 @@ export class PromiseAssert extends Promise<any> implements IRequestPromise {
public response(cb: any) {
const selfData = this[requestData];
return injectResponse(this, this.then(function(body) {
cb(selfData.response);
return body;
}));
return injectResponse(
this,
this.then(function (body) {
cb(selfData.response);
return body;
})
);
}
public send(data: any) {
this[requestData].request.end(data);
return this;
}
}
function injectResponse(smartObject: any, promise: Promise<any>): Promise<any> {
promise[requestData] = smartObject[requestData];
return promise;
}
function smartRequest(options: any): Promise<any> {
const smartObject: any = {};
@ -101,9 +107,9 @@ function smartRequest(options: any): Promise<any> {
smartObject[requestData].error = Error();
Error.captureStackTrace(smartObject[requestData].error, smartRequest);
const promiseResult: Promise<any> = new PromiseAssert(function(resolve, reject) {
const promiseResult: Promise<any> = new PromiseAssert(function (resolve, reject) {
// store request reference on symbol
smartObject[requestData].request = request(options, function(err, res, body) {
smartObject[requestData].request = request(options, function (err, res, body) {
if (err) {
return reject(err);
}
@ -118,4 +124,3 @@ function smartRequest(options: any): Promise<any> {
}
export default smartRequest;

@ -1,12 +1,11 @@
import assert from 'assert';
import _ from 'lodash';
import {API_MESSAGE, HEADERS, HTTP_STATUS, TOKEN_BASIC} from '@verdaccio/dev-commons';
import {buildToken} from "@verdaccio/utils";
import { API_MESSAGE, HEADERS, HTTP_STATUS, TOKEN_BASIC } from '@verdaccio/dev-commons';
import { buildToken } from '@verdaccio/utils';
import smartRequest from './request';
import {IServerBridge} from './types';
import { IServerBridge } from './types';
import {CREDENTIALS} from "./constants"
import { CREDENTIALS } from './constants';
import getPackage from './fixtures/package';
const buildAuthHeader = (user, pass): string => {
@ -65,7 +64,6 @@ export default class Server implements IServerBridge {
});
}
public getPackage(name: string) {
return this.request({
uri: `/${encodeURIComponent(name)}`,
@ -139,7 +137,6 @@ export default class Server implements IServerBridge {
});
}
public addTag(name: string, tag: string, version: string) {
return this.request({
uri: `/${encodeURIComponent(name)}/${encodeURIComponent(tag)}`,
@ -161,20 +158,20 @@ export default class Server implements IServerBridge {
timeout: 1000,
});
promise.request(function(req) {
promise.request(function (req) {
req.write(data);
// it auto abort the request
setTimeout(function() {
setTimeout(function () {
req.req.abort();
}, 20);
});
return new Promise(function(resolve, reject) {
return new Promise(function (resolve, reject) {
promise
.then(function() {
.then(function () {
reject(Error('no error'));
})
.catch(function(err) {
.catch(function (err) {
if (err.code === 'ECONNRESET') {
// @ts-ignore
resolve();
@ -186,25 +183,25 @@ export default class Server implements IServerBridge {
}
public addPackage(name: string) {
return this.putPackage(name, getPackage(name))
.status(HTTP_STATUS.CREATED)
.body_ok(API_MESSAGE.PKG_CREATED);
return this.putPackage(name, getPackage(name)).status(HTTP_STATUS.CREATED).body_ok(API_MESSAGE.PKG_CREATED);
}
public whoami() {
return this.request({
uri: '/-/whoami'
}).status(HTTP_STATUS.OK)
.then(function(body) {
uri: '/-/whoami',
})
.status(HTTP_STATUS.OK)
.then(function (body) {
return body.username;
});
}
public ping() {
return this.request({
uri: '/-/ping'
}).status(HTTP_STATUS.OK)
.then(function(body) {
uri: '/-/ping',
})
.status(HTTP_STATUS.OK)
.then(function (body) {
return body;
});
}
@ -216,6 +213,6 @@ export default class Server implements IServerBridge {
headers: {
[HEADERS.CONTENT_TYPE]: HEADERS.JSON,
},
})
});
}
}

@ -1,24 +1,20 @@
import assert from 'assert';
import {fork} from 'child_process';
import { fork } from 'child_process';
import {HTTP_STATUS} from '@verdaccio/dev-commons';
import { HTTP_STATUS } from '@verdaccio/dev-commons';
import {CREDENTIALS} from './constants';
import {IVerdaccioConfig, IServerBridge, IServerProcess} from './types';
import { CREDENTIALS } from './constants';
import { IVerdaccioConfig, IServerBridge, IServerProcess } from './types';
const defaultBinPath = require.resolve('verdaccio/bin/verdaccio');
export default class VerdaccioProcess implements IServerProcess {
private bridge: IServerBridge;
private config: IVerdaccioConfig;
private childFork: any;
private isDebug: boolean;
private silence: boolean;
public constructor(config: IVerdaccioConfig,
bridge: IServerBridge,
silence = true,
isDebug = false) {
public constructor(config: IVerdaccioConfig, bridge: IServerBridge, silence = true, isDebug = false) {
this.config = config;
this.bridge = bridge;
this.silence = silence;
@ -34,7 +30,7 @@ export default class VerdaccioProcess implements IServerProcess {
private _start(verdaccioPath: string, resolve: Function, reject: Function) {
let childOptions = {
silent: this.silence
silent: this.silence,
};
if (this.isDebug) {
@ -44,39 +40,42 @@ export default class VerdaccioProcess implements IServerProcess {
childOptions = Object.assign({}, childOptions, {
execArgv: [`--inspect=${debugPort}`],
env: {
"NODE_DEBUG": 'request'
}
NODE_DEBUG: 'request',
},
});
}
const {configPath, port} = this.config;
const { configPath, port } = this.config;
this.childFork = fork(verdaccioPath, ['-c', configPath, '-l', port as string], childOptions);
this.childFork.on('message', (msg) => {
// verdaccio_started is a message that comes from verdaccio in debug mode that notify has been started
if ('verdaccio_started' in msg) {
this.bridge.debug().status(HTTP_STATUS.OK).then((body) => {
this.bridge.auth(CREDENTIALS.user, CREDENTIALS.password)
.status(HTTP_STATUS.CREATED)
.body_ok(new RegExp(CREDENTIALS.user))
.then(() => resolve([this, body.pid]), reject)
}, reject);
this.bridge
.debug()
.status(HTTP_STATUS.OK)
.then((body) => {
this.bridge
.auth(CREDENTIALS.user, CREDENTIALS.password)
.status(HTTP_STATUS.CREATED)
.body_ok(new RegExp(CREDENTIALS.user))
.then(() => resolve([this, body.pid]), reject);
}, reject);
}
});
this.childFork.on('error', (err) => {
reject([err, this])
reject([err, this]);
});
this.childFork.on('disconnect', (err) => {
reject([err, this])
reject([err, this]);
});
this.childFork.on('exit', (err) => {
reject([err, this])
reject([err, this]);
});
}
public stop(): void {
return this.childFork.kill('SIGINT');
}
}

@ -1,8 +1,8 @@
import fs from "fs";
import path from "path";
import os from "os";
import fs from 'fs';
import path from 'path';
import os from 'os';
import { pseudoRandomBytes } from 'crypto';
import { Version } from "@verdaccio/types";
import { Version } from '@verdaccio/types';
export function generateRamdonStorage() {
const tempStorage = pseudoRandomBytes(5).toString('hex');
@ -11,31 +11,28 @@ export function generateRamdonStorage() {
return path.join(tempRoot, tempStorage);
}
export function generateNewVersion(
pkgName: string,
version: string,
shashum = '238e7641e59508dc9c20eb4ad37a8aa57ab777b4'): Version {
export function generateNewVersion(pkgName: string, version: string, shashum = '238e7641e59508dc9c20eb4ad37a8aa57ab777b4'): Version {
// $FlowFixMe
return {
"name": pkgName,
"version": version,
"description": "",
"main": "index.js",
"dependencies": {
"test": "^1.4.1"
name: pkgName,
version: version,
description: '',
main: 'index.js',
dependencies: {
test: '^1.4.1',
},
"author": "",
"license": "ISC",
"readme": "ERROR: No README data found!",
"_id": `${pkgName}@${version}`,
"_npmVersion": "5.5.1",
"_npmUser": {
"name": "Foo"
author: '',
license: 'ISC',
readme: 'ERROR: No README data found!',
_id: `${pkgName}@${version}`,
_npmVersion: '5.5.1',
_npmUser: {
name: 'Foo',
},
"dist": {
"integrity": "sha512-zVEqt1JUCOPsash9q4wMkJEDPD+QCx95TRhQII+JnoS31uBUKoZxhzvvUJCcLVy2CQG4QdwXARU7dYWPnrwhGg==",
"shasum": shashum,
"tarball": `http:\/\/localhost:4873\/${pkgName}\/-\/${pkgName}-${version}.tgz`
}
}
dist: {
integrity: 'sha512-zVEqt1JUCOPsash9q4wMkJEDPD+QCx95TRhQII+JnoS31uBUKoZxhzvvUJCcLVy2CQG4QdwXARU7dYWPnrwhGg==',
shasum: shashum,
tarball: `http:\/\/localhost:4873\/${pkgName}\/-\/${pkgName}-${version}.tgz`,
},
};
}

@ -1,7 +1,6 @@
import {IVerdaccioConfig} from './types';
import { IVerdaccioConfig } from './types';
export class VerdaccioConfig implements IVerdaccioConfig {
public storagePath: string;
public configPath: string;
public domainPath: string;

@ -1,9 +1,9 @@
import _ from 'lodash';
import {HTTP_STATUS} from '@verdaccio/dev-commons';
import { HTTP_STATUS } from '@verdaccio/dev-commons';
import { VerdaccioError } from '@verdaccio/commons-api';
import smartRequest, {PromiseAssert} from '../src/request';
import {mockServer} from '../src/mock';
import smartRequest, { PromiseAssert } from '../src/request';
import { mockServer } from '../src/mock';
import { IRequestPromise } from '../src/types';
describe('Request Functional', () => {
@ -19,7 +19,7 @@ describe('Request Functional', () => {
});
test('basic resolve', (done) => {
const requestPromise: IRequestPromise = new PromiseAssert(resolve => {
const requestPromise: IRequestPromise = new PromiseAssert((resolve) => {
resolve(1);
});
// @ts-ignore
@ -36,7 +36,7 @@ describe('Request Functional', () => {
mockRegistry = await mockServer(mockServerPort).init(binPath);
});
afterAll(function(done) {
afterAll(function (done) {
const [registry, pid] = mockRegistry;
registry.stop();
console.log(`registry ${pid} has been stopped`);
@ -47,53 +47,61 @@ describe('Request Functional', () => {
test('basic rest', (done) => {
const options: any = {
url: restTest,
method: 'GET'
method: 'GET',
};
smartRequest(options).then((result)=> {
smartRequest(options).then((result) => {
expect(_.isString(result)).toBeTruthy();
done();
})
});
});
describe('smartRequest Status', () => {
test('basic check status 200', (done) => {
const options: any = {
url: restTest,
method: 'GET'
method: 'GET',
};
// @ts-ignore
smartRequest(options).status(HTTP_STATUS.OK).then((result)=> {
expect(JSON.parse(result).name).toBe('jquery');
done();
})
smartRequest(options)
.status(HTTP_STATUS.OK)
.then((result) => {
expect(JSON.parse(result).name).toBe('jquery');
done();
});
});
test('basic ping status and empty response', (done) => {
const options: any = {
url: `${domainTest}/-/ping`,
method: 'GET'
method: 'GET',
};
// @ts-ignore
smartRequest(options).status(HTTP_STATUS.OK).then((result)=> {
expect(JSON.parse((result))).toEqual({});
done();
})
smartRequest(options)
.status(HTTP_STATUS.OK)
.then((result) => {
expect(JSON.parse(result)).toEqual({});
done();
});
});
test('basic check status 404', (done) => {
const options: any = {
url: 'http://www.google.fake',
method: 'GET'
method: 'GET',
};
// @ts-ignore
smartRequest(options).status(HTTP_STATUS.NOT_FOUND).then(() => {
// we do not intent to resolve this
}, (error: VerdaccioError) => {
expect(error.code).toBe('ENOTFOUND');
done();
})
smartRequest(options)
.status(HTTP_STATUS.NOT_FOUND)
.then(
() => {
// we do not intent to resolve this
},
(error: VerdaccioError) => {
expect(error.code).toBe('ENOTFOUND');
done();
}
);
});
});
});

@ -6,13 +6,13 @@ import constants from 'constants';
import { Application } from 'express';
import { assign, isObject, isFunction } from 'lodash';
import { Callback, ConfigWithHttps, HttpsConfKeyCert, HttpsConfPfx } from '@verdaccio/types';
import { Callback, ConfigWithHttps, HttpsConfKeyCert, HttpsConfPfx } from '@verdaccio/types';
import { API_ERROR, certPem, csrPem, keyPem } from '@verdaccio/dev-commons';
import server from '@verdaccio/server';
import { logger} from '@verdaccio/logger';
import { logger } from '@verdaccio/logger';
import { getListListenAddresses, resolveConfigPath } from './cli-utils';
import {displayExperimentsInfoBox} from "./experiments";
import { displayExperimentsInfoBox } from './experiments';
function launchServer(app, addr, config, configPath: string, pkgVersion: string, pkgName: string, callback: Callback): void {
let webServer;
@ -45,14 +45,13 @@ function startVerdaccio(config: any, cliListen: string, configPath: string, pkgV
}
server(config).then((app): void => {
const addresses = getListListenAddresses(cliListen, config.listen);
if ('experiments' in config) {
displayExperimentsInfoBox(config.experiments);
}
addresses.forEach(addr =>launchServer(app, addr, config, configPath, pkgVersion, pkgName, callback));
const addresses = getListListenAddresses(cliListen, config.listen);
if ('experiments' in config) {
displayExperimentsInfoBox(config.experiments);
}
);
addresses.forEach((addr) => launchServer(app, addr, config, configPath, pkgVersion, pkgName, callback));
});
}
function unlinkAddressPath(addr) {
@ -129,19 +128,15 @@ function handleHTTPS(app: Application, configPath: string, config: ConfigWithHtt
function listenDefaultCallback(webServer: Application, addr: any, pkgName: string, pkgVersion: string): void {
webServer
.listen(
addr.port || addr.path,
addr.host,
(): void => {
// send a message for tests
if (isFunction(process.send)) {
process.send({
verdaccio_started: true,
});
}
.listen(addr.port || addr.path, addr.host, (): void => {
// send a message for tests
if (isFunction(process.send)) {
process.send({
verdaccio_started: true,
});
}
)
.on('error', function(err): void {
})
.on('error', function (err): void {
logger.fatal({ err: err }, 'cannot create server: @{err.message}');
process.exit(2);
});

@ -48,9 +48,7 @@ export function getListListenAddresses(argListen: string, configListen: any): an
if (!parsedAddr) {
logger.logger.warn(
{ addr: addr },
'invalid address - @{addr}, we expect a port (e.g. "4873"),' +
' host:port (e.g. "localhost:4873") or full url' +
' (e.g. "http://localhost:4873/")'
'invalid address - @{addr}, we expect a port (e.g. "4873"),' + ' host:port (e.g. "localhost:4873") or full url' + ' (e.g. "http://localhost:4873/")'
);
}

@ -1,11 +1,11 @@
const logger = require('@verdaccio/logger');
export function displayExperimentsInfoBox(experiments) {
const experimentList = Object.keys(experiments);
if (experimentList.length >= 1) {
logger.logger.warn('⚠️ experiments are enabled, we recommend do not use experiments in production, comment out this section to disable it');
experimentList.forEach(experiment => {
logger.logger.warn(` - support for ${experiment} ${experiments[experiment] ? 'is enabled' : ' is disabled'}`);
});
}
const experimentList = Object.keys(experiments);
if (experimentList.length >= 1) {
logger.logger.warn('⚠️ experiments are enabled, we recommend do not use experiments in production, comment out this section to disable it');
experimentList.forEach((experiment) => {
logger.logger.warn(` - support for ${experiment} ${experiments[experiment] ? 'is enabled' : ' is disabled'}`);
});
}
}

@ -1,34 +1,34 @@
import { API_ERROR, certPem, csrPem, keyPem } from '@verdaccio/dev-commons';
import {resolveConfigPath} from "./cli-utils";
import { resolveConfigPath } from './cli-utils';
const logger = require('@verdaccio/logger');
export function logHTTPSWarning(storageLocation) {
logger.logger.fatal(
[
'You have enabled HTTPS and need to specify either ',
' "https.key", "https.cert" and "https.ca" or ',
' "https.pfx" and optionally "https.passphrase" ',
'to run https server',
'',
// commands are borrowed from node.js docs
'To quickly create self-signed certificate, use:',
' $ openssl genrsa -out ' + resolveConfigPath(storageLocation, keyPem) + ' 2048',
' $ openssl req -new -sha256 -key ' + resolveConfigPath(storageLocation, keyPem) + ' -out ' + resolveConfigPath(storageLocation, csrPem),
' $ openssl x509 -req -in ' +
resolveConfigPath(storageLocation, csrPem) +
' -signkey ' +
resolveConfigPath(storageLocation, keyPem) +
' -out ' +
resolveConfigPath(storageLocation, certPem),
'',
'And then add to config file (' + storageLocation + '):',
' https:',
` key: ${resolveConfigPath(storageLocation, keyPem)}`,
` cert: ${resolveConfigPath(storageLocation, certPem)}`,
` ca: ${resolveConfigPath(storageLocation, csrPem)}`,
].join('\n')
);
process.exit(2);
logger.logger.fatal(
[
'You have enabled HTTPS and need to specify either ',
' "https.key", "https.cert" and "https.ca" or ',
' "https.pfx" and optionally "https.passphrase" ',
'to run https server',
'',
// commands are borrowed from node.js docs
'To quickly create self-signed certificate, use:',
' $ openssl genrsa -out ' + resolveConfigPath(storageLocation, keyPem) + ' 2048',
' $ openssl req -new -sha256 -key ' + resolveConfigPath(storageLocation, keyPem) + ' -out ' + resolveConfigPath(storageLocation, csrPem),
' $ openssl x509 -req -in ' +
resolveConfigPath(storageLocation, csrPem) +
' -signkey ' +
resolveConfigPath(storageLocation, keyPem) +
' -out ' +
resolveConfigPath(storageLocation, certPem),
'',
'And then add to config file (' + storageLocation + '):',
' https:',
` key: ${resolveConfigPath(storageLocation, keyPem)}`,
` cert: ${resolveConfigPath(storageLocation, certPem)}`,
` ca: ${resolveConfigPath(storageLocation, csrPem)}`,
].join('\n')
);
process.exit(2);
}

@ -4,8 +4,8 @@ import fs from 'fs';
import selfsigned from 'selfsigned';
import { configExample } from '@verdaccio/mock';
import {DEFAULT_DOMAIN, DEFAULT_PROTOCOL} from '@verdaccio/dev-commons';
import {parseConfigFile} from '@verdaccio/utils';
import { DEFAULT_DOMAIN, DEFAULT_PROTOCOL } from '@verdaccio/dev-commons';
import { parseConfigFile } from '@verdaccio/utils';
import { logger } from '@verdaccio/logger';
@ -19,12 +19,11 @@ jest.mock('@verdaccio/logger', () => ({
trace: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
fatal: jest.fn()
}
fatal: jest.fn(),
},
}));
describe('startServer via API', () => {
const parseConfigurationFile = (name) => {
return parseConfigFile(path.join(__dirname, `./partials/config/yaml/${name}.yaml`));
};
@ -36,19 +35,18 @@ describe('startServer via API', () => {
const version = '1.0.0';
const port = '6000';
await startVerdaccio(configExample(), port, store, version, serverName,
(webServer, addrs, pkgName, pkgVersion) => {
expect(webServer).toBeDefined();
expect(addrs).toBeDefined();
expect(addrs.proto).toBe(DEFAULT_PROTOCOL);
expect(addrs.host).toBe(DEFAULT_DOMAIN);
expect(addrs.port).toBe(port);
expect(pkgName).toBeDefined();
expect(pkgVersion).toBeDefined();
expect(pkgVersion).toBe(version);
expect(pkgName).toBe(serverName);
done();
});
await startVerdaccio(configExample(), port, store, version, serverName, (webServer, addrs, pkgName, pkgVersion) => {
expect(webServer).toBeDefined();
expect(addrs).toBeDefined();
expect(addrs.proto).toBe(DEFAULT_PROTOCOL);
expect(addrs.host).toBe(DEFAULT_DOMAIN);
expect(addrs.port).toBe(port);
expect(pkgName).toBeDefined();
expect(pkgVersion).toBeDefined();
expect(pkgVersion).toBe(version);
expect(pkgName).toBe(serverName);
done();
});
});
test('should set keepAliveTimeout to 0 seconds', async (done) => {
@ -57,7 +55,12 @@ describe('startServer via API', () => {
const version = '1.0.0';
const port = '6100';
await startVerdaccio(configExample(parseConfigurationFile('server/keepalivetimeout-0')), port, store, version, serverName,
await startVerdaccio(
configExample(parseConfigurationFile('server/keepalivetimeout-0')),
port,
store,
version,
serverName,
(webServer, addrs, pkgName, pkgVersion) => {
expect(webServer).toBeDefined();
expect(webServer.keepAliveTimeout).toBeDefined();
@ -71,7 +74,8 @@ describe('startServer via API', () => {
expect(pkgVersion).toBe(version);
expect(pkgName).toBe(serverName);
done();
});
}
);
});
test('should set keepAliveTimeout to 60 seconds', async (done) => {
@ -80,7 +84,12 @@ describe('startServer via API', () => {
const version = '1.0.0';
const port = '6200';
await startVerdaccio(configExample(parseConfigurationFile('server/keepalivetimeout-60')), port, store, version, serverName,
await startVerdaccio(
configExample(parseConfigurationFile('server/keepalivetimeout-60')),
port,
store,
version,
serverName,
(webServer, addrs, pkgName, pkgVersion) => {
expect(webServer).toBeDefined();
expect(webServer.keepAliveTimeout).toBeDefined();
@ -94,7 +103,8 @@ describe('startServer via API', () => {
expect(pkgVersion).toBe(version);
expect(pkgName).toBe(serverName);
done();
});
}
);
});
test('should set keepAliveTimeout to 5 seconds per default', async (done) => {
@ -103,7 +113,12 @@ describe('startServer via API', () => {
const version = '1.0.0';
const port = '6300';
await startVerdaccio(configExample(parseConfigurationFile('server/keepalivetimeout-undefined')), port, store, version, serverName,
await startVerdaccio(
configExample(parseConfigurationFile('server/keepalivetimeout-undefined')),
port,
store,
version,
serverName,
(webServer, addrs, pkgName, pkgVersion) => {
expect(webServer).toBeDefined();
expect(webServer.keepAliveTimeout).toBeDefined();
@ -117,7 +132,8 @@ describe('startServer via API', () => {
expect(pkgVersion).toBe(version);
expect(pkgName).toBe(serverName);
done();
});
}
);
});
test('should provide all HTTPS server fails', async (done) => {
@ -160,14 +176,13 @@ describe('startServer via API', () => {
cert: certPath,
};
await startVerdaccio(conf, address, store, version, serverName,
(webServer, addrs) => {
expect(webServer).toBeDefined();
expect(addrs).toBeDefined();
expect(addrs.proto).toBe('https');
done();
await startVerdaccio(conf, address, store, version, serverName, (webServer, addrs) => {
expect(webServer).toBeDefined();
expect(addrs).toBeDefined();
expect(addrs.proto).toBe('https');
done();
});
})
});
test('should fails if config is missing', async () => {
try {
@ -177,6 +192,5 @@ describe('startServer via API', () => {
expect(e.message).toEqual('config file must be an object');
}
});
});
});

@ -1,76 +1,74 @@
import _ from "lodash";
import {DEFAULT_DOMAIN, DEFAULT_PORT, DEFAULT_PROTOCOL} from "@verdaccio/dev-commons";
import {getListListenAddresses} from "../src/cli-utils";
import _ from 'lodash';
import { DEFAULT_DOMAIN, DEFAULT_PORT, DEFAULT_PROTOCOL } from '@verdaccio/dev-commons';
import { getListListenAddresses } from '../src/cli-utils';
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()
}
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');
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);
});
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']);
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);
});
expect(_.isArray(addrs)).toBeTruthy();
expect(addrs).toHaveLength(0);
});
test('should return a list of 1 address provided', () => {
// @ts-ignore
const addrs = getListListenAddresses(null, '1000');
test('should return a list of 1 address provided', () => {
// @ts-ignore
const addrs = getListListenAddresses(null, '1000');
expect(_.isArray(addrs)).toBeTruthy();
expect(addrs).toHaveLength(1);
});
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']);
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);
});
expect(_.isArray(addrs)).toBeTruthy();
expect(addrs).toHaveLength(2);
});
test(`should return by default ${DEFAULT_PORT}`, () => {
// @ts-ignore
const [addrs] = getListListenAddresses();
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);
});
// @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);
});
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);
});
});

@ -1,6 +1,6 @@
import zlib from 'zlib';
import Stream from 'stream';
import URL, {UrlWithStringQuery} from 'url';
import URL, { UrlWithStringQuery } from 'url';
import JSONStream from 'JSONStream';
import _ from 'lodash';
import request from 'request';
@ -11,7 +11,7 @@ import { Config, Callback, Headers, Logger, Package } from '@verdaccio/types';
import { IProxy, UpLinkConfLocal } from '@verdaccio/dev-types';
const LoggerApi = require('@verdaccio/logger');
const encode = function(thing): string {
const encode = function (thing): string {
return encodeURIComponent(thing).replace(/^%40/, '@');
};
@ -107,16 +107,16 @@ class ProxyStorage implements IProxy {
if (this._statusCheck() === false) {
const streamRead = new Stream.Readable();
process.nextTick(function(): void {
process.nextTick(function (): void {
if (cb) {
cb(ErrorCode.getInternalError(API_ERROR.UPLINK_OFFLINE));
}
streamRead.emit('error', ErrorCode.getInternalError(API_ERROR.UPLINK_OFFLINE));
});
// $FlowFixMe
streamRead._read = function(): void {};
streamRead._read = function (): void {};
// preventing 'Uncaught, unspecified "error" event'
streamRead.on('error', function(): void {});
streamRead.on('error', function (): void {});
return streamRead;
}
@ -143,64 +143,66 @@ class ProxyStorage implements IProxy {
headers['Content-Type'] = headers['Content-Type'] || HEADERS.JSON;
}
const requestCallback = cb ? function(err, res, body): void {
let error;
const responseLength = err ? 0 : body.length;
// $FlowFixMe
processBody();
logActivity();
// $FlowFixMe
cb(err, res, body);
const requestCallback = cb
? function (err, res, body): void {
let error;
const responseLength = err ? 0 : body.length;
// $FlowFixMe
processBody();
logActivity();
// $FlowFixMe
cb(err, res, body);
/**
* Perform a decode.
*/
function processBody(): void {
if (err) {
error = err.message;
return;
}
/**
* Perform a decode.
*/
function processBody(): void {
if (err) {
error = err.message;
return;
}
if (options.json && res.statusCode < 300) {
try {
// $FlowFixMe
body = JSON.parse(body.toString(CHARACTER_ENCODING.UTF8));
} catch (_err) {
body = {};
err = _err;
error = err.message;
if (options.json && res.statusCode < 300) {
try {
// $FlowFixMe
body = JSON.parse(body.toString(CHARACTER_ENCODING.UTF8));
} catch (_err) {
body = {};
err = _err;
error = err.message;
}
}
if (!err && isObject(body)) {
if (_.isString(body.error)) {
error = body.error;
}
}
}
/**
* Perform a log.
*/
function logActivity(): void {
let message = "@{!status}, req: '@{request.method} @{request.url}'";
// FIXME: use LOG_VERDACCIO_BYTES
message += error ? ', error: @{!error}' : ', bytes: @{bytes.in}/@{bytes.out}';
self.logger.warn(
{
err: err || undefined, // if error is null/false change this to undefined so it wont log
request: { method: method, url: uri },
level: 35, // http
status: res != null ? res.statusCode : 'ERR',
error: error,
bytes: {
in: json ? json.length : 0,
out: responseLength || 0,
},
},
message
);
}
}
if (!err && isObject(body)) {
if (_.isString(body.error)) {
error = body.error;
}
}
}
/**
* Perform a log.
*/
function logActivity(): void {
let message = "@{!status}, req: '@{request.method} @{request.url}'";
// FIXME: use LOG_VERDACCIO_BYTES
message += error ? ', error: @{!error}' : ', bytes: @{bytes.in}/@{bytes.out}';
self.logger.warn(
{
err: err || undefined, // if error is null/false change this to undefined so it wont log
request: { method: method, url: uri },
level: 35, // http
status: res != null ? res.statusCode : 'ERR',
error: error,
bytes: {
in: json ? json.length : 0,
out: responseLength || 0,
},
},
message
);
}
} : undefined;
: undefined;
let requestOptions = {
url: uri,
@ -217,14 +219,14 @@ class ProxyStorage implements IProxy {
if (this.ca) {
requestOptions = Object.assign({}, requestOptions, {
ca: this.ca
ca: this.ca,
});
}
const req = request(requestOptions, requestCallback);
let statusCalled = false;
req.on('response', function(res): void {
req.on('response', function (res): void {
// FIXME: _verdaccio_aborted seems not used
// @ts-ignore
if (!req._verdaccio_aborted && !statusCalled) {
@ -249,7 +251,7 @@ class ProxyStorage implements IProxy {
})();
}
});
req.on('error', function(_err): void {
req.on('error', function (_err): void {
// FIXME: _verdaccio_aborted seems not used
// @ts-ignore
if (!req._verdaccio_aborted && !statusCalled) {
@ -465,7 +467,7 @@ class ProxyStorage implements IProxy {
},
});
readStream.on('response', function(res: any) {
readStream.on('response', function (res: any) {
if (res.statusCode === HTTP_STATUS.NOT_FOUND) {
return stream.emit('error', ErrorCode.getNotFound(API_ERROR.NOT_FILE_UPLINK));
}
@ -480,13 +482,13 @@ class ProxyStorage implements IProxy {
readStream.pipe(stream);
});
readStream.on('error', function(err) {
readStream.on('error', function (err) {
stream.emit('error', err);
});
readStream.on('data', function(data) {
readStream.on('data', function (data) {
current_length += data.length;
});
readStream.on('end', function(data) {
readStream.on('end', function (data) {
if (data) {
current_length += data.length;
}

@ -1,7 +1,7 @@
import {buildToken} from "@verdaccio/utils";
import { buildToken } from '@verdaccio/utils';
import {ERROR_CODE, TOKEN_BASIC, TOKEN_BEARER, DEFAULT_REGISTRY, HEADERS} from "@verdaccio/dev-commons";
import {setup} from '@verdaccio/logger';
import { ERROR_CODE, TOKEN_BASIC, TOKEN_BEARER, DEFAULT_REGISTRY, HEADERS } from '@verdaccio/dev-commons';
import { setup } from '@verdaccio/logger';
import { ProxyStorage } from '../src/up-storage';
@ -9,7 +9,7 @@ setup([]);
function createUplink(config) {
const defaultConfig = {
url: DEFAULT_REGISTRY
url: DEFAULT_REGISTRY,
};
const mergeConfig = Object.assign({}, defaultConfig, config);
// @ts-ignore
@ -20,12 +20,11 @@ function setHeaders(config: unknown = {}, headers: unknown = {}) {
const uplink = createUplink(config);
// @ts-ignore
return uplink._setHeaders({
headers
headers,
});
}
describe('uplink headers auth test', () => {
test('if set headers empty should return default headers', () => {
const headers = setHeaders();
const keys = Object.keys(headers);
@ -38,46 +37,49 @@ describe('uplink headers auth test', () => {
test('if assigns value invalid to attribute auth', () => {
const fnError = function () {
setHeaders({
auth: ''
auth: '',
});
};
expect(function ( ) {
expect(function () {
fnError();
}).toThrow(Error('Auth invalid'));
});
test('if assigns the header authorization', () => {
const headers = setHeaders({}, {
[HEADERS.AUTHORIZATION]: buildToken(TOKEN_BASIC, 'Zm9vX2Jhcg==')
});
const headers = setHeaders(
{},
{
[HEADERS.AUTHORIZATION]: buildToken(TOKEN_BASIC, 'Zm9vX2Jhcg=='),
}
);
expect(Object.keys(headers)).toHaveLength(4);
expect(headers[HEADERS.AUTHORIZATION]).toEqual(buildToken(TOKEN_BASIC, 'Zm9vX2Jhcg=='));
});
test(
'if assigns headers authorization and token the header precedes',
() => {
const headers = setHeaders({
test('if assigns headers authorization and token the header precedes', () => {
const headers = setHeaders(
{
auth: {
type: TOKEN_BEARER,
token: 'tokenBearer'
}
}, {
[HEADERS.AUTHORIZATION]: buildToken(TOKEN_BASIC, 'tokenBasic')
});
token: 'tokenBearer',
},
},
{
[HEADERS.AUTHORIZATION]: buildToken(TOKEN_BASIC, 'tokenBasic'),
}
);
expect(headers[HEADERS.AUTHORIZATION]).toEqual(buildToken(TOKEN_BASIC, 'tokenBasic'));
}
);
expect(headers[HEADERS.AUTHORIZATION]).toEqual(buildToken(TOKEN_BASIC, 'tokenBasic'));
});
test('set type auth basic', () => {
const headers = setHeaders({
auth: {
type: TOKEN_BASIC,
token: 'Zm9vX2Jhcg=='
}
token: 'Zm9vX2Jhcg==',
},
});
expect(Object.keys(headers)).toHaveLength(4);
@ -88,8 +90,8 @@ describe('uplink headers auth test', () => {
const headers = setHeaders({
auth: {
type: TOKEN_BEARER,
token: 'Zm9vX2Jhcf==='
}
token: 'Zm9vX2Jhcf===',
},
});
expect(Object.keys(headers)).toHaveLength(4);
@ -97,16 +99,16 @@ describe('uplink headers auth test', () => {
});
test('set auth type invalid', () => {
const fnError = function() {
const fnError = function () {
setHeaders({
auth: {
type: 'null',
token: 'Zm9vX2Jhcf==='
}
})
token: 'Zm9vX2Jhcf===',
},
});
};
expect(function ( ) {
expect(function () {
fnError();
}).toThrow(Error(`Auth type 'null' not allowed`));
});
@ -115,8 +117,8 @@ describe('uplink headers auth test', () => {
process.env.NPM_TOKEN = 'myToken';
const headers = setHeaders({
auth: {
type: TOKEN_BEARER
}
type: TOKEN_BEARER,
},
});
expect(headers[HEADERS.AUTHORIZATION]).toBe(buildToken(TOKEN_BEARER, 'myToken'));
@ -128,25 +130,24 @@ describe('uplink headers auth test', () => {
const headers = setHeaders({
auth: {
type: TOKEN_BASIC,
token_env: 'NPM_TOKEN_TEST'
}
token_env: 'NPM_TOKEN_TEST',
},
});
expect(headers[HEADERS.AUTHORIZATION]).toBe(buildToken(TOKEN_BASIC, 'myTokenTest'));
delete process.env.NPM_TOKEN_TEST;
});
test('if token not set', () => {
const fnError = function() {
const fnError = function () {
setHeaders({
auth: {
type: TOKEN_BASIC
}
type: TOKEN_BASIC,
},
});
};
expect(function( ) {
expect(function () {
fnError();
}).toThrow(ERROR_CODE.token_required);
});

@ -16,81 +16,81 @@ describe('Use proxy', () => {
});
test('local config should take priority', () => {
const x = setupProxy('http://x/x', {http_proxy: '123'}, {http_proxy: '456'});
const x = setupProxy('http://x/x', { http_proxy: '123' }, { http_proxy: '456' });
expect(x.proxy).toEqual('123');
});
test('no_proxy is invalid', () => {
let x = setupProxy('http://x/x', {http_proxy: '123', no_proxy: false}, {});
let x = setupProxy('http://x/x', { http_proxy: '123', no_proxy: false }, {});
expect(x.proxy).toEqual('123');
x = setupProxy('http://x/x', {http_proxy: '123', no_proxy: null}, {});
x = setupProxy('http://x/x', { http_proxy: '123', no_proxy: null }, {});
expect(x.proxy).toEqual('123');
x = setupProxy('http://x/x', {http_proxy: '123', no_proxy: []}, {});
x = setupProxy('http://x/x', { http_proxy: '123', no_proxy: [] }, {});
expect(x.proxy).toEqual('123');
x = setupProxy('http://x/x', {http_proxy: '123', no_proxy: ''}, {});
x = setupProxy('http://x/x', { http_proxy: '123', no_proxy: '' }, {});
expect(x.proxy).toEqual('123');
});
test('no_proxy - simple/include', () => {
let x = setupProxy('http://localhost', {http_proxy: '123'}, {no_proxy: 'localhost'});
let x = setupProxy('http://localhost', { http_proxy: '123' }, { no_proxy: 'localhost' });
expect(x.proxy).toEqual(undefined);
});
test('no_proxy - simple/not', () => {
let x = setupProxy('http://localhost', {http_proxy: '123'}, {no_proxy: 'blah'});
let x = setupProxy('http://localhost', { http_proxy: '123' }, { no_proxy: 'blah' });
expect(x.proxy).toEqual('123');
});
test('no_proxy - various, single string', () => {
let x = setupProxy('http://blahblah', {http_proxy: '123'}, {no_proxy: 'blah'});
let x = setupProxy('http://blahblah', { http_proxy: '123' }, { no_proxy: 'blah' });
expect(x.proxy).toEqual('123');
x = setupProxy('http://blah.blah', {}, {http_proxy: '123', no_proxy: 'blah'});
x = setupProxy('http://blah.blah', {}, { http_proxy: '123', no_proxy: 'blah' });
expect(x.proxy).toEqual(undefined);
x = setupProxy('http://blahblah', {}, {http_proxy: '123', no_proxy: '.blah'});
x = setupProxy('http://blahblah', {}, { http_proxy: '123', no_proxy: '.blah' });
expect(x.proxy).toEqual('123');
x = setupProxy('http://blah.blah', {http_proxy: '123', no_proxy: '.blah'}, {});
x = setupProxy('http://blah.blah', { http_proxy: '123', no_proxy: '.blah' }, {});
expect(x.proxy).toEqual(undefined);
x = setupProxy('http://blah', {http_proxy: '123', no_proxy: '.blah'}, {});
x = setupProxy('http://blah', { http_proxy: '123', no_proxy: '.blah' }, {});
expect(x.proxy).toEqual(undefined);
x = setupProxy('http://blahh', {http_proxy: '123', no_proxy: 'blah'}, {});
x = setupProxy('http://blahh', { http_proxy: '123', no_proxy: 'blah' }, {});
expect(x.proxy).toEqual('123');
});
test('no_proxy - various, array', () => {
let x = setupProxy('http://blahblah', {http_proxy: '123'}, {no_proxy: 'foo,bar,blah'});
let x = setupProxy('http://blahblah', { http_proxy: '123' }, { no_proxy: 'foo,bar,blah' });
expect(x.proxy).toEqual('123');
x = setupProxy('http://blah.blah', {http_proxy: '123'}, {no_proxy: 'foo,bar,blah'});
x = setupProxy('http://blah.blah', { http_proxy: '123' }, { no_proxy: 'foo,bar,blah' });
expect(x.proxy).toEqual(undefined);
x = setupProxy('http://blah.foo', {http_proxy: '123'}, {no_proxy: 'foo,bar,blah'});
x = setupProxy('http://blah.foo', { http_proxy: '123' }, { no_proxy: 'foo,bar,blah' });
expect(x.proxy).toEqual(undefined);
x = setupProxy('http://foo.baz', {http_proxy: '123'}, {no_proxy: 'foo,bar,blah'});
x = setupProxy('http://foo.baz', { http_proxy: '123' }, { no_proxy: 'foo,bar,blah' });
expect(x.proxy).toEqual('123');
x = setupProxy('http://blahblah', {http_proxy: '123'}, {no_proxy: ['foo', 'bar', 'blah']});
x = setupProxy('http://blahblah', { http_proxy: '123' }, { no_proxy: ['foo', 'bar', 'blah'] });
expect(x.proxy).toEqual('123');
x = setupProxy('http://blah.blah', {http_proxy: '123'}, {no_proxy: ['foo', 'bar', 'blah']});
x = setupProxy('http://blah.blah', { http_proxy: '123' }, { no_proxy: ['foo', 'bar', 'blah'] });
expect(x.proxy).toEqual(undefined);
});
test('no_proxy - hostport', () => {
let x = setupProxy('http://localhost:80', {http_proxy: '123'}, {no_proxy: 'localhost'});
let x = setupProxy('http://localhost:80', { http_proxy: '123' }, { no_proxy: 'localhost' });
expect(x.proxy).toEqual(undefined);
x = setupProxy('http://localhost:8080', {http_proxy: '123'}, {no_proxy: 'localhost'});
x = setupProxy('http://localhost:8080', { http_proxy: '123' }, { no_proxy: 'localhost' });
expect(x.proxy).toEqual(undefined);
});
test('no_proxy - secure', () => {
let x = setupProxy('https://something', {http_proxy: '123'}, {});
let x = setupProxy('https://something', { http_proxy: '123' }, {});
expect(x.proxy).toEqual(undefined);
x = setupProxy('https://something', {https_proxy: '123'}, {});
x = setupProxy('https://something', { https_proxy: '123' }, {});
expect(x.proxy).toEqual('123');
x = setupProxy('https://something', {http_proxy: '456', https_proxy: '123'}, {});
x = setupProxy('https://something', { http_proxy: '456', https_proxy: '123' }, {});
expect(x.proxy).toEqual('123');
});
});

@ -4,23 +4,20 @@ import { $ResponseExtend, $RequestExtend, $NextFunctionVer } from '@verdaccio/de
export default (app: Application, selfPath: string): void => {
// Hook for tests only
app.get(
'/-/_debug',
function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
const doGarbabeCollector = _.isNil(global.gc) === false;
app.get('/-/_debug', function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
const doGarbabeCollector = _.isNil(global.gc) === false;
if (doGarbabeCollector) {
global.gc();
}
next({
pid: process.pid,
// @ts-ignore
main: process.mainModule.filename,
conf: selfPath,
mem: process.memoryUsage(),
gc: doGarbabeCollector
});
if (doGarbabeCollector) {
global.gc();
}
);
next({
pid: process.pid,
// @ts-ignore
main: process.mainModule.filename,
conf: selfPath,
mem: process.memoryUsage(),
gc: doGarbabeCollector,
});
});
};

@ -21,7 +21,7 @@ import { log, final, errorReportingMiddleware } from '@verdaccio/middleware';
import hookDebug from './debug';
const defineAPI = function(config: IConfig, storage: IStorageHandler): any {
const defineAPI = function (config: IConfig, storage: IStorageHandler): any {
const auth: IAuth = new Auth(config);
const app: Application = express();
// run in production mode by default, just in case
@ -32,14 +32,14 @@ const defineAPI = function(config: IConfig, storage: IStorageHandler): any {
// Router setup
app.use(log(config));
app.use(errorReportingMiddleware);
app.use(function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
app.use(function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
res.setHeader('X-Powered-By', config.user_agent);
next();
});
app.use(compression());
app.get('/favicon.ico', function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
app.get('/favicon.ico', function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
req.url = '/-/static/favicon.png';
next();
});
@ -55,7 +55,7 @@ const defineAPI = function(config: IConfig, storage: IStorageHandler): any {
logger: logger,
};
const plugins: IPluginMiddleware<IConfig>[] = loadPlugin(config, config.middlewares, plugin_params, function(plugin: IPluginMiddleware<IConfig>) {
const plugins: IPluginMiddleware<IConfig>[] = loadPlugin(config, config.middlewares, plugin_params, function (plugin: IPluginMiddleware<IConfig>) {
return plugin.register_middlewares;
});
plugins.forEach((plugin: IPluginMiddleware<IConfig>) => {
@ -70,17 +70,17 @@ const defineAPI = function(config: IConfig, storage: IStorageHandler): any {
app.use('/', renderWebMiddleware(config, auth, storage));
app.use('/-/verdaccio/', webAPI(config, auth, storage));
} else {
app.get('/', function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer) {
app.get('/', function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer) {
next(ErrorCode.getNotFound(API_ERROR.WEB_DISABLED));
});
}
// Catch 404
app.get('/*', function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer) {
app.get('/*', function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer) {
next(ErrorCode.getNotFound(API_ERROR.FILE_NOT_FOUND));
});
app.use(function(err: HttpError, req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer) {
app.use(function (err: HttpError, req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer) {
if (_.isError(err)) {
if (err.code === 'ECONNABORT' && res.statusCode === HTTP_STATUS.NOT_MODIFIED) {
return next();
@ -102,7 +102,7 @@ const defineAPI = function(config: IConfig, storage: IStorageHandler): any {
return app;
};
export default (async function(configHash: any): Promise<any> {
export default (async function (configHash: any): Promise<any> {
setup(configHash.logs);
const config: IConfig = new AppConfig(_.cloneDeep(configHash));
// register middleware plugins

@ -1,53 +1,50 @@
const json = {
"_id": "@scope\/pk1-test",
"name": "@scope\/pk1-test",
"description": "",
"dist-tags": {
"latest": "1.0.6"
_id: '@scope/pk1-test',
name: '@scope/pk1-test',
description: '',
'dist-tags': {
latest: '1.0.6',
},
"versions": {
"1.0.6": {
"name": "@scope\/pk1-test",
"version": "1.0.6",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
versions: {
'1.0.6': {
name: '@scope/pk1-test',
version: '1.0.6',
description: '',
main: 'index.js',
scripts: {
test: 'echo "Error: no test specified" && exit 1',
},
"keywords": [
],
"author": {
"name": "User NPM",
"email": "user@domain.com"
keywords: [],
author: {
name: 'User NPM',
email: 'user@domain.com',
},
"license": "ISC",
"dependencies": {
"verdaccio": "^2.7.2"
license: 'ISC',
dependencies: {
verdaccio: '^2.7.2',
},
"readme": "# test",
"readmeFilename": "README.md",
"_id": "@scope\/pk1-test@1.0.6",
"_npmVersion": "5.5.1",
"_nodeVersion": "8.7.0",
"_npmUser": {
readme: '# test',
readmeFilename: 'README.md',
_id: '@scope/pk1-test@1.0.6',
_npmVersion: '5.5.1',
_nodeVersion: '8.7.0',
_npmUser: {},
dist: {
integrity: 'sha512-6gHiERpiDgtb3hjqpQH5/i7zRmvYi9pmCjQf2ZMy3QEa9wVk9RgdZaPWUt7ZOnWUPFjcr9cmE6dUBf+XoPoH4g==',
shasum: '2c03764f651a9f016ca0b7620421457b619151b9',
tarball: 'http://localhost:5555/@scope/pk1-test/-/@scope/pk1-test-1.0.6.tgz',
},
"dist": {
"integrity": "sha512-6gHiERpiDgtb3hjqpQH5\/i7zRmvYi9pmCjQf2ZMy3QEa9wVk9RgdZaPWUt7ZOnWUPFjcr9cmE6dUBf+XoPoH4g==",
"shasum": "2c03764f651a9f016ca0b7620421457b619151b9",
"tarball": "http:\/\/localhost:5555\/@scope\/pk1-test\/-\/@scope\/pk1-test-1.0.6.tgz"
}
}
},
},
"readme": "# test",
"_attachments": {
"@scope\/pk1-test-1.0.6.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
}
}
}
readme: '# test',
_attachments: {
'@scope/pk1-test-1.0.6.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,
},
},
};
module.exports = json;

@ -1,37 +1,36 @@
import { Package } from "@verdaccio/types";
import { Package } from '@verdaccio/types';
export function generateVersion(pkgName, version) {
return {
"name": pkgName,
"version": version,
"description": "some foo dependency",
"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": "^4.0.0"
},
"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`
}
}
return {
name: pkgName,
version: version,
description: 'some foo dependency',
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: '^4.0.0',
},
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`,
},
};
}
/**
@ -41,88 +40,87 @@ export function generateVersion(pkgName, version) {
* @param _versions
*/
export function generatePackageUnpublish(pkgName: string, _versions: string[] = ['1.0.0']): Package {
const latest: string = _versions[_versions.length - 1];
const versions = _versions.reduce((cat, version) => {
cat[version] = generateVersion(pkgName, version);
return cat;
}, {});
const latest: string = _versions[_versions.length - 1];
const versions = _versions.reduce((cat, version) => {
cat[version] = generateVersion(pkgName, version);
return cat;
}, {});
// @ts-ignore
return {
"_id": pkgName,
"name": pkgName,
"readme": "# test",
// users usually is present when run npm star [pkg]
"users": {},
"dist-tags": {
"latest": latest
},
"versions": versions,
}
// @ts-ignore
return {
_id: pkgName,
name: pkgName,
readme: '# test',
// users usually is present when run npm star [pkg]
users: {},
'dist-tags': {
latest: latest,
},
versions: versions,
};
}
export function generateStarMedatada(pkgName: string, users): any {
return {
"_id": pkgName,
"_rev": "3-b0cdaefc9bdb77c8",
"users": users
}
return {
_id: pkgName,
_rev: '3-b0cdaefc9bdb77c8',
users: users,
};
}
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
}
}
}
// @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,
},
},
};
}
export function generateDeprecateMetadata(pkgName: string, version = '1.0.0', deprecated:string = ''): Package {
export function generateDeprecateMetadata(pkgName: string, version = '1.0.0', deprecated: string = ''): Package {
const res = {
...generatePackageMetadata(pkgName, version),
_attachments: {},

@ -2,20 +2,15 @@ import path from 'path';
import request from 'supertest';
import _ from 'lodash';
import {
HEADERS,
HTTP_STATUS,
HEADER_TYPE,
API_MESSAGE,
TOKEN_BEARER,
} from '@verdaccio/dev-commons';
import {buildToken, encodeScopedUri} from '@verdaccio/utils';
import {setup, logger} from '@verdaccio/logger';
import { HEADERS, HTTP_STATUS, HEADER_TYPE, API_MESSAGE, TOKEN_BEARER } from '@verdaccio/dev-commons';
import { buildToken, encodeScopedUri } from '@verdaccio/utils';
import { setup, logger } from '@verdaccio/logger';
import {mockServer} from '@verdaccio/mock';
import { mockServer } from '@verdaccio/mock';
import {
configExample, DOMAIN_SERVERS,
configExample,
DOMAIN_SERVERS,
getNewToken,
getPackage,
putPackage,
@ -24,15 +19,9 @@ import {
generateUnPublishURI,
} from '@verdaccio/mock';
import publishMetadata from './helpers/publish-api';
import {
generateDeprecateMetadata,
generatePackageMetadata,
generatePackageUnpublish,
generateStarMedatada, generateVersion
} from './helpers/utils';
import endPointAPI from '../../src';
import publishMetadata from './helpers/publish-api';
import { generateDeprecateMetadata, generatePackageMetadata, generatePackageUnpublish, generateStarMedatada, generateVersion } from './helpers/utils';
setup([]);
@ -53,24 +42,28 @@ describe('endpoint unit test', () => {
let app;
let mockRegistry;
beforeAll(async function(done) {
beforeAll(async function (done) {
const store = generateRamdonStorage();
const mockServerPort = 55549;
const configForTest = configExample({
filters: {
[path.join(__dirname, './plugin/filter')]: {
pkg: 'npm_test',
version: '2.0.0'
}
const configForTest = configExample(
{
filters: {
[path.join(__dirname, './plugin/filter')]: {
pkg: 'npm_test',
version: '2.0.0',
},
},
storage: store,
self_path: store,
uplinks: {
npmjs: {
url: `http://${DOMAIN_SERVERS}:${mockServerPort}`,
},
},
},
storage: store,
self_path: store,
uplinks: {
npmjs: {
url: `http://${DOMAIN_SERVERS}:${mockServerPort}`
}
}
}, 'api.spec.yaml', __dirname);
'api.spec.yaml',
__dirname
);
app = await endPointAPI(configForTest);
const binPath = require.resolve('verdaccio/bin/verdaccio');
@ -79,7 +72,7 @@ describe('endpoint unit test', () => {
done();
});
afterAll(function(done) {
afterAll(function (done) {
const [registry, pid] = mockRegistry;
registry.stop();
logger.info(`registry ${pid} has been stopped`);
@ -96,7 +89,7 @@ describe('endpoint unit test', () => {
.set(HEADERS.AUTHORIZATION, 'FakeHader')
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.expect(HTTP_STATUS.FORBIDDEN)
.end(function(err, res) {
.end(function (err, res) {
expect(res.body.error).toBeDefined();
expect(res.body.error).toMatch(/authorization required to access package auth-package/);
done();
@ -109,7 +102,7 @@ describe('endpoint unit test', () => {
.set(HEADERS.AUTHORIZATION, TOKEN_BEARER)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.expect(HTTP_STATUS.FORBIDDEN)
.end(function(err, res) {
.end(function (err, res) {
expect(res.body.error).toBeDefined();
expect(res.body.error).toMatch(/authorization required to access package auth-package/);
done();
@ -122,7 +115,7 @@ describe('endpoint unit test', () => {
.set(HEADERS.AUTHORIZATION, buildToken(TOKEN_BEARER, '12345'))
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.expect(HTTP_STATUS.FORBIDDEN)
.end(function(err, res) {
.end(function (err, res) {
expect(res.body.error).toBeDefined();
expect(res.body.error).toMatch(/authorization required to access package auth-package/);
done();
@ -130,14 +123,13 @@ describe('endpoint unit test', () => {
});
});
test('should test add a new user', (done) => {
request(app)
.put(`/-/user/org.couchdb.user:${credentials.name}`)
.send(credentials)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.CREATED)
.end(function(err, res) {
.end(function (err, res) {
if (err) {
return done(err);
}
@ -154,7 +146,7 @@ describe('endpoint unit test', () => {
.set(HEADERS.AUTHORIZATION, buildToken(TOKEN_BEARER, token))
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.OK)
.end(function(err, res) {
.end(function (err, res) {
expect(err).toBeNull();
expect(res.body).toBeDefined();
expect(res.body.name).toMatch(/vue/);
@ -173,13 +165,12 @@ describe('endpoint unit test', () => {
// proxy: npmjs
test('should fetch jquery package from remote uplink', (done) => {
request(app)
.get('/jquery')
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.OK)
.end(function(err, res) {
.end(function (err, res) {
if (err) {
return done(err);
}
@ -191,13 +182,12 @@ describe('endpoint unit test', () => {
});
test('should fetch jquery specific version package from remote uplink', (done) => {
request(app)
.get('/jquery/1.5.1')
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.OK)
.end(function(err, res) {
.end(function (err, res) {
if (err) {
return done(err);
}
@ -209,13 +199,12 @@ describe('endpoint unit test', () => {
});
test('should fetch jquery specific tag package from remote uplink', (done) => {
request(app)
.get('/jquery/latest')
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.OK)
.end(function(err, res) {
.end(function (err, res) {
if (err) {
return done(err);
}
@ -227,13 +216,12 @@ describe('endpoint unit test', () => {
});
test('should fails on fetch jquery specific tag package from remote uplink', (done) => {
request(app)
.get('/jquery/never-will-exist-this-tag')
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.NOT_FOUND)
.end(function(err) {
.end(function (err) {
if (err) {
return done(err);
}
@ -242,13 +230,12 @@ describe('endpoint unit test', () => {
});
test('should not found a unexisting remote package under scope', (done) => {
request(app)
.get('/@verdaccio/not-found')
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.NOT_FOUND)
.end(function(err) {
.end(function (err) {
if (err) {
return done(err);
}
@ -263,7 +250,7 @@ describe('endpoint unit test', () => {
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.OK)
.end(function(err, res) {
.end(function (err, res) {
if (err) {
return done(err);
}
@ -275,12 +262,12 @@ describe('endpoint unit test', () => {
test('should not found when a filter fails', (done) => {
request(app)
// Filter errors look like other uplink errors
// Filter errors look like other uplink errors
.get('/trigger-filter-failure')
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.NOT_FOUND)
.end(function(err) {
.end(function (err) {
if (err) {
return done(err);
}
@ -290,13 +277,12 @@ describe('endpoint unit test', () => {
});
test('should forbid access to remote package', (done) => {
request(app)
.get('/forbidden-place')
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.UNAUTHORIZED)
.end(function(err) {
.end(function (err) {
if (err) {
return done(err);
}
@ -305,12 +291,11 @@ describe('endpoint unit test', () => {
});
test('should fetch a tarball from remote uplink', (done) => {
request(app)
.get('/jquery/-/jquery-1.5.1.tgz')
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.OCTET_STREAM)
.expect(HTTP_STATUS.OK)
.end(function(err, res) {
.end(function (err, res) {
if (err) {
return done(err);
}
@ -321,12 +306,11 @@ describe('endpoint unit test', () => {
});
test('should fetch a scoped tarball from remote uplink', (done) => {
request(app)
.get('/@jquery/jquery/-/@jquery/jquery-1.5.1.tgz')
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.OCTET_STREAM)
.expect(HTTP_STATUS.OK)
.end(function(err, res) {
.end(function (err, res) {
if (err) {
return done(err);
}
@ -337,12 +321,11 @@ describe('endpoint unit test', () => {
});
test('should fails fetch a tarball from remote uplink', (done) => {
request(app)
.get('/jquery/-/jquery-not-found-tarball-0.0.1.tgz')
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.OCTET_STREAM)
.expect(HTTP_STATUS.NOT_FOUND)
.end(function(err) {
.end(function (err) {
if (err) {
expect(err).not.toBeNull();
return done(err);
@ -351,20 +334,19 @@ describe('endpoint unit test', () => {
done();
});
});
});
describe('should test dist-tag api', () => {
const jqueryVersion = '2.1.2';
const jqueryUpdatedVersion = {
'beta': '3.0.0',
'jota': '1.6.3'
beta: '3.0.0',
jota: '1.6.3',
};
test('should set a new tag on jquery', (done) => {
putVersion(app, '/jquery/verdaccio-tag', jqueryVersion)
.expect(HTTP_STATUS.CREATED)
.end(function(err, res) {
.end(function (err, res) {
if (err) {
expect(err).toBeNull();
return done(err);
@ -377,13 +359,12 @@ describe('endpoint unit test', () => {
});
test('should fetch all tag for jquery', (done) => {
request(app)
.get('/-/package/jquery/dist-tags')
.set('accept-encoding', HEADERS.JSON)
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.expect(HTTP_STATUS.OK)
.end(function(err, res) {
.end(function (err, res) {
if (err) {
expect(err).toBeNull();
return done(err);
@ -396,13 +377,12 @@ describe('endpoint unit test', () => {
});
test('should update a new tag on jquery', (done) => {
request(app)
.post('/-/package/jquery/dist-tags')
.send(JSON.stringify(jqueryUpdatedVersion))
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.expect(HTTP_STATUS.CREATED)
.end(function(err, res) {
.end(function (err, res) {
if (err) {
expect(err).toBeNull();
return done(err);
@ -415,13 +395,12 @@ describe('endpoint unit test', () => {
});
test('should fetch all tags for jquery and ccheck previous update', (done) => {
request(app)
.get('/-/package/jquery/dist-tags')
.set('accept-encoding', HEADERS.JSON)
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.expect(HTTP_STATUS.OK)
.end(function(err, res) {
.end(function (err, res) {
if (err) {
expect(err).toBeNull();
return done(err);
@ -434,14 +413,13 @@ describe('endpoint unit test', () => {
});
test('should set a remove a tag on jquery', (done) => {
request(app)
.del('/-/package/jquery/dist-tags/verdaccio-tag')
.set('accept-encoding', HEADERS.JSON)
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
// .expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.CREATED)
.end(function(err, res) {
.end(function (err, res) {
if (err) {
expect(err).toBeNull();
return done(err);
@ -452,7 +430,6 @@ describe('endpoint unit test', () => {
done();
});
});
});
describe('should test search api', () => {
@ -465,7 +442,7 @@ describe('endpoint unit test', () => {
// .set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.expect(HEADERS.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.OK)
.end(function(err) {
.end(function (err) {
if (err) {
expect(err).toBeNull();
return done(err);
@ -475,7 +452,6 @@ describe('endpoint unit test', () => {
done();
});
});
});
describe('should test publish/unpublish api', () => {
@ -494,14 +470,13 @@ describe('endpoint unit test', () => {
}
const newVersion = '2.0.1';
const [newErr] = await putPackage(request(app), `/${encodeScopedUri(pkgName)}`,
generatePackageMetadata(pkgName, newVersion), token);
const [newErr] = await putPackage(request(app), `/${encodeScopedUri(pkgName)}`, generatePackageMetadata(pkgName, newVersion), token);
if (newErr) {
expect(newErr).toBeNull();
return done(newErr);
}
const deletePayload = generatePackageUnpublish(pkgName, ['2.0.0']);
const deletePayload = generatePackageUnpublish(pkgName, ['2.0.0']);
const [err2, res2] = await putPackage(request(app), generateUnPublishURI(pkgName), deletePayload, token);
expect(err2).toBeNull();
@ -551,14 +526,13 @@ describe('endpoint unit test', () => {
const newVersion = '1.0.0';
const token = await getNewToken(request(app), credentials);
const [newErr] = await putPackage(request(app), `/${encodeScopedUri(pkgName)}`,
generatePackageMetadata(pkgName, newVersion), token);
const [newErr] = await putPackage(request(app), `/${encodeScopedUri(pkgName)}`, generatePackageMetadata(pkgName, newVersion), token);
if (newErr) {
expect(newErr).toBeNull();
return done(newErr);
}
const deletePayload = generatePackageUnpublish(pkgName, ['2.0.0']);
const deletePayload = generatePackageUnpublish(pkgName, ['2.0.0']);
const [err2, res2] = await putPackage(request(app), generateUnPublishURI(pkgName), deletePayload, token);
expect(err2).not.toBeNull();
@ -588,8 +562,7 @@ describe('endpoint unit test', () => {
const newVersion = '1.0.0';
const token = await getNewToken(request(app), credentials);
const [newErr, resp] = await putPackage(request(app), `/${encodeScopedUri(pkgName)}`,
generatePackageMetadata(pkgName, newVersion), token);
const [newErr, resp] = await putPackage(request(app), `/${encodeScopedUri(pkgName)}`, generatePackageMetadata(pkgName, newVersion), token);
expect(newErr).not.toBeNull();
expect(resp.body.error).toMatch(/user jota_only_unpublish_fail is not allowed to publish package only-unpublish/);
@ -604,11 +577,15 @@ describe('endpoint unit test', () => {
.put('/super-admin-can-unpublish')
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.set(HEADERS.AUTHORIZATION, buildToken(TOKEN_BEARER, token))
.send(JSON.stringify(_.assign({}, publishMetadata, {
name: 'super-admin-can-unpublish'
})))
.send(
JSON.stringify(
_.assign({}, publishMetadata, {
name: 'super-admin-can-unpublish',
})
)
)
.expect(HTTP_STATUS.CREATED)
.end(function(err, res) {
.end(function (err, res) {
if (err) {
expect(err).toBeNull();
return done(err);
@ -622,7 +599,7 @@ describe('endpoint unit test', () => {
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.set(HEADERS.AUTHORIZATION, buildToken(TOKEN_BEARER, token))
.expect(HTTP_STATUS.CREATED)
.end(function(err, res) {
.end(function (err, res) {
expect(err).toBeNull();
expect(res.body.ok).toBeDefined();
expect(res.body.ok).toMatch(API_MESSAGE.PKG_REMOVED);
@ -638,11 +615,15 @@ describe('endpoint unit test', () => {
.put('/all-can-unpublish')
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.set(HEADERS.AUTHORIZATION, buildToken(TOKEN_BEARER, token))
.send(JSON.stringify(_.assign({}, publishMetadata, {
name: 'all-can-unpublish'
})))
.send(
JSON.stringify(
_.assign({}, publishMetadata, {
name: 'all-can-unpublish',
})
)
)
.expect(HTTP_STATUS.CREATED)
.end(function(err, res) {
.end(function (err, res) {
if (err) {
expect(err).toBeNull();
return done(err);
@ -656,7 +637,7 @@ describe('endpoint unit test', () => {
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.set(HEADERS.AUTHORIZATION, buildToken(TOKEN_BEARER, token))
.expect(HTTP_STATUS.CREATED)
.end(function(err, res) {
.end(function (err, res) {
expect(err).toBeNull();
expect(res.body.ok).toBeDefined();
expect(res.body.ok).toMatch(API_MESSAGE.PKG_REMOVED);
@ -667,91 +648,98 @@ describe('endpoint unit test', () => {
});
describe('should test star and stars api', () => {
const pkgName = '@scope/starPackage';
const credentials = { name: 'jota_star', password: 'secretPass' };
let token = '';
beforeAll(async (done) =>{
token = await getNewToken(request(app), credentials);
await putPackage(request(app), `/${pkgName}`, generatePackageMetadata(pkgName), token);
const pkgName = '@scope/starPackage';
const credentials = { name: 'jota_star', password: 'secretPass' };
let token = '';
beforeAll(async (done) => {
token = await getNewToken(request(app), credentials);
await putPackage(request(app), `/${pkgName}`, generatePackageMetadata(pkgName), token);
done();
});
test('should star a package', (done) => {
request(app)
.put(`/${pkgName}`)
.set(HEADERS.AUTHORIZATION, buildToken(TOKEN_BEARER, token))
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.send(
JSON.stringify(
generateStarMedatada(pkgName, {
[credentials.name]: true,
})
)
)
.expect(HTTP_STATUS.OK)
.end(function (err, res) {
if (err) {
expect(err).toBeNull();
return done(err);
}
expect(res.body.success).toBeDefined();
expect(res.body.success).toBeTruthy();
done();
});
});
});
test('should star a package', (done) => {
request(app)
.put(`/${pkgName}`)
.set(HEADERS.AUTHORIZATION, buildToken(TOKEN_BEARER, token))
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.send(JSON.stringify(generateStarMedatada(pkgName, {
[credentials.name]: true
})))
.expect(HTTP_STATUS.OK)
.end(function(err, res) {
if (err) {
expect(err).toBeNull();
return done(err);
}
expect(res.body.success).toBeDefined();
expect(res.body.success).toBeTruthy();
done();
});
});
test('should unstar a package', (done) => {
request(app)
.put(`/${pkgName}`)
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.set(HEADERS.AUTHORIZATION, buildToken(TOKEN_BEARER, token))
.send(JSON.stringify(generateStarMedatada(pkgName, {})))
.expect(HTTP_STATUS.OK)
.end(function (err, res) {
if (err) {
expect(err).toBeNull();
return done(err);
}
expect(res.body.success).toBeDefined();
expect(res.body.success).toBeTruthy();
done();
});
});
test('should unstar a package', (done) => {
test('should retrieve stars list with credentials', async (done) => {
request(app)
.put(`/${pkgName}`)
.set(HEADERS.AUTHORIZATION, buildToken(TOKEN_BEARER, token))
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.send(generateStarMedatada(pkgName, { [credentials.name]: true }))
.expect(HTTP_STATUS.OK)
.end(function (err) {
if (err) {
expect(err).toBeNull();
return done(err);
}
request(app)
.put(`/${pkgName}`)
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.set(HEADERS.AUTHORIZATION, buildToken(TOKEN_BEARER, token))
.send(JSON.stringify(generateStarMedatada(pkgName, {})))
.expect(HTTP_STATUS.OK)
.end(function(err, res) {
if (err) {
expect(err).toBeNull();
return done(err);
}
expect(res.body.success).toBeDefined();
expect(res.body.success).toBeTruthy();
done();
});
});
test('should retrieve stars list with credentials', async (done) => {
request(app)
.put(`/${pkgName}`)
.set(HEADERS.AUTHORIZATION, buildToken(TOKEN_BEARER, token))
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.send(generateStarMedatada(pkgName, {[credentials.name]: true}))
.expect(HTTP_STATUS.OK).end(function(err) {
.get('/-/_view/starredByUser')
.set(HEADERS.AUTHORIZATION, buildToken(TOKEN_BEARER, token))
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.send(
JSON.stringify({
key: [credentials.name],
})
)
.expect(HTTP_STATUS.OK)
.end(function (err, res) {
if (err) {
expect(err).toBeNull();
return done(err);
expect(err).toBeNull();
return done(err);
}
request(app)
.get('/-/_view/starredByUser')
.set(HEADERS.AUTHORIZATION, buildToken(TOKEN_BEARER, token))
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.send(JSON.stringify({
key: [credentials.name]
}))
.expect(HTTP_STATUS.OK)
.end(function(err, res) {
if (err) {
expect(err).toBeNull();
return done(err);
}
expect(res.body.rows).toBeDefined();
expect(res.body.rows).toHaveLength(1);
done();
});
});
});
expect(res.body.rows).toBeDefined();
expect(res.body.rows).toHaveLength(1);
done();
});
});
});
});
describe('should test (un)deprecate api', () => {
const pkgName = '@scope/deprecate';
const credentials = { name: 'jota_deprecate', password: 'secretPass' };
const version = '1.0.0'
const version = '1.0.0';
let token = '';
beforeAll(async (done) =>{
beforeAll(async (done) => {
token = await getNewToken(request(app), credentials);
await putPackage(request(app), `/${pkgName}`, generatePackageMetadata(pkgName, version), token);
done();
@ -764,7 +752,7 @@ describe('endpoint unit test', () => {
expect(err).toBeNull();
return done(err);
}
const [,res] = await getPackage(request(app), '', pkgName);
const [, res] = await getPackage(request(app), '', pkgName);
expect(res.body.versions[version].deprecated).toEqual('get deprecated');
done();
});
@ -778,7 +766,7 @@ describe('endpoint unit test', () => {
expect(err).toBeNull();
return done(err);
}
const [,res] = await getPackage(request(app), '', pkgName);
const [, res] = await getPackage(request(app), '', pkgName);
expect(res.body.versions[version].deprecated).not.toBeDefined();
done();
});
@ -797,7 +785,7 @@ describe('endpoint unit test', () => {
expect(err2).not.toBeNull();
expect(res2.body.error).toBeDefined();
expect(res2.body.error).toMatch(/user only_unpublish is not allowed to publish package @scope\/deprecate/);
})
});
test('should deprecate multiple packages', async (done) => {
await putPackage(request(app), `/${pkgName}`, generatePackageMetadata(pkgName, '1.0.1'), token);
@ -807,11 +795,11 @@ describe('endpoint unit test', () => {
deprecated: 'get deprecated',
};
await putPackage(request(app), `/${encodeScopedUri(pkgName)}`, pkg, token);
const [,res] = await getPackage(request(app), '', pkgName);
const [, res] = await getPackage(request(app), '', pkgName);
expect(res.body.versions[version].deprecated).toEqual('get deprecated');
expect(res.body.versions['1.0.1'].deprecated).toEqual('get deprecated');
done()
})
done();
});
});
});
});

@ -1,16 +1,14 @@
import path from "path";
import path from 'path';
import express from 'express';
import request from 'request';
import {API_ERROR} from '@verdaccio/dev-commons';
import {parseConfigFile} from "@verdaccio/utils";
import { API_ERROR } from '@verdaccio/dev-commons';
import { parseConfigFile } from '@verdaccio/utils';
import { setup } from '@verdaccio/logger';
import endPointAPI from '../../src';
setup([
{type: 'stdout', format: 'pretty', level: 'trace'}
]);
setup([{ type: 'stdout', format: 'pretty', level: 'trace' }]);
const app = express();
const server = require('http').createServer(app);
@ -23,10 +21,10 @@ describe('basic system test', () => {
let port;
jest.setTimeout(20000);
beforeAll(async function(done) {
beforeAll(async function (done) {
const config = parseConfigFile(parseConfigurationFile('basic.yaml'));
app.use(await endPointAPI(config));
server.listen(0, function() {
server.listen(0, function () {
port = server.address().port;
done();
});
@ -36,24 +34,30 @@ describe('basic system test', () => {
server.close(done);
});
test('server should respond on /', done => {
request({
url: 'http://localhost:' + port + '/',
}, function(err, res, body) {
expect(err).toBeNull();
expect(body).toMatch(/Verdaccio/);
done();
});
test('server should respond on /', (done) => {
request(
{
url: 'http://localhost:' + port + '/',
},
function (err, res, body) {
expect(err).toBeNull();
expect(body).toMatch(/Verdaccio/);
done();
}
);
});
test('server should respond on /___not_found_package', done => {
request({
json: true,
url: `http://localhost:${port}/___not_found_package`,
}, function(err, res, body) {
expect(err).toBeNull();
expect(body.error).toMatch(API_ERROR.NO_PACKAGE);
done();
});
test('server should respond on /___not_found_package', (done) => {
request(
{
json: true,
url: `http://localhost:${port}/___not_found_package`,
},
function (err, res, body) {
expect(err).toBeNull();
expect(body.error).toMatch(API_ERROR.NO_PACKAGE);
done();
}
);
});
});

@ -1,12 +1,12 @@
import path from 'path';
import request from 'supertest';
import {HEADERS, HTTP_STATUS, HEADER_TYPE, TOKEN_BEARER, TOKEN_BASIC, API_ERROR} from '@verdaccio/dev-commons';
import {mockServer, generateRamdonStorage} from '@verdaccio/mock';
import {buildUserBuffer, buildToken} from '@verdaccio/utils';
import {configExample, DOMAIN_SERVERS, addUser, getPackage, loginUserToken} from '@verdaccio/mock';
import { HEADERS, HTTP_STATUS, HEADER_TYPE, TOKEN_BEARER, TOKEN_BASIC, API_ERROR } from '@verdaccio/dev-commons';
import { mockServer, generateRamdonStorage } from '@verdaccio/mock';
import { buildUserBuffer, buildToken } from '@verdaccio/utils';
import { configExample, DOMAIN_SERVERS, addUser, getPackage, loginUserToken } from '@verdaccio/mock';
import {setup, logger} from '@verdaccio/logger';
import { setup, logger } from '@verdaccio/logger';
import endPointAPI from '../../src';
@ -22,18 +22,22 @@ describe('endpoint user auth JWT unit test', () => {
let mockRegistry;
const FAKE_TOKEN: string = buildToken(TOKEN_BEARER, 'fake');
beforeAll(async function(done) {
beforeAll(async function (done) {
const mockServerPort = 55546;
const store = generateRamdonStorage();
const configForTest = configExample({
storage: store,
uplinks: {
remote: {
url: `http://${DOMAIN_SERVERS}:${mockServerPort}`
}
const configForTest = configExample(
{
storage: store,
uplinks: {
remote: {
url: `http://${DOMAIN_SERVERS}:${mockServerPort}`,
},
},
self_path: store,
},
self_path: store
}, 'jwt.yaml', __dirname);
'jwt.yaml',
__dirname
);
app = await endPointAPI(configForTest);
const binPath = require.resolve('verdaccio/bin/verdaccio');
@ -42,7 +46,7 @@ describe('endpoint user auth JWT unit test', () => {
done();
});
afterAll(function(done) {
afterAll(function (done) {
const [registry, pid] = mockRegistry;
registry.stop();
logger.info(`registry ${pid} has been stopped`);
@ -86,12 +90,13 @@ describe('endpoint user auth JWT unit test', () => {
const token = buildUserBuffer(credentials.name, credentials.password).toString('base64');
// put should exist in request
// @ts-ignore
request(app).put(`/-/user/org.couchdb.user:${credentials.name}/-rev/undefined`)
request(app)
.put(`/-/user/org.couchdb.user:${credentials.name}/-rev/undefined`)
.send(credentials)
.set(HEADERS.AUTHORIZATION, buildToken(TOKEN_BASIC, token))
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.CREATED)
.end(function(err, res) {
.end(function (err, res) {
expect(err).toBeNull();
expect(res.body.ok).toBeDefined();
expect(res.body.token).toBeDefined();

@ -1,16 +1,14 @@
import path from 'path';
import request from 'supertest';
import {setup, logger} from '@verdaccio/logger'
import { setup, logger } from '@verdaccio/logger';
setup([]);
import { HEADERS, HTTP_STATUS } from '@verdaccio/dev-commons';
import { generateRamdonStorage, mockServer, configExample, DOMAIN_SERVERS } from '@verdaccio/mock';
import endPointAPI from '../../src';
import {generateRamdonStorage, mockServer, configExample, DOMAIN_SERVERS} from '@verdaccio/mock';
describe('api with no limited access configuration', () => {
let app;
let mockRegistry;
@ -19,14 +17,18 @@ describe('api with no limited access configuration', () => {
beforeAll(async (done) => {
const mockServerPort = 55530;
const configForTest = configExample({
self_path: store,
uplinks: {
remote: {
url: `http://${DOMAIN_SERVERS}:${mockServerPort}`
}
const configForTest = configExample(
{
self_path: store,
uplinks: {
remote: {
url: `http://${DOMAIN_SERVERS}:${mockServerPort}`,
},
},
},
}, 'pkg.access.yaml', __dirname);
'pkg.access.yaml',
__dirname
);
app = await endPointAPI(configForTest);
const binPath = require.resolve('verdaccio/bin/verdaccio');
@ -35,8 +37,7 @@ describe('api with no limited access configuration', () => {
done();
});
afterAll(function(done) {
afterAll(function (done) {
const [registry, pid] = mockRegistry;
registry.stop();
logger.info(`registry ${pid} has been stopped`);
@ -45,16 +46,14 @@ describe('api with no limited access configuration', () => {
});
describe('test proxy packages partially restricted', () => {
test('should test fails on fetch endpoint /-/not-found', (done) => {
request(app)
// @ts-ignore
// @ts-ignore
.get('/not-found-for-sure')
.set(HEADERS.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HEADERS.CONTENT_TYPE, /json/)
.expect(HTTP_STATUS.NOT_FOUND)
.end(function(err) {
.end(function (err) {
if (err) {
return done(err);
}
@ -70,7 +69,7 @@ describe('api with no limited access configuration', () => {
.set(HEADERS.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HEADERS.CONTENT_TYPE, /json/)
.expect(HTTP_STATUS.OK)
.end(function(err) {
.end(function (err) {
if (err) {
return done(err);
}
@ -86,7 +85,7 @@ describe('api with no limited access configuration', () => {
.set(HEADERS.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HEADERS.CONTENT_TYPE, /json/)
.expect(HTTP_STATUS.OK)
.end(function(err) {
.end(function (err) {
if (err) {
return done(err);
}
@ -95,5 +94,4 @@ describe('api with no limited access configuration', () => {
});
});
});
});

@ -1,17 +1,16 @@
import path from 'path';
import request from 'supertest';
import {mockServer} from '@verdaccio/mock';
import {API_ERROR, HTTP_STATUS, SUPPORT_ERRORS} from '@verdaccio/dev-commons';
import {setup, logger} from '@verdaccio/logger';
import { mockServer } from '@verdaccio/mock';
import { API_ERROR, HTTP_STATUS, SUPPORT_ERRORS } from '@verdaccio/dev-commons';
import { setup, logger } from '@verdaccio/logger';
import {generateRamdonStorage, getNewToken, getProfile, postProfile, configExample, DOMAIN_SERVERS} from '@verdaccio/mock';
import { generateRamdonStorage, getNewToken, getProfile, postProfile, configExample, DOMAIN_SERVERS } from '@verdaccio/mock';
import endPointAPI from '../../src';
setup([]);
describe('endpoint user profile', () => {
let app;
let mockRegistry;
@ -20,15 +19,19 @@ describe('endpoint user profile', () => {
beforeAll(async (done) => {
const store = generateRamdonStorage();
const mockServerPort = 55544;
const configForTest = configExample({
storage: store,
uplinks: {
remote: {
url: `http://${DOMAIN_SERVERS}:${mockServerPort}`
}
const configForTest = configExample(
{
storage: store,
uplinks: {
remote: {
url: `http://${DOMAIN_SERVERS}:${mockServerPort}`,
},
},
self_path: store,
},
self_path: store
}, 'profile.yaml', __dirname);
'profile.yaml',
__dirname
);
app = await endPointAPI(configForTest);
const binPath = require.resolve('verdaccio/bin/verdaccio');
@ -37,7 +40,7 @@ describe('endpoint user profile', () => {
done();
});
afterAll(function(done) {
afterAll(function (done) {
const [registry, pid] = mockRegistry;
registry.stop();
logger.info(`registry ${pid} has been stopped`);
@ -45,7 +48,6 @@ describe('endpoint user profile', () => {
done();
});
test('should fetch a profile of logged user', async (done) => {
const credentials = { name: 'JotaJWT', password: 'secretPass' };
const token = await getNewToken(request(app), credentials);
@ -62,7 +64,7 @@ describe('endpoint user profile', () => {
password: {
new: '12345678',
old: credentials.password,
}
},
};
const token = await getNewToken(request(app), credentials);
const [err1, res1] = await postProfile(request(app), body, token);
@ -78,7 +80,7 @@ describe('endpoint user profile', () => {
password: {
new: 'p1',
old: credentials.password,
}
},
};
const token = await getNewToken(request(app), credentials);
const [, resp] = await postProfile(request(app), body, token, HTTP_STATUS.UNAUTHORIZED);
@ -94,7 +96,7 @@ describe('endpoint user profile', () => {
test('should report TFA is disabled', async (done) => {
const credentials = { name: 'userTest2002', password: 'secretPass002' };
const body = {
tfa: {}
tfa: {},
};
const token = await getNewToken(request(app), credentials);
const [, resp] = await postProfile(request(app), body, token, HTTP_STATUS.SERVICE_UNAVAILABLE);

@ -1,14 +1,14 @@
import path from "path";
import path from 'path';
import _ from 'lodash';
import { Config as AppConfig } from '@verdaccio/config';
import {Config, UpLinkConf} from '@verdaccio/types';
import { Config, UpLinkConf } from '@verdaccio/types';
import { VerdaccioError } from '@verdaccio/commons-api';
import {IProxy} from '@verdaccio/dev-types';
import { API_ERROR, HTTP_STATUS } from "@verdaccio/dev-commons";
import { IProxy } from '@verdaccio/dev-types';
import { API_ERROR, HTTP_STATUS } from '@verdaccio/dev-commons';
import { mockServer, configExample, DOMAIN_SERVERS } from '@verdaccio/mock';
import { ProxyStorage } from '@verdaccio/proxy';
import {setup, logger} from '@verdaccio/logger';
import { setup, logger } from '@verdaccio/logger';
setup([]);
@ -16,7 +16,7 @@ describe('UpStorge', () => {
const mockServerPort = 55547;
let mockRegistry;
const uplinkDefault = {
url: `http://0.0.0.0:${mockServerPort}`
url: `http://0.0.0.0:${mockServerPort}`,
};
const generateProxy = (config: UpLinkConf = uplinkDefault) => {
const appConfig: Config = new AppConfig(configExample());
@ -30,7 +30,7 @@ describe('UpStorge', () => {
mockRegistry = await mockServer(mockServerPort, { storePath, silence: true }).init(binPath);
});
afterAll(function(done) {
afterAll(function (done) {
const [registry, pid] = mockRegistry;
registry.stop();
logger.info(`registry ${pid} has been stopped`);
@ -59,7 +59,7 @@ describe('UpStorge', () => {
test('should be get remote metadata with etag', (done) => {
const proxy = generateProxy();
proxy.getRemoteMetadata('jquery', {etag: '123456'}, (err, data, etag) => {
proxy.getRemoteMetadata('jquery', { etag: '123456' }, (err, data, etag) => {
expect(err).toBeNull();
expect(_.isString(etag)).toBeTruthy();
expect(data.name).toBe('jquery');
@ -70,7 +70,7 @@ describe('UpStorge', () => {
test('should be get remote metadata package does not exist', (done) => {
const proxy = generateProxy();
proxy.getRemoteMetadata('@verdaccio/fake-package', {etag: '123456'}, (err) => {
proxy.getRemoteMetadata('@verdaccio/fake-package', { etag: '123456' }, (err) => {
expect(err).not.toBeNull();
expect(err.statusCode).toBe(HTTP_STATUS.NOT_FOUND);
expect(err.message).toMatch(API_ERROR.NOT_PACKAGE_UPLINK);
@ -79,23 +79,21 @@ describe('UpStorge', () => {
});
});
describe('UpStorge::fetchTarball', () => {
test('should fetch a tarball from uplink', (done) => {
const proxy = generateProxy();
const tarball = `http://${DOMAIN_SERVERS}:${mockServerPort}/jquery/-/jquery-1.5.1.tgz`;
const stream = proxy.fetchTarball(tarball);
stream.on('error', function(err) {
stream.on('error', function (err) {
expect(err).toBeNull();
done();
});
stream.on('content-length', function(contentLength) {
stream.on('content-length', function (contentLength) {
expect(contentLength).toBeDefined();
done();
});
});
test('should throw a 404 on fetch a tarball from uplink', (done) => {
@ -103,7 +101,7 @@ describe('UpStorge', () => {
const tarball = `http://${DOMAIN_SERVERS}:${mockServerPort}/jquery/-/no-exist-1.5.1.tgz`;
const stream = proxy.fetchTarball(tarball);
stream.on('error', function(err: VerdaccioError) {
stream.on('error', function (err: VerdaccioError) {
expect(err).not.toBeNull();
expect(err.statusCode).toBe(HTTP_STATUS.NOT_FOUND);
expect(err.message).toMatch(API_ERROR.NOT_FILE_UPLINK);
@ -111,11 +109,10 @@ describe('UpStorge', () => {
done();
});
stream.on('content-length', function(contentLength) {
stream.on('content-length', function (contentLength) {
expect(contentLength).toBeDefined();
done();
});
});
test('should be offline uplink', (done) => {
@ -126,14 +123,14 @@ describe('UpStorge', () => {
// to test a uplink is offline we have to be try 3 times
// the default failed request are set to 2
process.nextTick(function() {
stream.on('error', function(err) {
process.nextTick(function () {
stream.on('error', function (err) {
expect(err).not.toBeNull();
// expect(err.statusCode).toBe(404);
expect(proxy.failed_requests).toBe(1);
const streamSecondTry = proxy.fetchTarball(tarball);
streamSecondTry.on('error', function(err) {
streamSecondTry.on('error', function (err) {
expect(err).not.toBeNull();
/*
code: 'ENOTFOUND',
@ -142,7 +139,7 @@ describe('UpStorge', () => {
// expect(err.statusCode).toBe(404);
expect(proxy.failed_requests).toBe(2);
const streamThirdTry = proxy.fetchTarball(tarball);
streamThirdTry.on('error', function(err: VerdaccioError) {
streamThirdTry.on('error', function (err: VerdaccioError) {
expect(err).not.toBeNull();
expect(err.statusCode).toBe(HTTP_STATUS.INTERNAL_ERROR);
expect(proxy.failed_requests).toBe(2);
@ -156,16 +153,13 @@ describe('UpStorge', () => {
});
describe('UpStorge::isUplinkValid', () => {
describe('valid use cases', () => {
const validateUpLink = (
url: string,
tarBallUrl = `${url}/artifactory/api/npm/npm/pk1-juan/-/pk1-juan-1.0.7.tgz`) => {
const validateUpLink = (url: string, tarBallUrl = `${url}/artifactory/api/npm/npm/pk1-juan/-/pk1-juan-1.0.7.tgz`) => {
const uplinkConf = { url };
const proxy: IProxy = generateProxy(uplinkConf);
return proxy.isUplinkValid(tarBallUrl);
}
};
test('should validate tarball path against uplink', () => {
expect(validateUpLink('https://artifactory.mydomain.com')).toBe(true);
@ -190,8 +184,7 @@ describe('UpStorge', () => {
// corner case https://github.com/verdaccio/verdaccio/issues/571
test('should validate tarball path against uplink case#6', () => {
// same protocol, same domain, port === 443 which is also the standard for https
expect(validateUpLink('https://my.domain.test',
`https://my.domain.test:443/artifactory/api/npm/npm/pk1-juan/-/pk1-juan-1.0.7.tgz`)).toBe(true);
expect(validateUpLink('https://my.domain.test', `https://my.domain.test:443/artifactory/api/npm/npm/pk1-juan/-/pk1-juan-1.0.7.tgz`)).toBe(true);
});
test('should validate tarball path against uplink case#7', () => {
@ -253,7 +246,5 @@ describe('UpStorge', () => {
expect(proxy.isUplinkValid(tarBallUrl)).toBe(false);
});
});
});
});

@ -1,69 +1,35 @@
import path from 'path';
import fs from 'fs';
import {Writable} from 'stream';
import { Writable } from 'stream';
import { Config as AppConfig } from '@verdaccio/config';
import { Storage } from '@verdaccio/store';
import {IStorageHandler} from '@verdaccio/dev-types';
import { IStorageHandler } from '@verdaccio/dev-types';
import {Config} from '@verdaccio/types';
import {API_ERROR, HTTP_STATUS} from '@verdaccio/dev-commons';
import {mockServer, configExample, DOMAIN_SERVERS, generateRamdonStorage} from '@verdaccio/mock';
import { Config } from '@verdaccio/types';
import { API_ERROR, HTTP_STATUS } from '@verdaccio/dev-commons';
import { mockServer, configExample, DOMAIN_SERVERS, generateRamdonStorage } from '@verdaccio/mock';
import {setup, logger} from '@verdaccio/logger';
import { setup, logger } from '@verdaccio/logger';
setup([]);
const mockServerPort = 55548;
const generateStorage = async function() {
const generateStorage = async function () {
const storagePath = generateRamdonStorage();
const storageConfig = configExample({
self_path: storagePath,
storage: storagePath,
uplinks: {
npmjs: {
url: `http://${DOMAIN_SERVERS}:${mockServerPort}`
}
}
}, 'store.spec.yaml', __dirname);
const config: Config = new AppConfig(storageConfig);
const store: IStorageHandler = new Storage(config);
await store.init(config, []);
return store;
};
const generateSameUplinkStorage = async function() {
const storagePath = generateRamdonStorage();
console.log("-->storagePath", storagePath);
const storageConfig = configExample({
self_path: storagePath,
storage: storagePath,
packages: {
jquery: {
access: ['$all'],
publish: ['$all'],
proxy: ['cached'],
const storageConfig = configExample(
{
self_path: storagePath,
storage: storagePath,
uplinks: {
npmjs: {
url: `http://${DOMAIN_SERVERS}:${mockServerPort}`,
},
},
'@jquery/*': {
access: ['$all'],
publish: ['$all'],
proxy: ['notcached'],
}
},
uplinks: {
cached: {
url: `http://${DOMAIN_SERVERS}:${mockServerPort}`,
cache: true,
},
notcached: {
url: `http://${DOMAIN_SERVERS}:${mockServerPort}`,
cache: false,
}
}
}, 'store.spec.yaml', __dirname);
'store.spec.yaml',
__dirname
);
const config: Config = new AppConfig(storageConfig);
const store: IStorageHandler = new Storage(config);
@ -72,23 +38,65 @@ const generateSameUplinkStorage = async function() {
return store;
};
const createNullStream = () => new Writable({
write: function(chunk, encoding, next) {
next();
}
});
const generateSameUplinkStorage = async function () {
const storagePath = generateRamdonStorage();
console.log('-->storagePath', storagePath);
const storageConfig = configExample(
{
self_path: storagePath,
storage: storagePath,
packages: {
jquery: {
access: ['$all'],
publish: ['$all'],
proxy: ['cached'],
},
'@jquery/*': {
access: ['$all'],
publish: ['$all'],
proxy: ['notcached'],
},
},
uplinks: {
cached: {
url: `http://${DOMAIN_SERVERS}:${mockServerPort}`,
cache: true,
},
notcached: {
url: `http://${DOMAIN_SERVERS}:${mockServerPort}`,
cache: false,
},
},
},
'store.spec.yaml',
__dirname
);
const config: Config = new AppConfig(storageConfig);
const store: IStorageHandler = new Storage(config);
await store.init(config, []);
return store;
};
const createNullStream = () =>
new Writable({
write: function (chunk, encoding, next) {
next();
},
});
describe('StorageTest', () => {
let mockRegistry;
beforeAll(async done => {
beforeAll(async (done) => {
const binPath = require.resolve('verdaccio/bin/verdaccio');
const storePath = path.join(__dirname, '/mock/store');
mockRegistry = await mockServer(mockServerPort, { storePath, silence: true }).init(binPath);
done();
});
afterAll(function(done) {
afterAll(function (done) {
const [registry, pid] = mockRegistry;
registry.stop();
logger.info(`registry ${pid} has been stopped`);
@ -185,7 +193,7 @@ describe('StorageTest', () => {
test.skip('should not touch if the package exists and has no uplinks', async (done) => {
const storagePath = generateRamdonStorage();
const storage: IStorageHandler = await generateStorage() as IStorageHandler;
const storage: IStorageHandler = (await generateStorage()) as IStorageHandler;
const metadataSource = path.join(__dirname, '../../partials/metadata');
const metadataPath = path.join(storagePath, 'npm_test/package.json');

@ -2,16 +2,12 @@ import path from 'path';
import request from 'supertest';
import _ from 'lodash';
import {
HEADERS,
HTTP_STATUS,
HEADER_TYPE, TOKEN_BEARER, API_ERROR, SUPPORT_ERRORS,
} from '@verdaccio/dev-commons';
import { HEADERS, HTTP_STATUS, HEADER_TYPE, TOKEN_BEARER, API_ERROR, SUPPORT_ERRORS } from '@verdaccio/dev-commons';
import {buildToken} from '@verdaccio/utils';
import {generateRamdonStorage, DOMAIN_SERVERS, mockServer, getNewToken, configExample} from '@verdaccio/mock';
import { buildToken } from '@verdaccio/utils';
import { generateRamdonStorage, DOMAIN_SERVERS, mockServer, getNewToken, configExample } from '@verdaccio/mock';
import {setup, logger} from '@verdaccio/logger';
import { setup, logger } from '@verdaccio/logger';
import endPointAPI from '../../src';
@ -27,13 +23,13 @@ const generateTokenCLI = async (app, token, payload): Promise<any> => {
.send(JSON.stringify(payload))
.set(HEADERS.AUTHORIZATION, buildToken(TOKEN_BEARER, token))
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.end(function(err, resp) {
.end(function (err, resp) {
if (err) {
return reject([err, resp]);
}
resolve([err, resp]);
});
});
});
};
const deleteTokenCLI = async (app, token, tokenToDelete): Promise<any> => {
@ -43,7 +39,7 @@ const deleteTokenCLI = async (app, token, tokenToDelete): Promise<any> => {
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.set(HEADERS.AUTHORIZATION, buildToken(TOKEN_BEARER, token))
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.end(function(err, resp) {
.end(function (err, resp) {
if (err) {
return reject([err, resp]);
}
@ -57,18 +53,22 @@ describe('endpoint unit test', () => {
let mockRegistry;
let token;
beforeAll(async function(done) {
beforeAll(async function (done) {
const store = generateRamdonStorage();
const mockServerPort = 55543;
const configForTest = configExample({
storage: store,
self_path: store,
uplinks: {
npmjs: {
url: `http://${DOMAIN_SERVERS}:${mockServerPort}`
}
}
}, 'token.spec.yaml', __dirname);
const configForTest = configExample(
{
storage: store,
self_path: store,
uplinks: {
npmjs: {
url: `http://${DOMAIN_SERVERS}:${mockServerPort}`,
},
},
},
'token.spec.yaml',
__dirname
);
app = await endPointAPI(configForTest);
const binPath = require.resolve('verdaccio/bin/verdaccio');
@ -78,7 +78,7 @@ describe('endpoint unit test', () => {
done();
});
afterAll(function(done) {
afterAll(function (done) {
const [registry, pid] = mockRegistry;
registry.stop();
logger.info(`registry ${pid} has been stopped`);
@ -93,12 +93,12 @@ describe('endpoint unit test', () => {
.set(HEADERS.AUTHORIZATION, buildToken(TOKEN_BEARER, token))
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.OK)
.end(function(err, resp) {
.end(function (err, resp) {
if (err) {
return done(err);
}
const { objects, urls} = resp.body;
const { objects, urls } = resp.body;
expect(objects).toHaveLength(0);
expect(urls.next).toEqual('');
done();
@ -109,7 +109,7 @@ describe('endpoint unit test', () => {
await generateTokenCLI(app, token, {
password: credentials.password,
readonly: false,
cidr_whitelist: []
cidr_whitelist: [],
});
request(app)
@ -117,12 +117,12 @@ describe('endpoint unit test', () => {
.set(HEADERS.AUTHORIZATION, buildToken(TOKEN_BEARER, token))
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.OK)
.end(function(err, resp) {
.end(function (err, resp) {
if (err) {
return done(err);
}
const { objects, urls} = resp.body;
const { objects, urls } = resp.body;
expect(objects).toHaveLength(1);
const [tokenGenerated] = objects;
@ -141,7 +141,7 @@ describe('endpoint unit test', () => {
const res = await generateTokenCLI(app, token, {
password: credentials.password,
readonly: false,
cidr_whitelist: []
cidr_whitelist: [],
});
const t = res[1].body.token;
@ -153,7 +153,7 @@ describe('endpoint unit test', () => {
.set(HEADERS.AUTHORIZATION, buildToken(TOKEN_BEARER, token))
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.OK)
.end(function(err) {
.end(function (err) {
if (err) {
return done(err);
}
@ -171,7 +171,7 @@ describe('endpoint unit test', () => {
await generateTokenCLI(app, token, {
password: 'wrongPassword',
readonly: false,
cidr_whitelist: []
cidr_whitelist: [],
});
done();
} catch (e) {
@ -187,7 +187,7 @@ describe('endpoint unit test', () => {
try {
const res = await generateTokenCLI(app, token, {
password: credentials.password,
cidr_whitelist: []
cidr_whitelist: [],
});
expect(res[0]).toBeNull();

@ -1,12 +1,11 @@
import path from 'path';
import request from 'supertest';
import { HEADERS, API_ERROR, HTTP_STATUS, HEADER_TYPE, DIST_TAGS } from '@verdaccio/dev-commons';
import { HEADERS, API_ERROR, HTTP_STATUS, HEADER_TYPE, DIST_TAGS} from '@verdaccio/dev-commons';
import { addUser, mockServer, DOMAIN_SERVERS, configExample, generateRamdonStorage } from '@verdaccio/mock';
import {addUser, mockServer, DOMAIN_SERVERS, configExample, generateRamdonStorage} from '@verdaccio/mock';
import {setup, logger} from '@verdaccio/logger';
import { setup, logger } from '@verdaccio/logger';
import endPointAPI from '../../src';
import forbiddenPlace from './partials/forbidden-place';
import publishMetadata from './partials/publish-api';
@ -23,15 +22,19 @@ describe('endpoint web unit test', () => {
beforeAll(async (done) => {
const store = generateRamdonStorage();
const mockServerPort = 55523;
const configForTest = configExample({
storage: store,
self_path: store,
uplinks: {
remote: {
url: `http://${DOMAIN_SERVERS}:${mockServerPort}`
}
const configForTest = configExample(
{
storage: store,
self_path: store,
uplinks: {
remote: {
url: `http://${DOMAIN_SERVERS}:${mockServerPort}`,
},
},
},
}, 'web.yaml', __dirname);
'web.yaml',
__dirname
);
app = await endPointAPI(configForTest);
const binPath = require.resolve('verdaccio/bin/verdaccio');
const storePath = path.join(__dirname, '/mock/store');
@ -39,7 +42,7 @@ describe('endpoint web unit test', () => {
done();
});
afterAll(function(done) {
afterAll(function (done) {
const [registry, pid] = mockRegistry;
registry.stop();
logger.info(`registry ${pid} has been stopped`);
@ -55,19 +58,15 @@ describe('endpoint web unit test', () => {
.send(JSON.stringify(publishMetadata))
.expect(HTTP_STATUS.CREATED);
await request(app)
.put('/forbidden-place')
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.send(JSON.stringify(forbiddenPlace))
.expect(HTTP_STATUS.CREATED);
await request(app).put('/forbidden-place').set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON).send(JSON.stringify(forbiddenPlace)).expect(HTTP_STATUS.CREATED);
});
describe('Packages', () => {
test('should display all packages', (done) => {
request(app)
.get('/-/verdaccio/packages' )
.get('/-/verdaccio/packages')
.expect(HTTP_STATUS.OK)
.end(function(err, res) {
.end(function (err, res) {
expect(res.body).toHaveLength(1);
done();
});
@ -78,7 +77,7 @@ describe('endpoint web unit test', () => {
.get('/-/verdaccio/package/readme/@scope/pk1-test')
.expect(HTTP_STATUS.OK)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.TEXT_CHARSET)
.end(function(err, res) {
.end(function (err, res) {
expect(res.text).toMatch('<h1 id="test">test</h1>\n');
done();
});
@ -90,7 +89,7 @@ describe('endpoint web unit test', () => {
.get('/-/verdaccio/package/readme/@scope/404')
.expect(HTTP_STATUS.OK)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.TEXT_CHARSET)
.end(function(err, res) {
.end(function (err, res) {
expect(res.body.error).toMatch(API_ERROR.NO_PACKAGE);
done();
});
@ -101,7 +100,7 @@ describe('endpoint web unit test', () => {
.get('/-/verdaccio/sidebar/@scope/pk1-test')
.expect(HTTP_STATUS.OK)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.end(function(err, res) {
.end(function (err, res) {
// console.log("-->", res);
// expect(err).toBeNull();
@ -121,7 +120,7 @@ describe('endpoint web unit test', () => {
.get('/-/verdaccio/sidebar/@scope/pk1-test?v=1.0.6')
.expect(HTTP_STATUS.OK)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.end(function(err, res) {
.end(function (err, res) {
const sideBarInfo = res.body;
const latestVersion = publishMetadata.versions[publishMetadata[DIST_TAGS].latest];
@ -138,7 +137,7 @@ describe('endpoint web unit test', () => {
.get('/-/verdaccio/sidebar/@scope/404')
.expect(HTTP_STATUS.NOT_FOUND)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.end(function() {
.end(function () {
done();
});
});
@ -148,19 +147,18 @@ describe('endpoint web unit test', () => {
.get('/-/verdaccio/sidebar/@scope/pk1-test?v=0.0.0-not-found')
.expect(HTTP_STATUS.NOT_FOUND)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.end(function() {
.end(function () {
done();
});
});
});
describe('Search', () => {
test('should search pk1-test', (done) => {
request(app)
.get('/-/verdaccio/search/scope')
.expect(HTTP_STATUS.OK)
.end(function(err, res) {
.end(function (err, res) {
expect(res.body).toHaveLength(1);
done();
});
@ -170,7 +168,7 @@ describe('endpoint web unit test', () => {
request(app)
.get('/-/verdaccio/search/@')
.expect(HTTP_STATUS.OK)
.end(function(err, res) {
.end(function (err, res) {
// in a normal world, the output would be 1
// https://github.com/verdaccio/verdaccio/issues/345
// should fix this
@ -183,7 +181,7 @@ describe('endpoint web unit test', () => {
request(app)
.get('/-/verdaccio/search/forbidden-place')
.expect(HTTP_STATUS.OK)
.end(function(err, res) {
.end(function (err, res) {
// this is expected since we are not logged
// and forbidden-place is allow_access: 'nobody'
expect(res.body).toHaveLength(0);
@ -203,10 +201,10 @@ describe('endpoint web unit test', () => {
.post('/-/verdaccio/login')
.send({
username: credentials.name,
password: credentials.password
password: credentials.password,
})
.expect(HTTP_STATUS.OK)
.end(function(err, res) {
.end(function (err, res) {
expect(err).toBeNull();
expect(res.body.error).toBeUndefined();
expect(res.body.token).toBeDefined();
@ -219,13 +217,15 @@ describe('endpoint web unit test', () => {
test('should fails on log unvalid user', (done) => {
request(app)
.post('/-/verdaccio/login')
.send(JSON.stringify({
username: 'fake',
password: 'fake'
}))
.send(
JSON.stringify({
username: 'fake',
password: 'fake',
})
)
// FIXME: there should be 401
.expect(HTTP_STATUS.OK)
.end(function(err, res) {
.end(function (err, res) {
expect(res.body.error).toMatch(/bad username\/password, access denied/);
done();
});

@ -2,7 +2,7 @@ import assert from 'assert';
import UrlNode from 'url';
import _ from 'lodash';
import { ErrorCode, isObject, getLatestVersion, tagVersion, validateName } from '@verdaccio/utils';
import {API_ERROR, DIST_TAGS, HTTP_STATUS, STORAGE, SUPPORT_ERRORS, USERS} from '@verdaccio/dev-commons';
import { API_ERROR, DIST_TAGS, HTTP_STATUS, STORAGE, SUPPORT_ERRORS, USERS } from '@verdaccio/dev-commons';
import { createTarballHash } from '@verdaccio/utils';
import { loadPlugin } from '@verdaccio/loaders';
import LocalDatabase from '@verdaccio/local-storage';
@ -24,19 +24,20 @@ import {
Author,
CallbackAction,
onSearchPackage,
onEndSearchPackage, StorageUpdateCallback,
onEndSearchPackage,
StorageUpdateCallback,
} from '@verdaccio/types';
import { IStorage, StringValue } from '@verdaccio/dev-types';
import { VerdaccioError } from '@verdaccio/commons-api';
import {
prepareSearchPackage ,
prepareSearchPackage,
generatePackageTemplate,
normalizePackage,
generateRevision,
getLatestReadme,
cleanUpReadme,
normalizeContributors
normalizeContributors,
} from './storage-utils';
/**
@ -60,7 +61,7 @@ class LocalStorage implements IStorage {
return callback(ErrorCode.getNotFound('this package cannot be added'));
}
storage.createPackage(name, generatePackageTemplate(name), err => {
storage.createPackage(name, generatePackageTemplate(name), (err) => {
// FIXME: it will be fixed here https://github.com/verdaccio/verdaccio/pull/1360
// @ts-ignore
if (_.isNull(err) === false && (err.code === STORAGE.FILE_EXIST_ERROR || err.code === HTTP_STATUS.CONFLICT)) {
@ -200,7 +201,7 @@ class LocalStorage implements IStorage {
if (change) {
this.logger.debug({ name }, 'updating package @{name} info');
this._writePackage(name, packageLocalJson, function(err): void {
this._writePackage(name, packageLocalJson, function (err): void {
callback(err, packageLocalJson);
});
} else {
@ -337,11 +338,11 @@ class LocalStorage implements IStorage {
*/
public changePackage(name: string, incomingPkg: Package, revision: string | void, callback: Callback): void {
if (!isObject(incomingPkg.versions) || !isObject(incomingPkg[DIST_TAGS])) {
this.logger.debug({name}, `changePackage bad data for @{name}`);
this.logger.debug({ name }, `changePackage bad data for @{name}`);
return callback(ErrorCode.getBadData());
}
this.logger.debug({name}, `changePackage udapting package for @{name}`);
this.logger.debug({ name }, `changePackage udapting package for @{name}`);
this._updatePackage(
name,
(localData: Package, cb: CallbackAction): void => {
@ -378,7 +379,7 @@ class LocalStorage implements IStorage {
localData[DIST_TAGS] = incomingPkg[DIST_TAGS];
cb(null);
},
function(err): void {
function (err): void {
if (err) {
return callback(err);
}
@ -434,10 +435,10 @@ class LocalStorage implements IStorage {
const _transform = uploadStream._transform;
const storage = this._getLocalStorage(name);
uploadStream.abort = function(): void {};
uploadStream.done = function(): void {};
uploadStream.abort = function (): void {};
uploadStream.done = function (): void {};
uploadStream._transform = function(data, ...args): void {
uploadStream._transform = function (data, ...args): void {
shaOneHash.update(data);
// measure the length for validation reasons
length += data.length;
@ -463,15 +464,15 @@ class LocalStorage implements IStorage {
const writeStream: IUploadTarball = storage.writeTarball(filename);
writeStream.on('error', err => {
writeStream.on('error', (err) => {
// @ts-ignore
if (err.code === STORAGE.FILE_EXIST_ERROR || err.code === HTTP_STATUS.CONFLICT) {
uploadStream.emit('error', ErrorCode.getConflict());
uploadStream.abort();
// @ts-ignore
// @ts-ignore
} else if (err.code === STORAGE.NO_SUCH_FILE_ERROR || err.code === HTTP_STATUS.NOT_FOUND) {
// check if package exists to throw an appropriate message
this.getPackageMetadata(name, function(_err: VerdaccioError, _res: Package): void {
this.getPackageMetadata(name, function (_err: VerdaccioError, _res: Package): void {
if (_err) {
uploadStream.emit('error', _err);
} else {
@ -483,7 +484,7 @@ class LocalStorage implements IStorage {
}
});
writeStream.on('open', function(): void {
writeStream.on('open', function (): void {
// re-emitting open because it's handled in storage.js
uploadStream.emit('open');
});
@ -497,7 +498,7 @@ class LocalStorage implements IStorage {
};
cb(null);
},
function(err): void {
function (err): void {
if (err) {
uploadStream.emit('error', err);
} else {
@ -507,11 +508,11 @@ class LocalStorage implements IStorage {
);
});
uploadStream.abort = function(): void {
uploadStream.abort = function (): void {
writeStream.abort();
};
uploadStream.done = function(): void {
uploadStream.done = function (): void {
if (!length) {
uploadStream.emit('error', ErrorCode.getBadData('refusing to accept zero-length file'));
writeStream.abort();
@ -569,13 +570,13 @@ class LocalStorage implements IStorage {
const readTarballStream = storage.readTarball(filename);
const e404 = ErrorCode.getNotFound;
stream.abort = function(): void {
stream.abort = function (): void {
if (_.isNil(readTarballStream) === false) {
readTarballStream.abort();
}
};
readTarballStream.on('error', function(err) {
readTarballStream.on('error', function (err) {
// @ts-ignore
if (err.code === STORAGE.NO_SUCH_FILE_ERROR || err.code === HTTP_STATUS.NOT_FOUND) {
stream.emit('error', e404('no such file available'));
@ -584,11 +585,11 @@ class LocalStorage implements IStorage {
}
});
readTarballStream.on('content-length', function(content): void {
readTarballStream.on('content-length', function (content): void {
stream.emit('content-length', content);
});
readTarballStream.on('open', function(): void {
readTarballStream.on('open', function (): void {
// re-emitting open because it's handled in storage.js
stream.emit('open');
readTarballStream.pipe(stream);
@ -786,21 +787,21 @@ class LocalStorage implements IStorage {
}
private _deleteAttachments(storage: any, attachments: string[], callback: Callback): void {
this.logger.debug({l: attachments.length }, `[storage/_deleteAttachments] delete attachments total: @{l}`);
const unlinkNext = function(cb): void {
this.logger.debug({ l: attachments.length }, `[storage/_deleteAttachments] delete attachments total: @{l}`);
const unlinkNext = function (cb): void {
if (_.isEmpty(attachments)) {
return cb();
}
const attachment = attachments.shift();
storage.deletePackage(attachment, function(): void {
storage.deletePackage(attachment, function (): void {
unlinkNext(cb);
});
};
unlinkNext(function(): void {
unlinkNext(function (): void {
// try to unlink the directory, but ignore errors because it can fail
storage.removePackage(function(err): void {
storage.removePackage(function (err): void {
callback(err);
});
});
@ -849,10 +850,14 @@ class LocalStorage implements IStorage {
logger: this.logger,
};
const plugins: IPluginStorage<Config>[] = loadPlugin<IPluginStorage<Config>>(this.config, this.config.store, plugin_params, (plugin): IPluginStorage<Config> => {
return plugin.getPackageStorage;
});
const plugins: IPluginStorage<Config>[] = loadPlugin<IPluginStorage<Config>>(
this.config,
this.config.store,
plugin_params,
(plugin): IPluginStorage<Config> => {
return plugin.getPackageStorage;
}
);
return _.head(plugins);
}

@ -103,6 +103,4 @@ class Search implements IWebSearch {
const SearchInstance = new Search();
export {
SearchInstance
}
export { SearchInstance };

@ -29,15 +29,13 @@ export function generatePackageTemplate(name: string): Package {
export function normalizePackage(pkg: Package): Package {
const pkgProperties = ['versions', 'dist-tags', '_distfiles', '_attachments', '_uplinks', 'time'];
pkgProperties.forEach(
(key): void => {
const pkgProp = pkg[key];
pkgProperties.forEach((key): void => {
const pkgProp = pkg[key];
if (_.isNil(pkgProp) || isObject(pkgProp) === false) {
pkg[key] = {};
}
if (_.isNil(pkgProp) || isObject(pkgProp) === false) {
pkg[key] = {};
}
);
});
if (_.isString(pkg._rev) === false) {
pkg._rev = STORAGE.DEFAULT_REVISION;
@ -71,7 +69,7 @@ export function getLatestReadme(pkg: Package): string {
// In case of empty readme - trying to get ANY readme in the following order: 'next','beta','alpha','test','dev','canary'
const readmeDistTagsPriority = ['next', 'beta', 'alpha', 'test', 'dev', 'canary'];
readmeDistTagsPriority.forEach(function(tag): string | void {
readmeDistTagsPriority.forEach(function (tag): string | void {
if (readme) {
return readme;
}
@ -131,80 +129,62 @@ export function cleanUpLinksRef(keepUpLinkData: boolean, result: Package): Packa
* @param {*} localStorage
*/
export function checkPackageLocal(name: string, localStorage: IStorage): Promise<any> {
return new Promise(
(resolve, reject): void => {
localStorage.getPackageMetadata(
name,
(err, results): void => {
if (!_.isNil(err) && err.status !== HTTP_STATUS.NOT_FOUND) {
return reject(err);
}
if (results) {
return reject(ErrorCode.getConflict(API_ERROR.PACKAGE_EXIST));
}
return resolve();
}
);
}
);
return new Promise((resolve, reject): void => {
localStorage.getPackageMetadata(name, (err, results): void => {
if (!_.isNil(err) && err.status !== HTTP_STATUS.NOT_FOUND) {
return reject(err);
}
if (results) {
return reject(ErrorCode.getConflict(API_ERROR.PACKAGE_EXIST));
}
return resolve();
});
});
}
export function publishPackage(name: string, metadata: any, localStorage: IStorage): Promise<any> {
return new Promise(
(resolve, reject): void => {
localStorage.addPackage(
name,
metadata,
(err, latest): void => {
if (!_.isNull(err)) {
return reject(err);
} else if (!_.isUndefined(latest)) {
SearchInstance.add(latest);
}
return resolve();
}
);
}
);
return new Promise((resolve, reject): void => {
localStorage.addPackage(name, metadata, (err, latest): void => {
if (!_.isNull(err)) {
return reject(err);
} else if (!_.isUndefined(latest)) {
SearchInstance.add(latest);
}
return resolve();
});
});
}
export function checkPackageRemote(name: string, isAllowPublishOffline: boolean, syncMetadata: Function): Promise<any> {
return new Promise(
(resolve, reject): void => {
syncMetadata(
name,
null,
{},
(err, packageJsonLocal, upLinksErrors): void => {
// something weird
if (err && err.status !== HTTP_STATUS.NOT_FOUND) {
return reject(err);
}
return new Promise((resolve, reject): void => {
syncMetadata(name, null, {}, (err, packageJsonLocal, upLinksErrors): void => {
// something weird
if (err && err.status !== HTTP_STATUS.NOT_FOUND) {
return reject(err);
}
// checking package exist already
if (_.isNil(packageJsonLocal) === false) {
return reject(ErrorCode.getConflict(API_ERROR.PACKAGE_EXIST));
}
// checking package exist already
if (_.isNil(packageJsonLocal) === false) {
return reject(ErrorCode.getConflict(API_ERROR.PACKAGE_EXIST));
}
for (let errorItem = 0; errorItem < upLinksErrors.length; errorItem++) {
// checking error
// if uplink fails with a status other than 404, we report failure
if (_.isNil(upLinksErrors[errorItem][0]) === false) {
if (upLinksErrors[errorItem][0].status !== HTTP_STATUS.NOT_FOUND) {
if (isAllowPublishOffline) {
return resolve();
}
return reject(ErrorCode.getServiceUnavailable(API_ERROR.UPLINK_OFFLINE_PUBLISH));
}
for (let errorItem = 0; errorItem < upLinksErrors.length; errorItem++) {
// checking error
// if uplink fails with a status other than 404, we report failure
if (_.isNil(upLinksErrors[errorItem][0]) === false) {
if (upLinksErrors[errorItem][0].status !== HTTP_STATUS.NOT_FOUND) {
if (isAllowPublishOffline) {
return resolve();
}
}
return resolve();
return reject(ErrorCode.getServiceUnavailable(API_ERROR.UPLINK_OFFLINE_PUBLISH));
}
}
);
}
);
}
return resolve();
});
});
}
export function mergeUplinkTimeIntoLocal(localMetadata: Package, remoteMetadata: Package): any {

@ -5,31 +5,18 @@ import _ from 'lodash';
import { ProxyStorage } from '@verdaccio/proxy';
import { API_ERROR, HTTP_STATUS, DIST_TAGS } from '@verdaccio/dev-commons';
import { ReadTarball } from '@verdaccio/streams';
import {
ErrorCode,
normalizeDistTags,
validateMetadata,
isObject,
hasProxyTo,
} from '@verdaccio/utils';
import { ErrorCode, normalizeDistTags, validateMetadata, isObject, hasProxyTo } from '@verdaccio/utils';
import { setupUpLinks, updateVersionsHiddenUpLink } from '@verdaccio/proxy';
import { IReadTarball, IUploadTarball, Versions, Package, Config, MergeTags, Version, DistFile, Callback, Logger } from '@verdaccio/types';
import { IStorage, IProxy, IStorageHandler, ProxyList, StringValue, IGetPackageOptions, ISyncUplinks, IPluginFilters } from '@verdaccio/dev-types';
import { GenericBody, TokenFilter, Token } from '@verdaccio/types';
import { logger } from '@verdaccio/logger';
import { VerdaccioError } from '@verdaccio/commons-api';
import { SearchInstance }from './search';
import { SearchInstance } from './search';
import { LocalStorage } from './local-storage';
import { mergeVersions } from './metadata-utils';
import {
checkPackageLocal,
publishPackage,
checkPackageRemote,
cleanUpLinksRef,
mergeUplinkTimeIntoLocal,
generatePackageTemplate
} from './storage-utils';
import { checkPackageLocal, publishPackage, checkPackageRemote, cleanUpLinksRef, mergeUplinkTimeIntoLocal, generatePackageTemplate } from './storage-utils';
class Storage implements IStorageHandler {
public localStorage: IStorage;
@ -41,7 +28,7 @@ class Storage implements IStorageHandler {
public constructor(config: Config) {
this.config = config;
this.uplinks = setupUpLinks(config);
this.logger = logger.child({module: 'storage'});
this.logger = logger.child({ module: 'storage' });
this.filters = [];
// @ts-ignore
this.localStorage = null;
@ -152,7 +139,7 @@ class Storage implements IStorageHandler {
*/
public getTarball(name: string, filename: string): IReadTarball {
const readStream = new ReadTarball({});
readStream.abort = function() {};
readStream.abort = function () {};
const self = this;
@ -163,48 +150,37 @@ class Storage implements IStorageHandler {
// flow: should be IReadTarball
let localStream: any = self.localStorage.getTarball(name, filename);
let isOpen = false;
localStream.on(
'error',
(err): any => {
if (isOpen || err.status !== HTTP_STATUS.NOT_FOUND) {
return readStream.emit('error', err);
}
// local reported 404
const err404 = err;
localStream.abort();
localStream = null; // we force for garbage collector
self.localStorage.getPackageMetadata(
name,
(err, info: Package): void => {
if (_.isNil(err) && info._distfiles && _.isNil(info._distfiles[filename]) === false) {
// information about this file exists locally
serveFile(info._distfiles[filename]);
} else {
// we know nothing about this file, trying to get information elsewhere
self._syncUplinksMetadata(
name,
info,
{},
(err, info: Package): any => {
if (_.isNil(err) === false) {
return readStream.emit('error', err);
}
if (_.isNil(info._distfiles) || _.isNil(info._distfiles[filename])) {
return readStream.emit('error', err404);
}
serveFile(info._distfiles[filename]);
}
);
}
}
);
localStream.on('error', (err): any => {
if (isOpen || err.status !== HTTP_STATUS.NOT_FOUND) {
return readStream.emit('error', err);
}
);
localStream.on('content-length', function(v): void {
// local reported 404
const err404 = err;
localStream.abort();
localStream = null; // we force for garbage collector
self.localStorage.getPackageMetadata(name, (err, info: Package): void => {
if (_.isNil(err) && info._distfiles && _.isNil(info._distfiles[filename]) === false) {
// information about this file exists locally
serveFile(info._distfiles[filename]);
} else {
// we know nothing about this file, trying to get information elsewhere
self._syncUplinksMetadata(name, info, {}, (err, info: Package): any => {
if (_.isNil(err) === false) {
return readStream.emit('error', err);
}
if (_.isNil(info._distfiles) || _.isNil(info._distfiles[filename])) {
return readStream.emit('error', err404);
}
serveFile(info._distfiles[filename]);
});
}
});
});
localStream.on('content-length', function (v): void {
readStream.emit('content-length', v);
});
localStream.on('open', function(): void {
localStream.on('open', function (): void {
isOpen = true;
localStream.pipe(readStream);
});
@ -239,24 +215,24 @@ class Storage implements IStorageHandler {
savestream = self.localStorage.addTarball(name, filename);
}
let on_open = function(): void {
let on_open = function (): void {
// prevent it from being called twice
on_open = function() {};
on_open = function () {};
const rstream2 = uplink.fetchTarball(file.url);
rstream2.on('error', function(err): void {
rstream2.on('error', function (err): void {
if (savestream) {
savestream.abort();
}
savestream = null;
readStream.emit('error', err);
});
rstream2.on('end', function(): void {
rstream2.on('end', function (): void {
if (savestream) {
savestream.done();
}
});
rstream2.on('content-length', function(v): void {
rstream2.on('content-length', function (v): void {
readStream.emit('content-length', v);
if (savestream) {
savestream.emit('content-length', v);
@ -269,11 +245,11 @@ class Storage implements IStorageHandler {
};
if (savestream) {
savestream.on('open', function(): void {
savestream.on('open', function (): void {
on_open();
});
savestream.on('error', function(err): void {
savestream.on('error', function (err): void {
self.logger.warn({ err: err, fileName: file }, 'error saving file @{fileName}: @{err.message}\n@{err.stack}');
if (savestream) {
savestream.abort();
@ -301,32 +277,29 @@ class Storage implements IStorageHandler {
* @property {function} options.callback Callback for receive data
*/
public getPackage(options: IGetPackageOptions): void {
this.localStorage.getPackageMetadata(
options.name,
(err, data): void => {
if (err && (!err.status || err.status >= HTTP_STATUS.INTERNAL_ERROR)) {
// report internal errors right away
this.localStorage.getPackageMetadata(options.name, (err, data): void => {
if (err && (!err.status || err.status >= HTTP_STATUS.INTERNAL_ERROR)) {
// report internal errors right away
return options.callback(err);
}
this._syncUplinksMetadata(options.name, data, { req: options.req, uplinksLook: options.uplinksLook }, function getPackageSynUpLinksCallback(
err,
result: Package,
uplinkErrors
): void {
if (err) {
return options.callback(err);
}
this._syncUplinksMetadata(options.name, data, { req: options.req, uplinksLook: options.uplinksLook }, function getPackageSynUpLinksCallback(
err,
result: Package,
uplinkErrors
): void {
if (err) {
return options.callback(err);
}
normalizeDistTags(cleanUpLinksRef(options.keepUpLinkData, result));
normalizeDistTags(cleanUpLinksRef(options.keepUpLinkData, result));
// npm can throw if this field doesn't exist
result._attachments = {};
// npm can throw if this field doesn't exist
result._attachments = {};
options.callback(null, result, uplinkErrors);
});
}
);
options.callback(null, result, uplinkErrors);
});
});
}
/**
@ -348,7 +321,7 @@ class Storage implements IStorageHandler {
async.eachSeries(
Object.keys(this.uplinks),
function(up_name, cb): void {
function (up_name, cb): void {
// shortcut: if `local=1` is supplied, don't call uplinks
if (options.req.query.local !== undefined) {
return cb();
@ -356,40 +329,34 @@ class Storage implements IStorageHandler {
// search by keyword for each uplink
const lstream: IUploadTarball = self.uplinks[up_name].search(options);
// join streams
lstream.pipe(
stream,
{ end: false }
);
lstream.on('error', function(err): void {
lstream.pipe(stream, { end: false });
lstream.on('error', function (err): void {
self.logger.error({ err: err }, 'uplink error: @{err.message}');
cb();
cb = function(): void {};
cb = function (): void {};
});
lstream.on('end', function(): void {
lstream.on('end', function (): void {
cb();
cb = function(): void {};
cb = function (): void {};
});
stream.abort = function(): void {
stream.abort = function (): void {
if (lstream.abort) {
lstream.abort();
}
cb();
cb = function(): void {};
cb = function (): void {};
};
},
// executed after all series
function(): void {
function (): void {
// attach a local search results
const lstream: IReadTarball = self.localStorage.search(startkey, options);
stream.abort = function(): void {
stream.abort = function (): void {
lstream.abort();
};
lstream.pipe(
stream,
{ end: true }
);
lstream.on('error', function(err: VerdaccioError): void {
lstream.pipe(stream, { end: true });
lstream.on('error', function (err: VerdaccioError): void {
self.logger.error({ err: err }, 'search error: @{err.message}');
stream.end();
});
@ -405,49 +372,47 @@ class Storage implements IStorageHandler {
*/
public getLocalDatabase(callback: Callback): void {
const self = this;
this.localStorage.storagePlugin.get(
(err, locals): void => {
if (err) {
callback(err);
}
const packages: Version[] = [];
const getPackage = function(itemPkg): void {
self.localStorage.getPackageMetadata(locals[itemPkg], function(err, pkgMetadata: Package): void {
if (_.isNil(err)) {
const latest = pkgMetadata[DIST_TAGS].latest;
if (latest && pkgMetadata.versions[latest]) {
const version: Version = pkgMetadata.versions[latest];
const timeList = pkgMetadata.time as GenericBody;
const time = timeList[latest];
// @ts-ignore
version.time = time;
// Add for stars api
// @ts-ignore
version.users = pkgMetadata.users;
packages.push(version);
} else {
self.logger.warn({ package: locals[itemPkg] }, 'package @{package} does not have a "latest" tag?');
}
}
if (itemPkg >= locals.length - 1) {
callback(null, packages);
} else {
getPackage(itemPkg + 1);
}
});
};
if (locals.length) {
getPackage(0);
} else {
callback(null, []);
}
this.localStorage.storagePlugin.get((err, locals): void => {
if (err) {
callback(err);
}
);
const packages: Version[] = [];
const getPackage = function (itemPkg): void {
self.localStorage.getPackageMetadata(locals[itemPkg], function (err, pkgMetadata: Package): void {
if (_.isNil(err)) {
const latest = pkgMetadata[DIST_TAGS].latest;
if (latest && pkgMetadata.versions[latest]) {
const version: Version = pkgMetadata.versions[latest];
const timeList = pkgMetadata.time as GenericBody;
const time = timeList[latest];
// @ts-ignore
version.time = time;
// Add for stars api
// @ts-ignore
version.users = pkgMetadata.users;
packages.push(version);
} else {
self.logger.warn({ package: locals[itemPkg] }, 'package @{package} does not have a "latest" tag?');
}
}
if (itemPkg >= locals.length - 1) {
callback(null, packages);
} else {
getPackage(itemPkg + 1);
}
});
};
if (locals.length) {
getPackage(0);
} else {
callback(null, []);
}
});
}
/**
@ -488,59 +453,55 @@ class Storage implements IStorageHandler {
_options.etag = upLinkMeta.etag;
}
upLink.getRemoteMetadata(
name,
_options,
(err, upLinkResponse, eTag): void => {
if (err && err.remoteStatus === 304) {
upLinkMeta.fetched = Date.now();
}
if (err || !upLinkResponse) {
return cb(null, [err || ErrorCode.getInternalError('no data')]);
}
try {
validateMetadata(upLinkResponse, name);
} catch (err) {
self.logger.error(
{
sub: 'out',
err: err,
},
'package.json validating error @{!err.message}\n@{err.stack}'
);
return cb(null, [err]);
}
packageInfo._uplinks[upLink.upname] = {
etag: eTag,
fetched: Date.now(),
};
packageInfo.time = mergeUplinkTimeIntoLocal(packageInfo, upLinkResponse);
updateVersionsHiddenUpLink(upLinkResponse.versions, upLink);
try {
mergeVersions(packageInfo, upLinkResponse);
} catch (err) {
self.logger.error(
{
sub: 'out',
err: err,
},
'package.json parsing error @{!err.message}\n@{err.stack}'
);
return cb(null, [err]);
}
// if we got to this point, assume that the correct package exists
// on the uplink
found = true;
cb();
upLink.getRemoteMetadata(name, _options, (err, upLinkResponse, eTag): void => {
if (err && err.remoteStatus === 304) {
upLinkMeta.fetched = Date.now();
}
);
if (err || !upLinkResponse) {
return cb(null, [err || ErrorCode.getInternalError('no data')]);
}
try {
validateMetadata(upLinkResponse, name);
} catch (err) {
self.logger.error(
{
sub: 'out',
err: err,
},
'package.json validating error @{!err.message}\n@{err.stack}'
);
return cb(null, [err]);
}
packageInfo._uplinks[upLink.upname] = {
etag: eTag,
fetched: Date.now(),
};
packageInfo.time = mergeUplinkTimeIntoLocal(packageInfo, upLinkResponse);
updateVersionsHiddenUpLink(upLinkResponse.versions, upLink);
try {
mergeVersions(packageInfo, upLinkResponse);
} catch (err) {
self.logger.error(
{
sub: 'out',
err: err,
},
'package.json parsing error @{!err.message}\n@{err.stack}'
);
return cb(null, [err]);
}
// if we got to this point, assume that the correct package exists
// on the uplink
found = true;
cb();
});
},
// @ts-ignore
(err: Error, upLinksErrors: any): AsyncResultArrayCallback<unknown, Error> => {
@ -620,4 +581,4 @@ class Storage implements IStorageHandler {
}
}
export { Storage }
export { Storage };

@ -1,20 +1,19 @@
import path from 'path';
import rimRaf from 'rimraf';
import {Config as AppConfig } from '@verdaccio/config';
import { Config as AppConfig } from '@verdaccio/config';
// @ts-ignore
import { logger, setup} from '@verdaccio/logger';
import { logger, setup } from '@verdaccio/logger';
import { configExample, generateNewVersion } from '@verdaccio/mock';
import {IStorage} from '@verdaccio/dev-types';
import { LocalStorage } from '../src/local-storage';
import { IStorage } from '@verdaccio/dev-types';
const readMetadata = (fileName = 'metadata') => readFile(`../fixtures/${fileName}`).toString();
import {Config, MergeTags, Package} from '@verdaccio/types';
import { API_ERROR, HTTP_STATUS, DIST_TAGS} from '@verdaccio/dev-commons';
import { Config, MergeTags, Package } from '@verdaccio/types';
import { API_ERROR, HTTP_STATUS, DIST_TAGS } from '@verdaccio/dev-commons';
import { VerdaccioError } from '@verdaccio/commons-api';
import {generatePackageTemplate} from '../src/storage-utils';
import {readFile} from './fixtures/test.utils';
import { LocalStorage } from '../src/local-storage';
import { generatePackageTemplate } from '../src/storage-utils';
import { readFile } from './fixtures/test.utils';
setup([]);
@ -26,16 +25,18 @@ describe('LocalStorage', () => {
const tarballName2 = `${pkgName}-add-tarball-1.0.5.tgz`;
const getStorage = (LocalStorageClass = LocalStorage) => {
const config: Config = new AppConfig(configExample({
self_path: path.join('../partials/store')
}));
const config: Config = new AppConfig(
configExample({
self_path: path.join('../partials/store'),
})
);
return new LocalStorageClass(config, logger);
}
};
const getPackageMetadataFromStore = (pkgName: string): Promise<Package> => {
return new Promise((resolve) => {
storage.getPackageMetadata(pkgName, (err, data ) => {
storage.getPackageMetadata(pkgName, (err, data) => {
resolve(data);
});
});
@ -143,7 +144,7 @@ describe('LocalStorage', () => {
await addNewVersion(pkgName, '3.0.0');
const tags: MergeTags = {
beta: '3.0.0',
latest: '2.0.0'
latest: '2.0.0',
};
storage.mergeTags(pkgName, tags, async (err, data) => {
@ -166,7 +167,7 @@ describe('LocalStorage', () => {
await addNewVersion(pkgName, '2.0.0');
await addNewVersion(pkgName, '3.0.0');
const tags: MergeTags = {
beta: '9999.0.0'
beta: '9999.0.0',
};
storage.mergeTags(pkgName, tags, async (err) => {
@ -180,7 +181,7 @@ describe('LocalStorage', () => {
test('should fails on mergeTags', async (done) => {
const tags: MergeTags = {
beta: '3.0.0',
latest: '2.0.0'
latest: '2.0.0',
};
storage.mergeTags('not-found', tags, async (err) => {
@ -215,7 +216,7 @@ describe('LocalStorage', () => {
await addPackageToStore(pkgName, generatePackageTemplate(pkgName));
await addNewVersion(pkgName, version);
storage.addVersion(pkgName, version, generateNewVersion(pkgName, version), '', err => {
storage.addVersion(pkgName, version, generateNewVersion(pkgName, version), '', (err) => {
expect(err).not.toBeNull();
expect(err.statusCode).toEqual(HTTP_STATUS.CONFLICT);
expect(err.message).toMatch(API_ERROR.PACKAGE_EXIST);
@ -230,7 +231,7 @@ describe('LocalStorage', () => {
const tarballName = `${pkgName}-${version}.tgz`;
await addTarballToStore(pkgName, tarballName);
storage.addVersion(pkgName, version, generateNewVersion(pkgName, version, 'fake'), '', err => {
storage.addVersion(pkgName, version, generateNewVersion(pkgName, version, 'fake'), '', (err) => {
expect(err).not.toBeNull();
expect(err.statusCode).toEqual(HTTP_STATUS.BAD_REQUEST);
expect(err.message).toMatch(/shasum error/);
@ -258,18 +259,18 @@ describe('LocalStorage', () => {
const pkgName = 'add-update-versions-test-1';
const version = '1.0.2';
let _storage;
beforeEach(done => {
beforeEach((done) => {
class MockLocalStorage extends LocalStorage {}
// @ts-ignore
MockLocalStorage.prototype._writePackage = jest.fn(LocalStorage.prototype._writePackage)
MockLocalStorage.prototype._writePackage = jest.fn(LocalStorage.prototype._writePackage);
_storage = getStorage(MockLocalStorage);
rimRaf(path.join(configExample().storage, pkgName), async () => {
await addPackageToStore(pkgName, generatePackageTemplate(pkgName));
await addNewVersion(pkgName, '1.0.1');
await addNewVersion(pkgName, version);
done();
})
})
});
});
test('should update versions from external source', async (done) => {
_storage.updateVersions(pkgName, metadata, (err, data) => {
@ -290,22 +291,22 @@ describe('LocalStorage', () => {
});
});
test('should not update if the metadata match', done => {
_storage.updateVersions(pkgName, metadata, e => {
expect(e).toBeNull()
_storage.updateVersions(pkgName, metadata, err => {
expect(err).toBeNull()
test('should not update if the metadata match', (done) => {
_storage.updateVersions(pkgName, metadata, (e) => {
expect(e).toBeNull();
_storage.updateVersions(pkgName, metadata, (err) => {
expect(err).toBeNull();
expect(_storage._writePackage).toHaveBeenCalledTimes(1);
done()
})
})
})
done();
});
});
});
});
describe('LocalStorage::changePackage', () => {
const pkgName = 'change-package';
test('should unpublish a version', async done => {
test('should unpublish a version', async (done) => {
await addPackageToStore(pkgName, generatePackageTemplate(pkgName));
await addNewVersion(pkgName, '1.0.1');
await addNewVersion(pkgName, '1.0.2');
@ -315,7 +316,7 @@ describe('LocalStorage', () => {
storage.changePackage(pkgName, metadata, rev, (err) => {
expect(err).toBeUndefined();
storage.getPackageMetadata(pkgName, (err, data ) => {
storage.getPackageMetadata(pkgName, (err, data) => {
expect(err).toBeNull();
expect(data.versions['1.0.1']).toBeDefined();
expect(data.versions['1.0.2']).toBeUndefined();
@ -327,9 +328,7 @@ describe('LocalStorage', () => {
});
describe('LocalStorage::tarball operations', () => {
describe('LocalStorage::addTarball', () => {
test('should add a new tarball', (done) => {
const tarballData = JSON.parse(readMetadata('addTarball'));
const stream = storage.addTarball(pkgName, tarballName);
@ -338,7 +337,7 @@ describe('LocalStorage', () => {
expect(err).toBeNull();
done();
});
stream.on('success', function() {
stream.on('success', function () {
done();
});
@ -353,7 +352,7 @@ describe('LocalStorage', () => {
expect(err).toBeNull();
done();
});
stream.on('success', function() {
stream.on('success', function () {
done();
});
@ -394,7 +393,7 @@ describe('LocalStorage', () => {
test('should fails on use invalid package name on add a new tarball', (done) => {
const stream = storage.addTarball(pkgName, `${pkgName}-fails-add-tarball-1.0.4.tgz`);
stream.on('error', function(err: VerdaccioError) {
stream.on('error', function (err: VerdaccioError) {
expect(err).not.toBeNull();
expect(err.statusCode).toEqual(HTTP_STATUS.BAD_DATA);
expect(err.message).toMatch(/refusing to accept zero-length file/);
@ -407,7 +406,7 @@ describe('LocalStorage', () => {
test('should fails on abort on add a new tarball', (done) => {
const stream = storage.addTarball('__proto__', `${pkgName}-fails-add-tarball-1.0.4.tgz`);
stream.abort();
stream.on('error', function(err: VerdaccioError) {
stream.on('error', function (err: VerdaccioError) {
expect(err).not.toBeNull();
expect(err.statusCode).toEqual(HTTP_STATUS.FORBIDDEN);
expect(err.message).toMatch(/can't use this filename/);
@ -416,10 +415,8 @@ describe('LocalStorage', () => {
stream.done();
});
});
describe('LocalStorage::removeTarball', () => {
test('should remove a tarball', (done) => {
storage.removeTarball(pkgName, tarballName2, 'rev', (err, pkg) => {
expect(err).toBeNull();
@ -441,18 +438,18 @@ describe('LocalStorage', () => {
describe('LocalStorage::getTarball', () => {
test('should get a existing tarball', (done) => {
const stream = storage.getTarball(pkgName, tarballName);
stream.on('content-length', function(contentLength) {
stream.on('content-length', function (contentLength) {
expect(contentLength).toBe(279);
done();
});
stream.on('open', function() {
stream.on('open', function () {
done();
});
});
test('should fails on get a tarball that does not exist', (done) => {
const stream = storage.getTarball('fake', tarballName);
stream.on('error', function(err: VerdaccioError) {
stream.on('error', function (err: VerdaccioError) {
expect(err).not.toBeNull();
expect(err.statusCode).toEqual(HTTP_STATUS.NOT_FOUND);
expect(err.message).toMatch(/no such file available/);
@ -470,16 +467,15 @@ describe('LocalStorage', () => {
expect(pkg.name).toEqual(pkgName);
});
stream.on('error', function(err) {
stream.on('error', function (err) {
expect(err).not.toBeNull();
done();
});
stream.on('end', function() {
stream.on('end', function () {
done();
});
});
});
});
@ -502,7 +498,7 @@ describe('LocalStorage', () => {
test('should fails with package not found', (done) => {
const pkgName = 'npm_test_fake';
storage.removePackage(pkgName, err => {
storage.removePackage(pkgName, (err) => {
expect(err).not.toBeNull();
expect(err.message).toMatch(/no such package available/);
done();
@ -510,7 +506,7 @@ describe('LocalStorage', () => {
});
test('should fails with @scoped package not found', (done) => {
storage.removePackage(pkgNameScoped, err => {
storage.removePackage(pkgNameScoped, (err) => {
expect(err).not.toBeNull();
expect(err.message).toMatch(API_ERROR.NO_PACKAGE);
done();
@ -518,5 +514,4 @@ describe('LocalStorage', () => {
});
});
});
});

@ -2,74 +2,62 @@ import assert from 'assert';
import { semverSort } from '@verdaccio/utils';
import { setup } from '@verdaccio/logger';
import {mergeVersions} from '../src/metadata-utils';
import { mergeVersions } from '../src/metadata-utils';
setup([]);
describe('Storage._merge_versions versions', () => {
test('simple', () => {
let pkg = {
'versions': {a: 1, b: 1, c: 1},
versions: { a: 1, b: 1, c: 1 },
'dist-tags': {},
};
// @ts-ignore
mergeVersions(pkg, {versions: {a: 2, q: 2}});
mergeVersions(pkg, { versions: { a: 2, q: 2 } });
assert.deepEqual(pkg, {
'versions': {a: 1, b: 1, c: 1, q: 2},
versions: { a: 1, b: 1, c: 1, q: 2 },
'dist-tags': {},
});
});
test('dist-tags - compat', () => {
let pkg = {
'versions': {},
'dist-tags': {q: '1.1.1', w: '2.2.2'},
versions: {},
'dist-tags': { q: '1.1.1', w: '2.2.2' },
};
// @ts-ignore
mergeVersions(pkg, {'dist-tags': {q: '2.2.2', w: '3.3.3', t: '4.4.4'}});
mergeVersions(pkg, { 'dist-tags': { q: '2.2.2', w: '3.3.3', t: '4.4.4' } });
assert.deepEqual(pkg, {
'versions': {},
'dist-tags': {q: '2.2.2', w: '3.3.3', t: '4.4.4'},
versions: {},
'dist-tags': { q: '2.2.2', w: '3.3.3', t: '4.4.4' },
});
});
test('dist-tags - staging', () => {
let pkg = {
versions: {},
// we've been locally publishing 1.1.x in preparation for the next
// public release
'dist-tags': {q:'1.1.10',w:'2.2.2'},
'dist-tags': { q: '1.1.10', w: '2.2.2' },
};
// 1.1.2 is the latest public release, but we want to continue testing
// against our local 1.1.10, which may end up published as 1.1.3 in the
// future
// @ts-ignore
mergeVersions(pkg, {'dist-tags':{q:'1.1.2',w:'3.3.3',t:'4.4.4'}})
mergeVersions(pkg, { 'dist-tags': { q: '1.1.2', w: '3.3.3', t: '4.4.4' } });
assert.deepEqual(pkg, {
versions: {},
'dist-tags': {q:'1.1.10',w:'3.3.3',t:'4.4.4'},
'dist-tags': { q: '1.1.10', w: '3.3.3', t: '4.4.4' },
});
});
test('semverSort', () => {
assert.deepEqual(semverSort(['1.2.3', '1.2', '1.2.3a', '1.2.3c', '1.2.3-b']),
['1.2.3a',
'1.2.3-b',
'1.2.3c',
'1.2.3']
);
assert.deepEqual(semverSort(['1.2.3', '1.2', '1.2.3a', '1.2.3c', '1.2.3-b']), ['1.2.3a', '1.2.3-b', '1.2.3c', '1.2.3']);
});
});

@ -32,12 +32,12 @@ const packages = [
];
describe('search', () => {
beforeAll(async function() {
beforeAll(async function () {
const config = new Config(configExample());
const storage = new Storage(config);
await storage.init(config);
SearchInstance.configureStorage(storage);
packages.map(function(item) {
packages.map(function (item) {
// @ts-ignore
SearchInstance.add(item);
});

@ -1,8 +1,8 @@
import {Package} from '@verdaccio/types';
import { Package } from '@verdaccio/types';
import { STORAGE, DIST_TAGS } from '@verdaccio/dev-commons';
import {normalizePackage, mergeUplinkTimeIntoLocal} from '../src/storage-utils';
import { normalizePackage, mergeUplinkTimeIntoLocal } from '../src/storage-utils';
import {readFile} from "./fixtures/test.utils";
import { readFile } from './fixtures/test.utils';
describe('Storage Utils', () => {
describe('normalizePackage', () => {
@ -48,13 +48,13 @@ describe('Storage Utils', () => {
describe('mergeTime', () => {
const vGroup1 = {
"1.0.15": "2018-06-12T23:15:05.864Z",
"1.0.16": "2018-06-12T23:17:46.578Z",
"1.0.17": "2018-06-12T23:20:59.106Z"
'1.0.15': '2018-06-12T23:15:05.864Z',
'1.0.16': '2018-06-12T23:17:46.578Z',
'1.0.17': '2018-06-12T23:20:59.106Z',
};
const vGroup2 = {
"1.0.6": "2018-06-07T05:50:21.505Z",
"1.0.7": "2018-06-12T20:35:07.621Z"
'1.0.6': '2018-06-07T05:50:21.505Z',
'1.0.7': '2018-06-12T20:35:07.621Z',
};
test('mergeTime basic', () => {
const pkg1: Package = {
@ -63,9 +63,9 @@ describe('Storage Utils', () => {
_rev: '',
_uplinks: {},
time: {
"modified": "2018-06-13T06:44:45.747Z",
"created": "2018-06-07T05:50:21.505Z",
...vGroup1
modified: '2018-06-13T06:44:45.747Z',
created: '2018-06-07T05:50:21.505Z',
...vGroup1,
},
name: '',
versions: {},
@ -79,17 +79,16 @@ describe('Storage Utils', () => {
_uplinks: {},
name: '',
time: {
"modified": "2019-06-13T06:44:45.747Z",
"created": "2019-06-07T05:50:21.505Z",
...vGroup2
modified: '2019-06-13T06:44:45.747Z',
created: '2019-06-07T05:50:21.505Z',
...vGroup2,
},
versions: {},
[DIST_TAGS]: {},
};
const mergedPkg = mergeUplinkTimeIntoLocal(pkg1, pkg2);
expect(Object.keys(mergedPkg)).toEqual(["modified", "created",
...Object.keys(vGroup1), ...Object.keys(vGroup2)]);
expect(Object.keys(mergedPkg)).toEqual(['modified', 'created', ...Object.keys(vGroup1), ...Object.keys(vGroup2)]);
});
test('mergeTime remote empty', () => {
@ -100,9 +99,9 @@ describe('Storage Utils', () => {
_uplinks: {},
name: '',
time: {
"modified": "2018-06-13T06:44:45.747Z",
"created": "2018-06-07T05:50:21.505Z",
...vGroup1
modified: '2018-06-13T06:44:45.747Z',
created: '2018-06-07T05:50:21.505Z',
...vGroup1,
},
versions: {},
[DIST_TAGS]: {},
@ -118,7 +117,7 @@ describe('Storage Utils', () => {
[DIST_TAGS]: {},
};
const mergedPkg = mergeUplinkTimeIntoLocal(pkg1, pkg2);
expect(Object.keys(mergedPkg)).toEqual(["modified", "created", ...Object.keys(vGroup1)]);
expect(Object.keys(mergedPkg)).toEqual(['modified', 'created', ...Object.keys(vGroup1)]);
});
});
});

@ -23,7 +23,7 @@ export const defaultNonLoggedUserRoles = [
ROLES.$ANONYMOUS,
// groups without '$' are going to be deprecated eventually
ROLES.DEPRECATED_ALL,
ROLES.DEPRECATED_ANONYMOUS
ROLES.DEPRECATED_ANONYMOUS,
];
/**
@ -65,14 +65,17 @@ 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}`);
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}`);
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}`);
logger.trace({ remote: user.name }, `auth/allow_action: access granted to: @{user}`);
return callback(null, true);
}
@ -88,18 +91,18 @@ export function allow_action(action: ActionsAllowed, logger): AllowAction {
*
*/
export function handleSpecialUnpublish(logger): any {
return function(user: RemoteUser, pkg: AuthPackageAllow, callback: AllowActionCallback): 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]) as string[]).length > 0;
logger.trace({user: user.name, name: pkg.name, hasGroups}, `fallback unpublish for @{name} has groups: @{hasGroups} for @{user}`);
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}`);
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);
};
}

@ -2,7 +2,6 @@ import assert from 'assert';
import _ from 'lodash';
import minimatch from 'minimatch';
import { PackageList, UpLinksConfList } from '@verdaccio/types';
import { MatchedPackage, LegacyPackageList } from '@verdaccio/dev-types';
import { ErrorCode } from './utils';
@ -20,23 +19,21 @@ const BLACKLIST = {
* @return {Array}
*/
export function normalizeUserList(groupsList: any): any {
const result: any[] = [];
if (_.isNil(groupsList)) {
return result;
}
const result: any[] = [];
if (_.isNil(groupsList)) {
return result;
}
// if it's a string, split it to array
if (_.isString(groupsList)) {
const groupsArray = groupsList.split(/\s+/);
// if it's a string, split it to array
if (_.isString(groupsList)) {
const groupsArray = groupsList.split(/\s+/);
result.push(groupsArray);
} else if (Array.isArray(groupsList)) {
result.push(groupsList);
} else {
throw ErrorCode.getInternalError(
'CONFIG: bad package acl (array or string expected): ' + JSON.stringify(groupsList)
);
}
result.push(groupsArray);
} else if (Array.isArray(groupsList)) {
result.push(groupsList);
} else {
throw ErrorCode.getInternalError('CONFIG: bad package acl (array or string expected): ' + JSON.stringify(groupsList));
}
return _.flatten(result);
}
@ -58,10 +55,7 @@ export function uplinkSanityCheck(uplinks: UpLinksConfList, users: any = BLACKLI
}
export function sanityCheckNames(item: string, users: any): any {
assert(
item !== 'all' && item !== 'owner' && item !== 'anonymous' && item !== 'undefined' && item !== 'none',
'CONFIG: reserved uplink name: ' + item
);
assert(item !== 'all' && item !== 'owner' && item !== 'anonymous' && item !== 'undefined' && item !== 'none', 'CONFIG: reserved uplink name: ' + item);
assert(!item.match(/\s/), 'CONFIG: invalid uplink name: ' + item);
assert(_.isNil(users[item]), 'CONFIG: duplicate uplink name: ' + item);
users[item] = true;
@ -90,7 +84,7 @@ export function hasProxyTo(pkg: string, upLink: string, packages: PackageList):
const matchedPkg: MatchedPackage = getMatchedPackagesSpec(pkg, packages);
const proxyList = typeof matchedPkg !== 'undefined' ? matchedPkg.proxy : [];
if (proxyList) {
return proxyList.some(curr => upLink === curr);
return proxyList.some((curr) => upLink === curr);
}
return false;
@ -112,7 +106,7 @@ export function normalisePackageAccess(packages: LegacyPackageList): LegacyPacka
normalizedPkgs['**'] = {
access: [],
publish: [],
proxy: []
proxy: [],
};
}
@ -126,9 +120,7 @@ export function normalisePackageAccess(packages: LegacyPackageList): LegacyPacka
normalizedPkgs[pkg].publish = normalizeUserList(packageAccess.publish);
normalizedPkgs[pkg].proxy = normalizeUserList(packageAccess.proxy);
// if unpublish is not defined, we set to false to fallback in publish access
normalizedPkgs[pkg].unpublish = _.isUndefined(packageAccess.unpublish)
? false
: normalizeUserList(packageAccess.unpublish);
normalizedPkgs[pkg].unpublish = _.isUndefined(packageAccess.unpublish) ? false : normalizeUserList(packageAccess.unpublish);
}
}

@ -42,9 +42,7 @@ 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 {
@ -59,7 +57,7 @@ export function generateRandomHexString(length = 8): string {
* @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(
payload,
secretOrPrivateKey,

@ -8,16 +8,7 @@ import YAML from 'js-yaml';
import { Request } from 'express';
import sanitizyReadme from '@verdaccio/readme';
import {
APP_ERROR,
DEFAULT_PORT,
DEFAULT_DOMAIN,
DEFAULT_PROTOCOL,
CHARACTER_ENCODING,
HEADERS,
DIST_TAGS,
DEFAULT_USER,
} from '@verdaccio/dev-commons';
import { APP_ERROR, DEFAULT_PORT, DEFAULT_DOMAIN, DEFAULT_PROTOCOL, CHARACTER_ENCODING, HEADERS, DIST_TAGS, DEFAULT_USER } from '@verdaccio/dev-commons';
import { Package, Version, Author } from '@verdaccio/types';
import { StringValue } from '@verdaccio/dev-types';
@ -34,7 +25,6 @@ import {
getCode,
} from '@verdaccio/commons-api';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
// eslint-disable-next-line @typescript-eslint/no-var-requires
@ -184,12 +174,7 @@ export function convertDistRemoteToLocalTarballUrls(pkg: Package, req: Request,
* @param {*} uri
* @return {String} a parsed url
*/
export function getLocalRegistryTarballUri(
uri: string,
pkgName: string,
req: Request,
urlPrefix: string | void
): string {
export function getLocalRegistryTarballUri(uri: string, pkgName: string, req: Request, urlPrefix: string | void): string {
const currentHost = req.headers.host;
if (!currentHost) {
@ -287,10 +272,10 @@ export function parseAddress(urlAddress: any): any {
* Function filters out bad semver versions and sorts the array.
* @return {Array} sorted Array
*/
export function semverSort(listVersions: string[], /* logger */): string[] {
export function semverSort(listVersions: string[] /* logger */): string[] {
return (
listVersions
.filter(function(x): boolean {
.filter(function (x): boolean {
if (!semver.parse(x, true)) {
// FIXME: logger is always undefined
// logger.warn({ ver: x }, 'ignoring bad version @{ver}');
@ -365,8 +350,8 @@ export function parseInterval(interval: any): number {
}
let result = 0;
let last_suffix = Infinity;
interval.split(/\s+/).forEach(function(x): void {
if (!x){
interval.split(/\s+/).forEach(function (x): void {
if (!x) {
return;
}
const m = x.match(/^((0|[1-9][0-9]*)(\.[0-9]+)?)(ms|s|m|h|d|w|M|y|)$/);
@ -452,7 +437,7 @@ export function fileExists(path: string): boolean {
}
export function sortByName(packages: any[], orderAscending: boolean | void = true): string[] {
return packages.slice().sort(function(a, b): number {
return packages.slice().sort(function (a, b): number {
const comparatorNames = a.name.toLowerCase() < b.name.toLowerCase();
return orderAscending ? (comparatorNames ? -1 : 1) : comparatorNames ? 1 : -1;

Some files were not shown because too many files have changed in this diff Show More