0
Fork 0
mirror of https://github.com/verdaccio/verdaccio.git synced 2025-04-01 02:42:23 -05:00

chore: update eslint dependencies (#2126)

* chore: update eslint

* chore: update rules and style

* chore: aling formatting

* chore: update ci rules

* chore: aling formatting

* chore: aling formatting
This commit is contained in:
Juan Picado 2021-03-14 08:42:46 +01:00 committed by GitHub
parent f3c0f4e417
commit 93468211d6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
156 changed files with 6445 additions and 4047 deletions

View file

@ -1,18 +1,102 @@
{
"extends": [
"@verdaccio"
"eslint:recommended",
"google",
"plugin:react/recommended",
"plugin:jest/recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"plugin:import/typescript",
"plugin:jsx-a11y/recommended",
"prettier"
],
"plugins": ["import", "jest", "jsx-a11y", "react-hooks"],
"env": {
"es6": true,
"node": true,
"jest": true
},
"globals": {
"__APP_VERSION__": true
},
"parserOptions": {
"allowImportExportEverywhere": true,
"sourceType": "module",
"ecmaVersion": 11,
"ecmaFeatures": {
"impliedStrict": true,
"jsx": true
}
},
"settings": {
"import/resolver": {
"node": {
"extensions": [".js", ".jsx", ".ts", ".tsx"]
}
}
},
"parser": "@typescript-eslint/parser",
"rules": {
"@typescript-eslint/no-var-requires": 0,
"curly": ["error", "all"],
"react/prop-types": 0,
"jest/no-export": 0,
"jest/no-test-callback": 0,
"jest/expect-expect": 0,
"jest/no-try-expect": 0,
"jest/no-done-callback": "off",
"jest/no-conditional-expect": "off",
"keyword-spacing": "off",
"no-tabs": "off",
"no-useless-escape": "off",
"padded-blocks": "off",
"require-jsdoc": "off",
"valid-jsdoc": "off",
"import/order": ["error"],
"eol-last": "error",
"no-irregular-whitespace": "error",
"no-mixed-spaces-and-tabs": ["error", "smart-tabs"],
"no-trailing-spaces": "error",
"camelcase": "off",
"guard-for-in": "error",
"new-cap": "error",
"max-len": ["error", 160],
"no-console": ["error", { "allow": ["warn"] }],
"no-constant-condition": "error",
"no-debugger": "error",
"no-empty": "error",
"no-fallthrough": "error",
"no-invalid-this": "error",
"no-new-require": "error",
"no-undef": "error",
"no-unreachable": "error",
"no-var": "error",
"one-var": "error",
"prefer-rest-params": "error",
"prefer-spread": "error",
"handle-callback-err": 0,
"prefer-const": 0,
"@typescript-eslint/camelcase": 0,
"@typescript-eslint/ban-ts-ignore": 0,
"@typescript-eslint/no-var-requires": 0,
"@typescript-eslint/no-inferrable-types": 0,
"@typescript-eslint/no-empty-function": 0,
"@typescript-eslint/no-this-alias": ["warn"],
"@typescript-eslint/no-this-alias": 0,
"@typescript-eslint/no-use-before-define": 0,
"@typescript-eslint/array-type": ["warn"],
"@typescript-eslint/array-type": ["error"],
"@typescript-eslint/no-explicit-any": 0,
"@typescript-eslint/indent": 0,
"@typescript-eslint/interface-name-prefix": 0,
"import/order": 0
"@typescript-eslint/ban-ts-comment": 0,
"@typescript-eslint/ban-types": 0,
"@typescript-eslint/explicit-module-boundary-types": 0,
// rules to fix
"no-unused-vars": ["warn", { "vars": "all", "args": "none" }],
"jest/no-identical-title": ["warn"],
"prefer-promise-reject-errors": ["warn"],
"jest/no-disabled-tests": ["warn"],
"jest/no-commented-out-tests": ["warn"],
"@typescript-eslint/prefer-optional-chain": ["warn"],
"@typescript-eslint/explicit-member-accessibility": ["warn"],
"@typescript-eslint/no-unused-vars": ["warn"]
}
}

View file

@ -9,7 +9,7 @@ jobs:
strategy:
fail-fast: false
matrix:
node_version: [10, 12, 13, 14, 15]
node_version: [10, 12, 14, 15]
runs-on: ubuntu-latest

29
.prettierignore Normal file
View file

@ -0,0 +1,29 @@
.cache/
**/corrupted-package/package.json
**/corrupted.json
**/invalid.js
**/invalid.json
**/pnpm-lock.yaml
**/verdaccio-corrupted.db.json
**/wrong.package.json
/website/.cache/
/website/crowdin/
/website/public/
/website/src/dictionaries/
CHANGELOG.md
CONTRIBUTORS.md
node_modules/
**/coverage/**
**/build/*.js
test/unit/partials/*
build/*
.github/
.vscode/
wiki/
.yarnrc.yml
yarn-error.log
.yarn/
test/functional/store/*
storage_default_storage/*
docker-examples/
.prettierignore

View file

@ -1,12 +1,11 @@
{
"endOfLine": "lf",
"useTabs": false,
"printWidth": 160,
"printWidth": 180,
"tabWidth": 2,
"singleQuote": true,
"requirePragma": true,
"bracketSpacing": true,
"jsxBracketSameLine": true,
"trailingComma": "es5",
"semi": true,
"parser": "typescript"
"semi": true
}

View file

@ -1 +1 @@
require.requireActual('babel/polyfill');
jest.requireActual('babel/polyfill');

View file

@ -88,7 +88,8 @@
"@types/node": "12.12.21",
"@types/request": "2.48.3",
"@types/semver": "6.2.0",
"@typescript-eslint/eslint-plugin": "2.12.0",
"@typescript-eslint/eslint-plugin": "4.13.0",
"@typescript-eslint/parser": "4.13.0",
"@verdaccio/eslint-config": "^8.5.0",
"@verdaccio/types": "^9.7.2",
"all-contributors-cli": "6.20.0",
@ -100,7 +101,17 @@
"codecov": "3.8.1",
"cross-env": "7.0.3",
"detect-secrets": "1.0.6",
"eslint": "6.8.0",
"eslint": "7.19.0",
"eslint-config-google": "0.14.0",
"eslint-config-prettier": "7.2.0",
"eslint-plugin-babel": "5.3.1",
"eslint-plugin-import": "2.22.1",
"eslint-plugin-jest": "24.1.3",
"eslint-plugin-jsx-a11y": "6.4.1",
"eslint-plugin-react": "7.22.0",
"eslint-plugin-react-hooks": "4.2.0",
"eslint-plugin-simple-import-sort": "7.0.0",
"eslint-plugin-verdaccio": "9.6.1",
"fs-extra": "9.1.0",
"get-stdin": "7.0.0",
"husky": "2.7.0",
@ -111,7 +122,7 @@
"lint-staged": "8.2.1",
"lockfile-lint": "4.3.7",
"nock": "12.0.3",
"prettier": "1.19.1",
"prettier": "2.2.1",
"puppeteer": "5.5.0",
"rimraf": "3.0.2",
"selfsigned": "1.10.8",
@ -139,6 +150,8 @@
"type-check": "tsc --noEmit",
"type-check:watch": "yarn run type-check -- --watch",
"pretest": "yarn run code:build",
"format": "prettier --single-quote --trailing-comma none --write \"{src,test}/**/*.ts\"",
"format:check": "prettier --check \"**/*.{js,jsx,ts,tsx,json,yml,yaml,md}\" --debug-check",
"test": "yarn run test:unit",
"test:clean": "npx jest --clearCache",
"test:unit": "cross-env NODE_ENV=test TZ=UTC FORCE_COLOR=1 jest --config ./jest.config.js --maxWorkers 2 --passWithNoTests",
@ -149,9 +162,8 @@
"pre:ci": "yarn run lint",
"coverage:publish": "codecov",
"lint": "yarn run type-check && yarn run lint:ts",
"lint:ts": "eslint . --ext .js,.ts",
"lint:ts": "eslint \"**/*.{js,jsx,ts,tsx}\"",
"lint:lockfile": "lockfile-lint --path yarn.lock --type yarn --validate-https --allowed-hosts verdaccio npm yarn",
"format": "prettier --single-quote --trailing-comma none --write \"{src,test}/**/*.ts\"",
"dev:start": "yarn babel-node --extensions \".ts,.tsx\" src/lib/cli",
"code:build": "yarn babel src/ --out-dir build/ --copy-files --extensions \".ts,.tsx\" --source-maps inline",
"code:docker-build": "yarn babel src/ --out-dir build/ --copy-files --extensions \".ts,.tsx\"",
@ -172,11 +184,6 @@
"lint-staged": {
"relative": true,
"linters": {
"*.yaml": [
"prettier --parser yaml --no-config --single-quote --write",
"detect-secrets-launcher --baseline .secrets-baseline",
"git add"
],
"*": [
"eslint .",
"prettier --write",

View file

@ -4,20 +4,23 @@ import { $ResponseExtend, $RequestExtend, $NextFunctionVer } from '../../../type
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();
if (doGarbabeCollector) {
global.gc();
}
next({
pid: process.pid,
// @ts-ignore
main: process.mainModule.filename,
conf: selfPath,
mem: process.memoryUsage(),
gc: doGarbabeCollector
});
}
next({
pid: process.pid,
// @ts-ignore
main: process.mainModule.filename,
conf: selfPath,
mem: process.memoryUsage(),
gc: doGarbabeCollector,
});
});
);
};

View file

@ -1,22 +1,32 @@
import mime from 'mime';
import _ from 'lodash';
import { media, allow } from '../../middleware';
import{ Router } from 'express';
import{ IAuth, $ResponseExtend, $RequestExtend, $NextFunctionVer, IStorageHandler } from '../../../../types';
import { API_MESSAGE, HTTP_STATUS, DIST_TAGS } from '../../../lib/constants';
import { Router } from 'express';
import { VerdaccioError } from '@verdaccio/commons-api';
import { Package } from '@verdaccio/types';
import { media, allow } from '../../middleware';
import {
IAuth,
$ResponseExtend,
$RequestExtend,
$NextFunctionVer,
IStorageHandler
} from '../../../../types';
import { API_MESSAGE, HTTP_STATUS, DIST_TAGS } from '../../../lib/constants';
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);
}
@ -28,48 +38,74 @@ export default function(route: Router, auth: IAuth, storage: IStorageHandler): v
// tagging a package
route.put('/:package/:tag', can('publish'), media(mime.getType('json')), tag_package_version);
route.post('/-/package/:package/dist-tags/:tag', can('publish'), media(mime.getType('json')), tag_package_version);
route.post(
'/-/package/:package/dist-tags/:tag',
can('publish'),
media(mime.getType('json')),
tag_package_version
);
route.put('/-/package/:package/dist-tags/:tag', can('publish'), media(mime.getType('json')), tag_package_version);
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 {
const tags = {};
tags[req.params.tag] = null;
storage.mergeTags(req.params.package, tags, function(err: VerdaccioError): $NextFunctionVer {
if (err) {
return next(err);
}
res.status(HTTP_STATUS.CREATED);
return next({
ok: API_MESSAGE.TAG_REMOVED,
});
});
});
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 {
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 {
if (err) {
return next(err);
}
next(info[DIST_TAGS]);
},
});
});
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);
}
res.status(HTTP_STATUS.CREATED);
return next({
ok: API_MESSAGE.TAG_UPDATED,
res.status(HTTP_STATUS.CREATED);
return next({
ok: API_MESSAGE.TAG_REMOVED
});
});
});
});
}
);
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 {
if (err) {
return next(err);
}
next(info[DIST_TAGS]);
}
});
}
);
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);
}
res.status(HTTP_STATUS.CREATED);
return next({
ok: API_MESSAGE.TAG_UPDATED
});
}
);
}
);
}

View file

@ -1,19 +1,31 @@
import _ from 'lodash';
import { Router } from 'express';
import { Config, Package } from '@verdaccio/types';
import { allow } from '../../middleware';
import { convertDistRemoteToLocalTarballUrls, getVersion, ErrorCode } from '../../../lib/utils';
import { HEADERS, DIST_TAGS, API_ERROR } from '../../../lib/constants';
import { Router } from 'express';
import { Config, Package } from '@verdaccio/types';
import { IAuth, $ResponseExtend, $RequestExtend, $NextFunctionVer, IStorageHandler } from '../../../../types';
import {
IAuth,
$ResponseExtend,
$RequestExtend,
$NextFunctionVer,
IStorageHandler
} from '../../../../types';
const downloadStream = (packageName: string, filename: string, storage: any, req: $RequestExtend, res: $ResponseExtend): void => {
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.report_error(err);
});
@ -21,53 +33,70 @@ 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 {
if (err) {
return next(err);
}
metadata = convertDistRemoteToLocalTarballUrls(metadata, req, config.url_prefix);
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);
}
metadata = convertDistRemoteToLocalTarballUrls(metadata, req, config.url_prefix);
let queryVersion = req.params.version;
if (_.isNil(queryVersion)) {
return next(metadata);
}
let queryVersion = req.params.version;
if (_.isNil(queryVersion)) {
return next(metadata);
}
let version = getVersion(metadata, queryVersion);
if (_.isNil(version) === false) {
return next(version);
}
let version = getVersion(metadata, queryVersion);
if (_.isNil(version) === false) {
return next(version);
}
if (_.isNil(metadata[DIST_TAGS]) === false) {
if (_.isNil(metadata[DIST_TAGS][queryVersion]) === false) {
queryVersion = metadata[DIST_TAGS][queryVersion];
version = getVersion(metadata, queryVersion);
if (_.isNil(version) === false) {
return next(version);
if (_.isNil(metadata[DIST_TAGS]) === false) {
if (_.isNil(metadata[DIST_TAGS][queryVersion]) === false) {
queryVersion = metadata[DIST_TAGS][queryVersion];
version = getVersion(metadata, queryVersion);
if (_.isNil(version) === false) {
return next(version);
}
}
}
}
return next(ErrorCode.getNotFound(`${API_ERROR.VERSION_NOT_EXIST}: ${req.params.version}`));
};
return next(ErrorCode.getNotFound(`${API_ERROR.VERSION_NOT_EXIST}: ${req.params.version}`));
};
storage.getPackage({
name: req.params.package,
uplinksLook: true,
req,
callback: getPackageMetaCallback,
});
});
storage.getPackage({
name: req.params.package,
uplinksLook: true,
req,
callback: getPackageMetaCallback
});
}
);
route.get('/:scopedPackage/-/:scope/:filename', can('access'), function(req: $RequestExtend, res: $ResponseExtend): void {
const { scopedPackage, filename } = req.params;
route.get(
'/:scopedPackage/-/:scope/:filename',
can('access'),
function (req: $RequestExtend, res: $ResponseExtend): void {
const { scopedPackage, filename } = req.params;
downloadStream(scopedPackage, filename, storage, req, res);
});
downloadStream(scopedPackage, filename, storage, req, res);
}
);
route.get('/:package/-/:filename', can('access'), function(req: $RequestExtend, res: $ResponseExtend): void {
downloadStream(req.params.package, req.params.filename, storage, req, res);
});
route.get(
'/:package/-/:filename',
can('access'),
function (req: $RequestExtend, res: $ResponseExtend): void {
downloadStream(req.params.package, req.params.filename, storage, req, res);
}
);
}

View file

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

View file

@ -1,20 +1,37 @@
import _ from 'lodash';
import Path from 'path';
import _ from 'lodash';
import mime from 'mime';
import { API_MESSAGE, HEADERS, DIST_TAGS, API_ERROR, HTTP_STATUS } from '../../../lib/constants';
import {validateMetadata, isObject, ErrorCode, hasDiffOneKey, isRelatedToDeprecation} from '../../../lib/utils';
import { media, expectJson, allow } from '../../middleware';
import { notify } from '../../../lib/notify';
import star from './star';
import { Router } from 'express';
import { Config, Callback, MergeTags, Version, Package } from '@verdaccio/types';
import { IAuth, $ResponseExtend, $RequestExtend, $NextFunctionVer, IStorageHandler } from '../../../../types';
import { logger } from '../../../lib/logger';
import {isPublishablePackage} from "../../../lib/storage-utils";
import { API_MESSAGE, HEADERS, DIST_TAGS, API_ERROR, HTTP_STATUS } from '../../../lib/constants';
import {
validateMetadata,
isObject,
ErrorCode,
hasDiffOneKey,
isRelatedToDeprecation
} from '../../../lib/utils';
import { media, expectJson, allow } from '../../middleware';
import { notify } from '../../../lib/notify';
export default function publish(router: Router, auth: IAuth, storage: IStorageHandler, config: Config): void {
import {
IAuth,
$ResponseExtend,
$RequestExtend,
$NextFunctionVer,
IStorageHandler
} from '../../../../types';
import { logger } from '../../../lib/logger';
import { isPublishablePackage } from '../../../lib/storage-utils';
import star from './star';
export default function publish(
router: Router,
auth: IAuth,
storage: IStorageHandler,
config: Config
): void {
const can = allow(auth);
/**
@ -76,7 +93,13 @@ export default function publish(router: Router, auth: IAuth, storage: IStorageHa
}
*
*/
router.put('/:package/:_rev?/:revision?', can('publish'), media(mime.getType('json')), expectJson, publishPackage(storage, config, auth));
router.put(
'/:package/:_rev?/:revision?',
can('publish'),
media(mime.getType('json')),
expectJson,
publishPackage(storage, config, auth)
);
/**
* Un-publishing an entire package.
@ -89,13 +112,29 @@ export default function publish(router: Router, auth: IAuth, storage: IStorageHa
router.delete('/:package/-rev/*', can('unpublish'), unPublishPackage(storage));
// removing a tarball
router.delete('/:package/-/:filename/-rev/:revision', can('unpublish'), can('publish'), removeTarball(storage));
router.delete(
'/:package/-/:filename/-rev/:revision',
can('unpublish'),
can('publish'),
removeTarball(storage)
);
// uploading package tarball
router.put('/:package/-/:filename/*', can('publish'), media(HEADERS.OCTET_STREAM), uploadPackageTarball(storage));
router.put(
'/:package/-/:filename/*',
can('publish'),
media(HEADERS.OCTET_STREAM),
uploadPackageTarball(storage)
);
// adding a version
router.put('/:package/:version/-tag/:tag', can('publish'), media(mime.getType('json')), expectJson, addVersion(storage));
router.put(
'/:package/:version/-tag/:tag',
can('publish'),
media(mime.getType('json')),
expectJson,
addVersion(storage)
);
}
/**
@ -103,20 +142,20 @@ 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?
@ -128,18 +167,18 @@ export function publishPackage(storage: IStorageHandler, config: Config, auth: I
/**
* 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): void {
const afterChange = function (error, okMessage, metadata): void {
const metadataCopy: Package = { ...metadata };
const { _attachments, versions } = metadataCopy;
@ -153,15 +192,18 @@ export function publishPackage(storage: IStorageHandler, config: Config, auth: I
res.status(HTTP_STATUS.CREATED);
return next({
ok: okMessage,
success: true,
success: true
});
}
// 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,37 +219,49 @@ 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) {
if (error) {
return next(error);
}
const versionToPublish = Object.keys(versions)[0];
const versionMetadataToPublish = versions[versionToPublish];
versionMetadataToPublish.readme = _.isNil(versionMetadataToPublish.readme) === false ? String(versionMetadataToPublish.readme) : '';
createVersion(versionToPublish, versionMetadataToPublish, function(error) {
createTarball(
Path.basename(firstAttachmentKey),
_attachments[firstAttachmentKey],
function (error) {
if (error) {
return next(error);
}
addTags(metadataCopy[DIST_TAGS], async function(error) {
const versionToPublish = Object.keys(versions)[0];
const versionMetadataToPublish = versions[versionToPublish];
versionMetadataToPublish.readme =
_.isNil(versionMetadataToPublish.readme) === false
? String(versionMetadataToPublish.readme)
: '';
createVersion(versionToPublish, versionMetadataToPublish, function (error) {
if (error) {
return next(error);
}
try {
await notify(metadataCopy, config, req.remote_user, `${metadataCopy.name}@${versionToPublish}`);
} catch (error) {
logger.error({ error }, 'notify batch service has failed: @{error}');
}
addTags(metadataCopy[DIST_TAGS], async function (error) {
if (error) {
return next(error);
}
res.status(HTTP_STATUS.CREATED);
return next({ ok: okMessage, success: true });
try {
await notify(
metadataCopy,
config,
req.remote_user,
`${metadataCopy.name}@${versionToPublish}`
);
} catch (error) {
logger.error({ error }, 'notify batch service has failed: @{error}');
}
res.status(HTTP_STATUS.CREATED);
return next({ ok: okMessage, success: true });
});
});
});
});
}
);
};
if (isPublishablePackage(req.body) === false && isObject(req.body.users)) {
@ -218,27 +272,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 +302,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 +320,24 @@ 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,18 +346,18 @@ 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);
}
res.status(HTTP_STATUS.CREATED);
return next({
ok: API_MESSAGE.PKG_PUBLISHED,
ok: API_MESSAGE.PKG_PUBLISHED
});
});
};
@ -307,32 +367,32 @@ 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.report_error(err);
});
stream.on('success', function() {
stream.on('success', function () {
res.status(HTTP_STATUS.CREATED);
return next({
ok: API_MESSAGE.TARBALL_UPLOADED,
ok: API_MESSAGE.TARBALL_UPLOADED
});
});
};

View file

@ -1,19 +1,19 @@
import { HEADERS } from "../../../lib/constants";
import { HEADERS } from '../../../lib/constants';
/**
* @prettier
*/
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,
@ -34,8 +34,10 @@ export default function(route, auth, storage): void {
* when request /-/all/since, response is an array
*/
const respShouldBeArray = req.path.endsWith('/since');
if (!respShouldBeArray) res.set('Date', 'Mon, 10 Oct 1983 00:12:48 GMT');
const check_finish = function(): void {
if (!respShouldBeArray) {
res.set('Date', 'Mon, 10 Oct 1983 00:12:48 GMT');
}
const check_finish = function (): void {
if (!received_end) {
return;
}
@ -64,7 +66,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) {
@ -92,11 +94,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();
});

View file

@ -1,12 +1,14 @@
// @flow
import { USERS, HTTP_STATUS } from '../../../lib/constants';
import {Response} from 'express';
import {$RequestExtend, $NextFunctionVer, IStorageHandler} from '../../../../types';
import { Response } from 'express';
import _ from 'lodash';
import { USERS, HTTP_STATUS } from '../../../lib/constants';
import { $RequestExtend, $NextFunctionVer, IStorageHandler } from '../../../../types';
import { logger } from '../../../lib/logger';
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]) {
@ -21,21 +23,21 @@ 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);
}
res.status(HTTP_STATUS.OK);
next({
success: true,
success: true
});
};
storage.getPackage({
name,
req,
callback: function(err, info) {
callback: function (err, info) {
if (err) {
return next(err);
}
@ -44,22 +46,31 @@ export default function(storage: IStorageHandler): (req: $RequestExtend, res: Re
const localStarUsers = info[USERS];
// Check is star or unstar
const isStar = Object.keys(newStarUser).includes(remoteUsername);
if (_.isNil(localStarUsers) === false && validateInputs(newStarUser, localStarUsers, remoteUsername, isStar)) {
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);
});
},
}
});
};
}

View file

@ -3,32 +3,35 @@
*/
import _ from 'lodash';
import { USERS, HTTP_STATUS } from '../../../lib/constants';
import { Response, Router } from 'express';
import { $RequestExtend, $NextFunctionVer, IStorageHandler } from '../../../../types';
import { Package } from '@verdaccio/types';
import { USERS, HTTP_STATUS } from '../../../lib/constants';
import { $RequestExtend, $NextFunctionVer, IStorageHandler } from '../../../../types';
type Packages = Package[];
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;
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;
storage.getLocalDatabase((err, localPackages: Packages) => {
if (err) {
return next(err);
}
storage.getLocalDatabase((err, localPackages: Packages) => {
if (err) {
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({
rows: filteredPackages.map((filteredPackage: Package) => ({
value: filteredPackage.name,
})),
res.status(HTTP_STATUS.OK);
next({
rows: filteredPackages.map((filteredPackage: Package) => ({
value: filteredPackage.name
}))
});
});
});
});
}
);
}

View file

@ -1,89 +1,120 @@
import _ from 'lodash';
import Cookies from 'cookies';
import { ErrorCode } from '../../../lib/utils';
import { API_ERROR, API_MESSAGE, HTTP_STATUS } from '../../../lib/constants';
import { createRemoteUser, createSessionToken, getApiToken, getAuthenticatedMessage, validatePassword } from '../../../lib/auth-utils';
import { logger } from '../../../lib/logger';
import { Config, RemoteUser } from '@verdaccio/types';
import { Response, Router } from 'express';
import { ErrorCode } from '../../../lib/utils';
import { API_ERROR, API_MESSAGE, HTTP_STATUS } from '../../../lib/constants';
import {
createRemoteUser,
createSessionToken,
getApiToken,
getAuthenticatedMessage,
validatePassword
} from '../../../lib/auth-utils';
import { logger } from '../../../lib/logger';
import { $RequestExtend, $ResponseExtend, $NextFunctionVer, IAuth } from '../../../../types';
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 {
const { name, password } = req.body;
const remoteName = req.remote_user.name;
if (_.isNil(remoteName) === false && _.isNil(name) === false && remoteName === name) {
auth.authenticate(name, password, async function callbackAuthenticate(err, user): Promise<void> {
if (err) {
logger.trace({ name, err }, 'authenticating for user @{username} failed. Error: @{err.message}');
return next(ErrorCode.getCode(HTTP_STATUS.UNAUTHORIZED, API_ERROR.BAD_USERNAME_PASSWORD));
}
const restoredRemoteUser: RemoteUser = createRemoteUser(name, user.groups || []);
const token = await getApiToken(auth, config, restoredRemoteUser, password);
res.status(HTTP_STATUS.CREATED);
return next({
ok: getAuthenticatedMessage(req.remote_user.name),
token,
});
});
} else {
if (validatePassword(password) === false) {
// eslint-disable-next-line new-cap
return next(ErrorCode.getCode(HTTP_STATUS.BAD_REQUEST, API_ERROR.PASSWORD_SHORT()));
}
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,
// and npm accepts only an 409 error.
// So, changing status code here.
return next(ErrorCode.getCode(err.status, err.message) || ErrorCode.getConflict(err.message));
}
return next(err);
}
const token = name && password ? await getApiToken(auth, config, user, password) : undefined;
req.remote_user = user;
res.status(HTTP_STATUS.CREATED);
return next({
ok: `user '${req.body.name}' created`,
token,
});
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.delete('/-/user/token/*', function(req: $RequestExtend, res: Response, next: $NextFunctionVer): void {
res.status(HTTP_STATUS.OK);
next({
ok: API_MESSAGE.LOGGED_OUT,
});
});
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;
if (_.isNil(remoteName) === false && _.isNil(name) === false && remoteName === name) {
auth.authenticate(
name,
password,
async function callbackAuthenticate(err, user): Promise<void> {
if (err) {
logger.trace(
{ name, err },
'authenticating for user @{username} failed. Error: @{err.message}'
);
return next(
ErrorCode.getCode(HTTP_STATUS.UNAUTHORIZED, API_ERROR.BAD_USERNAME_PASSWORD)
);
}
const restoredRemoteUser: RemoteUser = createRemoteUser(name, user.groups || []);
const token = await getApiToken(auth, config, restoredRemoteUser, password);
res.status(HTTP_STATUS.CREATED);
return next({
ok: getAuthenticatedMessage(req.remote_user.name),
token
});
}
);
} else {
if (validatePassword(password) === false) {
// eslint-disable-next-line new-cap
return next(ErrorCode.getCode(HTTP_STATUS.BAD_REQUEST, API_ERROR.PASSWORD_SHORT()));
}
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,
// and npm accepts only an 409 error.
// So, changing status code here.
return next(
ErrorCode.getCode(err.status, err.message) || ErrorCode.getConflict(err.message)
);
}
return next(err);
}
const token =
name && password ? await getApiToken(auth, config, user, password) : undefined;
req.remote_user = user;
res.status(HTTP_STATUS.CREATED);
return next({
ok: `user '${req.body.name}' created`,
token
});
});
}
}
);
route.delete(
'/-/user/token/*',
function (req: $RequestExtend, res: Response, next: $NextFunctionVer): void {
res.status(HTTP_STATUS.OK);
next({
ok: API_MESSAGE.LOGGED_OUT
});
}
);
// placeholder 'cause npm require to be authenticated to publish
// we do not do any real authentication yet
route.post('/_session', Cookies.express(), function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
res.cookies.set('AuthSession', String(Math.random()), createSessionToken());
route.post(
'/_session',
Cookies.express(),
function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
res.cookies.set('AuthSession', String(Math.random()), createSessionToken());
next({
ok: true,
name: 'somebody',
roles: [],
});
});
next({
ok: true,
name: 'somebody',
roles: []
});
}
);
}

View file

@ -1,9 +1,9 @@
import _ from 'lodash';
import { Response, Router } from 'express';
import { API_ERROR, APP_ERROR, HTTP_STATUS, SUPPORT_ERRORS } from '../../../../lib/constants';
import { ErrorCode } from '../../../../lib/utils';
import { validatePassword } from '../../../../lib/auth-utils';
import { Response, Router } from 'express';
import { $NextFunctionVer, $RequestExtend, IAuth } from '../../../../../types';
export interface Profile {
@ -17,7 +17,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,
@ -27,58 +27,68 @@ export default function(route: Router, auth: IAuth): void {
created: '',
updated: '',
cidr_whitelist: null,
fullname: '',
fullname: ''
};
}
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));
}
res.status(HTTP_STATUS.UNAUTHORIZED);
return next({
message: API_ERROR.MUST_BE_LOGGED,
});
});
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({
message: API_ERROR.MUST_BE_LOGGED,
});
}
const { password, tfa } = req.body;
const { name } = req.remote_user;
if (_.isNil(password) === false) {
if (validatePassword(password.new) === false) {
/* eslint new-cap:off */
return next(ErrorCode.getCode(HTTP_STATUS.UNAUTHORIZED, API_ERROR.PASSWORD_SHORT()));
/* eslint new-cap:off */
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));
}
auth.changePassword(
name,
password.old,
password.new,
(err, isUpdated): $NextFunctionVer => {
if (_.isNull(err) === false) {
return next(ErrorCode.getCode(err.status, err.message) || ErrorCode.getConflict(err.message));
}
if (isUpdated) {
return next(buildProfile(req.remote_user.name));
}
return next(ErrorCode.getInternalError(API_ERROR.INTERNAL_SERVER_ERROR));
}
);
} else if (_.isNil(tfa) === false) {
return next(ErrorCode.getCode(HTTP_STATUS.SERVICE_UNAVAILABLE, SUPPORT_ERRORS.TFA_DISABLED));
} else {
return next(ErrorCode.getCode(HTTP_STATUS.INTERNAL_ERROR, APP_ERROR.PROFILE_ERROR));
res.status(HTTP_STATUS.UNAUTHORIZED);
return next({
message: API_ERROR.MUST_BE_LOGGED
});
}
});
);
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({
message: API_ERROR.MUST_BE_LOGGED
});
}
const { password, tfa } = req.body;
const { name } = req.remote_user;
if (_.isNil(password) === false) {
if (validatePassword(password.new) === false) {
/* eslint new-cap:off */
return next(ErrorCode.getCode(HTTP_STATUS.UNAUTHORIZED, API_ERROR.PASSWORD_SHORT()));
/* eslint new-cap:off */
}
auth.changePassword(
name,
password.old,
password.new,
(err, isUpdated): $NextFunctionVer => {
if (_.isNull(err) === false) {
return next(
ErrorCode.getCode(err.status, err.message) || ErrorCode.getConflict(err.message)
);
}
if (isUpdated) {
return next(buildProfile(req.remote_user.name));
}
return next(ErrorCode.getInternalError(API_ERROR.INTERNAL_SERVER_ERROR));
}
);
} else if (_.isNil(tfa) === false) {
return next(
ErrorCode.getCode(HTTP_STATUS.SERVICE_UNAVAILABLE, SUPPORT_ERRORS.TFA_DISABLED)
);
} else {
return next(ErrorCode.getCode(HTTP_STATUS.INTERNAL_ERROR, APP_ERROR.PROFILE_ERROR));
}
}
);
}

View file

@ -1,98 +1,105 @@
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);
if(typeof person === 'object')
for(const field of Object.values(person))
if(typeof field === 'string' && field.includes(search))
return true;
return false;
}
const matcher = function(q) {
const match = q.match(/author:(.*)/)
if(match !== null)
return (pkg) => personMatch(pkg.author, match[1])
function compileTextSearch(textSearch: string): (pkg: Package) => boolean {
const personMatch = (person, search) => {
if (typeof person === 'string') {
return person.includes(search);
}
// 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))
};
if (typeof person === 'object') {
for (const field of Object.values(person)) {
if (typeof field === 'string' && field.includes(search)) {
return true;
}
}
}
const textMatchers = (textSearch || '').split(' ').map(matcher);
return (pkg) => textMatchers.every(m => m(pkg));
return false;
};
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));
};
};
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])
size = parseInt(size) || 20;
from = parseInt(from) || 0;
const isInteresting = compileTextSearch(text);
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]);
const resultStream = storage.search(0, {req: {query: {local: true}}});
const resultBuf = [] as any;
let completed = false;
size = parseInt(size) || 20;
from = parseInt(from) || 0;
const sendResponse = (): void => {
completed = true;
resultStream.destroy()
const isInteresting = compileTextSearch(text);
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 resultStream = storage.search(0, { req: { query: { local: true } } });
const resultBuf = [] as any;
let completed = false;
res.status(200)
.json(response)
}
const sendResponse = (): void => {
completed = true;
resultStream.destroy();
resultStream.on('data', (pkg)=>{
if(!isInteresting(pkg))
return;
resultBuf.push(pkg)
if(!completed && resultBuf.length >= size + from)
sendResponse();
})
resultStream.on('end', ()=>{
if(!completed)
sendResponse()
})
})
}
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);
};
resultStream.on('data', (pkg) => {
if (!isInteresting(pkg)) {
return;
}
resultBuf.push(pkg);
if (!completed && resultBuf.length >= size + from) {
sendResponse();
}
});
resultStream.on('end', () => {
if (!completed) {
sendResponse();
}
});
});
}

View file

@ -1,123 +1,143 @@
import _ from 'lodash';
import { Response, Router } from 'express';
import { Config, RemoteUser, Token } from '@verdaccio/types';
import { HTTP_STATUS, SUPPORT_ERRORS } from '../../../../lib/constants';
import {ErrorCode, mask} from '../../../../lib/utils';
import { ErrorCode, mask } from '../../../../lib/utils';
import { getApiToken } from '../../../../lib/auth-utils';
import { stringToMD5 } from '../../../../lib/crypto-utils';
import { logger } from '../../../../lib/logger';
import { Response, Router } from 'express';
import {$NextFunctionVer, $RequestExtend, IAuth, IStorageHandler} from '../../../../../types';
import { Config, RemoteUser, Token } from '@verdaccio/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());
}
);
}

View file

@ -1,22 +1,16 @@
import { Response, Router } from 'express';
import { $RequestExtend, $NextFunctionVer } from '../../../../types';
export default function(route: Router): void {
route.get(
'/whoami',
(req: $RequestExtend, res: Response, next: $NextFunctionVer): void => {
if (req.headers.referer === 'whoami') {
next({ username: req.remote_user.name });
} else {
next('route');
}
}
);
route.get(
'/-/whoami',
(req: $RequestExtend, res: Response, next: $NextFunctionVer): any => {
export default function (route: Router): void {
route.get('/whoami', (req: $RequestExtend, res: Response, next: $NextFunctionVer): void => {
if (req.headers.referer === 'whoami') {
next({ username: req.remote_user.name });
} else {
next('route');
}
);
});
route.get('/-/whoami', (req: $RequestExtend, res: Response, next: $NextFunctionVer): any => {
next({ username: req.remote_user.name });
});
}

View file

@ -1,9 +1,9 @@
import { IAuth, IStorageHandler } from '../../../types';
import { Config } from '@verdaccio/types';
import _ from 'lodash';
import express from 'express';
import bodyParser from 'body-parser';
import { IAuth, IStorageHandler } from '../../../types';
import whoami from './api/whoami';
import ping from './api/ping';
import user from './api/user';
@ -15,11 +15,17 @@ import stars from './api/stars';
import profile from './api/v1/profile';
import token from './api/v1/token';
import v1Search from './api/v1/search'
import v1Search from './api/v1/search';
const { match, validateName, validatePackage, encodeScopePackage, antiLoop } = require('../middleware');
const {
match,
validateName,
validatePackage,
encodeScopePackage,
antiLoop
} = require('../middleware');
export default function(config: Config, auth: IAuth, storage: IStorageHandler) {
export default function (config: Config, auth: IAuth, storage: IStorageHandler) {
/* eslint new-cap:off */
const app = express.Router();
/* eslint new-cap:off */
@ -58,7 +64,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) {

View file

@ -3,22 +3,28 @@ import express, { Application } from 'express';
import compression from 'compression';
import cors from 'cors';
import { HttpError } from 'http-errors';
import { Config as IConfig, IPluginMiddleware, IPluginStorageFilter } from '@verdaccio/types';
import Storage from '../lib/storage';
import loadPlugin from '../lib/plugin-loader';
import hookDebug from './debug';
import Auth from '../lib/auth';
import apiEndpoint from './endpoint';
import { ErrorCode } from '../lib/utils';
import { API_ERROR, HTTP_STATUS } from '../lib/constants';
import AppConfig from '../lib/config';
import {
$ResponseExtend,
$RequestExtend,
$NextFunctionVer,
IStorageHandler,
IAuth
} from '../../types';
import { setup, logger } from '../lib/logger';
import webAPI from './web/api';
import web from './web';
import { $ResponseExtend, $RequestExtend, $NextFunctionVer, IStorageHandler, IAuth } from '../../types';
import { Config as IConfig, IPluginMiddleware, IPluginStorageFilter } from '@verdaccio/types';
import { setup, logger } from '../lib/logger';
import apiEndpoint from './endpoint';
import hookDebug from './debug';
import { log, final, errorReportingMiddleware } from './middleware';
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
@ -29,17 +35,20 @@ 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 {
req.url = '/-/static/favicon.png';
next();
});
app.get(
'/favicon.ico',
function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
req.url = '/-/static/favicon.png';
next();
}
);
// Hook for tests only
if (config._debug) {
@ -49,12 +58,17 @@ const defineAPI = function(config: IConfig, storage: IStorageHandler): any {
// register middleware plugins
const plugin_params = {
config: config,
logger: logger,
logger: logger
};
const plugins: IPluginMiddleware<IConfig>[] = loadPlugin(config, config.middlewares, plugin_params, function(plugin: IPluginMiddleware<IConfig>) {
return plugin.register_middlewares;
});
const plugins: IPluginMiddleware<IConfig>[] = loadPlugin(
config,
config.middlewares,
plugin_params,
function (plugin: IPluginMiddleware<IConfig>) {
return plugin.register_middlewares;
}
);
plugins.forEach((plugin: IPluginMiddleware<IConfig>) => {
plugin.register_middlewares(app, auth, storage);
});
@ -67,17 +81,22 @@ const defineAPI = function(config: IConfig, storage: IStorageHandler): any {
app.use('/', web(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();
@ -99,15 +118,20 @@ 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
const plugin_params = {
config: config,
logger: logger,
logger: logger
};
const filters = loadPlugin(config, config.filters || {}, plugin_params, (plugin: IPluginStorageFilter<IConfig>) => plugin.filter_metadata);
const filters = loadPlugin(
config,
config.filters || {},
plugin_params,
(plugin: IPluginStorageFilter<IConfig>) => plugin.filter_metadata
);
const storage: IStorageHandler = new Storage(config);
// waits until init calls have been initialized
await storage.init(config, filters);

View file

@ -1,15 +1,33 @@
import _ from 'lodash';
import { validateName as utilValidateName, validatePackage as utilValidatePackage, getVersionFromTarball, isObject, ErrorCode } from '../lib/utils';
import { API_ERROR, HEADER_TYPE, HEADERS, HTTP_STATUS, TOKEN_BASIC, TOKEN_BEARER } from '../lib/constants';
import { Config, Package, RemoteUser } from '@verdaccio/types';
import { VerdaccioError } from '@verdaccio/commons-api';
import {
validateName as utilValidateName,
validatePackage as utilValidatePackage,
getVersionFromTarball,
isObject,
ErrorCode
} from '../lib/utils';
import {
API_ERROR,
HEADER_TYPE,
HEADERS,
HTTP_STATUS,
TOKEN_BASIC,
TOKEN_BEARER
} from '../lib/constants';
import { stringToMD5 } from '../lib/crypto-utils';
import { $ResponseExtend, $RequestExtend, $NextFunctionVer, IAuth } from '../../types';
import { Config, Package, RemoteUser } from '@verdaccio/types';
import { logger } from '../lib/logger';
import { VerdaccioError } from '@verdaccio/commons-api';
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 {
@ -18,7 +36,11 @@ export function match(regexp: RegExp): any {
};
}
export function setSecurityWebHeaders(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
export function setSecurityWebHeaders(
req: $RequestExtend,
res: $ResponseExtend,
next: $NextFunctionVer
): void {
// disable loading in frames (clickjacking, etc.)
res.header(HEADERS.FRAMES_OPTIONS, 'deny');
// avoid stablish connections outside of domain
@ -32,7 +54,13 @@ export function setSecurityWebHeaders(req: $RequestExtend, res: $ResponseExtend,
// flow: express does not match properly
// flow info https://github.com/flowtype/flow-typed/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+express
export function validateName(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer, value: string, name: string): void {
export function validateName(
req: $RequestExtend,
res: $ResponseExtend,
next: $NextFunctionVer,
value: string,
name: string
): void {
if (value === '-') {
// special case in couchdb usually
next('route');
@ -45,7 +73,13 @@ export function validateName(req: $RequestExtend, res: $ResponseExtend, next: $N
// flow: express does not match properly
// flow info https://github.com/flowtype/flow-typed/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+express
export function validatePackage(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer, value: string, name: string): void {
export function validatePackage(
req: $RequestExtend,
res: $ResponseExtend,
next: $NextFunctionVer,
value: string,
name: string
): void {
if (value === '-') {
// special case in couchdb usually
next('route');
@ -57,16 +91,28 @@ 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]));
next(
ErrorCode.getCode(
HTTP_STATUS.UNSUPPORTED_MEDIA,
'wrong content-type, expect: ' +
expect +
', got: ' +
req.headers[HEADER_TYPE.CONTENT_TYPE]
)
);
} else {
next();
}
};
}
export function encodeScopePackage(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
export function encodeScopePackage(
req: $RequestExtend,
res: $ResponseExtend,
next: $NextFunctionVer
): void {
if (req.url.indexOf('@') !== -1) {
// e.g.: /@org/pkg/1.2.3 -> /@org%2Fpkg/1.2.3, /@org%2Fpkg/1.2.3 -> /@org%2Fpkg/1.2.3
req.url = req.url.replace(/^(\/@[^\/%]+)\/(?!$)/, '$1%2F');
@ -74,7 +120,11 @@ export function encodeScopePackage(req: $RequestExtend, res: $ResponseExtend, ne
next();
}
export function expectJson(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
export function expectJson(
req: $RequestExtend,
res: $ResponseExtend,
next: $NextFunctionVer
): void {
if (!isObject(req.body)) {
return next(ErrorCode.getBadRequest("can't parse incoming json"));
}
@ -82,7 +132,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(',');
@ -98,26 +148,37 @@ 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 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}`);
logger.trace(
{ action, user: remote.name },
`[middleware/allow][@{action}] allow for @{user}`
);
auth['allow_' + action]({ packageName, packageVersion }, remote, function(error, allowed): void {
req.resume();
if (error) {
next(error);
} else if (allowed) {
next();
} else {
// last plugin (that's our built-in one) returns either
// cb(err) or cb(null, true), so this should never happen
throw ErrorCode.getInternalError(API_ERROR.PLUGIN_ERROR);
auth['allow_' + action](
{ packageName, packageVersion },
remote,
function (error, allowed): void {
req.resume();
if (error) {
next(error);
} else if (allowed) {
next();
} else {
// last plugin (that's our built-in one) returns either
// cb(err) or cb(null, true), so this should never happen
throw ErrorCode.getInternalError(API_ERROR.PLUGIN_ERROR);
}
}
});
);
};
};
}
@ -128,7 +189,12 @@ export interface MiddlewareError {
export type FinalBody = Package | MiddlewareError | string;
export function final(body: FinalBody, req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
export function final(
body: FinalBody,
req: $RequestExtend,
res: $ResponseExtend,
next: $NextFunctionVer
): void {
if (res.statusCode === HTTP_STATUS.UNAUTHORIZED && !res.getHeader(HEADERS.WWW_AUTH)) {
// they say it's required for 401, so...
res.header(HEADERS.WWW_AUTH, `${TOKEN_BASIC}, ${TOKEN_BEARER}`);
@ -148,7 +214,10 @@ export function final(body: FinalBody, req: $RequestExtend, res: $ResponseExtend
}
// don't send etags with errors
if (!res.statusCode || (res.statusCode >= HTTP_STATUS.OK && res.statusCode < HTTP_STATUS.MULTIPLE_CHOICES)) {
if (
!res.statusCode ||
(res.statusCode >= HTTP_STATUS.OK && res.statusCode < HTTP_STATUS.MULTIPLE_CHOICES)
) {
res.header(HEADERS.ETAG, '"' + stringToMD5(body as string) + '"');
}
} else {
@ -170,7 +239,8 @@ export function final(body: FinalBody, req: $RequestExtend, res: $ResponseExtend
res.send(body);
}
export const LOG_STATUS_MESSAGE = "@{status}, user: @{user}(@{remoteIP}), req: '@{request.method} @{request.url}'";
export const LOG_STATUS_MESSAGE =
"@{status}, user: @{user}(@{remoteIP}), req: '@{request.method} @{request.url}'";
export const LOG_VERDACCIO_ERROR = `${LOG_STATUS_MESSAGE}, error: @{!error}`;
export const LOG_VERDACCIO_BYTES = `${LOG_STATUS_MESSAGE}, bytes: @{bytes.in}/@{bytes.out}`;
@ -192,7 +262,7 @@ export function log(config: Config) {
req.url = req.originalUrl;
// avoid log noise data from static content
if (req.originalUrl.match(/static/) === null) {
req.log.info({req: req, ip: req.ip}, "@{ip} requested '@{req.method} @{req.url}'");
req.log.info({ req: req, ip: req.ip }, "@{ip} requested '@{req.method} @{req.url}'");
}
req.originalUrl = req.url;
@ -206,7 +276,7 @@ export function log(config: Config) {
let bytesin = 0;
if (config?.experiments?.bytesin_off !== true) {
req.on('data', function(chunk): void {
req.on('data', function (chunk): void {
bytesin += chunk.length;
});
}
@ -215,7 +285,7 @@ export function log(config: Config) {
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
@ -223,7 +293,7 @@ export function log(config: Config) {
};
let logHasBeenCalled = false;
const log = function(): void {
const log = function (): void {
if (logHasBeenCalled) {
return;
}
@ -243,33 +313,33 @@ export function log(config: Config) {
// avoid log noise data from static content
if (req.url.match(/static/) === null) {
req.log.warn(
{
request: {
method: req.method,
url: req.url,
},
level: 35, // http
user: (req.remote_user && req.remote_user.name) || null,
remoteIP,
status: res.statusCode,
error: res._verdaccio_error,
bytes: {
in: bytesin,
out: bytesout,
},
{
request: {
method: req.method,
url: req.url
},
message
level: 35, // http
user: (req.remote_user && req.remote_user.name) || null,
remoteIP,
status: res.statusCode,
error: res._verdaccio_error,
bytes: {
in: bytesin,
out: bytesout
}
},
message
);
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;
}
@ -279,14 +349,18 @@ export function log(config: Config) {
log();
};
next();
}
};
}
// Middleware
export function errorReportingMiddleware(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
export function errorReportingMiddleware(
req: $RequestExtend,
res: $ResponseExtend,
next: $NextFunctionVer
): void {
res.report_error =
res.report_error ||
function(err: VerdaccioError): void {
function (err: VerdaccioError): void {
if (err.status && err.status >= HTTP_STATUS.BAD_REQUEST && err.status < 600) {
if (!res.headersSent) {
res.status(err.status);

View file

@ -1,20 +1,19 @@
import { Router } from 'express';
import bodyParser from 'body-parser';
import { Config } from '@verdaccio/types';
import Search from '../../lib/search';
import { match, validateName, validatePackage, setSecurityWebHeaders } from '../middleware';
import { IAuth, IStorageHandler } from '../../../types';
import addUserAuthApi from './endpoint/user';
import addPackageWebApi from './endpoint/package';
import addSearchWebApi from './endpoint/search';
import Search from '../../lib/search';
import { match, validateName, validatePackage, setSecurityWebHeaders } from '../middleware';
import { Config } from '@verdaccio/types';
import { IAuth, IStorageHandler } from '../../../types';
const route = Router(); /* eslint new-cap: 0 */
/*
This file include all verdaccio only API(Web UI), for npm API please see ../endpoint/
*/
export default function(config: Config, auth: IAuth, storage: IStorageHandler): Router {
export default function (config: Config, auth: IAuth, storage: IStorageHandler): Router {
Search.configureStorage(storage);
// validate all of these params as a package name

View file

@ -1,4 +1,6 @@
import _ from 'lodash';
import { Router } from 'express';
import { Config, Package } from '@verdaccio/types';
import {
addScope,
addGravatarSupport,
@ -14,115 +16,148 @@ import { allow } from '../../middleware';
import { DIST_TAGS, HEADER_TYPE, HEADERS, HTTP_STATUS } from '../../../lib/constants';
import { generateGravatarUrl } from '../../../utils/user';
import { logger } from '../../../lib/logger';
import { Router } from 'express';
import { IAuth, $ResponseExtend, $RequestExtend, $NextFunctionVer, IStorageHandler, $SidebarPackage } from '../../../../types';
import { Config, Package } from '@verdaccio/types';
import {
IAuth,
$ResponseExtend,
$RequestExtend,
$NextFunctionVer,
IStorageHandler,
$SidebarPackage
} from '../../../../types';
const getOrder = (order = 'asc') => {
return order === 'asc';
};
export type PackcageExt = Package & { author: any, dist?: {tarball: string} };
export type PackcageExt = Package & { author: any; dist?: { tarball: string } };
function addPackageWebApi(route: Router, storage: IStorageHandler, auth: IAuth, config: Config): void {
function addPackageWebApi(
route: Router,
storage: IStorageHandler,
auth: IAuth,
config: Config
): void {
const can = allow(auth);
const checkAllow = (name, remoteUser): Promise<boolean> =>
new Promise(
(resolve, reject): void => {
try {
auth.allow_access(
{ packageName: name },
remoteUser,
(err, allowed): void => {
if (err) {
resolve(false);
}
resolve(allowed);
}
);
} catch (err) {
reject(err);
}
new Promise((resolve, reject): void => {
try {
auth.allow_access({ packageName: name }, remoteUser, (err, allowed): void => {
if (err) {
resolve(false);
}
resolve(allowed);
});
} catch (err) {
reject(err);
}
);
});
// Get list of all visible package
route.get('/packages', function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
storage.getLocalDatabase(async function(err, packages): Promise<void> {
if (err) {
throw err;
}
async function processPackages(packages: PackcageExt[] = []): Promise<any> {
const permissions: PackcageExt[] = [];
const packgesCopy = packages.slice();
for (const pkg of packgesCopy) {
const pkgCopy = { ...pkg };
pkgCopy.author = formatAuthor(pkg.author);
try {
if (await checkAllow(pkg.name, req.remote_user)) {
if (config.web) {
pkgCopy.author.avatar = generateGravatarUrl(pkgCopy.author.email, config.web.gravatar);
}
if (!_.isNil(pkgCopy.dist) && !_.isNull(pkgCopy.dist.tarball)) {
pkgCopy.dist.tarball = getLocalRegistryTarballUri(pkgCopy.dist.tarball, pkg.name, req, config.url_prefix);
}
permissions.push(pkgCopy);
}
} catch (err) {
logger.logger.error({ name: pkg.name, error: err }, 'permission process for @{name} has failed: @{error}');
throw err;
}
route.get(
'/packages',
function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
storage.getLocalDatabase(async function (err, packages): Promise<void> {
if (err) {
throw err;
}
return permissions;
}
async function processPackages(packages: PackcageExt[] = []): Promise<any> {
const permissions: PackcageExt[] = [];
const packgesCopy = packages.slice();
for (const pkg of packgesCopy) {
const pkgCopy = { ...pkg };
pkgCopy.author = formatAuthor(pkg.author);
try {
if (await checkAllow(pkg.name, req.remote_user)) {
if (config.web) {
pkgCopy.author.avatar = generateGravatarUrl(
pkgCopy.author.email,
config.web.gravatar
);
}
if (!_.isNil(pkgCopy.dist) && !_.isNull(pkgCopy.dist.tarball)) {
pkgCopy.dist.tarball = getLocalRegistryTarballUri(
pkgCopy.dist.tarball,
pkg.name,
req,
config.url_prefix
);
}
permissions.push(pkgCopy);
}
} catch (err) {
logger.logger.error(
{ name: pkg.name, error: err },
'permission process for @{name} has failed: @{error}'
);
throw err;
}
}
const { web } = config;
// @ts-ignore
const order: boolean = config.web ? getOrder(web.sort_packages) : true;
return permissions;
}
next(sortByName(await processPackages(packages), order));
});
});
const { web } = config;
// @ts-ignore
const order: boolean = config.web ? getOrder(web.sort_packages) : true;
next(sortByName(await processPackages(packages), order));
});
}
);
// Get package readme
route.get('/package/readme/(@:scope/)?:package/:version?', can('access'), function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
const packageName = req.params.scope ? addScope(req.params.scope, req.params.package) : req.params.package;
route.get(
'/package/readme/(@:scope/)?:package/:version?',
can('access'),
function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
const packageName = req.params.scope
? addScope(req.params.scope, req.params.package)
: req.params.package;
storage.getPackage({
name: packageName,
uplinksLook: true,
req,
callback: function(err, info): void {
if (err) {
return next(err);
storage.getPackage({
name: packageName,
uplinksLook: true,
req,
callback: function (err, info): void {
if (err) {
return next(err);
}
res.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.TEXT_PLAIN);
next(parseReadme(info.name, info.readme));
}
});
}
);
res.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.TEXT_PLAIN);
next(parseReadme(info.name, info.readme));
},
});
});
route.get(
'/sidebar/(@:scope/)?:package',
can('access'),
function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
const packageName: string = req.params.scope
? addScope(req.params.scope, req.params.package)
: req.params.package;
route.get('/sidebar/(@:scope/)?:package', can('access'), function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
const packageName: string = req.params.scope ? addScope(req.params.scope, req.params.package) : req.params.package;
storage.getPackage({
name: packageName,
uplinksLook: true,
keepUpLinkData: true,
req,
callback: function(err: Error, info: $SidebarPackage): void {
if (_.isNil(err)) {
const {v} = req.query;
let sideBarInfo: any = _.clone(info);
sideBarInfo.versions = convertDistRemoteToLocalTarballUrls(info, req, config.url_prefix).versions;
if (isVersionValid(info, v)) {
// @ts-ignore
sideBarInfo.latest = sideBarInfo.versions[v];
sideBarInfo.latest.author = formatAuthor(sideBarInfo.latest.author);
storage.getPackage({
name: packageName,
uplinksLook: true,
keepUpLinkData: true,
req,
callback: function (err: Error, info: $SidebarPackage): void {
if (_.isNil(err)) {
const { v } = req.query;
let sideBarInfo: any = _.clone(info);
sideBarInfo.versions = convertDistRemoteToLocalTarballUrls(
info,
req,
config.url_prefix
).versions;
if (isVersionValid(info, v)) {
// @ts-ignore
sideBarInfo.latest = sideBarInfo.versions[v];
sideBarInfo.latest.author = formatAuthor(sideBarInfo.latest.author);
} else {
sideBarInfo.latest = sideBarInfo.versions[info[DIST_TAGS].latest];
if (sideBarInfo?.latest) {
@ -145,8 +180,9 @@ function addPackageWebApi(route: Router, storage: IStorageHandler, auth: IAuth,
res.end();
}
}
});
});
});
}
);
}
export default addPackageWebApi;

View file

@ -3,49 +3,62 @@
* @flow
*/
import { Router } from 'express';
import { Package } from '@verdaccio/types';
import Search from '../../../lib/search';
import { DIST_TAGS } from '../../../lib/constants';
import { Router } from 'express';
import { IAuth, $ResponseExtend, $RequestExtend, $NextFunctionVer, IStorageHandler } from '../../../../types';
import { Package } from '@verdaccio/types';
import {
IAuth,
$ResponseExtend,
$RequestExtend,
$NextFunctionVer,
IStorageHandler
} from '../../../../types';
function addSearchWebApi(route: Router, storage: IStorageHandler, auth: IAuth): void {
// Search package
route.get('/search/:anything', function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
const results: any = Search.query(req.params.anything);
// FUTURE: figure out here the correct type
const packages: any[] = [];
route.get(
'/search/:anything',
function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
const results: any = Search.query(req.params.anything);
// FUTURE: figure out here the correct type
const packages: any[] = [];
const getPackageInfo = function(i): void {
storage.getPackage({
name: results[i].ref,
uplinksLook: false,
callback: (err, entry: Package): void => {
if (!err && entry) {
auth.allow_access({ packageName: entry.name }, req.remote_user, function(err, allowed): void {
if (err || !allowed) {
return;
}
const getPackageInfo = function (i): void {
storage.getPackage({
name: results[i].ref,
uplinksLook: false,
callback: (err, entry: Package): void => {
if (!err && entry) {
auth.allow_access(
{ packageName: entry.name },
req.remote_user,
function (err, allowed): void {
if (err || !allowed) {
return;
}
packages.push(entry.versions[entry[DIST_TAGS].latest]);
});
packages.push(entry.versions[entry[DIST_TAGS].latest]);
}
);
}
if (i >= results.length - 1) {
next(packages);
} else {
getPackageInfo(i + 1);
}
}
});
};
if (i >= results.length - 1) {
next(packages);
} else {
getPackageInfo(i + 1);
}
},
});
};
if (results.length) {
getPackageInfo(0);
} else {
next([]);
if (results.length) {
getPackageInfo(0);
} else {
next([]);
}
}
});
);
}
export default addSearchWebApi;

View file

@ -4,16 +4,16 @@
*/
import _ from 'lodash';
import { API_ERROR, APP_ERROR, HTTP_STATUS } from '../../../lib/constants';
import { Router, Response, Request } from 'express';
import { Config, RemoteUser, JWTSignOptions } from '@verdaccio/types';
import { API_ERROR, APP_ERROR, HTTP_STATUS } from '../../../lib/constants';
import { IAuth, $NextFunctionVer } from '../../../../types';
import { ErrorCode } from '../../../lib/utils';
import { getSecurity, validatePassword } from '../../../lib/auth-utils';
function addUserAuthApi(route: Router, auth: IAuth, config: Config): void {
route.post('/login', function(req: Request, res: Response, next: $NextFunctionVer): void {
route.post('/login', function (req: Request, res: Response, next: $NextFunctionVer): void {
const { username, password } = req.body;
auth.authenticate(
@ -29,39 +29,42 @@ function addUserAuthApi(route: Router, auth: IAuth, config: Config): void {
next({
token: await auth.jwtEncrypt(user, jWTSignOptions),
username: req.remote_user.name,
username: req.remote_user.name
});
}
}
);
});
route.put('/reset_password', function(req: Request, res: Response, next: $NextFunctionVer): void {
if (_.isNil(req.remote_user.name)) {
res.status(HTTP_STATUS.UNAUTHORIZED);
return next({
// FUTURE: update to a more meaningful message
message: API_ERROR.MUST_BE_LOGGED,
});
}
route.put(
'/reset_password',
function (req: Request, res: Response, next: $NextFunctionVer): void {
if (_.isNil(req.remote_user.name)) {
res.status(HTTP_STATUS.UNAUTHORIZED);
return next({
// FUTURE: update to a more meaningful message
message: API_ERROR.MUST_BE_LOGGED
});
}
const { password } = req.body;
const { name } = req.remote_user;
const { password } = req.body;
const { name } = req.remote_user;
if (validatePassword(password.new) === false) {
auth.changePassword(name as string, password.old, password.new, (err, isUpdated): void => {
if (_.isNil(err) && isUpdated) {
next({
ok: true,
});
} else {
return next(ErrorCode.getInternalError(API_ERROR.INTERNAL_SERVER_ERROR));
}
});
} else {
return next(ErrorCode.getCode(HTTP_STATUS.BAD_REQUEST, APP_ERROR.PASSWORD_VALIDATION));
if (validatePassword(password.new) === false) {
auth.changePassword(name as string, password.old, password.new, (err, isUpdated): void => {
if (_.isNil(err) && isUpdated) {
next({
ok: true
});
} else {
return next(ErrorCode.getInternalError(API_ERROR.INTERNAL_SERVER_ERROR));
}
});
} else {
return next(ErrorCode.getCode(HTTP_STATUS.BAD_REQUEST, APP_ERROR.PASSWORD_VALIDATION));
}
}
});
);
}
export default addUserAuthApi;

View file

@ -2,10 +2,10 @@
* @prettier
*/
import _ from 'lodash';
import fs from 'fs';
import path from 'path';
import _ from 'lodash';
import express from 'express';
import { combineBaseUrl, getWebProtocol, isHTTPProtocol } from '../../lib/utils';
@ -25,7 +25,7 @@ export function loadTheme(config) {
config,
config.theme,
{},
function(plugin) {
function (plugin) {
return _.isString(plugin);
},
'verdaccio-theme'
@ -43,7 +43,7 @@ export function validatePrimaryColor(primaryColor) {
return primaryColor;
}
const sendFileCallback = next => err => {
const sendFileCallback = (next) => (err) => {
if (!err) {
return;
}
@ -54,7 +54,7 @@ const sendFileCallback = next => err => {
}
};
export default function(config, auth, storage) {
export default function (config, auth, storage) {
Search.configureStorage(storage);
/* eslint new-cap:off */
const router = express.Router();
@ -73,13 +73,13 @@ export default function(config, auth, storage) {
// Note: `path.join` will break on Windows, because it transforms `/` to `\`
// Use POSIX version `path.posix.join` instead.
logoURI = path.posix.join('/-/static/', path.basename(logoURI));
router.get(logoURI, function(req, res, next) {
router.get(logoURI, function (req, res, next) {
res.sendFile(path.resolve(config.web.logo), sendFileCallback(next));
});
}
// Static
router.get('/-/static/*', function(req, res, next) {
router.get('/-/static/*', function (req, res, next) {
const filename = req.params[0];
const file = `${themePath}/${filename}`;
res.sendFile(file, sendFileCallback(next));
@ -106,7 +106,7 @@ export default function(config, auth, storage) {
primaryColor,
title,
scope,
language,
language
};
const webPage = template
@ -124,11 +124,11 @@ export default function(config, auth, storage) {
res.send(webPage);
}
router.get('/-/web/:section/*', function(req, res) {
router.get('/-/web/:section/*', function (req, res) {
renderHTML(req, res);
});
router.get('/', function(req, res) {
router.get('/', function (req, res) {
renderHTML(req, res);
});

View file

@ -9,5 +9,5 @@ const APP_ROOT = path.resolve(__dirname, '../../');
module.exports = {
APP_ROOT,
SRC_ROOT: path.resolve(APP_ROOT, 'src/'),
DIST_PATH: path.resolve(APP_ROOT, 'static/'),
DIST_PATH: path.resolve(APP_ROOT, 'static/')
};

View file

@ -1,5 +1,5 @@
// @flow
import {startVerdaccio} from './lib/bootstrap';
import { startVerdaccio } from './lib/bootstrap';
export default startVerdaccio;

View file

@ -1,14 +1,39 @@
import _ from 'lodash';
import {
RemoteUser,
Package,
Callback,
Config,
Security,
APITokenOptions,
JWTOptions,
IPluginAuth
} from '@verdaccio/types';
import {
CookieSessionToken,
IAuthWebUI,
AuthMiddlewarePayload,
AuthTokenHeader,
BasicPayload
} from '../../types';
import { logger } from '../lib/logger';
import { convertPayloadToBase64, ErrorCode } from './utils';
import { API_ERROR, HTTP_STATUS, ROLES, TIME_EXPIRATION_7D, TOKEN_BASIC, TOKEN_BEARER, DEFAULT_MIN_LIMIT_PASSWORD } from './constants';
import {
API_ERROR,
HTTP_STATUS,
ROLES,
TIME_EXPIRATION_7D,
TOKEN_BASIC,
TOKEN_BEARER,
DEFAULT_MIN_LIMIT_PASSWORD
} from './constants';
import { RemoteUser, Package, Callback, Config, Security, APITokenOptions, JWTOptions, IPluginAuth } from '@verdaccio/types';
import { CookieSessionToken, IAuthWebUI, AuthMiddlewarePayload, AuthTokenHeader, BasicPayload } from '../../types';
import { aesDecrypt, verifyPayload } from './crypto-utils';
import { logger } from '../lib/logger';
export function validatePassword(password: string, minLength: number = DEFAULT_MIN_LIMIT_PASSWORD): boolean {
export function validatePassword(
password: string,
minLength: number = DEFAULT_MIN_LIMIT_PASSWORD
): boolean {
return typeof password === 'string' && password.length >= minLength;
}
@ -18,12 +43,18 @@ export function validatePassword(password: string, minLength: number = DEFAULT_M
*/
export function createRemoteUser(name: string, pluginGroups: string[]): RemoteUser {
const isGroupValid: boolean = Array.isArray(pluginGroups);
const groups = (isGroupValid ? pluginGroups : []).concat([ROLES.$ALL, ROLES.$AUTH, ROLES.DEPRECATED_ALL, ROLES.DEPRECATED_AUTH, ROLES.ALL]);
const groups = (isGroupValid ? pluginGroups : []).concat([
ROLES.$ALL,
ROLES.$AUTH,
ROLES.DEPRECATED_ALL,
ROLES.DEPRECATED_AUTH,
ROLES.ALL
]);
return {
name,
groups,
real_groups: pluginGroups,
real_groups: pluginGroups
};
}
@ -36,27 +67,34 @@ export function createAnonymousRemoteUser(): RemoteUser {
name: undefined,
// groups without '$' are going to be deprecated eventually
groups: [ROLES.$ALL, ROLES.$ANONYMOUS, ROLES.DEPRECATED_ALL, ROLES.DEPRECATED_ANONYMOUS],
real_groups: [],
real_groups: []
};
}
export function allow_action(action: string): Function {
return function(user: RemoteUser, pkg: Package, callback: Callback): void {
logger.trace({remote: user.name}, `[auth/allow_action]: user: @{user.name}`);
return function (user: RemoteUser, pkg: Package, callback: Callback): void {
logger.trace({ remote: user.name }, `[auth/allow_action]: user: @{user.name}`);
const { name, groups } = user;
const groupAccess = pkg[action];
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);
}
if (name) {
callback(ErrorCode.getForbidden(`user ${name} is not allowed to ${action} package ${pkg.name}`));
callback(
ErrorCode.getForbidden(`user ${name} is not allowed to ${action} package ${pkg.name}`)
);
} else {
callback(ErrorCode.getUnauthorized(`authorization required to ${action} package ${pkg.name}`));
callback(
ErrorCode.getUnauthorized(`authorization required to ${action} package ${pkg.name}`)
);
}
};
}
@ -65,18 +103,24 @@ export function allow_action(action: string): Function {
*
*/
export function handleSpecialUnpublish(): any {
return function(user: RemoteUser, pkg: Package, callback: Callback): void {
return function (user: RemoteUser, pkg: Package, callback: Callback): void {
const action = 'unpublish';
// verify whether the unpublish prop has been defined
const isUnpublishMissing: boolean = _.isNil(pkg[action]);
const hasGroups: boolean = isUnpublishMissing ? false : pkg[action].length > 0;
logger.trace({user: user.name, name: pkg.name, hasGroups}, `fallback unpublish for @{name} has groups: @{hasGroups} for @{user}`);
logger.trace(
{ user: user.name, name: pkg.name, hasGroups },
`fallback unpublish for @{name} has groups: @{hasGroups} for @{user}`
);
if (isUnpublishMissing || hasGroups === false) {
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)(user, pkg, callback);
};
}
@ -96,7 +140,7 @@ export function getDefaultPlugins(): IPluginAuth<Config> {
allow_access: allow_action('access'),
// @ts-ignore
allow_publish: allow_action('publish'),
allow_unpublish: handleSpecialUnpublish(),
allow_unpublish: handleSpecialUnpublish()
};
}
@ -105,25 +149,25 @@ export function createSessionToken(): CookieSessionToken {
return {
// npmjs.org sets 10h expire
expires: new Date(Date.now() + tenHoursTime),
expires: new Date(Date.now() + tenHoursTime)
};
}
const defaultWebTokenOptions: JWTOptions = {
sign: {
// The expiration token for the website is 7 days
expiresIn: TIME_EXPIRATION_7D,
expiresIn: TIME_EXPIRATION_7D
},
verify: {},
verify: {}
};
const defaultApiTokenConf: APITokenOptions = {
legacy: true,
legacy: true
};
export const defaultSecurity: Security = {
web: defaultWebTokenOptions,
api: defaultApiTokenConf,
api: defaultApiTokenConf
};
export function getSecurity(config: Config): Security {
@ -148,13 +192,20 @@ export function isAESLegacy(security: Security): boolean {
return _.isNil(legacy) === false && _.isNil(jwt) && legacy === true;
}
export async function getApiToken(auth: IAuthWebUI, config: Config, remoteUser: RemoteUser, aesPassword: string): Promise<string> {
export async function getApiToken(
auth: IAuthWebUI,
config: Config,
remoteUser: RemoteUser,
aesPassword: string
): Promise<string> {
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'));
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.
@ -164,9 +215,10 @@ export async function getApiToken(auth: IAuthWebUI, config: Config, remoteUser:
return await auth.jwtEncrypt(remoteUser, jwt.sign);
}
return await new Promise((resolve): void => {
resolve(auth.aesEncrypt(buildUserBuffer(remoteUser.name as string, aesPassword)).toString('base64'));
resolve(
auth.aesEncrypt(buildUserBuffer(remoteUser.name as string, aesPassword)).toString('base64')
);
});
}
export function parseAuthTokenHeader(authorizationHeader: string): AuthTokenHeader {
@ -227,7 +279,11 @@ export function isAuthHeaderValid(authorization: string): boolean {
return authorization.split(' ').length === 2;
}
export function getMiddlewareCredentials(security: Security, secret: string, authorizationHeader: string): AuthMiddlewarePayload {
export function getMiddlewareCredentials(
security: Security,
secret: string,
authorizationHeader: string
): AuthMiddlewarePayload {
if (isAESLegacy(security)) {
const credentials = parseAESCredentials(authorizationHeader, secret);
if (!credentials) {

View file

@ -1,8 +1,22 @@
import _ from 'lodash';
import { VerdaccioError } from '@verdaccio/commons-api';
import {API_ERROR, SUPPORT_ERRORS, TOKEN_BASIC, TOKEN_BEARER} from './constants';
import {
Config,
Logger,
Callback,
IPluginAuth,
RemoteUser,
JWTSignOptions,
Security,
AuthPluginPackage,
AllowAccess,
PackageAccess
} from '@verdaccio/types';
import { NextFunction } from 'express';
import loadPlugin from '../lib/plugin-loader';
import { $RequestExtend, $ResponseExtend, IAuth, AESPayload } from '../../types';
import { API_ERROR, SUPPORT_ERRORS, TOKEN_BASIC, TOKEN_BEARER } from './constants';
import { aesEncrypt, signPayload } from './crypto-utils';
import {
getDefaultPlugins,
@ -14,15 +28,11 @@ import {
isAESLegacy,
parseAuthTokenHeader,
parseBasicPayload,
createRemoteUser,
createRemoteUser
} from './auth-utils';
import { convertPayloadToBase64, ErrorCode } from './utils';
import { getMatchedPackagesSpec } from './config-utils';
import { Config, Logger, Callback, IPluginAuth, RemoteUser, JWTSignOptions, Security, AuthPluginPackage, AllowAccess, PackageAccess } from '@verdaccio/types';
import { NextFunction } from 'express';
import { $RequestExtend, $ResponseExtend, IAuth, AESPayload } from '../../types';
/* eslint-disable @typescript-eslint/no-var-requires */
const LoggerApi = require('./logger');
@ -43,7 +53,7 @@ class Auth implements IAuth {
private _loadPlugin(config: Config): IPluginAuth<Config>[] {
const pluginOptions = {
config,
logger: this.logger,
logger: this.logger
};
return loadPlugin<IPluginAuth<Config>>(
@ -63,39 +73,39 @@ class Auth implements IAuth {
this.plugins.push(getDefaultPlugins());
}
public changePassword(username: string, password: string, newPassword: string, cb: Callback): void {
const validPlugins = _.filter(this.plugins, plugin => _.isFunction(plugin.changePassword));
public changePassword(
username: string,
password: string,
newPassword: string,
cb: Callback
): void {
const validPlugins = _.filter(this.plugins, (plugin) => _.isFunction(plugin.changePassword));
if (_.isEmpty(validPlugins)) {
return cb(ErrorCode.getInternalError(SUPPORT_ERRORS.PLUGIN_MISSING_INTERFACE));
}
if (_.isEmpty(validPlugins)) {
return cb(ErrorCode.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 {
@ -109,9 +119,12 @@ 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}');
self.logger.trace(
{ username, err },
'authenticating for user @{username} failed. Error: @{err.message}'
);
return cb(err);
}
@ -132,7 +145,10 @@ class Auth implements IAuth {
throw new TypeError(API_ERROR.BAD_FORMAT_USER_GROUP);
}
self.logger.trace({ username, groups }, 'authentication for user @{username} was successfully. Groups: @{groups}');
self.logger.trace(
{ username, groups },
'authentication for user @{username} was successfully. Groups: @{groups}'
);
return cb(err, createRemoteUser(username, groups));
}
next();
@ -150,16 +166,21 @@ class Auth implements IAuth {
let method = 'adduser';
if (_.isFunction(plugin[method]) === false) {
method = 'add_user';
self.logger.warn('the plugin method add_user in the auth plugin is deprecated and will be removed in next major release, notify to the plugin author');
self.logger.warn(
'the plugin method add_user in the auth plugin is deprecated and will be removed in next major release, notify to the plugin author'
);
}
if (_.isFunction(plugin[method]) === false) {
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}');
self.logger.trace(
{ user, err: err.message },
'the user @{user} could not being added. Error: @{err}'
);
return cb(err);
}
if (ok) {
@ -175,10 +196,18 @@ class Auth implements IAuth {
/**
* Allow user to access a package.
*/
public allow_access({ packageName, packageVersion }: AuthPluginPackage, user: RemoteUser, callback: Callback): void {
public allow_access(
{ packageName, packageVersion }: AuthPluginPackage,
user: RemoteUser,
callback: Callback
): void {
const plugins = this.plugins.slice(0);
const pkgAllowAcces: AllowAccess = { name: packageName, version: packageVersion };
const pkg = Object.assign({}, pkgAllowAcces, getMatchedPackagesSpec(packageName, this.config.packages)) as AllowAccess & PackageAccess;
const pkg = Object.assign(
{},
pkgAllowAcces,
getMatchedPackagesSpec(packageName, this.config.packages)
) as AllowAccess & PackageAccess;
const self = this;
this.logger.trace({ packageName }, 'allow access for @{packageName}');
@ -189,9 +218,12 @@ 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}');
self.logger.trace(
{ packageName, err },
'forbidden access for @{packageName}. Error: @{err.message}'
);
return callback(err);
}
@ -205,37 +237,49 @@ class Auth implements IAuth {
})();
}
public allow_unpublish({ packageName, packageVersion }: AuthPluginPackage, user: RemoteUser, callback: Callback): void {
const pkg = Object.assign({ name: packageName, version: packageVersion }, getMatchedPackagesSpec(packageName, this.config.packages));
public allow_unpublish(
{ packageName, packageVersion }: AuthPluginPackage,
user: RemoteUser,
callback: Callback
): void {
const pkg = Object.assign(
{ name: packageName, version: packageVersion },
getMatchedPackagesSpec(packageName, this.config.packages)
);
this.logger.trace({ packageName }, 'allow unpublish for @{packageName}');
for (const plugin of this.plugins) {
if (_.isNil(plugin) || _.isFunction(plugin.allow_unpublish) === false) {
this.logger.trace({ packageName }, 'allow unpublish for @{packageName} plugin does not implement allow_unpublish');
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);
}
});
}
}
}
@ -243,39 +287,48 @@ class Auth implements IAuth {
/**
* Allow user to publish a package.
*/
public allow_publish({ packageName, packageVersion }: AuthPluginPackage, user: RemoteUser, callback: Callback): void {
public allow_publish(
{ packageName, packageVersion }: AuthPluginPackage,
user: RemoteUser,
callback: Callback
): void {
const plugins = this.plugins.slice(0);
const self = this;
const pkg = Object.assign({ name: packageName, version: packageVersion }, getMatchedPackagesSpec(packageName, this.config.packages));
this.logger.trace({ packageName, plugins: this.plugins.length }, 'allow publish for @{packageName} init | plugins: @{plugins}');
const pkg = Object.assign(
{ name: packageName, version: packageVersion },
getMatchedPackagesSpec(packageName, this.config.packages)
);
this.logger.trace(
{ packageName, plugins: this.plugins.length },
'allow publish for @{packageName} init | plugins: @{plugins}'
);
(function next(): void {
const plugin = plugins.shift();
if (_.isNil(plugin) || _.isFunction(plugin.allow_publish) === false) {
self.logger.trace({ packageName }, 'allow publish for @{packageName} plugin does not implement allow_publish');
self.logger.trace(
{ packageName },
'allow publish for @{packageName} plugin does not implement allow_publish'
);
return next();
}
// @ts-ignore
plugin.allow_publish(
user,
pkg,
(err: VerdaccioError, ok: boolean): void => {
if (_.isNil(err) === false && _.isError(err)) {
self.logger.trace({ packageName }, 'forbidden publish for @{packageName}');
return callback(err);
}
if (ok) {
self.logger.trace({ packageName }, 'allowed publish for @{packageName}');
return callback(null, ok);
}
self.logger.trace({ packageName }, 'allow publish skip validation for @{packageName}');
next(); // cb(null, false) causes next plugin to roll
plugin.allow_publish(user, pkg, (err: VerdaccioError, ok: boolean): void => {
if (_.isNil(err) === false && _.isError(err)) {
self.logger.trace({ packageName }, 'forbidden publish for @{packageName}');
return callback(err);
}
);
if (ok) {
self.logger.trace({ packageName }, 'allowed publish for @{packageName}');
return callback(null, ok);
}
self.logger.trace({ packageName }, 'allow publish skip validation for @{packageName}');
next(); // cb(null, false) causes next plugin to roll
});
})();
}
@ -291,7 +344,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)
@ -333,25 +386,27 @@ class Auth implements IAuth {
};
}
private _handleJWTAPIMiddleware(req: $RequestExtend, security: Security, secret: string, authorization: string, next: Function): void {
private _handleJWTAPIMiddleware(
req: $RequestExtend,
security: Security,
secret: string,
authorization: string,
next: Function
): void {
const { scheme, token } = parseAuthTokenHeader(authorization);
if (scheme.toUpperCase() === TOKEN_BASIC.toUpperCase()) {
// 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);
@ -366,23 +421,25 @@ class Auth implements IAuth {
}
}
private _handleAESMiddleware(req: $RequestExtend, security: Security, secret: string, authorization: string, next: Function): void {
private _handleAESMiddleware(
req: $RequestExtend,
security: Security,
secret: string,
authorization: string,
next: Function
): void {
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(ErrorCode.getBadRequest(API_ERROR.BAD_AUTH_HEADER));
@ -453,7 +510,7 @@ class Auth implements IAuth {
const payload: RemoteUser = {
real_groups: realGroupsValidated,
name,
groups: groupedGroups,
groups: groupedGroups
};
const token: string = await signPayload(payload, this.secret, signOptions);

View file

@ -1,25 +1,29 @@
import { assign, isObject, isFunction } from 'lodash';
import express from 'express';
import URL from 'url';
import fs from 'fs';
import http from 'http';
import https from 'https';
import constants from 'constants';
import endPointAPI from '../api/index';
import { getListListenAddresses, resolveConfigPath } from './cli/utils';
import { API_ERROR, certPem, csrPem, keyPem } from './constants';
import express from 'express';
import { assign, isObject, isFunction } from 'lodash';
import { Callback, ConfigWithHttps, HttpsConfKeyCert, HttpsConfPfx } from '@verdaccio/types';
import { Application } from 'express';
import endPointAPI from '../api/index';
import { API_ERROR, certPem, csrPem, keyPem } from './constants';
import { getListListenAddresses, resolveConfigPath } from './cli/utils';
const logger = require('./logger');
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'}`);
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'}`
);
});
}
}
@ -32,7 +36,14 @@ function displayExperimentsInfoBox(experiments) {
* @param {String} pkgVersion
* @param {String} pkgName
*/
function startVerdaccio(config: any, cliListen: string, configPath: string, pkgVersion: string, pkgName: string, callback: Callback): void {
function startVerdaccio(
config: any,
cliListen: string,
configPath: string,
pkgVersion: string,
pkgName: string,
callback: Callback
): void {
if (isObject(config) === false) {
throw new Error(API_ERROR.CONFIG_BAD_FORMAT);
}
@ -41,28 +52,30 @@ function startVerdaccio(config: any, cliListen: string, configPath: string, pkgV
displayExperimentsInfoBox(config.experiments);
}
endPointAPI(config).then(
(app): void => {
const addresses = getListListenAddresses(cliListen, config.listen);
endPointAPI(config).then((app): void => {
const addresses = getListListenAddresses(cliListen, config.listen);
addresses.forEach(function(addr): void {
let webServer;
if (addr.proto === 'https') {
webServer = handleHTTPS(app, configPath, config);
} else {
// http
webServer = http.createServer(app);
}
if (config.server && typeof config.server.keepAliveTimeout !== 'undefined' && config.server.keepAliveTimeout !== 'null') {
// library definition for node is not up to date (doesn't contain recent 8.0 changes)
webServer.keepAliveTimeout = config.server.keepAliveTimeout * 1000;
}
unlinkAddressPath(addr);
addresses.forEach(function (addr): void {
let webServer;
if (addr.proto === 'https') {
webServer = handleHTTPS(app, configPath, config);
} else {
// http
webServer = http.createServer(app);
}
if (
config.server &&
typeof config.server.keepAliveTimeout !== 'undefined' &&
config.server.keepAliveTimeout !== 'null'
) {
// library definition for node is not up to date (doesn't contain recent 8.0 changes)
webServer.keepAliveTimeout = config.server.keepAliveTimeout * 1000;
}
unlinkAddressPath(addr);
callback(webServer, addr, pkgName, pkgVersion);
});
}
);
callback(webServer, addr, pkgName, pkgVersion);
});
});
}
function unlinkAddressPath(addr) {
@ -82,7 +95,10 @@ function logHTTPSWarning(storageLocation) {
// 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 req -new -sha256 -key ' +
resolveConfigPath(storageLocation, keyPem) +
' -out ' +
resolveConfigPath(storageLocation, csrPem),
' $ openssl x509 -req -in ' +
resolveConfigPath(storageLocation, csrPem) +
' -signkey ' +
@ -93,16 +109,20 @@ function logHTTPSWarning(storageLocation) {
'And then add to config file (' + storageLocation + '):',
' https:',
` key: ${resolveConfigPath(storageLocation, keyPem)}`,
` cert: ${resolveConfigPath(storageLocation, certPem)}`,
` cert: ${resolveConfigPath(storageLocation, certPem)}`
].join('\n')
);
process.exit(2);
}
function handleHTTPS(app: express.Application, configPath: string, config: ConfigWithHttps): https.Server {
function handleHTTPS(
app: express.Application,
configPath: string,
config: ConfigWithHttps
): https.Server {
try {
let httpsOptions = {
secureOptions: constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3, // disable insecure SSLv2 and SSLv3
secureOptions: constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3 // disable insecure SSLv2 and SSLv3
};
const keyCertConfig = config.https as HttpsConfKeyCert;
@ -117,7 +137,7 @@ function handleHTTPS(app: express.Application, configPath: string, config: Confi
const { pfx, passphrase } = pfxConfig;
httpsOptions = assign(httpsOptions, {
pfx: fs.readFileSync(pfx),
passphrase: passphrase || '',
passphrase: passphrase || ''
});
} else {
const { key, cert, ca } = keyCertConfig;
@ -125,8 +145,8 @@ function handleHTTPS(app: express.Application, configPath: string, config: Confi
key: fs.readFileSync(key),
cert: fs.readFileSync(cert),
...(ca && {
ca: fs.readFileSync(ca),
}),
ca: fs.readFileSync(ca)
})
});
}
return https.createServer(httpsOptions, app);
@ -137,21 +157,22 @@ function handleHTTPS(app: express.Application, configPath: string, config: Confi
}
}
function listenDefaultCallback(webServer: Application, addr: any, pkgName: string, pkgVersion: string): void {
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.logger.fatal({ err: err }, 'cannot create server: @{err.message}');
process.exit(2);
});
@ -161,15 +182,15 @@ function listenDefaultCallback(webServer: Application, addr: any, pkgName: strin
addr: addr.path
? URL.format({
protocol: 'unix',
pathname: addr.path,
pathname: addr.path
})
: URL.format({
protocol: addr.proto,
hostname: addr.host,
port: addr.port,
pathname: '/',
pathname: '/'
}),
version: pkgName + '/' + pkgVersion,
version: pkgName + '/' + pkgVersion
},
'http address - @{addr} - @{version}'
);

View file

@ -2,34 +2,40 @@
/* eslint no-sync:0 */
/* eslint no-empty:0 */
/**
* @prettier
*/
import path from 'path';
import semver from 'semver';
import { bgYellow, bgRed } from 'kleur';
import {startVerdaccio, listenDefaultCallback} from './bootstrap';
import { startVerdaccio, listenDefaultCallback } from './bootstrap';
import findConfigFile from './config-path';
import { parseConfigFile } from './utils';
require('pkginfo')(module);
if (process.getuid && process.getuid() === 0) {
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! ***"
)
);
}
const MIN_NODE_VERSION = '6.9.0';
if (semver.satisfies(process.version, `>=${MIN_NODE_VERSION}`) === false) {
global.console.error(bgRed(`Verdaccio requires at least Node.js ${MIN_NODE_VERSION} or higher, please upgrade your Node.js distribution`));
global.console.error(
bgRed(
`Verdaccio requires at least Node.js ${MIN_NODE_VERSION} or higher, please upgrade your Node.js distribution`
)
);
process.exit(1);
}
process.title = 'verdaccio';
// eslint-disable-next-line import/order
const logger = require('./logger');
logger.setup(null, {logStart: false}); // default setup
logger.setup(null, { logStart: false }); // default setup
const envinfo = require('envinfo');
const commander = require('commander');
@ -51,40 +57,50 @@ function init() {
try {
configPathLocation = findConfigFile(commander.config);
verdaccioConfiguration = parseConfigFile(configPathLocation);
process.title = verdaccioConfiguration.web && verdaccioConfiguration.web.title || 'verdaccio';
process.title = (verdaccioConfiguration.web && verdaccioConfiguration.web.title) || 'verdaccio';
if (!verdaccioConfiguration.self_path) {
verdaccioConfiguration.self_path = path.resolve(configPathLocation);
}
if (!verdaccioConfiguration.https) {
verdaccioConfiguration.https = {enable: false};
verdaccioConfiguration.https = { enable: false };
}
logger.logger.warn({file: configPathLocation}, 'config file - @{file}');
logger.logger.warn({ file: configPathLocation }, 'config file - @{file}');
startVerdaccio(verdaccioConfiguration, cliListener, configPathLocation, pkgVersion, pkgName, listenDefaultCallback);
startVerdaccio(
verdaccioConfiguration,
cliListener,
configPathLocation,
pkgVersion,
pkgName,
listenDefaultCallback
);
} catch (err) {
logger.logger.fatal({file: configPathLocation, err: err}, 'cannot open config file @{file}: @{!err.message}');
logger.logger.fatal(
{ file: configPathLocation, err: err },
'cannot open config file @{file}: @{!err.message}'
);
process.exit(1);
}
}
if (commander.info) {
// eslint-disable-next-line no-console
console.log('\nEnvironment Info:');
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'],
});
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);
})();
} else if (commander.args.length == 1 && !commander.config) {
} else if (commander.args.length == 1 && !commander.config) {
// handling "verdaccio [config]" case if "-c" is missing in command line
commander.config = commander.args.pop();
init();
@ -94,10 +110,12 @@ if (commander.info) {
init();
}
process.on('uncaughtException', function(err) {
logger.logger.fatal( {
err: err,
},
'uncaught exception, please report this\n@{err.stack}' );
process.on('uncaughtException', function (err) {
logger.logger.fatal(
{
err: err
},
'uncaught exception, please report this\n@{err.stack}'
);
process.exit(255);
});

View file

@ -10,7 +10,7 @@ import { DEFAULT_PORT } from '../constants';
const logger = require('../logger');
export const resolveConfigPath = function(storageLocation: string, file: string) {
export const resolveConfigPath = function (storageLocation: string, file: string) {
return path.resolve(path.dirname(storageLocation), file);
};
@ -38,7 +38,7 @@ export function getListListenAddresses(argListen: string, configListen: any): an
addresses = [DEFAULT_PORT];
}
addresses = addresses
.map(function(addr): string {
.map(function (addr): string {
const parsedAddr = parseAddress(addr);
if (!parsedAddr) {

View file

@ -1,8 +1,8 @@
import fs from 'fs';
import _ from 'lodash';
import Path from 'path';
import { logger } from './logger';
import _ from 'lodash';
import mkdirp from 'mkdirp';
import { logger } from './logger';
import { folderExists, fileExists } from './utils';
import { CHARACTER_ENCODING } from './constants';
@ -16,7 +16,7 @@ const pkgJSON = require('../../package.json');
export type SetupDirectory = {
path: string;
type: string
type: string;
};
/**
@ -34,7 +34,9 @@ function findConfigFile(configPath: string): string {
throw new Error('no configuration files can be processed');
}
const primaryConf: any = _.find(configPaths, (configLocation: any) => fileExists(configLocation.path));
const primaryConf: any = _.find(configPaths, (configLocation: any) =>
fileExists(configLocation.path)
);
if (_.isNil(primaryConf) === false) {
return primaryConf.path;
}
@ -69,7 +71,8 @@ function updateStorageLinks(configLocation, defaultConfig): string {
// $XDG_DATA_HOME defines the base directory relative to which user specific data files should be stored,
// If $XDG_DATA_HOME is either not set or empty, a default equal to $HOME/.local/share should be used.
// $FlowFixMe
let dataDir = process.env.XDG_DATA_HOME || Path.join(process.env.HOME as string, '.local', 'share');
let dataDir =
process.env.XDG_DATA_HOME || Path.join(process.env.HOME as string, '.local', 'share');
if (folderExists(dataDir)) {
dataDir = Path.resolve(Path.join(dataDir, pkgJSON.name, 'storage'));
return defaultConfig.replace(/^storage: .\/storage$/m, `storage: ${dataDir}`);
@ -78,13 +81,17 @@ 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;
}
@ -95,7 +102,7 @@ const getXDGDirectory = (): SetupDirectory | void => {
if (XDGConfig && folderExists(XDGConfig)) {
return {
path: Path.join(XDGConfig, pkgJSON.name, CONFIG_FILE),
type: XDG,
type: XDG
};
}
};
@ -106,7 +113,7 @@ const getWindowsDirectory = (): SetupDirectory | void => {
if (process.platform === WIN32 && process.env.APPDATA && folderExists(process.env.APPDATA)) {
return {
path: Path.resolve(Path.join(process.env.APPDATA, pkgJSON.name, CONFIG_FILE)),
type: WIN,
type: WIN
};
}
};
@ -114,14 +121,14 @@ const getWindowsDirectory = (): SetupDirectory | void => {
const getRelativeDefaultDirectory = (): SetupDirectory => {
return {
path: Path.resolve(Path.join('.', pkgJSON.name, CONFIG_FILE)),
type: 'def',
type: 'def'
};
};
const getOldDirectory = (): SetupDirectory => {
return {
path: Path.resolve(Path.join('.', CONFIG_FILE)),
type: 'old',
type: 'old'
};
};

View file

@ -3,21 +3,20 @@
* @flow
*/
import _ from 'lodash';
import assert from 'assert';
import _ from 'lodash';
import minimatch from 'minimatch';
import { ErrorCode } from './utils';
import { PackageList, UpLinksConfList } from '@verdaccio/types';
import { MatchedPackage, LegacyPackageList } from '../../types';
import { ErrorCode } from './utils';
const BLACKLIST = {
all: true,
anonymous: true,
undefined: true,
owner: true,
none: true,
none: true
};
/**
@ -47,7 +46,10 @@ export function normalizeUserList(oldFormat: any, newFormat: any): any {
return _.flatten(result);
}
export function uplinkSanityCheck(uplinks: UpLinksConfList, users: any = BLACKLIST): UpLinksConfList {
export function uplinkSanityCheck(
uplinks: UpLinksConfList,
users: any = BLACKLIST
): UpLinksConfList {
const newUplinks = _.clone(uplinks);
let newUsers = _.clone(users);
@ -65,7 +67,11 @@ 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',
item !== 'all' &&
item !== 'owner' &&
item !== 'anonymous' &&
item !== 'undefined' &&
item !== 'none',
'CONFIG: reserved uplink name: ' + item
);
assert(!item.match(/\s/), 'CONFIG: invalid uplink name: ' + item);
@ -96,7 +102,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;
@ -124,11 +130,20 @@ export function normalisePackageAccess(packages: LegacyPackageList): LegacyPacka
_.isObject(packages[pkg]) && _.isArray(packages[pkg]) === false,
`CONFIG: bad "'${pkg}'" package description (object expected)`
);
normalizedPkgs[pkg].access = normalizeUserList(packages[pkg].allow_access, packages[pkg].access);
normalizedPkgs[pkg].access = normalizeUserList(
packages[pkg].allow_access,
packages[pkg].access
);
delete normalizedPkgs[pkg].allow_access;
normalizedPkgs[pkg].publish = normalizeUserList(packages[pkg].allow_publish, packages[pkg].publish);
normalizedPkgs[pkg].publish = normalizeUserList(
packages[pkg].allow_publish,
packages[pkg].publish
);
delete normalizedPkgs[pkg].allow_publish;
normalizedPkgs[pkg].proxy = normalizeUserList(packages[pkg].proxy_access, packages[pkg].proxy);
normalizedPkgs[pkg].proxy = normalizeUserList(
packages[pkg].proxy_access,
packages[pkg].proxy
);
delete normalizedPkgs[pkg].proxy_access;
// if unpublish is not defined, we set to false to fallback in publish access
normalizedPkgs[pkg].unpublish = _.isUndefined(packages[pkg].unpublish)

View file

@ -1,14 +1,17 @@
import _ from 'lodash';
import assert from 'assert';
import { generateRandomHexString } from './crypto-utils';
import { getMatchedPackagesSpec, normalisePackageAccess, sanityCheckUplinksProps, uplinkSanityCheck } from './config-utils';
import { getUserAgent, isObject } from './utils';
import { APP_ERROR } from './constants';
import _ from 'lodash';
import { PackageList, Config as AppConfig, Security, Logger } from '@verdaccio/types';
import { MatchedPackage, StartUpConfig } from '../../types';
import { generateRandomHexString } from './crypto-utils';
import {
getMatchedPackagesSpec,
normalisePackageAccess,
sanityCheckUplinksProps,
uplinkSanityCheck
} from './config-utils';
import { getUserAgent, isObject } from './utils';
import { APP_ERROR } from './constants';
const LoggerApi = require('./logger');
const strategicConfigProps = ['uplinks', 'packages'];
@ -54,7 +57,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] = {};
}
@ -72,13 +75,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

View file

@ -39,22 +39,22 @@ export const HEADERS = {
OCTET_STREAM: 'application/octet-stream; charset=utf-8',
TEXT_CHARSET: 'text/plain; charset=utf-8',
WWW_AUTH: 'WWW-Authenticate',
GZIP: 'gzip',
GZIP: 'gzip'
};
export const CHARACTER_ENCODING = {
UTF8: 'utf8',
UTF8: 'utf8'
};
export const HEADER_TYPE = {
CONTENT_ENCODING: 'content-encoding',
CONTENT_TYPE: 'content-type',
CONTENT_LENGTH: 'content-length',
ACCEPT_ENCODING: 'accept-encoding',
ACCEPT_ENCODING: 'accept-encoding'
};
export const ERROR_CODE = {
token_required: 'token is required',
token_required: 'token is required'
};
export const TOKEN_BASIC = 'Basic';
@ -69,7 +69,7 @@ export const ROLES = {
$ANONYMOUS: '$anonymous',
DEPRECATED_ALL: '@all',
DEPRECATED_AUTH: '@authenticated',
DEPRECATED_ANONYMOUS: '@anonymous',
DEPRECATED_ANONYMOUS: '@anonymous'
};
export const HTTP_STATUS = {
@ -87,7 +87,7 @@ export const HTTP_STATUS = {
INTERNAL_ERROR: 500,
NOT_IMPLEMENTED: 501,
SERVICE_UNAVAILABLE: 503,
LOOP_DETECTED: 508,
LOOP_DETECTED: 508
};
export const API_MESSAGE = {
@ -100,14 +100,14 @@ export const API_MESSAGE = {
TAG_UPDATED: 'tags updated',
TAG_REMOVED: 'tag removed',
TAG_ADDED: 'package tagged',
LOGGED_OUT: 'Logged out',
LOGGED_OUT: 'Logged out'
};
export const SUPPORT_ERRORS = {
PLUGIN_MISSING_INTERFACE: 'the plugin does not provide implementation of the requested feature',
TFA_DISABLED: 'the two-factor authentication is not yet supported',
STORAGE_NOT_IMPLEMENT: 'the storage does not support token saving',
PARAMETERS_NOT_VALID: 'the parameters are not valid',
PARAMETERS_NOT_VALID: 'the parameters are not valid'
};
export const API_ERROR = {
@ -142,13 +142,13 @@ export const API_ERROR = {
RESOURCE_UNAVAILABLE: 'resource unavailable',
BAD_PACKAGE_DATA: 'bad incoming package data',
USERNAME_PASSWORD_REQUIRED: 'username and password is required',
USERNAME_ALREADY_REGISTERED: 'username is already registered',
USERNAME_ALREADY_REGISTERED: 'username is already registered'
};
export const APP_ERROR = {
CONFIG_NOT_VALID: 'CONFIG: it does not look like a valid config file',
PROFILE_ERROR: 'profile unexpected error',
PASSWORD_VALIDATION: 'not valid password',
PASSWORD_VALIDATION: 'not valid password'
};
export const DEFAULT_NO_README = 'ERROR: No README data found!';
@ -158,12 +158,12 @@ export const WEB_TITLE = 'Verdaccio';
export const PACKAGE_ACCESS = {
SCOPE: '@*/*',
ALL: '**',
ALL: '**'
};
export const STORAGE = {
PACKAGE_FILE_NAME: 'package.json',
FILE_EXIST_ERROR: 'EEXISTS',
NO_SUCH_FILE_ERROR: 'ENOENT',
DEFAULT_REVISION: '0-0000000000000000',
DEFAULT_REVISION: '0-0000000000000000'
};

View file

@ -42,23 +42,25 @@ export function createTarballHash(): Hash {
* @return {String}
*/
export function stringToMD5(data: Buffer | string): string {
return createHash('md5')
.update(data)
.digest('hex');
return createHash('md5').update(data).digest('hex');
}
export function generateRandomHexString(length = 8): string {
return pseudoRandomBytes(length).toString('hex');
}
export async function signPayload(payload: RemoteUser, secretOrPrivateKey: string, options: JWTSignOptions): Promise<string> {
return new Promise(function(resolve, reject): Promise<string> {
export async function signPayload(
payload: RemoteUser,
secretOrPrivateKey: string,
options: JWTSignOptions
): Promise<string> {
return new Promise(function (resolve, reject): Promise<string> {
return jwt.sign(
payload,
secretOrPrivateKey,
{
notBefore: '1', // Make sure the time will not rollback :)
...options,
...options
},
(error, token) => (error ? reject(error) : resolve(token))
);

View file

@ -1,12 +1,6 @@
import assert from 'assert';
import UrlNode from 'url';
import _ from 'lodash';
import { ErrorCode, isObject, getLatestVersion, tagVersion, validateName } from './utils';
import { generatePackageTemplate, normalizePackage, generateRevision, getLatestReadme, cleanUpReadme, normalizeContributors } from './storage-utils';
import {API_ERROR, DIST_TAGS, HTTP_STATUS, STORAGE, SUPPORT_ERRORS, USERS} from './constants';
import { createTarballHash } from './crypto-utils';
import { prepareSearchPackage } from './storage-utils';
import loadPlugin from '../lib/plugin-loader';
import LocalDatabase from '@verdaccio/local-storage';
import { UploadTarball, ReadTarball } from '@verdaccio/streams';
import {
@ -26,10 +20,24 @@ import {
Author,
CallbackAction,
onSearchPackage,
onEndSearchPackage, StorageUpdateCallback,
onEndSearchPackage,
StorageUpdateCallback
} from '@verdaccio/types';
import { IStorage, StringValue } from '../../types';
import { VerdaccioError } from '@verdaccio/commons-api';
import loadPlugin from '../lib/plugin-loader';
import { IStorage, StringValue } from '../../types';
import { ErrorCode, isObject, getLatestVersion, tagVersion, validateName } from './utils';
import {
generatePackageTemplate,
normalizePackage,
generateRevision,
getLatestReadme,
cleanUpReadme,
normalizeContributors
} from './storage-utils';
import { API_ERROR, DIST_TAGS, HTTP_STATUS, STORAGE, SUPPORT_ERRORS, USERS } from './constants';
import { createTarballHash } from './crypto-utils';
import { prepareSearchPackage } from './storage-utils';
/**
* Implements Storage interface (same for storage.js, local-storage.js, up-storage.js).
@ -52,10 +60,13 @@ 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)) {
if (
_.isNull(err) === false &&
(err.code === STORAGE.FILE_EXIST_ERROR || err.code === HTTP_STATUS.CONFLICT)
) {
return callback(ErrorCode.getConflict());
}
@ -95,7 +106,10 @@ class LocalStorage implements IStorage {
this.storagePlugin.remove(name, (removeFailed: Error): void => {
if (removeFailed) {
// This will happen when database is locked
this.logger.debug({ name }, `[storage/removePackage] the database is locked, removed has failed for @{name}`);
this.logger.debug(
{ name },
`[storage/removePackage] the database is locked, removed has failed for @{name}`
);
return callback(ErrorCode.getBadData(removeFailed.message));
}
@ -150,7 +164,7 @@ class LocalStorage implements IStorage {
if (_.isNil(packageLocalJson._distfiles[filename])) {
const hash: DistFile = (packageLocalJson._distfiles[filename] = {
url: version.dist.tarball,
sha: version.dist.shasum,
sha: version.dist.shasum
});
/* eslint spaced-comment: 0 */
// $FlowFixMe
@ -165,7 +179,10 @@ class LocalStorage implements IStorage {
}
for (const tag in packageInfo[DIST_TAGS]) {
if (!packageLocalJson[DIST_TAGS][tag] || packageLocalJson[DIST_TAGS][tag] !== packageInfo[DIST_TAGS][tag]) {
if (
!packageLocalJson[DIST_TAGS][tag] ||
packageLocalJson[DIST_TAGS][tag] !== packageInfo[DIST_TAGS][tag]
) {
change = true;
packageLocalJson[DIST_TAGS][tag] = packageInfo[DIST_TAGS][tag];
}
@ -192,7 +209,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 {
@ -209,7 +226,13 @@ class LocalStorage implements IStorage {
* @param {*} tag
* @param {*} callback
*/
public addVersion(name: string, version: string, metadata: Version, tag: StringValue, callback: CallbackAction): void {
public addVersion(
name: string,
version: string,
metadata: Version,
tag: StringValue,
callback: CallbackAction
): void {
this._updatePackage(
name,
(data, cb: Callback): void => {
@ -230,7 +253,10 @@ class LocalStorage implements IStorage {
const tarball = metadata.dist.tarball.replace(/.*\//, '');
if (isObject(data._attachments[tarball])) {
if (_.isNil(data._attachments[tarball].shasum) === false && _.isNil(metadata.dist.shasum) === false) {
if (
_.isNil(data._attachments[tarball].shasum) === false &&
_.isNil(metadata.dist.shasum) === false
) {
if (data._attachments[tarball].shasum != metadata.dist.shasum) {
const errorMessage = `shasum error, ${data._attachments[tarball].shasum} != ${metadata.dist.shasum}`;
return cb(ErrorCode.getBadRequest(errorMessage));
@ -327,13 +353,18 @@ class LocalStorage implements IStorage {
* @param {*} callback
* @return {Function}
*/
public changePackage(name: string, incomingPkg: Package, revision: string | void, callback: Callback): void {
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 => {
@ -355,10 +386,16 @@ class LocalStorage implements IStorage {
const incomingDeprecated = incomingVersion.deprecated;
if (incomingDeprecated != localData.versions[version].deprecated) {
if (!incomingDeprecated) {
this.logger.info({ name: name, version: version }, 'undeprecating @{name}@@{version}');
this.logger.info(
{ name: name, version: version },
'undeprecating @{name}@@{version}'
);
delete localData.versions[version].deprecated;
} else {
this.logger.info({ name: name, version: version }, 'deprecating @{name}@@{version}');
this.logger.info(
{ name: name, version: version },
'deprecating @{name}@@{version}'
);
localData.versions[version].deprecated = incomingDeprecated;
}
localData.time!.modified = new Date().toISOString();
@ -370,7 +407,7 @@ class LocalStorage implements IStorage {
localData[DIST_TAGS] = incomingPkg[DIST_TAGS];
cb(null);
},
function(err): void {
function (err): void {
if (err) {
return callback(err);
}
@ -385,7 +422,12 @@ class LocalStorage implements IStorage {
* @param {*} revision
* @param {*} callback
*/
public removeTarball(name: string, filename: string, revision: string, callback: CallbackAction): void {
public removeTarball(
name: string,
filename: string,
revision: string,
callback: CallbackAction
): void {
assert(validateName(filename));
this._updatePackage(
@ -426,10 +468,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;
@ -455,15 +497,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 {
@ -475,7 +517,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');
});
@ -485,11 +527,11 @@ class LocalStorage implements IStorage {
name,
function updater(data, cb): void {
data._attachments[filename] = {
shasum: shaOneHash.digest('hex'),
shasum: shaOneHash.digest('hex')
};
cb(null);
},
function(err): void {
function (err): void {
if (err) {
uploadStream.emit('error', err);
} else {
@ -499,11 +541,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();
@ -561,13 +603,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'));
@ -576,11 +618,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);
@ -738,14 +780,24 @@ class LocalStorage implements IStorage {
* @param {*} callback callback that gets invoked after it's all updated
* @return {Function}
*/
private _updatePackage(name: string, updateHandler: StorageUpdateCallback, callback: CallbackAction): void {
private _updatePackage(
name: string,
updateHandler: StorageUpdateCallback,
callback: CallbackAction
): void {
const storage: IPackageStorage = this._getLocalStorage(name);
if (!storage) {
return callback(ErrorCode.getNotFound());
}
storage.updatePackage(name, updateHandler, this._writePackage.bind(this), normalizePackage, callback);
storage.updatePackage(
name,
updateHandler,
this._writePackage.bind(this),
normalizePackage,
callback
);
}
/**
@ -778,21 +830,24 @@ 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);
});
});
@ -838,20 +893,27 @@ class LocalStorage implements IStorage {
private _loadStorePlugin(): IPluginStorage<Config> | void {
const plugin_params = {
config: this.config,
logger: this.logger,
logger: this.logger
};
const plugins: IPluginStorage<Config>[] = loadPlugin<IPluginStorage<Config>>(this.config, this.config.store, plugin_params, (plugin): IPluginStorage<Config> => {
return plugin.getPackageStorage;
});
// eslint-disable-next-line max-len
const plugins: IPluginStorage<Config>[] = loadPlugin<IPluginStorage<Config>>(
this.config,
this.config.store,
plugin_params,
(plugin): IPluginStorage<Config> => {
return plugin.getPackageStorage;
}
);
return _.head(plugins);
}
public saveToken(token: Token): Promise<any> {
if (_.isFunction(this.storagePlugin.saveToken) === false) {
return Promise.reject(ErrorCode.getCode(HTTP_STATUS.SERVICE_UNAVAILABLE, SUPPORT_ERRORS.PLUGIN_MISSING_INTERFACE));
return Promise.reject(
ErrorCode.getCode(HTTP_STATUS.SERVICE_UNAVAILABLE, SUPPORT_ERRORS.PLUGIN_MISSING_INTERFACE)
);
}
return this.storagePlugin.saveToken(token);
@ -859,15 +921,19 @@ class LocalStorage implements IStorage {
public deleteToken(user: string, tokenKey: string): Promise<any> {
if (_.isFunction(this.storagePlugin.deleteToken) === false) {
return Promise.reject(ErrorCode.getCode(HTTP_STATUS.SERVICE_UNAVAILABLE, SUPPORT_ERRORS.PLUGIN_MISSING_INTERFACE));
return Promise.reject(
ErrorCode.getCode(HTTP_STATUS.SERVICE_UNAVAILABLE, SUPPORT_ERRORS.PLUGIN_MISSING_INTERFACE)
);
}
return this.storagePlugin.deleteToken(user, tokenKey);
}
public readTokens(filter: TokenFilter): Promise<Array<Token>> {
public readTokens(filter: TokenFilter): Promise<Token[]> {
if (_.isFunction(this.storagePlugin.readTokens) === false) {
return Promise.reject(ErrorCode.getCode(HTTP_STATUS.SERVICE_UNAVAILABLE, SUPPORT_ERRORS.PLUGIN_MISSING_INTERFACE));
return Promise.reject(
ErrorCode.getCode(HTTP_STATUS.SERVICE_UNAVAILABLE, SUPPORT_ERRORS.PLUGIN_MISSING_INTERFACE)
);
}
return this.storagePlugin.readTokens(filter);

View file

@ -1,8 +1,8 @@
/* eslint-disable */
import {prettyTimestamped} from "./logger/format/pretty-timestamped";
import {pretty} from "./logger/format/pretty";
import {jsonFormat} from "./logger/format/json";
import { prettyTimestamped } from './logger/format/pretty-timestamped';
import { pretty } from './logger/format/pretty';
import { jsonFormat } from './logger/format/json';
const cluster = require('cluster');
const Logger = require('bunyan');
@ -49,7 +49,7 @@ function setup(logs, { logStart } = { logStart: true }) {
logs = DEFAULT_LOGGER_CONF;
}
logs.forEach(function(target: LoggerTarget) {
logs.forEach(function (target: LoggerTarget) {
let level = target.level || 35;
if (level === 'http') {
level = 35;
@ -78,7 +78,7 @@ function setup(logs, { logStart } = { logStart: true }) {
const rotateStream = {
type: 'raw',
level,
stream,
stream
};
if (logStart) {
@ -94,8 +94,11 @@ function setup(logs, { logStart } = { logStart: true }) {
let destinationIsTTY = false;
if (target.type === 'file') {
// destination stream
destination = require('fs').createWriteStream(target.path, { flags: 'a', encoding: 'utf8' });
destination.on('error', function(err) {
destination = require('fs').createWriteStream(target.path, {
flags: 'a',
encoding: 'utf8'
});
destination.on('error', function (err) {
stream.emit('error', err);
});
} else if (target.type === 'stdout' || target.type === 'stderr') {
@ -107,16 +110,16 @@ function setup(logs, { logStart } = { logStart: true }) {
if (target.format === 'pretty') {
// making fake stream for pretty printing
stream.write = obj => {
stream.write = (obj) => {
destination.write(pretty(obj, destinationIsTTY));
};
} else if (target.format === 'pretty-timestamped') {
// making fake stream for pretty printing
stream.write = obj => {
stream.write = (obj) => {
destination.write(prettyTimestamped(obj, destinationIsTTY));
};
} else {
stream.write = obj => {
stream.write = (obj) => {
destination.write(jsonFormat(obj, destinationIsTTY));
};
}
@ -127,7 +130,7 @@ function setup(logs, { logStart } = { logStart: true }) {
// @ts-ignore
level,
// @ts-ignore
stream: stream,
stream: stream
});
}
});
@ -139,8 +142,8 @@ function setup(logs, { logStart } = { logStart: true }) {
serializers: {
err: Logger.stdSerializers.err,
req: Logger.stdSerializers.req,
res: Logger.stdSerializers.res,
},
res: Logger.stdSerializers.res
}
});
// In case of an empty log file, we ensure there is always something logged. This also helps see if the server
@ -149,7 +152,7 @@ function setup(logs, { logStart } = { logStart: true }) {
logger.warn('Verdaccio started');
}
process.on('SIGUSR2', function() {
process.on('SIGUSR2', function () {
// https://github.com/trentm/node-bunyan#stream-type-rotating-file
if (logger) {
/**

View file

@ -1,10 +1,9 @@
import {fillInMsgTemplate} from "../formatter";
import { fillInMsgTemplate } from '../formatter';
const Logger = require('bunyan');
export function jsonFormat(obj, hasColors): string {
const msg = fillInMsgTemplate(obj.msg, obj, hasColors);
const msg = fillInMsgTemplate(obj.msg, obj, hasColors);
return `${JSON.stringify({ ...obj, msg }, Logger.safeCycles())}\n`;
return `${JSON.stringify({ ...obj, msg }, Logger.safeCycles())}\n`;
}

View file

@ -1,6 +1,6 @@
import {formatLoggingDate} from "../utils";
import {printMessage} from "../formatter";
import { formatLoggingDate } from '../utils';
import { printMessage } from '../formatter';
export function prettyTimestamped(obj, hasColors): string {
return `[${formatLoggingDate(obj.time)}] ${printMessage(obj.level, obj.msg, obj, hasColors)}\n`;
return `[${formatLoggingDate(obj.time)}] ${printMessage(obj.level, obj.msg, obj, hasColors)}\n`;
}

View file

@ -1,5 +1,5 @@
import {printMessage} from "../formatter";
import { printMessage } from '../formatter';
export function pretty(obj, hasColors): string {
return `${printMessage(obj.level, obj.msg, obj, hasColors)}\n`;
return `${printMessage(obj.level, obj.msg, obj, hasColors)}\n`;
}

View file

@ -1,9 +1,9 @@
import { inspect } from 'util';
import { isObject, pad } from '../utils';
import { red, green } from 'kleur';
import { white } from 'kleur';
import {calculateLevel, levels, subsystems} from "./levels";
import { isObject, pad } from '../utils';
import { calculateLevel, levels, subsystems } from './levels';
let LEVEL_VALUE_MAX = 0;
for (const l in levels) {
@ -36,7 +36,6 @@ export function printMessage(type, msg, templateObjects, hasColors) {
export function fillInMsgTemplate(msg, obj: unknown, colors): string {
return msg.replace(/@{(!?[$A-Za-z_][$0-9A-Za-z\._]*)}/g, (_, name): string => {
let str = obj;
let is_error;
if (name[0] === '!') {

View file

@ -2,13 +2,13 @@ import { yellow, green, black, blue, red, magenta, cyan, white } from 'kleur';
// level to color
export const levels = {
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
};
/**
@ -17,35 +17,35 @@ export const levels = {
* @return {String} security level
*/
export function calculateLevel(x) {
switch (true) {
case x < 15:
return 'trace';
case x < 25:
return 'debug';
case x < 35:
return 'info';
case x == 35:
return 'http';
case x < 45:
return 'warn';
case x < 55:
return 'error';
default:
return 'fatal';
}
switch (true) {
case x < 15:
return 'trace';
case x < 25:
return 'debug';
case x < 35:
return 'info';
case x == 35:
return 'http';
case x < 45:
return 'warn';
case x < 55:
return 'error';
default:
return 'fatal';
}
}
export const subsystems = [
{
in: green('<--'),
out: yellow('-->'),
fs: black('-=-'),
default: blue('---'),
},
{
in: '<--',
out: '-->',
fs: '-=-',
default: '---',
},
{
in: green('<--'),
out: yellow('-->'),
fs: black('-=-'),
default: blue('---')
},
{
in: '<--',
out: '-->',
fs: '-=-',
default: '---'
}
];

View file

@ -3,5 +3,5 @@ import dayjs from 'dayjs';
export const FORMAT_DATE = 'YYYY-MM-DD HH:mm:ss';
export function formatLoggingDate(time: string): string {
return dayjs(time).format(FORMAT_DATE);
return dayjs(time).format(FORMAT_DATE);
}

View file

@ -5,9 +5,8 @@
import semver from 'semver';
import _ from 'lodash';
import { DIST_TAGS } from './constants';
import { Package } from '@verdaccio/types';
import { DIST_TAGS } from './constants';
/**
* Function gets a local info and an info from uplinks and tries to merge it

View file

@ -1,13 +1,18 @@
import Handlebars from 'handlebars';
import _ from 'lodash';
import { notifyRequest } from './notify-request';
import { OptionsWithUrl } from 'request';
import { Config, Package, RemoteUser } from '@verdaccio/types';
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 || '');
@ -16,7 +21,7 @@ export function handleNotify(metadata: Package, notifyEntry, remoteUser: RemoteU
}
}
const template: HandlebarsTemplateDelegate = Handlebars.compile(notifyEntry.content);
const template = 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 */
@ -30,13 +35,13 @@ export function handleNotify(metadata: Package, notifyEntry, remoteUser: RemoteU
const options: OptionsWithUrl = {
body: content,
url: '',
url: ''
};
// provides fallback support, it's accept an Object {} and Array of {}
if (notifyEntry.headers && _.isArray(notifyEntry.headers)) {
const header = {};
notifyEntry.headers.map(function(item): void {
notifyEntry.headers.map(function (item): void {
if (Object.is(item, item)) {
for (const key in item) {
/* eslint no-prototype-builtins: 0 */
@ -60,17 +65,34 @@ export function handleNotify(metadata: Package, notifyEntry, remoteUser: RemoteU
return notifyRequest(options, content);
}
export function sendNotification(metadata: Package, notify: Notification, remoteUser: RemoteUser, publishedPackage: string): Promise<any> {
export function sendNotification(
metadata: Package,
notify: any,
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 Notification, remoteUser, publishedPackage);
return sendNotification(
metadata,
(config.notify as unknown) as any,
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();

View file

@ -1,24 +1,22 @@
import isNil from 'lodash/isNil';
import { logger } from '../logger';
import request, { RequiredUriUrl } from 'request';
import { logger } from '../logger';
import { HTTP_STATUS } from '../constants';
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'));
});
});
}

View file

@ -1,7 +1,7 @@
import Path from 'path';
import _ from 'lodash';
import { logger } from './logger';
import { Config, IPlugin } from '@verdaccio/types';
import { logger } from './logger';
import { MODULE_NOT_FOUND } from './constants';
/**
@ -96,31 +96,45 @@ export default function loadPlugin<T extends IPlugin<T>>(
}
if (plugin === null) {
logger.error({ content: pluginId, prefix }, 'plugin not found. try npm install @{prefix}-@{content}');
logger.error(
{ content: pluginId, prefix },
'plugin not found. try npm install @{prefix}-@{content}'
);
throw Error(`
${prefix}-${pluginId} plugin not found. try "npm install ${prefix}-${pluginId}"`);
}
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)) {
logger.error({ content: pluginId, prefix }, "@{prefix}-@{content} doesn't look like a valid plugin");
logger.error(
{ content: pluginId, prefix },
"@{prefix}-@{content} doesn't look like a valid plugin"
);
throw Error(`sanity check has failed, "${pluginId}" is not a valid plugin`);
}
logger.warn({ content: pluginId, prefix }, 'Plugin successfully loaded: @{prefix}-@{content}');
logger.warn(
{ content: pluginId, prefix },
'Plugin successfully loaded: @{prefix}-@{content}'
);
return plugin;
}
);

View file

@ -15,7 +15,7 @@ class Search implements IWebSearch {
* Constructor.
*/
public constructor() {
this.index = lunrMutable(function(): void {
this.index = lunrMutable(function (): void {
// FIXME: there is no types for this library
/* eslint no-invalid-this:off */
// @ts-ignore
@ -43,11 +43,13 @@ class Search implements IWebSearch {
public query(query: string): any[] {
const localStorage = this.storage.localStorage as IStorage;
return query === '*' ? localStorage.storagePlugin.get((items): any => {
items.map(function(pkg): any {
return { ref: pkg, score: 1 };
});
}) : this.index.search(`*${query}*`);
return query === '*'
? localStorage.storagePlugin.get((items): any => {
items.map(function (pkg): any {
return { ref: pkg, score: 1 };
});
})
: this.index.search(`*${query}*`);
}
/**
@ -61,7 +63,7 @@ class Search implements IWebSearch {
description: pkg.description,
version: `v${pkg.version}`,
keywords: pkg.keywords,
author: pkg._npmUser ? pkg._npmUser.name : '???',
author: pkg._npmUser ? pkg._npmUser.name : '???'
});
}

View file

@ -1,10 +1,10 @@
import _ from 'lodash';
import { Package, Version, Author } from '@verdaccio/types';
import { generateRandomHexString } from '../lib/crypto-utils';
import { IStorage } from '../../types';
import { ErrorCode, isObject, normalizeDistTags, semverSort } from './utils';
import Search from './search';
import { generateRandomHexString } from '../lib/crypto-utils';
import { Package, Version, Author } from '@verdaccio/types';
import { IStorage } from '../../types';
import { API_ERROR, HTTP_STATUS, DIST_TAGS, USERS, STORAGE } from './constants';
export function generatePackageTemplate(name: string): Package {
@ -18,7 +18,7 @@ export function generatePackageTemplate(name: string): Package {
_uplinks: {},
_distfiles: {},
_attachments: {},
_rev: '',
_rev: ''
};
}
@ -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.map(function(tag): string | void {
readmeDistTagsPriority.map(function (tag): string | void {
if (readme) {
return readme;
}
@ -99,15 +97,24 @@ export function normalizeContributors(contributors: Author[]): Author[] {
} else if (_.isString(contributors)) {
return [
{
name: contributors,
},
name: contributors
}
];
}
return contributors;
}
export const WHITELIST = ['_rev', 'name', 'versions', 'dist-tags', 'readme', 'time', '_id', 'users'];
export const WHITELIST = [
'_rev',
'name',
'versions',
'dist-tags',
'readme',
'time',
'_id',
'users'
];
export function cleanUpLinksRef(keepUpLinkData: boolean, result: Package): Package {
const propertyToKeep = [...WHITELIST];
@ -131,80 +138,66 @@ 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)) {
Search.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)) {
Search.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);
}
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);
}
// 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 {
@ -218,7 +211,8 @@ export function mergeUplinkTimeIntoLocal(localMetadata: Package, remoteMetadata:
export function prepareSearchPackage(data: Package, time: unknown): any {
const listVersions: string[] = Object.keys(data.versions);
const versions: string[] = semverSort(listVersions);
const latest: string | undefined = data[DIST_TAGS] && data[DIST_TAGS].latest ? data[DIST_TAGS].latest : versions.pop();
const latest: string | undefined =
data[DIST_TAGS] && data[DIST_TAGS].latest ? data[DIST_TAGS].latest : versions.pop();
if (latest && data.versions[latest]) {
const version: Version = data.versions[latest];
@ -236,9 +230,9 @@ export function prepareSearchPackage(data: Package, time: unknown): any {
bugs: version.bugs,
license: version.license,
time: {
modified: time,
modified: time
},
versions,
versions
};
return pkg;

View file

@ -1,22 +1,49 @@
import _ from 'lodash';
import assert from 'assert';
import async, { AsyncResultArrayCallback } from 'async';
import Stream from 'stream';
import _ from 'lodash';
import async, { AsyncResultArrayCallback } from 'async';
import { ReadTarball } from '@verdaccio/streams';
import {
IReadTarball,
IUploadTarball,
Versions,
Package,
Config,
MergeTags,
Version,
DistFile,
Callback,
Logger
} from '@verdaccio/types';
import { GenericBody, TokenFilter, Token } from '@verdaccio/types';
import { VerdaccioError } from '@verdaccio/commons-api';
import {
IStorage,
IProxy,
IStorageHandler,
ProxyList,
StringValue,
IGetPackageOptions,
ISyncUplinks,
IPluginFilters
} from '../../types';
import { logger } from '../lib/logger';
import ProxyStorage from './up-storage';
import Search from './search';
import { API_ERROR, HTTP_STATUS, DIST_TAGS } from './constants';
import LocalStorage from './local-storage';
import { ReadTarball } from '@verdaccio/streams';
import { checkPackageLocal, publishPackage, checkPackageRemote, cleanUpLinksRef, mergeUplinkTimeIntoLocal, generatePackageTemplate } from './storage-utils';
import {
checkPackageLocal,
publishPackage,
checkPackageRemote,
cleanUpLinksRef,
mergeUplinkTimeIntoLocal,
generatePackageTemplate
} from './storage-utils';
import { setupUpLinks, updateVersionsHiddenUpLink } from './uplink-util';
import { mergeVersions } from './metadata-utils';
import { ErrorCode, normalizeDistTags, validateMetadata, isObject } from './utils';
import { IStorage, IProxy, IStorageHandler, ProxyList, StringValue, IGetPackageOptions, ISyncUplinks, IPluginFilters } from '../../types';
import { IReadTarball, IUploadTarball, Versions, Package, Config, MergeTags, Version, DistFile, Callback, Logger } from '@verdaccio/types';
import { hasProxyTo } from './config-utils';
import { logger } from '../lib/logger';
import { GenericBody, TokenFilter, Token } from '@verdaccio/types';
import { VerdaccioError } from '@verdaccio/commons-api';
class Storage implements IStorageHandler {
public localStorage: IStorage;
@ -50,7 +77,11 @@ class Storage implements IStorageHandler {
public async addPackage(name: string, metadata: any, callback: Function): Promise<void> {
try {
await checkPackageLocal(name, this.localStorage);
await checkPackageRemote(name, this._isAllowPublishOffline(), this._syncUplinksMetadata.bind(this));
await checkPackageRemote(
name,
this._isAllowPublishOffline(),
this._syncUplinksMetadata.bind(this)
);
await publishPackage(name, metadata, this.localStorage as IStorage);
callback();
} catch (err) {
@ -59,10 +90,14 @@ class Storage implements IStorageHandler {
}
private _isAllowPublishOffline(): boolean {
return typeof this.config.publish !== 'undefined' && _.isBoolean(this.config.publish.allow_offline) && this.config.publish.allow_offline;
return (
typeof this.config.publish !== 'undefined' &&
_.isBoolean(this.config.publish.allow_offline) &&
this.config.publish.allow_offline
);
}
public readTokens(filter: TokenFilter): Promise<Array<Token>> {
public readTokens(filter: TokenFilter): Promise<Token[]> {
return this.localStorage.readTokens(filter);
}
@ -78,7 +113,13 @@ class Storage implements IStorageHandler {
* Add a new version of package {name} to a system
Used storages: local (write)
*/
public addVersion(name: string, version: string, metadata: Version, tag: StringValue, callback: Callback): void {
public addVersion(
name: string,
version: string,
metadata: Version,
tag: StringValue,
callback: Callback
): void {
this.localStorage.addVersion(name, version, metadata, tag, callback);
}
@ -95,7 +136,12 @@ class Storage implements IStorageHandler {
Function changes a package info from local storage and all uplinks with write access./
Used storages: local (write)
*/
public changePackage(name: string, metadata: Package, revision: string, callback: Callback): void {
public changePackage(
name: string,
metadata: Package,
revision: string,
callback: Callback
): void {
this.localStorage.changePackage(name, metadata, revision, callback);
}
@ -139,7 +185,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;
@ -150,48 +196,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);
});
@ -215,7 +250,7 @@ class Storage implements IStorageHandler {
{
url: file.url,
cache: true,
_autogenerated: true,
_autogenerated: true
},
self.config
);
@ -226,24 +261,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);
@ -256,12 +291,15 @@ class Storage implements IStorageHandler {
};
if (savestream) {
savestream.on('open', function(): void {
savestream.on('open', function (): void {
on_open();
});
savestream.on('error', function(err): void {
self.logger.warn({ err: err, fileName: file }, 'error saving file @{fileName}: @{err.message}\n@{err.stack}');
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();
}
@ -288,19 +326,17 @@ 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
return options.callback(err);
}
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 {
this._syncUplinksMetadata(
options.name,
data,
{ req: options.req, uplinksLook: options.uplinksLook },
function getPackageSynUpLinksCallback(err, result: Package, uplinkErrors): void {
if (err) {
return options.callback(err);
}
@ -311,9 +347,9 @@ class Storage implements IStorageHandler {
result._attachments = {};
options.callback(null, result, uplinkErrors);
});
}
);
}
);
});
}
/**
@ -335,7 +371,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();
@ -343,40 +379,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();
});
@ -392,15 +422,16 @@ class Storage implements IStorageHandler {
*/
public getLocalDatabase(callback: Callback): void {
const self = this;
this.localStorage.storagePlugin.get(
(err, locals): void => {
if (err) {
callback(err);
}
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 {
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]) {
@ -416,7 +447,10 @@ class Storage implements IStorageHandler {
packages.push(version);
} else {
self.logger.warn({ package: locals[itemPkg] }, 'package @{package} does not have a "latest" tag?');
self.logger.warn(
{ package: locals[itemPkg] },
'package @{package} does not have a "latest" tag?'
);
}
}
@ -425,16 +459,16 @@ class Storage implements IStorageHandler {
} else {
getPackage(itemPkg + 1);
}
});
};
}
);
};
if (locals.length) {
getPackage(0);
} else {
callback(null, []);
}
if (locals.length) {
getPackage(0);
} else {
callback(null, []);
}
);
});
}
/**
@ -442,7 +476,12 @@ class Storage implements IStorageHandler {
if package is available locally, it MUST be provided in pkginfo
returns callback(err, result, uplink_errors)
*/
public _syncUplinksMetadata(name: string, packageInfo: Package, options: ISyncUplinks, callback: Callback): void {
public _syncUplinksMetadata(
name: string,
packageInfo: Package,
options: ISyncUplinks,
callback: Callback
): void {
let found = true;
const self = this;
const upLinks: IProxy[] = [];
@ -475,59 +514,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> => {

View file

@ -1,17 +1,26 @@
import zlib from 'zlib';
import Stream, { Readable } from 'stream';
import URL, { UrlWithStringQuery } from 'url';
import JSONStream from 'JSONStream';
import _ from 'lodash';
import request from 'request';
import Stream, { Readable } from 'stream';
import URL, {UrlWithStringQuery} from 'url';
import { parseInterval, isObject, ErrorCode, buildToken } from './utils';
import { ReadTarball } from '@verdaccio/streams';
import { ERROR_CODE, TOKEN_BASIC, TOKEN_BEARER, HEADERS, HTTP_STATUS, API_ERROR, HEADER_TYPE, CHARACTER_ENCODING } from './constants';
import { Config, Callback, Headers, Logger, Package } from '@verdaccio/types';
import { IProxy, UpLinkConfLocal } from '../../types';
import { parseInterval, isObject, ErrorCode, buildToken } from './utils';
import {
ERROR_CODE,
TOKEN_BASIC,
TOKEN_BEARER,
HEADERS,
HTTP_STATUS,
API_ERROR,
HEADER_TYPE,
CHARACTER_ENCODING
} from './constants';
const LoggerApi = require('./logger');
const encode = function(thing): string {
const encode = function (thing): string {
return encodeURIComponent(thing).replace(/^%40/, '@');
};
@ -77,7 +86,7 @@ class ProxyStorage implements IProxy {
'Too big timeout value: ' + this.config.timeout,
'We changed time format to nginx-like one',
'(see http://nginx.org/en/docs/syntax.html)',
'so please update your config accordingly',
'so please update your config accordingly'
].join('\n')
);
}
@ -107,16 +116,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;
}
@ -133,7 +142,7 @@ class ProxyStorage implements IProxy {
{
method: method,
headers: headers,
uri: uri,
uri: uri
},
"making request: '@{method} @{uri}'"
);
@ -143,63 +152,65 @@ 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}'";
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}'";
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,
@ -211,7 +222,7 @@ class ProxyStorage implements IProxy {
gzip: true,
timeout: this.timeout,
strictSSL: this.strict_ssl,
agentOptions: this.agent_options,
agentOptions: this.agent_options
};
if (this.ca) {
@ -223,7 +234,7 @@ class ProxyStorage implements IProxy {
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) {
@ -238,17 +249,17 @@ class ProxyStorage implements IProxy {
{
request: {
method: method,
url: uri,
url: uri
},
level: 35, // http
status: _.isNull(res) === false ? res.statusCode : 'ERR',
status: _.isNull(res) === false ? res.statusCode : 'ERR'
},
message
);
})();
}
});
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) {
@ -397,8 +408,11 @@ class ProxyStorage implements IProxy {
public isUplinkValid(url: string): boolean {
// $FlowFixMe
const urlParsed: UrlWithStringQuery = URL.parse(url);
const isHTTPS = (urlDomainParsed: URL): boolean => urlDomainParsed.protocol === 'https:' && (urlParsed.port === null || urlParsed.port === '443');
const getHost = (urlDomainParsed): boolean => (isHTTPS(urlDomainParsed) ? urlDomainParsed.hostname : urlDomainParsed.host);
const isHTTPS = (urlDomainParsed: URL): boolean =>
urlDomainParsed.protocol === 'https:' &&
(urlParsed.port === null || urlParsed.port === '443');
const getHost = (urlDomainParsed): boolean =>
isHTTPS(urlDomainParsed) ? urlDomainParsed.hostname : urlDomainParsed.host;
const isMatchProtocol: boolean = urlParsed.protocol === this.url.protocol;
const isMatchHost: boolean = getHost(urlParsed) === getHost(this.url);
// @ts-ignore
@ -425,7 +439,7 @@ class ProxyStorage implements IProxy {
uri: `/${encode(name)}`,
json: true,
headers: headers,
req: options.req,
req: options.req
},
(err, res, body): void => {
if (err) {
@ -435,7 +449,9 @@ class ProxyStorage implements IProxy {
return callback(ErrorCode.getNotFound(API_ERROR.NOT_PACKAGE_UPLINK));
}
if (!(res.statusCode >= HTTP_STATUS.OK && res.statusCode < HTTP_STATUS.MULTIPLE_CHOICES)) {
const error = ErrorCode.getInternalError(`${API_ERROR.BAD_STATUS_CODE}: ${res.statusCode}`);
const error = ErrorCode.getInternalError(
`${API_ERROR.BAD_STATUS_CODE}: ${res.statusCode}`
);
// $FlowFixMe
error.remoteStatus = res.statusCode;
return callback(error);
@ -460,16 +476,19 @@ class ProxyStorage implements IProxy {
uri_full: url,
encoding: null,
headers: {
Accept: contentTypeAccept,
},
Accept: contentTypeAccept
}
});
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));
}
if (!(res.statusCode >= HTTP_STATUS.OK && res.statusCode < HTTP_STATUS.MULTIPLE_CHOICES)) {
return stream.emit('error', ErrorCode.getInternalError(`bad uplink status code: ${res.statusCode}`));
return stream.emit(
'error',
ErrorCode.getInternalError(`bad uplink status code: ${res.statusCode}`)
);
}
if (res.headers[HEADER_TYPE.CONTENT_LENGTH]) {
expected_length = res.headers[HEADER_TYPE.CONTENT_LENGTH];
@ -479,13 +498,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;
}
@ -507,8 +526,8 @@ class ProxyStorage implements IProxy {
uri: options.req.url,
req: options.req,
headers: {
referer: options.req.headers.referer,
},
referer: options.req.headers.referer
}
});
const parsePackage = (pkg: Package): void => {
@ -519,7 +538,10 @@ class ProxyStorage implements IProxy {
requestStream.on('response', (res): void => {
if (!String(res.statusCode).match(/^2\d\d$/)) {
return transformStream.emit('error', ErrorCode.getInternalError(`bad status code ${res.statusCode} from uplink`));
return transformStream.emit(
'error',
ErrorCode.getInternalError(`bad status code ${res.statusCode} from uplink`)
);
}
// See https://github.com/request/request#requestoptions-callback
@ -568,7 +590,9 @@ class ProxyStorage implements IProxy {
// FIXME: proxy logic is odd, something is wrong here.
// @ts-ignore
if (!this.proxy) {
headers['X-Forwarded-For'] = (req.headers['x-forwarded-for'] ? req.headers['x-forwarded-for'] + ', ' : '') + req.connection.remoteAddress;
headers['X-Forwarded-For'] =
(req.headers['x-forwarded-for'] ? req.headers['x-forwarded-for'] + ', ' : '') +
req.connection.remoteAddress;
}
}
@ -591,7 +615,7 @@ class ProxyStorage implements IProxy {
if (this.failed_requests >= this.max_fails) {
this.logger.warn(
{
host: this.url.host,
host: this.url.host
},
'host @{host} is back online'
);
@ -602,7 +626,7 @@ class ProxyStorage implements IProxy {
if (this.failed_requests === this.max_fails) {
this.logger.warn(
{
host: this.url.host,
host: this.url.host
},
'host @{host} is now offline'
);
@ -618,7 +642,10 @@ class ProxyStorage implements IProxy {
* @private
*/
private _ifRequestFailure(): boolean {
return this.failed_requests >= this.max_fails && Math.abs(Date.now() - (this.last_request_time as number)) < this.fail_timeout;
return (
this.failed_requests >= this.max_fails &&
Math.abs(Date.now() - (this.last_request_time as number)) < this.fail_timeout
);
}
/**
@ -628,7 +655,12 @@ class ProxyStorage implements IProxy {
* @param {*} mainconfig
* @param {*} isHTTPS
*/
private _setupProxy(hostname: string, config: UpLinkConfLocal, mainconfig: Config, isHTTPS: boolean): void {
private _setupProxy(
hostname: string,
config: UpLinkConfLocal,
mainconfig: Config,
isHTTPS: boolean
): void {
let noProxyList;
const proxy_key: string = isHTTPS ? 'https_proxy' : 'http_proxy';
@ -658,10 +690,15 @@ class ProxyStorage implements IProxy {
if (_.isArray(noProxyList)) {
for (let i = 0; i < noProxyList.length; i++) {
let noProxyItem = noProxyList[i];
if (noProxyItem[0] !== '.') noProxyItem = '.' + noProxyItem;
if (noProxyItem[0] !== '.') {
noProxyItem = '.' + noProxyItem;
}
if (hostname.lastIndexOf(noProxyItem) === hostname.length - noProxyItem.length) {
if (this.proxy) {
this.logger.debug({ url: this.url.href, rule: noProxyItem }, 'not using proxy for @{url}, excluded by @{rule} rule');
this.logger.debug(
{ url: this.url.href, rule: noProxyItem },
'not using proxy for @{url}, excluded by @{rule} rule'
);
// @ts-ignore
this.proxy = false;
}
@ -674,7 +711,10 @@ class ProxyStorage implements IProxy {
if (_.isString(this.proxy) === false) {
delete this.proxy;
} else {
this.logger.debug({ url: this.url.href, proxy: this.proxy }, 'using proxy @{proxy} for @{url}');
this.logger.debug(
{ url: this.url.href, proxy: this.proxy },
'using proxy @{proxy} for @{url}'
);
}
}
}

View file

@ -1,6 +1,6 @@
import ProxyStorage from './up-storage';
import { Versions, Config } from '@verdaccio/types';
import { IProxy, ProxyList } from '../../types';
import ProxyStorage from './up-storage';
/**
* Set up the Up Storage for each link.

View file

@ -1,30 +1,17 @@
/**
* @prettier
*/
import _ from 'lodash';
import fs from 'fs';
import assert from 'assert';
import URL from 'url';
import { IncomingHttpHeaders } from 'http2';
import _ from 'lodash';
import semver from 'semver';
import YAML from 'js-yaml';
import URL from 'url';
import sanitizyReadme from '@verdaccio/readme';
import {
APP_ERROR,
DEFAULT_PORT,
DEFAULT_DOMAIN,
DEFAULT_PROTOCOL,
CHARACTER_ENCODING,
HEADERS,
DIST_TAGS,
DEFAULT_USER,
} from './constants';
import { generateGravatarUrl, GENERIC_AVATAR } from '../utils/user';
import { Package, Version, Author } from '@verdaccio/types';
import { Request } from 'express';
import { StringValue, AuthorAvatar } from '../../types';
import { normalizeContributors } from './storage-utils';
import {
getConflict,
getBadData,
@ -34,9 +21,22 @@ import {
getForbidden,
getServiceUnavailable,
getNotFound,
getCode,
getCode
} from '@verdaccio/commons-api';
import { IncomingHttpHeaders } from 'http2';
import { generateGravatarUrl, GENERIC_AVATAR } from '../utils/user';
import { StringValue, AuthorAvatar } from '../../types';
import {
APP_ERROR,
DEFAULT_PORT,
DEFAULT_DOMAIN,
DEFAULT_PROTOCOL,
CHARACTER_ENCODING,
HEADERS,
DIST_TAGS,
DEFAULT_USER
} from './constants';
import { normalizeContributors } from './storage-utils';
import { logger } from './logger';
@ -139,7 +139,11 @@ export function validateMetadata(object: Package, name: string): Package {
* Create base url for registry.
* @return {String} base registry url
*/
export function combineBaseUrl(protocol: string, host: string | void, prefix?: string | void): string {
export function combineBaseUrl(
protocol: string,
host: string | void,
prefix?: string | void
): string {
const result = `${protocol}://${host}`;
const prefixOnlySlash = prefix === '/';
@ -170,7 +174,11 @@ export function extractTarballFromUrl(url: string): string {
* @param {*} config
* @return {String} a filtered package
*/
export function convertDistRemoteToLocalTarballUrls(pkg: Package, req: Request, urlPrefix: string | void): Package {
export function convertDistRemoteToLocalTarballUrls(
pkg: Package,
req: Request,
urlPrefix: string | void
): Package {
for (const ver in pkg.versions) {
if (Object.prototype.hasOwnProperty.call(pkg.versions, ver)) {
const distName = pkg.versions[ver].dist;
@ -271,7 +279,7 @@ export function parseAddress(urlAddress: any): any {
return {
proto: urlPattern[2] || DEFAULT_PROTOCOL,
host: urlPattern[6] || urlPattern[7] || DEFAULT_DOMAIN,
port: urlPattern[8] || DEFAULT_PORT,
port: urlPattern[8] || DEFAULT_PORT
};
}
@ -280,7 +288,7 @@ export function parseAddress(urlAddress: any): any {
if (urlPattern) {
return {
proto: urlPattern[2] || DEFAULT_PROTOCOL,
path: urlPattern[4],
path: urlPattern[4]
};
}
@ -294,7 +302,7 @@ export function parseAddress(urlAddress: any): any {
export function semverSort(listVersions: string[]): string[] {
return (
listVersions
.filter(function(x): boolean {
.filter(function (x): boolean {
if (!semver.parse(x, true)) {
logger.warn({ ver: x }, 'ignoring bad version @{ver}');
return false;
@ -354,7 +362,7 @@ const parseIntervalTable = {
d: 86400000,
w: 7 * 86400000,
M: 30 * 86400000,
y: 365 * 86400000,
y: 365 * 86400000
};
/**
@ -368,10 +376,16 @@ export function parseInterval(interval: any): number {
}
let result = 0;
let last_suffix = Infinity;
interval.split(/\s+/).forEach(function(x): void {
if (!x) return;
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|)$/);
if (!m || parseIntervalTable[m[4]] >= last_suffix || (m[4] === '' && last_suffix !== Infinity)) {
if (
!m ||
parseIntervalTable[m[4]] >= last_suffix ||
(m[4] === '' && last_suffix !== Infinity)
) {
throw Error('invalid interval: ' + interval);
}
last_suffix = parseIntervalTable[m[4]];
@ -405,7 +419,7 @@ export const ErrorCode = {
getForbidden,
getServiceUnavailable,
getNotFound,
getCode,
getCode
};
export function parseConfigFile(configPath: string): any {
@ -452,7 +466,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;
@ -474,7 +488,9 @@ export function deleteProperties(propertiesToDelete: string[], objectItem: any):
export function addGravatarSupport(pkgInfo: Package, online = true): AuthorAvatar {
const pkgInfoCopy = { ...pkgInfo } as any;
const author: any = _.get(pkgInfo, 'latest.author', null) as any;
const contributors: AuthorAvatar[] = normalizeContributors(_.get(pkgInfo, 'latest.contributors', []));
const contributors: AuthorAvatar[] = normalizeContributors(
_.get(pkgInfo, 'latest.contributors', [])
);
const maintainers = _.get(pkgInfo, 'latest.maintainers', []);
// for author.
@ -487,7 +503,7 @@ export function addGravatarSupport(pkgInfo: Package, online = true): AuthorAvata
pkgInfoCopy.latest.author = {
avatar: GENERIC_AVATAR,
email: '',
author,
author
};
}
@ -501,7 +517,7 @@ export function addGravatarSupport(pkgInfo: Package, online = true): AuthorAvata
contributor = {
avatar: GENERIC_AVATAR,
email: contributor,
name: contributor,
name: contributor
};
}
@ -564,7 +580,7 @@ export function formatAuthor(author: AuthorFormat): any {
let authorDetails = {
name: DEFAULT_USER,
email: '',
url: '',
url: ''
};
if (_.isNil(author)) {
@ -574,14 +590,14 @@ export function formatAuthor(author: AuthorFormat): any {
if (_.isString(author)) {
authorDetails = {
...authorDetails,
name: author as string,
name: author as string
};
}
if (_.isObject(author)) {
authorDetails = {
...authorDetails,
...(author as Author),
...(author as Author)
};
}

View file

@ -1,5 +1,7 @@
// @flow
export function spliceURL(...args: string[]): string {
return Array.from(args).reduce((lastResult, current) => lastResult + current).replace(/([^:])(\/)+(.)/g, `$1/$3`);
return Array.from(args)
.reduce((lastResult, current) => lastResult + current)
.replace(/([^:])(\/)+(.)/g, `$1/$3`);
}

View file

@ -1,27 +1,28 @@
// @flow
import {stringToMD5} from '../lib/crypto-utils';
import _ from 'lodash';
import { stringToMD5 } from '../lib/crypto-utils';
// this is a generic avatar
// https://www.iconfinder.com/icons/403017/anonym_avatar_default_head_person_unknown_user_icon
// license: free commercial usage
export const GENERIC_AVATAR = 'data:image/svg+xml;utf8,' +
encodeURIComponent(
'<svg height="100" viewBox="-27 24 100 100" width="100" xmlns="http://www.w3.org/' +
'2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><circle cx="23" cy="7' +
'4" id="a" r="50"/></defs><use fill="#F5EEE5" overflow="visible" xlink:href="#a"/' +
'><clipPath id="b"><use overflow="visible" xlink:href="#a"/></clipPath><g clip-pa' +
'th="url(#b)"><defs><path d="M36 95.9c0 4 4.7 5.2 7.1 5.8 7.6 2 22.8 5.9 22.8 5.9' +
' 3.2 1.1 5.7 3.5 7.1 6.6v9.8H-27v-9.8c1.3-3.1 3.9-5.5 7.1-6.6 0 0 15.2-3.9 22.8-' +
'5.9 2.4-.6 7.1-1.8 7.1-5.8V85h26v10.9z" id="c"/></defs><use fill="#E6C19C" overf' +
'low="visible" xlink:href="#c"/><clipPath id="d"><use overflow="visible" xlink:hr' +
'ef="#c"/></clipPath><path clip-path="url(#d)" d="M23.2 35h.2c3.3 0 8.2.2 11.4 2 ' +
'3.3 1.9 7.3 5.6 8.5 12.1 2.4 13.7-2.1 35.4-6.3 42.4-4 6.7-9.8 9.2-13.5 9.4H23h-.' +
'1c-3.7-.2-9.5-2.7-13.5-9.4-4.2-7-8.7-28.7-6.3-42.4 1.2-6.5 5.2-10.2 8.5-12.1 3.2' +
'-1.8 8.1-2 11.4-2h.2z" fill="#D4B08C"/></g><path d="M22.6 40c19.1 0 20.7 13.8 20' +
'.8 15.1 1.1 11.9-3 28.1-6.8 33.7-4 5.9-9.8 8.1-13.5 8.3h-.5c-3.8-.3-9.6-2.5-13.6' +
'-8.4-3.8-5.6-7.9-21.8-6.8-33.8C2.3 53.7 3.5 40 22.6 40z" fill="#F2CEA5"/></svg>'
);
export const GENERIC_AVATAR =
'data:image/svg+xml;utf8,' +
encodeURIComponent(
'<svg height="100" viewBox="-27 24 100 100" width="100" xmlns="http://www.w3.org/' +
'2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><circle cx="23" cy="7' +
'4" id="a" r="50"/></defs><use fill="#F5EEE5" overflow="visible" xlink:href="#a"/' +
'><clipPath id="b"><use overflow="visible" xlink:href="#a"/></clipPath><g clip-pa' +
'th="url(#b)"><defs><path d="M36 95.9c0 4 4.7 5.2 7.1 5.8 7.6 2 22.8 5.9 22.8 5.9' +
' 3.2 1.1 5.7 3.5 7.1 6.6v9.8H-27v-9.8c1.3-3.1 3.9-5.5 7.1-6.6 0 0 15.2-3.9 22.8-' +
'5.9 2.4-.6 7.1-1.8 7.1-5.8V85h26v10.9z" id="c"/></defs><use fill="#E6C19C" overf' +
'low="visible" xlink:href="#c"/><clipPath id="d"><use overflow="visible" xlink:hr' +
'ef="#c"/></clipPath><path clip-path="url(#d)" d="M23.2 35h.2c3.3 0 8.2.2 11.4 2 ' +
'3.3 1.9 7.3 5.6 8.5 12.1 2.4 13.7-2.1 35.4-6.3 42.4-4 6.7-9.8 9.2-13.5 9.4H23h-.' +
'1c-3.7-.2-9.5-2.7-13.5-9.4-4.2-7-8.7-28.7-6.3-42.4 1.2-6.5 5.2-10.2 8.5-12.1 3.2' +
'-1.8 8.1-2 11.4-2h.2z" fill="#D4B08C"/></g><path d="M22.6 40c19.1 0 20.7 13.8 20' +
'.8 15.1 1.1 11.9-3 28.1-6.8 33.7-4 5.9-9.8 8.1-13.5 8.3h-.5c-3.8-.3-9.6-2.5-13.6' +
'-8.4-3.8-5.6-7.9-21.8-6.8-33.8C2.3 53.7 3.5 40 22.6 40z" fill="#F2CEA5"/></svg>'
);
/**
* Generate gravatar url from email address

View file

@ -1,30 +1,30 @@
import fs from "fs";
import path from "path";
import os from "os";
import {yellow} from "kleur";
import {spawn} from "child_process";
import fs from 'fs';
import path from 'path';
import os from 'os';
import { yellow } from 'kleur';
import { spawn } from 'child_process';
import { npm } from '../utils/process';
import * as __global from '../utils/global.js';
module.exports = async () => {
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), 'verdaccio-cli-e2e-'));
__global.addItem('dir-root', tempRoot);
console.log(yellow(`Add temp root folder: ${tempRoot}`));
fs.copyFileSync(
path.join(__dirname, '../config/_bootstrap_verdaccio.yaml'),
path.join(tempRoot, 'verdaccio.yaml'),
);
// @ts-ignore
global.__namespace = __global;
console.log(`current directory: ${process.cwd()}`);
// @ts-ignore
global.registryProcess = spawn(
'node',
[path.resolve('./bin/verdaccio'), '-c', './verdaccio.yaml'],
// @ts-ignore
{ cwd: tempRoot, silence: false },
);
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), 'verdaccio-cli-e2e-'));
__global.addItem('dir-root', tempRoot);
console.log(yellow(`Add temp root folder: ${tempRoot}`));
fs.copyFileSync(
path.join(__dirname, '../config/_bootstrap_verdaccio.yaml'),
path.join(tempRoot, 'verdaccio.yaml')
);
// @ts-ignore
global.__namespace = __global;
console.log(`current directory: ${process.cwd()}`);
// @ts-ignore
global.registryProcess = spawn(
'node',
[path.resolve('./bin/verdaccio'), '-c', './verdaccio.yaml'],
// @ts-ignore
{ cwd: tempRoot, silence: false }
);
// publish current build version on local registry
await npm('publish', '--registry' ,'http://localhost:4873');
}
// publish current build version on local registry
await npm('publish', '--registry', 'http://localhost:4873');
};

View file

@ -1,4 +1,4 @@
module.exports = async function() {
// @ts-ignore
global.registryProcess.kill();
module.exports = async function () {
// @ts-ignore
global.registryProcess.kill();
};

View file

@ -5,14 +5,15 @@ import NodeEnvironment from 'jest-environment-node';
const __global = require('../utils/global');
// import { npm } from '../utils/process';
class E2ECliTestEnvironment extends NodeEnvironment {
constructor(config) {
super(config)
super(config);
}
async setup() {
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), 'verdaccio-suite-test-'));
const tempRoot = fs.mkdtempSync(
path.join(fs.realpathSync(os.tmpdir()), 'verdaccio-suite-test-')
);
__global.addItem('dir-root', tempRoot);
this.global.__namespace = __global;
console.log(`current directory: ${process.cwd()}`);

View file

@ -1,5 +1,13 @@
import { silentNpm } from '../../utils/process';
export function installVerdaccio(verdaccioInstall) {
return silentNpm('install', '--prefix', verdaccioInstall, 'verdaccio', '--registry' ,'http://localhost:4873', '--no-package-lock');
return silentNpm(
'install',
'--prefix',
verdaccioInstall,
'verdaccio',
'--registry',
'http://localhost:4873',
'--no-package-lock'
);
}

View file

@ -1,27 +1,32 @@
import path from 'path';
import {runVerdaccio} from '../../utils/process';
import {installVerdaccio} from "../__partials/npm_commands";
import { runVerdaccio } from '../../utils/process';
import { installVerdaccio } from '../__partials/npm_commands';
describe('verdaccio info', ()=> {
// @ts-ignore
const tempRootFolder = global.__namespace.getItem('dir-root');
const verdaccioInstall = path.join(tempRootFolder, 'verdaccio-root-info');
let registryProcess;
describe('verdaccio info', () => {
// @ts-ignore
const tempRootFolder = global.__namespace.getItem('dir-root');
const verdaccioInstall = path.join(tempRootFolder, 'verdaccio-root-info');
let registryProcess;
beforeAll(async () => {
await installVerdaccio(verdaccioInstall);
}, 30000);
beforeAll(async () => {
await installVerdaccio(verdaccioInstall);
}, 30000);
test('should run verdaccio info command', async () => {
const pathVerdaccioModule = require.resolve('verdaccio/bin/verdaccio', {
paths: [verdaccioInstall]
});
const hasMatch = await runVerdaccio(pathVerdaccioModule, verdaccioInstall, ['--info'], /Environment/);
test('should run verdaccio info command', async () => {
const pathVerdaccioModule = require.resolve('verdaccio/bin/verdaccio', {
paths: [verdaccioInstall]
});
const hasMatch = await runVerdaccio(
pathVerdaccioModule,
verdaccioInstall,
['--info'],
/Environment/
);
expect(hasMatch.ok).toBeTruthy();
}, 20000);
expect(hasMatch.ok).toBeTruthy();
}, 20000);
afterAll(() => {
registryProcess.kill();
});
afterAll(() => {
registryProcess.kill();
});
});

View file

@ -1,10 +1,10 @@
import path from 'path';
import fs from "fs";
import {installVerdaccio} from "../__partials/npm_commands";
import {spawnRegistry} from "../../utils/registry";
import {callRegistry} from "../../utils/web";
import fs from 'fs';
import { installVerdaccio } from '../__partials/npm_commands';
import { spawnRegistry } from '../../utils/registry';
import { callRegistry } from '../../utils/web';
describe('npm install', ()=> {
describe('npm install', () => {
jest.setTimeout(90000);
const port = '9012';
@ -24,10 +24,10 @@ describe('npm install', ()=> {
paths: [verdaccioInstall]
});
registryProcess = await spawnRegistry(pathVerdaccioModule,
['-c', configPath, '-l', port],
{ cwd: verdaccioInstall, silent: true }
);
registryProcess = await spawnRegistry(pathVerdaccioModule, ['-c', configPath, '-l', port], {
cwd: verdaccioInstall,
silent: true
});
const body = await callRegistry(`http://localhost:${port}/verdaccio`);
const parsedBody = JSON.parse(body);

View file

@ -1,12 +1,12 @@
import path from 'path';
import fs from "fs";
import * as __global from "../../utils/global";
import {spawnRegistry} from "../../utils/registry";
import {execAndWaitForOutputToMatch} from '../../utils/process';
import {installVerdaccio} from "../__partials/npm_commands";
import {expectFileToExist} from "../../utils/expect";
import fs from 'fs';
import * as __global from '../../utils/global';
import { spawnRegistry } from '../../utils/registry';
import { execAndWaitForOutputToMatch } from '../../utils/process';
import { installVerdaccio } from '../__partials/npm_commands';
import { expectFileToExist } from '../../utils/expect';
describe('npm install', ()=> {
describe('npm install', () => {
jest.setTimeout(90000);
const port = '9011';
@ -25,26 +25,37 @@ describe('npm install', ()=> {
const pathVerdaccioModule = require.resolve('verdaccio/bin/verdaccio', {
paths: [verdaccioInstall]
});
registryProcess = await spawnRegistry(pathVerdaccioModule,
['-c', configPath, '-l', port],
{ cwd: verdaccioInstall, silent: true }
);
registryProcess = await spawnRegistry(pathVerdaccioModule, ['-c', configPath, '-l', port], {
cwd: verdaccioInstall,
silent: true
});
});
test('should match on npm info verdaccio', async () => {
// FIXME: not the best match, looking for a better way to match the terminal output
const output = await execAndWaitForOutputToMatch('npm', ['info', 'verdaccio', '--registry'], /A lightweight private npm proxy registry/);
const output = await execAndWaitForOutputToMatch(
'npm',
['info', 'verdaccio', '--registry'],
/A lightweight private npm proxy registry/
);
expect(output.ok).toBeTruthy();
});
test('should install jquery', async () => {
const testCwd = path.join(tempRootFolder, '_jquery_');
await execAndWaitForOutputToMatch('npm', ['install', '--prefix', testCwd, 'jquery', '--registry' ,`http://localhost:${port}`], /''/, {
cwd: verdaccioInstall
});
await execAndWaitForOutputToMatch(
'npm',
['install', '--prefix', testCwd, 'jquery', '--registry', `http://localhost:${port}`],
/''/,
{
cwd: verdaccioInstall
}
);
const exist = await expectFileToExist(path.join(testCwd, 'node_modules', 'jquery', 'package.json'));
const exist = await expectFileToExist(
path.join(testCwd, 'node_modules', 'jquery', 'package.json')
);
expect(exist).toBeTruthy();
});

View file

@ -1,13 +1,13 @@
import * as fs from 'fs-extra';
export function expectFileToExist(fileName: string) {
return new Promise((resolve, reject) => {
fs.exists(fileName, (exist) => {
if (exist) {
resolve(exist);
} else {
reject(new Error(`File ${fileName} was expected to exist but not found...`));
}
});
});
return new Promise((resolve, reject) => {
fs.exists(fileName, (exist) => {
if (exist) {
resolve(exist);
} else {
reject(new Error(`File ${fileName} was expected to exist but not found...`));
}
});
});
}

View file

@ -1,5 +1,5 @@
import * as child_process from 'child_process';
import {SpawnOptions} from "child_process";
import { SpawnOptions } from 'child_process';
export async function _exec(options, cmd, args) {
let stdout = '';
@ -7,12 +7,12 @@ export async function _exec(options, cmd, args) {
const flags = [];
const cwd = process.cwd();
const env = options.env;
console.log(`Running \`${cmd} ${args.map(x => `"${x}"`).join(' ')}\`${flags}...`);
console.log(`Running \`${cmd} ${args.map((x) => `"${x}"`).join(' ')}\`${flags}...`);
console.log(`CWD: ${cwd}`);
console.log(`ENV: ${JSON.stringify(env)}`);
const spawnOptions = {
cwd,
...env ? { env } : {},
...(env ? { env } : {})
};
if (process.platform.startsWith('win')) {
@ -21,7 +21,6 @@ export async function _exec(options, cmd, args) {
spawnOptions['stdio'] = 'pipe';
}
const childProcess = child_process.spawn(cmd, args, spawnOptions);
childProcess.stdout.on('data', (data) => {
stdout += data.toString('utf-8');
@ -29,10 +28,11 @@ export async function _exec(options, cmd, args) {
return;
}
data.toString('utf-8')
data
.toString('utf-8')
.split(/[\n\r]+/)
.filter(line => line !== '')
.forEach(line => console.log(' ' + line));
.filter((line) => line !== '')
.forEach((line) => console.log(' ' + line));
});
childProcess.stderr.on('data', (data) => {
@ -41,10 +41,11 @@ export async function _exec(options, cmd, args) {
return;
}
data.toString('utf-8')
data
.toString('utf-8')
.split(/[\n\r]+/)
.filter(line => line !== '')
.forEach(line => console.error((' ' + line)));
.filter((line) => line !== '')
.forEach((line) => console.error(' ' + line));
});
const err = new Error(`Running "${cmd} ${args.join(' ')}" returned error code `);
@ -63,7 +64,7 @@ export async function _exec(options, cmd, args) {
childProcess.stdout.on('data', (data) => {
// console.log("-->data==>", data.toString(), data.toString().match(match));
if (data.toString().match(match)) {
resolve({ok: true, stdout, stderr });
resolve({ ok: true, stdout, stderr });
}
});
childProcess.stderr.on('data', (data) => {
@ -76,14 +77,14 @@ export async function _exec(options, cmd, args) {
}
export function execAndWaitForOutputToMatch(
cmd: string,
args: string[],
match: RegExp,
spawnOptions: SpawnOptions = {}): any {
cmd: string,
args: string[],
match: RegExp,
spawnOptions: SpawnOptions = {}
): any {
return _exec({ waitForMatch: match, ...spawnOptions, silence: true }, cmd, args);
}
export function npm(...args) {
return _exec({}, 'npm', args);
}
@ -93,5 +94,5 @@ export function runVerdaccio(cmd, installation, args, match: RegExp): any {
}
export function silentNpm(...args) {
return _exec({silent: true}, 'npm', args);
return _exec({ silent: true }, 'npm', args);
}

View file

@ -1,26 +1,21 @@
import {fork} from "child_process";
import { fork } from 'child_process';
export function prepareEnvironment(rootFolder: string, folderName: string) {
export function prepareEnvironment(rootFolder: string, folderName: string) {}
}
export function spawnRegistry(
verdaccioPath: string,
args: string[],
childOptions) {
return new Promise((resolve, reject) => {
let _childOptions = {silent: true, ...childOptions};
const childFork = fork(verdaccioPath, args, _childOptions);
childFork.on('message', (msg) => {
if ('verdaccio_started' in msg) {
resolve(childFork);
}
});
childFork.on('error', (err) => reject([err]));
childFork.on('disconnect', (err) => reject([err]));
childFork.on('exit', (err) => reject([err]));
});
export function spawnRegistry(verdaccioPath: string, args: string[], childOptions) {
return new Promise((resolve, reject) => {
let _childOptions = { silent: true, ...childOptions };
const childFork = fork(verdaccioPath, args, _childOptions);
childFork.on('message', (msg) => {
if ('verdaccio_started' in msg) {
resolve(childFork);
}
});
childFork.on('error', (err) => reject([err]));
childFork.on('disconnect', (err) => reject([err]));
childFork.on('exit', (err) => reject([err]));
});
}

View file

@ -1,9 +1,9 @@
import path from "path";
import fs from "fs";
import path from 'path';
import fs from 'fs';
export function copyConfigFile(rootFolder, configTemplate): string {
const configPath = path.join(rootFolder, 'verdaccio.yaml');
fs.copyFileSync(path.join(__dirname, configTemplate), configPath);
const configPath = path.join(rootFolder, 'verdaccio.yaml');
fs.copyFileSync(path.join(__dirname, configTemplate), configPath);
return configPath;
return configPath;
}

View file

@ -1,23 +1,22 @@
import {IncomingMessage} from 'http';
import { IncomingMessage } from 'http';
import request from 'request';
export function callRegistry(url: string): Promise<string> {
return new Promise((resolve, reject) => {
let options = {
url: url,
headers: { 'Accept': 'application/json' },
};
// @ts-ignore
request(options, (error: any, response: IncomingMessage, body: string) => {
if (error) {
reject(error);
// @ts-ignore
} else if (response.statusCode >= 400) {
reject(new Error(`Requesting "${url}" returned status code ${response.statusCode}.`));
} else {
resolve(body);
}
});
});
return new Promise((resolve, reject) => {
let options = {
url: url,
headers: { Accept: 'application/json' }
};
// @ts-ignore
request(options, (error: any, response: IncomingMessage, body: string) => {
if (error) {
reject(error);
// @ts-ignore
} else if (response.statusCode >= 400) {
reject(new Error(`Requesting "${url}" returned status code ${response.statusCode}.`));
} else {
resolve(body);
}
});
});
}

View file

@ -44,6 +44,7 @@ const json = {
"_attachments": {
"protected-pkg-5.0.5.tgz": {
"content_type": "application\/octet-stream",
// eslint-disable-next-line max-len
"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
}

View file

@ -44,6 +44,7 @@ const json = {
"_attachments": {
"@scope\/pk1-test-1.0.6.tgz": {
"content_type": "application\/octet-stream",
// eslint-disable-next-line max-len
"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
}

View file

@ -1,8 +1,14 @@
import fs from 'fs';
import path from 'path';
import {createTarballHash} from "../../../src/lib/crypto-utils";
import { HTTP_STATUS, DIST_TAGS} from "../../../src/lib/constants";
import {CREDENTIALS, DOMAIN_SERVERS, PORT_SERVER_1, PORT_SERVER_2, TARBALL} from "../config.functional";
import { createTarballHash } from '../../../src/lib/crypto-utils';
import { HTTP_STATUS, DIST_TAGS } from '../../../src/lib/constants';
import {
CREDENTIALS,
DOMAIN_SERVERS,
PORT_SERVER_1,
PORT_SERVER_2,
TARBALL
} from '../config.functional';
import whoIam from './whoIam';
import ping from './ping';
import fixturePkg from '../fixtures/package';
@ -15,14 +21,14 @@ function getPackage(name) {
return fixturePkg(name);
}
export default function(server: any, server2: any) {
export default function (server: any, server2: any) {
describe('basic test endpoints', () => {
const PKG_NAME = 'testpkg';
const PKG_VERSION = '0.0.1';
beforeAll(function() {
return server.auth(CREDENTIALS.user, CREDENTIALS.password)
beforeAll(function () {
return server
.auth(CREDENTIALS.user, CREDENTIALS.password)
.status(HTTP_STATUS.CREATED)
.body_ok(/'test'/);
});
@ -31,7 +37,6 @@ export default function(server: any, server2: any) {
ping(server);
describe('handling packages', () => {
beforeAll(function () {
return server.addPackage(PKG_NAME);
});
@ -40,29 +45,37 @@ export default function(server: any, server2: any) {
return server.addPackage('testpkg-single-tarball');
});
test('creating new package', () => {/* test for before() */
test('creating new package', () => {
/* test for before() */
});
test('downloading non-existent tarball', () => {
return server.getTarball(PKG_NAME, TARBALL)
return server
.getTarball(PKG_NAME, TARBALL)
.status(HTTP_STATUS.NOT_FOUND)
.body_error(/no such file/);
});
test('uploading incomplete tarball', () => {
return server.putTarballIncomplete(PKG_NAME, 'blahblah1', readfile('../fixtures/binary'), 3000);
return server.putTarballIncomplete(
PKG_NAME,
'blahblah1',
readfile('../fixtures/binary'),
3000
);
});
describe('publishing package', () => {
beforeAll(function () {
return server.putTarball(PKG_NAME, TARBALL, readfile('../fixtures/binary'))
return server
.putTarball(PKG_NAME, TARBALL, readfile('../fixtures/binary'))
.status(HTTP_STATUS.CREATED)
.body_ok(/.*/);
});
beforeAll(function () {
return server.putTarball('testpkg-single-tarball', 'single', readfile('../fixtures/binary'))
return server
.putTarball('testpkg-single-tarball', 'single', readfile('../fixtures/binary'))
.status(HTTP_STATUS.CREATED)
.body_ok(/.*/);
});
@ -90,13 +103,16 @@ export default function(server: any, server2: any) {
// testexp-incomplete
test('remove existing single tarball', () => {
return server.removeSingleTarball('testpkg-single-tarball', 'single').status(HTTP_STATUS.CREATED);
return server
.removeSingleTarball('testpkg-single-tarball', 'single')
.status(HTTP_STATUS.CREATED);
});
// testexp-incomplete
test('downloading newly created tarball', () => {
return server.getTarball(PKG_NAME, TARBALL)
return server
.getTarball(PKG_NAME, TARBALL)
.status(200)
.then(function (body) {
expect(body).toEqual(readfile('../fixtures/binary'));
@ -107,36 +123,43 @@ export default function(server: any, server2: any) {
let pkg = getPackage(PKG_NAME);
pkg.dist.shasum = createTarballHash().update('fake').digest('hex');
return server.putVersion(PKG_NAME, PKG_VERSION, pkg)
return server
.putVersion(PKG_NAME, PKG_VERSION, pkg)
.status(HTTP_STATUS.BAD_REQUEST)
.body_error(/shasum error/);
});
describe('publishing version', () => {
beforeAll(function () {
const pkg = getPackage(PKG_NAME);
pkg.dist.shasum = createTarballHash().update(readfile('../fixtures/binary')).digest('hex');
return server.putVersion(PKG_NAME, PKG_VERSION, pkg)
pkg.dist.shasum = createTarballHash()
.update(readfile('../fixtures/binary'))
.digest('hex');
return server
.putVersion(PKG_NAME, PKG_VERSION, pkg)
.status(HTTP_STATUS.CREATED)
.body_ok(/published/);
});
describe('should download a package', () => {
beforeAll(function() {
return server.auth(CREDENTIALS.user, CREDENTIALS.password)
beforeAll(function () {
return server
.auth(CREDENTIALS.user, CREDENTIALS.password)
.status(HTTP_STATUS.CREATED)
.body_ok(new RegExp(CREDENTIALS.user));
});
test('should download a newly created package from server1', () => {
return server.getPackage(PKG_NAME)
return server
.getPackage(PKG_NAME)
.status(HTTP_STATUS.OK)
.then(function (body) {
expect(body.name).toEqual(PKG_NAME);
expect(body.versions[PKG_VERSION].name).toEqual(PKG_NAME);
expect(body.versions[PKG_VERSION].dist.tarball).toEqual(`http://${DOMAIN_SERVERS}:${PORT_SERVER_1}/${PKG_NAME}/-/${TARBALL}`);
expect(body.versions[PKG_VERSION].dist.tarball).toEqual(
`http://${DOMAIN_SERVERS}:${PORT_SERVER_1}/${PKG_NAME}/-/${TARBALL}`
);
expect(body[DIST_TAGS]).toEqual({
latest: PKG_VERSION
});
@ -144,51 +167,53 @@ export default function(server: any, server2: any) {
});
test('should downloading a package from server2', () => {
return server2.getPackage(PKG_NAME)
return server2
.getPackage(PKG_NAME)
.status(HTTP_STATUS.OK)
.then(function (body) {
expect(body.name).toEqual(PKG_NAME);
expect(body.versions[PKG_VERSION].name).toEqual(PKG_NAME);
expect(body.versions[PKG_VERSION].dist.tarball).toEqual(`http://${DOMAIN_SERVERS}:${PORT_SERVER_2}/${PKG_NAME}/-/${TARBALL}`);
expect(body.versions[PKG_VERSION].dist.tarball).toEqual(
`http://${DOMAIN_SERVERS}:${PORT_SERVER_2}/${PKG_NAME}/-/${TARBALL}`
);
expect(body[DIST_TAGS]).toEqual({
latest: PKG_VERSION
});
});
});
});
});
});
});
describe('handle failures on endpoints', () => {
test('should fails trying to fetch non-existent package', () => {
return server.getPackage(PKG_NAME).status(HTTP_STATUS.NOT_FOUND).body_error(/no such package/);
return server
.getPackage(PKG_NAME)
.status(HTTP_STATUS.NOT_FOUND)
.body_error(/no such package/);
});
test(
'should fails on publish a version for non existing package',
() => {
return server.putVersion('testpxg', PKG_VERSION, getPackage('testpxg'))
.status(HTTP_STATUS.NOT_FOUND)
.body_error(/no such package/);
}
);
test('should fails on publish a version for non existing package', () => {
return server
.putVersion('testpxg', PKG_VERSION, getPackage('testpxg'))
.status(HTTP_STATUS.NOT_FOUND)
.body_error(/no such package/);
});
test('should be a package not found', () => {
return server.putTarball('nonExistingPackage', TARBALL, readfile('../fixtures/binary'))
return server
.putTarball('nonExistingPackage', TARBALL, readfile('../fixtures/binary'))
.status(HTTP_STATUS.NOT_FOUND)
.body_error(/no such/);
});
test('should fails on publish package in a bad uplink', () => {
return server.putPackage('baduplink', getPackage('baduplink'))
return server
.putPackage('baduplink', getPackage('baduplink'))
.status(HTTP_STATUS.SERVICE_UNAVAILABLE)
.body_error(/one of the uplinks is down, refuse to publish/);
});
});
});
}

View file

@ -1,6 +1,6 @@
import _ from 'lodash';
export default function(server) {
export default function (server) {
test('ping', () => {
return server.ping().then(function (data) {
// it's always an empty object

View file

@ -1,7 +1,6 @@
import {CREDENTIALS} from "../config.functional";
export default function(server) {
import { CREDENTIALS } from '../config.functional';
export default function (server) {
test('who am I?', () => {
return server.whoami().then(function (username) {
expect(username).toMatch(CREDENTIALS.user);

View file

@ -1,14 +1,20 @@
import {DOMAIN_SERVERS, PORT_SERVER_1, TARBALL} from '../config.functional';
import { DOMAIN_SERVERS, PORT_SERVER_1, TARBALL } from '../config.functional';
export default function(name, version = '0.0.0', port = PORT_SERVER_1, domain= `http://${DOMAIN_SERVERS}:${port}`,
fileName = TARBALL, readme = 'this is a readme'): any {
export default function (
name,
version = '0.0.0',
port = PORT_SERVER_1,
domain = `http://${DOMAIN_SERVERS}:${port}`,
fileName = TARBALL,
readme = 'this is a readme'
): any {
return {
name,
version,
readme,
dist: {
shasum: 'fake',
tarball: `${domain}/${encodeURIComponent(name)}/-/${fileName}`,
},
tarball: `${domain}/${encodeURIComponent(name)}/-/${fileName}`
}
};
}

View file

@ -3,7 +3,7 @@ import { setup } from '../../src/lib/logger';
setup([]);
import {IServerBridge} from '../types';
import { IServerBridge } from '../types';
import basic from './basic/basic';
import packageAccess from './package/access';
@ -29,7 +29,7 @@ import middleware from './plugins/middleware';
import upLinkCache from './uplinks/cache';
import uplinkTimeout from './uplinks/timeout';
describe('functional test verdaccio', function() {
describe('functional test verdaccio', function () {
jest.setTimeout(20000);
// @ts-ignore
const server1: IServerBridge = global.__SERVERS__[0];
@ -65,13 +65,12 @@ describe('functional test verdaccio', function() {
adduser(server1);
logout(server1);
basic(server1, server2);
simpleSearch(server1, server2, app)
simpleSearch(server1, server2, app);
});
process.on('unhandledRejection', function(err) {
console.error("unhandledRejection", err);
process.nextTick(function() {
process.on('unhandledRejection', function (err) {
console.error('unhandledRejection', err);
process.nextTick(function () {
throw err;
});
});

View file

@ -1,12 +1,12 @@
import { yellow, green, blue, magenta } from 'kleur';
import path from 'path';
import NodeEnvironment from 'jest-environment-node';
import {VerdaccioConfig} from '../../lib/verdaccio-server';
import { VerdaccioConfig } from '../../lib/verdaccio-server';
import VerdaccioProcess from '../../lib/server_process';
import Server from '../../lib/server';
import ExpressServer from './simple_server';
import {IServerBridge} from '../../types';
import {DOMAIN_SERVERS, PORT_SERVER_1, PORT_SERVER_2, PORT_SERVER_3} from '../config.functional';
import { IServerBridge } from '../../types';
import { DOMAIN_SERVERS, PORT_SERVER_1, PORT_SERVER_2, PORT_SERVER_3 } from '../config.functional';
const EXPRESS_PORT = 55550;
@ -14,7 +14,7 @@ class FunctionalEnvironment extends NodeEnvironment {
public config: any;
public constructor(config: any) {
super(config)
super(config);
}
public async startWeb() {
@ -23,11 +23,12 @@ class FunctionalEnvironment extends NodeEnvironment {
return await express.start(EXPRESS_PORT);
}
public async setup() {
const SILENCE_LOG = !process.env.VERDACCIO_DEBUG;
// @ts-ignore
const DEBUG_INJECT: boolean = process.env.VERDACCIO_DEBUG_INJECT ? process.env.VERDACCIO_DEBUG_INJECT : false;
const DEBUG_INJECT: boolean = process.env.VERDACCIO_DEBUG_INJECT
? process.env.VERDACCIO_DEBUG_INJECT
: false;
const forkList: any[] = [];
const serverList: IServerBridge[] = [];
const pathStore = path.join(__dirname, '../store');
@ -58,7 +59,9 @@ class FunctionalEnvironment extends NodeEnvironment {
const verdaccioConfig = new VerdaccioConfig(
path.join(pathStore, config.storage),
path.join(pathStore, config.config),
`http://${DOMAIN_SERVERS}:${config.port}/`, config.port);
`http://${DOMAIN_SERVERS}:${config.port}/`,
config.port
);
console.log(magenta(`Running registry ${config.config} on port ${config.port}`));
const server: IServerBridge = new Server(verdaccioConfig.domainPath);
serverList.push(server);
@ -80,7 +83,7 @@ class FunctionalEnvironment extends NodeEnvironment {
console.log(yellow('Teardown Test Environment.'));
// @ts-ignore
if (!this.global.__SERVERS_PROCESS__) {
throw new Error("There are no servers to stop");
throw new Error('There are no servers to stop');
}
// shutdown verdaccio
@ -95,7 +98,7 @@ class FunctionalEnvironment extends NodeEnvironment {
// @ts-ignore
public runScript(script: string) {
// @ts-ignore
// @ts-ignore
return super.runScript(script);
}
}

View file

@ -1,3 +1,3 @@
module.exports = async function() {
module.exports = async function () {
// here we should create dynamically config files
};

View file

@ -26,9 +26,11 @@ export default class ExpressServer {
public start(port: number): Promise<ExpressServer> {
return new Promise((resolve) => {
this.app.use(bodyParser.json());
this.app.use(bodyParser.urlencoded({
extended: true
}));
this.app.use(
bodyParser.urlencoded({
extended: true
})
);
this.server = this.app.listen(port, () => resolve(this));
});

View file

@ -1,14 +1,20 @@
import crypto from 'crypto';
import crypto from 'crypto';
import fs from 'fs';
import path from 'path';
function generateSha(key) {
// @ts-ignore
return crypto.createHash('sha1', 'binary').update(key).digest('hex');
return (
crypto
// @ts-ignore
.createHash('sha1', 'binary')
.update(key)
.digest('hex')
);
}
function readFile(filePath) {
return fs.readFileSync(path.join(__dirname, `/${filePath}`));
}
export { generateSha, readFile }
export { generateSha, readFile };

View file

@ -1,30 +1,32 @@
import _ from 'lodash';
import {HEADERS} from '../../../src/lib/constants';
import {notify} from '../../../src/lib/notify';
import {DOMAIN_SERVERS, PORT_SERVER_APP} from '../config.functional';
import { HEADERS } from '../../../src/lib/constants';
import { notify } from '../../../src/lib/notify';
import { DOMAIN_SERVERS, PORT_SERVER_APP } from '../config.functional';
import { RemoteUser } from '@verdaccio/types';
export default function(express) {
export default function (express) {
const config = {
notify: {
method: 'POST',
headers: [{
'Content-Type': HEADERS.JSON
}],
headers: [
{
'Content-Type': HEADERS.JSON
}
],
endpoint: `http://${DOMAIN_SERVERS}:${PORT_SERVER_APP}/api/notify`,
// eslint-disable-next-line max-len
content: `{"color":"green","message":"New package published: * {{ name }}*. Publisher name: * {{ publisher.name }} *.","notify":true,"message_format":"text"}`
}
};
const publisherInfo: RemoteUser = {
name: "publisher-name-test",
name: 'publisher-name-test',
real_groups: [],
groups: []
};
describe('notifications', () => {
function parseBody(notification) {
const jsonBody = JSON.parse(notification);
@ -41,26 +43,30 @@ export default function(express) {
});
});
test('notification should be send', done => {
test('notification should be send', (done) => {
const metadata = {
name: "pkg-test"
name: 'pkg-test'
};
// @ts-ignore
notify(metadata, config, publisherInfo, 'foo').then(function (body) {
const jsonBody = parseBody(body);
expect(
`New package published: * ${metadata.name}*. Publisher name: * ${publisherInfo.name} *.`).toBe(jsonBody.message);
done();
}, function (err) {
expect(err).toBeDefined();
done();
});
notify(metadata, config, publisherInfo, 'foo').then(
function (body) {
const jsonBody = parseBody(body);
expect(
`New package published: * ${metadata.name}*. Publisher name: * ${publisherInfo.name} *.`
).toBe(jsonBody.message);
done();
},
function (err) {
expect(err).toBeDefined();
done();
}
);
});
test('notification should be send single header', done => {
test('notification should be send single header', (done) => {
const metadata = {
name: "pkg-test"
name: 'pkg-test'
};
const configMultipleHeader = _.cloneDeep(config);
@ -70,68 +76,81 @@ export default function(express) {
};
// @ts-ignore
notify(metadata, configMultipleHeader, publisherInfo).then(function (body) {
const jsonBody = parseBody(body);
expect(`New package published: * ${metadata.name}*. Publisher name: * ${publisherInfo.name} *.`).toBe(jsonBody.message);
done();
}, function (err) {
expect(err).toBeDefined();
done();
});
});
test('notification should be send multiple notifications endpoints', done => {
const metadata = {
name: "pkg-test"
};
// let notificationsCounter = 0;
const multipleNotificationsEndpoint = {
notify: []
};
for (let i = 0; i < 10; i++) {
const notificationSettings = _.cloneDeep(config.notify);
// basically we allow al notifications
// @ts-ignore
notificationSettings.packagePattern = /^pkg-test$/;
// notificationSettings.packagePatternFlags = 'i';
// @ts-ignore
multipleNotificationsEndpoint.notify.push(notificationSettings);
}
// @ts-ignore
notify(metadata, multipleNotificationsEndpoint, publisherInfo).then(function (body) {
console.log("--->body", body);
body.forEach(function(notification) {
const jsonBody = parseBody(notification);
expect(`New package published: * ${metadata.name}*. Publisher name: * ${publisherInfo.name} *.`).toBe(jsonBody.message);
});
notify(metadata, configMultipleHeader, publisherInfo).then(
function (body) {
const jsonBody = parseBody(body);
expect(
`New package published: * ${metadata.name}*. Publisher name: * ${publisherInfo.name} *.`
).toBe(jsonBody.message);
done();
}, function (err) {
},
function (err) {
expect(err).toBeDefined();
done();
});
});
}
);
});
test('notification should fails', done => {
test('notification should be send multiple notifications endpoints', (done) => {
const metadata = {
name: "pkg-test"
name: 'pkg-test'
};
// let notificationsCounter = 0;
const multipleNotificationsEndpoint = {
notify: []
};
for (let i = 0; i < 10; i++) {
const notificationSettings = _.cloneDeep(config.notify);
// basically we allow al notifications
// @ts-ignore
notificationSettings.packagePattern = /^pkg-test$/;
// notificationSettings.packagePatternFlags = 'i';
// @ts-ignore
multipleNotificationsEndpoint.notify.push(notificationSettings);
}
// @ts-ignore
notify(metadata, multipleNotificationsEndpoint, publisherInfo).then(
function (body) {
console.log('--->body', body);
body.forEach(function (notification) {
const jsonBody = parseBody(notification);
expect(
`New package published: * ${metadata.name}*. Publisher name: * ${publisherInfo.name} *.`
).toBe(jsonBody.message);
});
done();
},
function (err) {
expect(err).toBeDefined();
done();
}
);
});
test('notification should fails', (done) => {
const metadata = {
name: 'pkg-test'
};
const configFail = _.cloneDeep(config);
configFail.notify.endpoint = `http://${DOMAIN_SERVERS}:${PORT_SERVER_APP}/api/notify/bad`;
// @ts-ignore
notify(metadata, configFail, publisherInfo).then(function () {
expect(false).toBe('This service should fails with status code 400');
done();
}, function (err) {
expect(err).toEqual('bad response');
done();
});
notify(metadata, configFail, publisherInfo).then(
function () {
expect(false).toBe('This service should fails with status code 400');
done();
},
function (err) {
expect(err).toEqual('bad response');
done();
}
);
});
test('publisher property should not be overridden if it exists in metadata', done => {
test('publisher property should not be overridden if it exists in metadata', (done) => {
const metadata = {
name: 'pkg-test',
publisher: {
@ -141,17 +160,18 @@ export default function(express) {
// @ts-ignore
notify(metadata, config, publisherInfo).then(
function(body) {
function (body) {
const jsonBody = parseBody(body);
expect(`New package published: * ${metadata.name}*. Publisher name: * ${metadata.publisher.name} *.`).toBe(jsonBody.message);
expect(
`New package published: * ${metadata.name}*. Publisher name: * ${metadata.publisher.name} *.`
).toBe(jsonBody.message);
done();
},
function(err) {
function (err) {
expect(err).toBeDefined();
done();
}
);
});
});
}

View file

@ -1,13 +1,12 @@
import {buildToken} from "../../../src/lib/utils";
import {API_ERROR, HTTP_STATUS, TOKEN_BASIC} from "../../../src/lib/constants";
import {CREDENTIALS} from "../config.functional";
import { buildToken } from '../../../src/lib/utils';
import { API_ERROR, HTTP_STATUS, TOKEN_BASIC } from '../../../src/lib/constants';
import { CREDENTIALS } from '../config.functional';
import fixturePkg from '../fixtures/package';
export default function(server) {
export default function (server) {
describe('package access control', () => {
const buildAccesToken = (auth) => {
return buildToken(TOKEN_BASIC, `${(Buffer.from(auth).toString('base64'))}`);
return buildToken(TOKEN_BASIC, `${Buffer.from(auth).toString('base64')}`);
};
/**
@ -17,18 +16,16 @@ export default function(server) {
* @param status {boolean}
*/
function checkAccess(auth, pkg, status) {
test(
`${(status ? 'allows' : 'forbids')} access ${auth} to ${pkg}`, () => {
server.authstr = auth ? buildAccesToken(auth) : undefined;
const req = server.getPackage(pkg);
test(`${status ? 'allows' : 'forbids'} access ${auth} to ${pkg}`, () => {
server.authstr = auth ? buildAccesToken(auth) : undefined;
const req = server.getPackage(pkg);
if (status === HTTP_STATUS.NOT_FOUND) {
return req.status(HTTP_STATUS.NOT_FOUND).body_error(API_ERROR.NO_PACKAGE);
} else if (status === HTTP_STATUS.FORBIDDEN) {
return req.status(HTTP_STATUS.FORBIDDEN).body_error(API_ERROR.NOT_ALLOWED);
}
if (status === HTTP_STATUS.NOT_FOUND) {
return req.status(HTTP_STATUS.NOT_FOUND).body_error(API_ERROR.NO_PACKAGE);
} else if (status === HTTP_STATUS.FORBIDDEN) {
return req.status(HTTP_STATUS.FORBIDDEN).body_error(API_ERROR.NOT_ALLOWED);
}
);
});
}
/**
@ -38,7 +35,7 @@ export default function(server) {
* @param status {boolean}
*/
function checkPublish(auth, pkg, status) {
test(`${(status ? 'allows' : 'forbids')} publish ${auth} to ${pkg}`, () => {
test(`${status ? 'allows' : 'forbids'} publish ${auth} to ${pkg}`, () => {
server.authstr = auth ? buildAccesToken(auth) : undefined;
const req = server.putPackage(pkg, fixturePkg(pkg));
if (status === HTTP_STATUS.NOT_FOUND) {

View file

@ -1,21 +1,23 @@
/* eslint-disable jest/no-standalone-expect */
import zlib from 'zlib';
import {readFile} from '../lib/test.utils';
import {HEADER_TYPE, HEADERS, HTTP_STATUS, CHARACTER_ENCODING} from "../../../src/lib/constants";
import { readFile } from '../lib/test.utils';
import { HEADER_TYPE, HEADERS, HTTP_STATUS, CHARACTER_ENCODING } from '../../../src/lib/constants';
export default function(server, express) {
export default function (server, express) {
const PKG_NAME = 'testexp_gzip';
const PKG_VERSION = '0.0.1';
const PKG_BAD_DATA = 'testexp_baddata';
const VERSION_TOTAL = 4;
describe('test gzip support', () => {
beforeAll(function() {
express.get(`/${PKG_NAME}`, function(req, res) {
const pkg = JSON.parse(readFile('../fixtures/publish.json5')
.toString(CHARACTER_ENCODING.UTF8)
.replace(/__NAME__/g, PKG_NAME)
.replace(/__VERSION__/g, PKG_VERSION));
beforeAll(function () {
express.get(`/${PKG_NAME}`, function (req, res) {
const pkg = JSON.parse(
readFile('../fixtures/publish.json5')
.toString(CHARACTER_ENCODING.UTF8)
.replace(/__NAME__/g, PKG_NAME)
.replace(/__VERSION__/g, PKG_VERSION)
);
// overcoming compress threshold
for (let i = 1; i <= VERSION_TOTAL; i++) {
@ -30,7 +32,7 @@ export default function(server, express) {
});
});
express.get(`/${PKG_BAD_DATA}`, function(req, res) {
express.get(`/${PKG_BAD_DATA}`, function (req, res) {
expect(req).toBeDefined();
expect(res).toBeDefined();
expect(req.headers[HEADER_TYPE.ACCEPT_ENCODING]).toBe(HEADERS.GZIP);
@ -44,37 +46,41 @@ export default function(server, express) {
});
test('should understand non gzipped data from uplink', () => {
return server.getPackage(PKG_NAME)
return server
.getPackage(PKG_NAME)
.status(HTTP_STATUS.OK)
.response((res) => {
expect(res.headers[HEADER_TYPE.CONTENT_ENCODING]).toBeUndefined();
}).then(body => {
})
.then((body) => {
expect(body.name).toBe(PKG_NAME);
expect(Object.keys(body.versions)).toHaveLength(VERSION_TOTAL);
});
});
test('should serve gzipped data', () => {
return server.request({
uri: `/${PKG_NAME}`,
encoding: null,
headers: {
[HEADER_TYPE.ACCEPT_ENCODING]: HEADERS.GZIP,
},
json: false,
}).status(HTTP_STATUS.OK)
.response(function(res) {
return server
.request({
uri: `/${PKG_NAME}`,
encoding: null,
headers: {
[HEADER_TYPE.ACCEPT_ENCODING]: HEADERS.GZIP
},
json: false
})
.status(HTTP_STATUS.OK)
.response(function (res) {
expect(res.headers[HEADER_TYPE.CONTENT_ENCODING]).toBe(HEADERS.GZIP);
})
.then(async function(body) {
.then(async function (body) {
// should fails since is zipped
expect(function() {
expect(function () {
JSON.parse(body.toString(CHARACTER_ENCODING.UTF8));
}).toThrow(/Unexpected/);
// we unzip content and check content
await new Promise(function(resolve) {
zlib.gunzip(body, function(err, buffer) {
await new Promise(function (resolve) {
zlib.gunzip(body, function (err, buffer) {
expect(err).toBeNull();
expect(buffer).not.toBeNull();
const unzipedBody = JSON.parse(buffer.toString());

View file

@ -1,32 +1,35 @@
import {HEADERS, HTTP_STATUS} from '../../../src/lib/constants';
import {DOMAIN_SERVERS, PORT_SERVER_1, PORT_SERVER_2} from '../config.functional';
import {generateSha} from '../lib/test.utils';
import {DIST_TAGS} from "../../../src/lib/constants";
import { HEADERS, HTTP_STATUS } from '../../../src/lib/constants';
import { DOMAIN_SERVERS, PORT_SERVER_1, PORT_SERVER_2 } from '../config.functional';
import { generateSha } from '../lib/test.utils';
import { DIST_TAGS } from '../../../src/lib/constants';
export default function(server, server2) {
export default function (server, server2) {
const SCOPE = '@test/scoped';
const PKG_VERSION = '1.0.0';
const PKG_NAME = 'scoped';
describe('test-scoped', () => {
beforeAll(function() {
return server.request({
uri: '/@test%2fscoped',
headers: {
'content-type': HEADERS.JSON,
},
method: 'PUT',
json: require('./scoped.json'),
}).status(HTTP_STATUS.CREATED);
beforeAll(function () {
return server
.request({
uri: '/@test%2fscoped',
headers: {
'content-type': HEADERS.JSON
},
method: 'PUT',
json: require('./scoped.json')
})
.status(HTTP_STATUS.CREATED);
});
test('should publish scope package', () => {});
describe('should get scoped packages tarball', () => {
const uploadScopedTarBall = (server) => {
return server.getTarball(SCOPE, `${PKG_NAME}-${PKG_VERSION}.tgz`)
return server
.getTarball(SCOPE, `${PKG_NAME}-${PKG_VERSION}.tgz`)
.status(HTTP_STATUS.OK)
.then(function(body) {
.then(function (body) {
// not real sha due to utf8 conversion
expect(generateSha(body)).toEqual('6e67b14e2c0e450b942e2bc8086b49e90f594790');
});
@ -39,19 +42,21 @@ export default function(server, server2) {
test('should be a scoped tarball from server2', () => {
return uploadScopedTarBall(server2);
});
});
describe('should retrieve scoped packages', () => {
const testScopePackage = (server, port) => server.getPackage(SCOPE)
.status(HTTP_STATUS.OK)
.then(function(body) {
expect(body.name).toBe(SCOPE);
expect(body.versions[PKG_VERSION].name).toBe(SCOPE);
expect(body.versions[PKG_VERSION].dist.tarball).toBe(
`http://${DOMAIN_SERVERS}:${port}/@test%2fscoped/-/${PKG_NAME}-${PKG_VERSION}.tgz`);
expect(body[DIST_TAGS]).toEqual({latest: PKG_VERSION});
});
const testScopePackage = (server, port) =>
server
.getPackage(SCOPE)
.status(HTTP_STATUS.OK)
.then(function (body) {
expect(body.name).toBe(SCOPE);
expect(body.versions[PKG_VERSION].name).toBe(SCOPE);
expect(body.versions[PKG_VERSION].dist.tarball).toBe(
`http://${DOMAIN_SERVERS}:${port}/@test%2fscoped/-/${PKG_NAME}-${PKG_VERSION}.tgz`
);
expect(body[DIST_TAGS]).toEqual({ latest: PKG_VERSION });
});
test('scoped package on server1', () => testScopePackage(server, PORT_SERVER_1));
test('scoped package on server2', () => testScopePackage(server2, PORT_SERVER_2));
@ -59,13 +64,16 @@ export default function(server, server2) {
describe('should retrieve a scoped packages under nginx', () => {
test('should work nginx workaround', () => {
return server2.request({
uri: '/@test/scoped/1.0.0'
}).status(HTTP_STATUS.OK)
.then(function(body) {
return server2
.request({
uri: '/@test/scoped/1.0.0'
})
.status(HTTP_STATUS.OK)
.then(function (body) {
expect(body.name).toEqual(SCOPE);
expect(body.dist.tarball).toEqual(
`http://${DOMAIN_SERVERS}:${PORT_SERVER_2}/@test%2fscoped/-/${PKG_NAME}-${PKG_VERSION}.tgz`);
`http://${DOMAIN_SERVERS}:${PORT_SERVER_2}/@test%2fscoped/-/${PKG_NAME}-${PKG_VERSION}.tgz`
);
});
});
});

View file

@ -1,11 +1,10 @@
import async from 'async';
import { HTTP_STATUS } from "../../../src/lib/constants";
import { HTTP_STATUS } from '../../../src/lib/constants';
let okTotalSum = 0;
import racePkg from '../fixtures/package';
export default function(server) {
import racePkg from '../fixtures/package';
export default function (server) {
describe('should test race condition on publish packages', () => {
const MAX_COUNT = 20;
const PKG_NAME = 'race';
@ -14,14 +13,15 @@ export default function(server) {
const UNAVAILABLE = 'unavailable';
beforeAll(function () {
return server.putPackage(PKG_NAME, racePkg(PKG_NAME))
return server
.putPackage(PKG_NAME, racePkg(PKG_NAME))
.status(HTTP_STATUS.CREATED)
.body_ok(/created new package/);
});
test('creating new package', () => {});
test('should uploading 10 same versions and ignore 9', callback => {
test('should uploading 10 same versions and ignore 9', (callback) => {
let listOfRequest = [];
for (let i = 0; i < MAX_COUNT; i++) {
// @ts-ignore
@ -30,11 +30,14 @@ export default function(server) {
data.rand = Math.random();
let _res;
server.putVersion(PKG_NAME, '0.0.1', data).response(function (res) {
_res = res;
}).then(function (body) {
callback(null, [_res, body]);
});
server
.putVersion(PKG_NAME, '0.0.1', data)
.response(function (res) {
_res = res;
})
.then(function (body) {
callback(null, [_res, body]);
});
});
}
@ -57,7 +60,10 @@ export default function(server) {
failCount++;
}
if (resp.statusCode === HTTP_STATUS.SERVICE_UNAVAILABLE && ~body.error.indexOf(UNAVAILABLE)) {
if (
resp.statusCode === HTTP_STATUS.SERVICE_UNAVAILABLE &&
~body.error.indexOf(UNAVAILABLE)
) {
failCount++;
}
});
@ -71,14 +77,15 @@ export default function(server) {
});
});
test('shoul uploading 10 diff versions and accept 10', callback => {
test('shoul uploading 10 diff versions and accept 10', (callback) => {
const listofRequest = [];
for (let i = 0; i < MAX_COUNT; i++) {
// @ts-ignore
listofRequest.push(function (callback) {
let _res;
server.putVersion(PKG_NAME, '0.1.' + String(i), racePkg(PKG_NAME))
server
.putVersion(PKG_NAME, '0.1.' + String(i), racePkg(PKG_NAME))
.response(function (res) {
_res = res;
})
@ -104,7 +111,10 @@ export default function(server) {
if (response.statusCode === HTTP_STATUS.CONFLICT && ~body.error.indexOf(PRESENT)) {
failcount++;
}
if (response.statusCode === HTTP_STATUS.SERVICE_UNAVAILABLE && ~body.error.indexOf(UNAVAILABLE)) {
if (
response.statusCode === HTTP_STATUS.SERVICE_UNAVAILABLE &&
~body.error.indexOf(UNAVAILABLE)
) {
failcount++;
}
});
@ -120,10 +130,14 @@ export default function(server) {
});
});
afterAll(function() {
return server.getPackage(PKG_NAME).status(HTTP_STATUS.OK).then(function (body) {
expect(Object.keys(body.versions)).toHaveLength(okTotalSum);
});
afterAll(function () {
return server
.getPackage(PKG_NAME)
.status(HTTP_STATUS.OK)
.then(function (body) {
// eslint-disable-next-line jest/no-standalone-expect
expect(Object.keys(body.versions)).toHaveLength(okTotalSum);
});
});
});
}

View file

@ -1,6 +1,6 @@
import {HTTP_STATUS, API_ERROR} from "../../../src/lib/constants";
import { HTTP_STATUS, API_ERROR } from '../../../src/lib/constants';
export default function(server2) {
export default function (server2) {
// credentials
const USER1 = 'authtest';
const USER2 = 'authtest2';
@ -13,39 +13,48 @@ export default function(server2) {
const UNEXISTING_PKG_NAME = 'test-auth-allow';
const requestAuthFail = (user, pass, message, statusCode) => {
return server2.auth(user, pass)
return server2
.auth(user, pass)
.status(statusCode)
.body_error(message)
.then(function() {
.then(function () {
return server2.whoami();
})
.then(function(username) {
.then(function (username) {
expect(username).toBeUndefined();
});
};
const requestAuthOk = (user, pass, regex, statusCode) => {
return server2.auth(user, pass)
return server2
.auth(user, pass)
.status(statusCode)
.body_ok(regex)
.then(function() {
.then(function () {
return server2.whoami();
})
.then(function(username) {
.then(function (username) {
expect(username).toBe(user);
});
};
describe('plugin authentication', () => {
describe('test users authentication', () => {
test('should not authenticate user1 with wrong password', () => {
return requestAuthFail(USER1, WRONG_PASSWORD, 'i don\'t like your password', HTTP_STATUS.UNAUTHORIZED);
return requestAuthFail(
USER1,
WRONG_PASSWORD,
"i don't like your password",
HTTP_STATUS.UNAUTHORIZED
);
});
test('should not authenticate user2 with wrong password', () => {
return requestAuthFail(USER2, WRONG_PASSWORD, 'i don\'t like your password', HTTP_STATUS.UNAUTHORIZED);
return requestAuthFail(
USER2,
WRONG_PASSWORD,
"i don't like your password",
HTTP_STATUS.UNAUTHORIZED
);
});
test('should right user2 password handled by plugin', () => {
@ -55,63 +64,68 @@ export default function(server2) {
test('should right user1 password handled by plugin', () => {
return requestAuthOk(USER1, CORRECT_PASSWORD, new RegExp(USER1), HTTP_STATUS.CREATED);
});
});
describe('test package access authorization', () => {
describe(`access with user ${USER1} on server2`, () => {
beforeAll(function() {
return server2.auth(USER1, CORRECT_PASSWORD)
beforeAll(function () {
return server2
.auth(USER1, CORRECT_PASSWORD)
.status(HTTP_STATUS.CREATED)
.body_ok(new RegExp(USER1));
});
test(`should fails (404) on access ${UNEXISTING_PKG_NAME}`, () => {
return server2.getPackage(UNEXISTING_PKG_NAME)
return server2
.getPackage(UNEXISTING_PKG_NAME)
.status(HTTP_STATUS.NOT_FOUND)
.body_error(API_ERROR.NO_PACKAGE);
});
test(`should fails (403) access ${ONLY_ACCESS_BY_USER_2}`, () => {
return server2.getPackage(ONLY_ACCESS_BY_USER_2)
return server2
.getPackage(ONLY_ACCESS_BY_USER_2)
.status(HTTP_STATUS.FORBIDDEN)
.body_error(API_ERROR.NOT_ALLOWED);
});
test(`should fails (404) access ${AUTH_PKG_ACCESS_NAME}`, () => {
return server2.getPackage(AUTH_PKG_ACCESS_NAME)
return server2
.getPackage(AUTH_PKG_ACCESS_NAME)
.status(HTTP_STATUS.NOT_FOUND)
.body_error(API_ERROR.NO_PACKAGE);
});
});
describe(`access with user ${USER2} on server2`, () => {
beforeAll(function() {
return server2.auth(USER2, CORRECT_PASSWORD)
beforeAll(function () {
return server2
.auth(USER2, CORRECT_PASSWORD)
.status(HTTP_STATUS.CREATED)
.body_ok(new RegExp(USER2));
});
test(`should fails (403) on access ${UNEXISTING_PKG_NAME}`, () => {
return server2.getPackage(UNEXISTING_PKG_NAME)
return server2
.getPackage(UNEXISTING_PKG_NAME)
.status(HTTP_STATUS.FORBIDDEN)
.body_error(API_ERROR.NOT_ALLOWED);
});
test(`should fails (403) on access ${DENY_PKG_NAME}`, () => {
return server2.getPackage(DENY_PKG_NAME)
return server2
.getPackage(DENY_PKG_NAME)
.status(HTTP_STATUS.FORBIDDEN)
.body_error(API_ERROR.NOT_ALLOWED);
});
test(`should fails (404) access ${AUTH_PKG_ACCESS_NAME}`, () => {
return server2.getPackage(AUTH_PKG_ACCESS_NAME)
return server2
.getPackage(AUTH_PKG_ACCESS_NAME)
.status(HTTP_STATUS.NOT_FOUND)
.body_error(API_ERROR.NO_PACKAGE);
});
});
});
});
}

View file

@ -1,23 +1,25 @@
import {HTTP_STATUS} from "../../../src/lib/constants";
import { HTTP_STATUS } from '../../../src/lib/constants';
export default function (server2) {
describe('test plugin middlewares', () => {
test('should serve the registered route ES5', () => {
return server2.request({
uri: '/test/route',
method: 'GET'
})
return server2
.request({
uri: '/test/route',
method: 'GET'
})
.status(HTTP_STATUS.OK)
.body_ok('this is a custom route')
})
.body_ok('this is a custom route');
});
test('should serve the registered route ES6', () => {
return server2.request({
uri: '/test/route/es6',
method: 'GET'
})
return server2
.request({
uri: '/test/route/es6',
method: 'GET'
})
.status(HTTP_STATUS.OK)
.body_ok('this is a custom route es6')
})
})
.body_ok('this is a custom route es6');
});
});
}

View file

@ -1,36 +1,34 @@
import {DEFAULT_NO_README, HTTP_STATUS} from '../../../src/lib/constants';
import { DEFAULT_NO_README, HTTP_STATUS } from '../../../src/lib/constants';
import pkgReadmeJSON from './pkg-readme.json';
import pkgNoReadmeJSON from './pkg-no-readme.json';
import pkgNoReadmeJSONOldFormat from './pkg-readme-npm6.json';
export default function (server, server2) {
describe('should test readme', () => {
const README_PKG1 = 'readme-test';
const README_PKG2 = 'readme-test-no-readme';
const README_MESSAGE = 'this is a readme';
const README_PKG3 = 'readme-test-npm6';
beforeAll(async function() {
await server.putPackage('readme-test', pkgReadmeJSON)
.status(HTTP_STATUS.CREATED);
await server.putPackage(README_PKG2, pkgNoReadmeJSON)
.status(HTTP_STATUS.CREATED);
await server.putPackage(README_PKG3, pkgNoReadmeJSONOldFormat)
.status(HTTP_STATUS.CREATED);
beforeAll(async function () {
await server.putPackage('readme-test', pkgReadmeJSON).status(HTTP_STATUS.CREATED);
await server.putPackage(README_PKG2, pkgNoReadmeJSON).status(HTTP_STATUS.CREATED);
await server.putPackage(README_PKG3, pkgNoReadmeJSONOldFormat).status(HTTP_STATUS.CREATED);
});
test('add pkg', () => {});
describe('should check readme file', () => {
const matchReadme = (serverRef, pkgName = README_PKG1, readmeMessage = README_MESSAGE) => {
return serverRef.request({
uri: `/-/verdaccio/package/readme/${pkgName}`
}).status(HTTP_STATUS.OK).then(function(body) {
expect(body).toEqual(`<p>${readmeMessage}</p>`);
});
return serverRef
.request({
uri: `/-/verdaccio/package/readme/${pkgName}`
})
.status(HTTP_STATUS.OK)
.then(function (body) {
expect(body).toEqual(`<p>${readmeMessage}</p>`);
});
};
test('should fetch server2 over uplink server1', () => {
@ -52,8 +50,6 @@ export default function (server, server2) {
test('should fetch not found readme package on local server1', () => {
return matchReadme(server2, README_PKG2, DEFAULT_NO_README);
});
});
});
}

View file

@ -1,33 +1,32 @@
import {API_ERROR, HEADER_TYPE, HTTP_STATUS} from '../../../src/lib/constants';
import {DOMAIN_SERVERS, PORT_SERVER_APP} from '../config.functional';
import { API_ERROR, HEADER_TYPE, HTTP_STATUS } from '../../../src/lib/constants';
import { DOMAIN_SERVERS, PORT_SERVER_APP } from '../config.functional';
const defaultPkg = {
'name': 'testexp-incomplete',
'versions': {
name: 'testexp-incomplete',
versions: {
'0.1.0': {
'name': 'testexp_tags',
'version': '0.1.0',
'dist': {
'shasum': 'fake',
'tarball': `http://${DOMAIN_SERVERS}:${PORT_SERVER_APP}/testexp-incomplete/-/content-length.tar.gz`,
},
name: 'testexp_tags',
version: '0.1.0',
dist: {
shasum: 'fake',
tarball: `http://${DOMAIN_SERVERS}:${PORT_SERVER_APP}/testexp-incomplete/-/content-length.tar.gz`
}
},
'0.1.1': {
'name': 'testexp_tags',
'version': '0.1.1',
'dist': {
'shasum': 'fake',
'tarball': `http://${DOMAIN_SERVERS}:${PORT_SERVER_APP}/testexp-incomplete/-/chunked.tar.gz`,
},
},
},
name: 'testexp_tags',
version: '0.1.1',
dist: {
shasum: 'fake',
tarball: `http://${DOMAIN_SERVERS}:${PORT_SERVER_APP}/testexp-incomplete/-/chunked.tar.gz`
}
}
}
};
export default function (server, express) {
const listofCalls = [HEADER_TYPE.CONTENT_LENGTH, 'chunked'];
describe('test send incomplete packages', () => {
beforeAll(function () {
express.get('/testexp-incomplete', function (_, res) {
res.send(defaultPkg);
@ -35,7 +34,7 @@ export default function (server, express) {
});
listofCalls.forEach((type) => {
test(`should not store tarballs / ${type}`, callback => {
test(`should not store tarballs / ${type}`, (callback) => {
let called;
express.get(`/testexp-incomplete/-/${type}.tar.gz`, function (_, response) {
if (called) {
@ -56,18 +55,21 @@ export default function (server, express) {
}, 10);
});
server.request({uri: '/testexp-incomplete/-/' + type + '.tar.gz'})
server
.request({ uri: '/testexp-incomplete/-/' + type + '.tar.gz' })
.status(HTTP_STATUS.OK)
.response(function (res) {
if (type !== 'chunked') {
expect(parseInt(res.headers[HEADER_TYPE.CONTENT_LENGTH], 10)).toBe(1e6);
}
}).then(function (body) {
})
.then(function (body) {
expect(body).toMatch(/test test test/);
});
function cb() {
server.request({uri: '/testexp-incomplete/-/' + type + '.tar.gz'})
server
.request({ uri: '/testexp-incomplete/-/' + type + '.tar.gz' })
.body_error(API_ERROR.INTERNAL_SERVER_ERROR)
.then(function () {
callback();

View file

@ -1,15 +1,16 @@
import {readFile} from '../lib/test.utils';
import {API_MESSAGE, HTTP_STATUS} from "../../../src/lib/constants";
import generatePkg from '../fixtures/package';
import {TARBALL} from '../config.functional';
import { readFile } from '../lib/test.utils';
import { API_MESSAGE, HTTP_STATUS } from '../../../src/lib/constants';
import generatePkg from '../fixtures/package';
import { TARBALL } from '../config.functional';
const getBinary = () => readFile('../fixtures/binary');
const getBinary = () => readFile('../fixtures/binary');
export default function (server, server2) {
describe('anti-loop testing', () => {
test('testing anti-loop', () => {
return server2.getPackage('testloop').status(HTTP_STATUS.NOT_FOUND)
return server2
.getPackage('testloop')
.status(HTTP_STATUS.NOT_FOUND)
.body_error(/no such package/);
});
});
@ -23,7 +24,8 @@ export default function (server, server2) {
describe(`testing mirror for ${pkg}`, () => {
beforeAll(function () {
return server2.putPackage(pkg, generatePkg(pkg))
return server2
.putPackage(pkg, generatePkg(pkg))
.status(HTTP_STATUS.CREATED)
.body_ok(API_MESSAGE.PKG_CREATED);
});
@ -32,7 +34,8 @@ export default function (server, server2) {
describe(`${pkg}`, () => {
beforeAll(function () {
return server2.putVersion(pkg, '0.1.1', generatePkg(pkg))
return server2
.putVersion(pkg, '0.1.1', generatePkg(pkg))
.status(HTTP_STATUS.CREATED)
.body_ok(/published/);
});
@ -45,7 +48,8 @@ export default function (server, server2) {
describe('should put a tarball', () => {
beforeAll(function () {
return server2.putTarball(pkg, TARBALL, getBinary())
return server2
.putTarball(pkg, TARBALL, getBinary())
.status(HTTP_STATUS.CREATED)
.body_ok(/.*/);
});
@ -53,7 +57,8 @@ export default function (server, server2) {
test(`should ${prefix} uploading new tarball`, () => {});
test(`should ${prefix} downloading tarball from server2`, () => {
return server2.getTarball(pkg, TARBALL)
return server2
.getTarball(pkg, TARBALL)
.status(HTTP_STATUS.OK)
.then(function (body) {
expect(body).toEqual(getBinary());
@ -65,7 +70,8 @@ export default function (server, server2) {
});
test(`should ${prefix} downloading tarball from server1`, () => {
return server.getTarball(pkg, TARBALL)
return server
.getTarball(pkg, TARBALL)
.status(HTTP_STATUS.OK)
.then(function (body) {
expect(body).toEqual(getBinary());

View file

@ -1,72 +1,86 @@
import {readFile} from '../lib/test.utils';
import {createTarballHash} from "../../../src/lib/crypto-utils";
import {API_ERROR, HTTP_STATUS} from "../../../src/lib/constants";
import {DOMAIN_SERVERS, PORT_SERVER_1, TARBALL} from '../config.functional';
import generatePkg from '../fixtures/package';
import {DIST_TAGS} from '../../../src/lib/constants';
import { readFile } from '../lib/test.utils';
import { createTarballHash } from '../../../src/lib/crypto-utils';
import { API_ERROR, HTTP_STATUS } from '../../../src/lib/constants';
import { DOMAIN_SERVERS, PORT_SERVER_1, TARBALL } from '../config.functional';
import generatePkg from '../fixtures/package';
import { DIST_TAGS } from '../../../src/lib/constants';
function getBinary() {
return readFile('../fixtures/binary');
}
export default function (server, server2) {
const PKG_NAME = 'test-nullstorage2';
const PKG_VERSION = '0.0.1';
// const TARBALL = `${PKG_NAME}-file.name`;
describe('should test a scenario when tarball is being fetch from uplink', () => {
describe(`should check whether ${PKG_NAME} is on server1`, () => {
test('should fails on fetch non-existent package on server1', () => {
return server.getPackage('test-nullstorage-nonexist').status(HTTP_STATUS.NOT_FOUND)
return server
.getPackage('test-nullstorage-nonexist')
.status(HTTP_STATUS.NOT_FOUND)
.body_error(API_ERROR.NO_PACKAGE);
});
});
describe(`should check whether ${PKG_NAME} is on server2`, () => {
beforeAll(function() {
beforeAll(function () {
return server2.addPackage(PKG_NAME);
});
test('should create a new package on server2', () => {/* test for before() */});
test('should create a new package on server2', () => {
/* test for before() */
});
test('should fails on download a non existent tarball from server1', () => {
return server.getTarball(PKG_NAME, TARBALL)
return server
.getTarball(PKG_NAME, TARBALL)
.status(HTTP_STATUS.NOT_FOUND)
.body_error(/no such file/);
});
describe(`should succesfully publish ${PKG_NAME} package on server2`, () => {
beforeAll(function() {
return server2.putTarball(PKG_NAME, TARBALL, getBinary()).status(HTTP_STATUS.CREATED).body_ok(/.*/);
beforeAll(function () {
return server2
.putTarball(PKG_NAME, TARBALL, getBinary())
.status(HTTP_STATUS.CREATED)
.body_ok(/.*/);
});
beforeAll(function() {
beforeAll(function () {
let pkg = generatePkg(PKG_NAME);
pkg.dist.shasum = createTarballHash().update(getBinary()).digest('hex');
return server2.putVersion(PKG_NAME, PKG_VERSION, pkg)
.status(HTTP_STATUS.CREATED).body_ok(/published/);
return server2
.putVersion(PKG_NAME, PKG_VERSION, pkg)
.status(HTTP_STATUS.CREATED)
.body_ok(/published/);
});
test(`should publish a new version for ${PKG_NAME} on server 2`, () => {/* test for before() */});
test(`should publish a new version for ${PKG_NAME} on server 2`, () => {
/* test for before() */
});
test(`should fetch the newly created published tarball for ${PKG_NAME} from server1 on server2`, () => {
return server.getTarball(PKG_NAME, TARBALL)
return server
.getTarball(PKG_NAME, TARBALL)
.status(HTTP_STATUS.OK)
.then(function(body) {
.then(function (body) {
expect(body).toEqual(getBinary());
});
});
test(`should fetch metadata for ${PKG_NAME} match from server1`, () => {
return server.getPackage(PKG_NAME)
return server
.getPackage(PKG_NAME)
.status(HTTP_STATUS.OK)
.then(function(body) {
.then(function (body) {
expect(body.name).toBe(PKG_NAME);
expect(body.versions[PKG_VERSION].name).toBe(PKG_NAME);
expect(body.versions[PKG_VERSION].dist.tarball).toBe(`http://${DOMAIN_SERVERS}:${PORT_SERVER_1}/${PKG_NAME}/-/${TARBALL}`);
expect(body[DIST_TAGS]).toEqual({latest: PKG_VERSION});
expect(body.versions[PKG_VERSION].dist.tarball).toBe(
`http://${DOMAIN_SERVERS}:${PORT_SERVER_1}/${PKG_NAME}/-/${TARBALL}`
);
expect(body[DIST_TAGS]).toEqual({ latest: PKG_VERSION });
});
});
});

View file

@ -1,38 +1,37 @@
import {DOMAIN_SERVERS, PORT_SERVER_APP} from '../config.functional';
import {API_ERROR, HEADER_TYPE, HTTP_STATUS} from '../../../src/lib/constants';
export default function(server, express) {
import { DOMAIN_SERVERS, PORT_SERVER_APP } from '../config.functional';
import { API_ERROR, HEADER_TYPE, HTTP_STATUS } from '../../../src/lib/constants';
export default function (server, express) {
describe('shoul test for unexpected client hangs', () => {
let handleResponseTarball;
beforeAll(function() {
express.get('/testexp-racycrash', function(request, response) {
beforeAll(function () {
express.get('/testexp-racycrash', function (request, response) {
response.send({
'name': 'testexp-racycrash',
'versions': {
name: 'testexp-racycrash',
versions: {
'0.1.0': {
'name': 'testexp_tags',
'version': '0.1.0',
'dist': {
'shasum': 'fake',
'tarball': `http://${DOMAIN_SERVERS}:${PORT_SERVER_APP}/testexp-racycrash/-/test.tar.gz`,
},
},
},
name: 'testexp_tags',
version: '0.1.0',
dist: {
shasum: 'fake',
tarball: `http://${DOMAIN_SERVERS}:${PORT_SERVER_APP}/testexp-racycrash/-/test.tar.gz`
}
}
}
});
});
express.get('/testexp-racycrash/-/test.tar.gz', function(request, response) {
express.get('/testexp-racycrash/-/test.tar.gz', function (request, response) {
handleResponseTarball(response);
});
});
test('should not crash on error if client disconnects', callback => {
handleResponseTarball = function(res) {
test('should not crash on error if client disconnects', (callback) => {
handleResponseTarball = function (res) {
res.header(HEADER_TYPE.CONTENT_LENGTH, 1e6);
res.write('test test test');
setTimeout(function() {
setTimeout(function () {
res.write('-');
// destroy the connection
res.socket.destroy();
@ -40,27 +39,28 @@ export default function(server, express) {
}, HTTP_STATUS.OK);
};
server.request({uri: '/testexp-racycrash/-/test.tar.gz'})
.then(function(body) {
expect(body).toEqual('test test test');
});
server.request({ uri: '/testexp-racycrash/-/test.tar.gz' }).then(function (body) {
expect(body).toEqual('test test test');
});
function cb() {
// test for NOT crashing
server.request({uri: '/testexp-racycrash'})
server
.request({ uri: '/testexp-racycrash' })
.status(HTTP_STATUS.OK)
.then(function() {
.then(function () {
callback();
});
}
});
test('should not store tarball', () => {
handleResponseTarball = function(res) {
handleResponseTarball = function (res) {
res.socket.destroy();
};
return server.request({uri: '/testexp-racycrash/-/test.tar.gz'})
return server
.request({ uri: '/testexp-racycrash/-/test.tar.gz' })
.body_error(API_ERROR.INTERNAL_SERVER_ERROR);
});
});

View file

@ -1,67 +1,76 @@
import _ from 'lodash';
import {HTTP_STATUS} from '../../../src/lib/constants';
export default function(server) {
import { HTTP_STATUS } from '../../../src/lib/constants';
export default function (server) {
describe('should test security on endpoints', () => {
beforeAll(function () {
return server.addPackage('testpkg-sec');
});
test('should fails on fetch bad pkg #1', () => {
return server.getPackage('__proto__')
return server
.getPackage('__proto__')
.status(HTTP_STATUS.FORBIDDEN)
.body_error(/invalid package/);
});
test('should fails on fetch bad pkg #2', () => {
return server.getPackage('__proto__')
return server
.getPackage('__proto__')
.status(HTTP_STATUS.FORBIDDEN)
.body_error(/invalid package/);
});
test('should do not fails on __proto__, connect stuff', () => {
return server.request({uri: '/testpkg-sec?__proto__=1'})
.then(function (body) {
// test for NOT outputting stack trace
expect(_.isNil(body) || _.isObject(body) || body.indexOf('node_modules')).toBeTruthy();
return server.request({ uri: '/testpkg-sec?__proto__=1' }).then(function (body) {
// test for NOT outputting stack trace
expect(_.isNil(body) || _.isObject(body) || body.indexOf('node_modules')).toBeTruthy();
// test for NOT crashing
return server.request({uri: '/testpkg-sec'}).status(HTTP_STATUS.OK);
});
// test for NOT crashing
return server.request({ uri: '/testpkg-sec' }).status(HTTP_STATUS.OK);
});
});
test('should fails and do not return __proto__ as an attachment', () => {
return server.request({uri: '/testpkg-sec/-/__proto__'})
return server
.request({ uri: '/testpkg-sec/-/__proto__' })
.status(HTTP_STATUS.FORBIDDEN)
.body_error(/invalid filename/);
});
test('should fails on fetch silly things - reading #1', () => {
return server.request({uri: '/testpkg-sec/-/../../../../../../../../etc/passwd'})
return server
.request({ uri: '/testpkg-sec/-/../../../../../../../../etc/passwd' })
.status(HTTP_STATUS.NOT_FOUND);
});
test('should fails on fetch silly things - reading #2', () => {
return server.request({uri: '/testpkg-sec/-/%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd'})
return server
.request({
uri:
'/testpkg-sec/-/%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd'
})
.status(HTTP_STATUS.FORBIDDEN)
.body_error(/invalid filename/);
});
test('should fails on fetch silly things - writing #1', () => {
return server.putTarball('testpkg-sec', '__proto__', '{}')
return server
.putTarball('testpkg-sec', '__proto__', '{}')
.status(HTTP_STATUS.FORBIDDEN)
.body_error(/invalid filename/);
});
test('should fails on fetch silly things - writing #3', () => {
return server.putTarball('testpkg-sec', 'node_modules', '{}')
return server
.putTarball('testpkg-sec', 'node_modules', '{}')
.status(HTTP_STATUS.FORBIDDEN)
.body_error(/invalid filename/);
});
test('should fails on fetch silly things - writing #4', () => {
return server.putTarball('testpkg-sec', '../testpkg.tgz', '{}')
return server
.putTarball('testpkg-sec', '../testpkg.tgz', '{}')
.status(HTTP_STATUS.FORBIDDEN)
.body_error(/invalid filename/);
});

View file

@ -1,12 +1,12 @@
import fs from 'fs';
import path from 'path';
import {TARBALL} from '../config.functional';
import {HTTP_STATUS} from "../../../src/lib/constants";
import {createTarballHash} from "../../../src/lib/crypto-utils";
import { TARBALL } from '../config.functional';
import { HTTP_STATUS } from '../../../src/lib/constants';
import { createTarballHash } from '../../../src/lib/crypto-utils';
import requirePackage from '../fixtures/package';
function readfile(filePath) {
const folder = path.join(__dirname , filePath);
const folder = path.join(__dirname, filePath);
return fs.readFileSync(folder);
}
@ -17,15 +17,17 @@ const pkgName = 'testpkg-gh29';
export default function (server, server2) {
describe('pkg-gh29 #1', () => {
test('downloading non-existent tarball #1 / srv2', () => {
return server2.getTarball(pkgName, TARBALL)
return server2
.getTarball(pkgName, TARBALL)
.status(HTTP_STATUS.NOT_FOUND)
.body_error(/no such package/);
});
});
describe('pkg-gh29 #2', () => {
beforeAll(function() {
return server.putPackage(pkgName, requirePackage(pkgName))
beforeAll(function () {
return server
.putPackage(pkgName, requirePackage(pkgName))
.status(HTTP_STATUS.CREATED)
.body_ok(/created new package/);
});
@ -33,14 +35,16 @@ export default function (server, server2) {
test('creating new package / srv1', () => {});
test('downloading non-existent tarball #2 / srv2', () => {
return server2.getTarball(pkgName, TARBALL)
return server2
.getTarball(pkgName, TARBALL)
.status(HTTP_STATUS.NOT_FOUND)
.body_error(/no such file available/);
});
describe('tarball', () => {
beforeAll(function() {
return server.putTarball(pkgName, TARBALL, readfile(binary))
beforeAll(function () {
return server
.putTarball(pkgName, TARBALL, readfile(binary))
.status(HTTP_STATUS.CREATED)
.body_ok(/.*/);
});
@ -48,10 +52,11 @@ export default function (server, server2) {
test('uploading new tarball / srv1', () => {});
describe('pkg version', () => {
beforeAll(function() {
beforeAll(function () {
const pkg = requirePackage(pkgName);
pkg.dist.shasum = createTarballHash().update(readfile(binary)).digest('hex');
return server.putVersion(pkgName, '0.0.1', pkg)
return server
.putVersion(pkgName, '0.0.1', pkg)
.status(HTTP_STATUS.CREATED)
.body_ok(/published/);
});
@ -59,9 +64,10 @@ export default function (server, server2) {
test('uploading new package version / srv1', () => {});
test('downloading newly created tarball / srv2', () => {
return server2.getTarball(pkgName, TARBALL)
return server2
.getTarball(pkgName, TARBALL)
.status(HTTP_STATUS.OK)
.then(function(body) {
.then(function (body) {
expect(body).toEqual(readfile(binary));
});
});

View file

@ -1,13 +1,13 @@
import {API_MESSAGE, HTTP_STATUS} from '../../../src/lib/constants';
import { API_MESSAGE, HTTP_STATUS } from '../../../src/lib/constants';
import pkgExample from './search.json';
export default function(server, server2, express) {
export default function (server, server2, express) {
describe('should test search a published package', () => {
const PKG_NAME = 'testpkg-search';
beforeAll(function() {
return server.putPackage(PKG_NAME, pkgExample)
beforeAll(function () {
return server
.putPackage(PKG_NAME, pkgExample)
.status(HTTP_STATUS.CREATED)
.body_ok(API_MESSAGE.PKG_CREATED);
});
@ -15,49 +15,47 @@ export default function(server, server2, express) {
describe('should test simple search', () => {
const check = (medatada) => {
medatada[PKG_NAME].time.modified = '2014-10-02T07:07:51.000Z';
expect(medatada[PKG_NAME]).toEqual(
{
'name': PKG_NAME,
'description': '',
'author': '',
'license': 'ISC',
'dist-tags': {
latest: '0.0.1'
},
'maintainers': [{
expect(medatada[PKG_NAME]).toEqual({
name: PKG_NAME,
description: '',
author: '',
license: 'ISC',
'dist-tags': {
latest: '0.0.1'
},
maintainers: [
{
name: 'alex',
email: 'user@domain.com'
}],
'readmeFilename': '',
'time': {
modified: '2014-10-02T07:07:51.000Z'
},
'versions': {
"0.0.1": "latest"
},
'repository': {
type: 'git', url: ''}
});
}
],
readmeFilename: '',
time: {
modified: '2014-10-02T07:07:51.000Z'
},
versions: {
'0.0.1': 'latest'
},
repository: {
type: 'git',
url: ''
}
});
};
beforeAll(function() {
beforeAll(function () {
express.get('/-/all', (req, res) => {
res.send({});
});
});
test('server1 - search', () => {
return server.request({uri: '/-/all'})
.status(HTTP_STATUS.OK)
.then(check);
return server.request({ uri: '/-/all' }).status(HTTP_STATUS.OK).then(check);
});
test('server2 - search', () => {
return server2.request({uri: '/-/all'})
.status(HTTP_STATUS.OK)
.then(check);
return server2.request({ uri: '/-/all' }).status(HTTP_STATUS.OK).then(check);
});
});
});
}

View file

@ -1,46 +1,56 @@
import {readFile} from '../lib/test.utils';
import {API_ERROR, HTTP_STATUS, CHARACTER_ENCODING} from "../../../src/lib/constants";
import { readFile } from '../lib/test.utils';
import { API_ERROR, HTTP_STATUS, CHARACTER_ENCODING } from '../../../src/lib/constants';
const readTags = () => readFile('../fixtures/publish.json5');
export default function(server) {
export default function (server) {
describe('should test add tag', () => {
const PKG_NAME = 'testpkg-tag';
const PKG_VERSION = '0.0.1';
test('should fails on add tag to non existing package', () => {
return server.addTag(PKG_NAME, 'tagtagtag', PKG_VERSION)
.status(HTTP_STATUS.NOT_FOUND).body_error(API_ERROR.NO_PACKAGE);
return server
.addTag(PKG_NAME, 'tagtagtag', PKG_VERSION)
.status(HTTP_STATUS.NOT_FOUND)
.body_error(API_ERROR.NO_PACKAGE);
});
describe('should test add tag to a package', () => {
beforeAll(function() {
return server.putPackage(PKG_NAME,
JSON.parse(readTags().toString(CHARACTER_ENCODING.UTF8).replace(/__NAME__/g, PKG_NAME)
.replace(/__VERSION__/g, PKG_VERSION))
).status(HTTP_STATUS.CREATED);
beforeAll(function () {
return server
.putPackage(
PKG_NAME,
JSON.parse(
readTags()
.toString(CHARACTER_ENCODING.UTF8)
.replace(/__NAME__/g, PKG_NAME)
.replace(/__VERSION__/g, PKG_VERSION)
)
)
.status(HTTP_STATUS.CREATED);
});
describe('should test valid formats tags', () => {
test('should fails on add a tag that do not exist', () => {
return server.addTag(PKG_NAME, 'tagtagtag', '4.0.0-no-exist')
return server
.addTag(PKG_NAME, 'tagtagtag', '4.0.0-no-exist')
.status(HTTP_STATUS.NOT_FOUND)
.body_error(API_ERROR.VERSION_NOT_EXIST);
});
test('should add tag succesfully minor version', () => {
return server.addTag(PKG_NAME, 'tagtagtag', PKG_VERSION)
return server
.addTag(PKG_NAME, 'tagtagtag', PKG_VERSION)
.status(HTTP_STATUS.CREATED)
.body_ok(/tagged/);
});
});
describe('should test handle invalid tag and version names', () => {
const INVALID_TAG ='tag/tag/tag';
const handleInvalidTag = function(tag, version) {
return server.addTag(PKG_NAME, tag, version)
const INVALID_TAG = 'tag/tag/tag';
const handleInvalidTag = function (tag, version) {
return server
.addTag(PKG_NAME, tag, version)
.status(HTTP_STATUS.FORBIDDEN)
.body_error(/invalid tag/);
};

View file

@ -1,49 +1,53 @@
import {generateSha} from '../lib/test.utils';
import {API_MESSAGE, HTTP_STATUS} from '../../../src/lib/constants';
import {DOMAIN_SERVERS, PORT_SERVER_1, PORT_SERVER_2, PORT_SERVER_3} from '../config.functional';
import {DIST_TAGS} from '../../../src/lib/constants';
import { generateSha } from '../lib/test.utils';
import { API_MESSAGE, HTTP_STATUS } from '../../../src/lib/constants';
import { DOMAIN_SERVERS, PORT_SERVER_1, PORT_SERVER_2, PORT_SERVER_3 } from '../config.functional';
import { DIST_TAGS } from '../../../src/lib/constants';
import pkgExample from './dist-tags-merge.json';
export default function(server, server2, server3) {
export default function (server, server2, server3) {
describe('should test preserve tags when publishing something', () => {
const PKG_NAME = 'testpkg-preserve';
const PKG_VERSION = '0.0.1';
beforeAll(function() {
return server.putPackage(PKG_NAME, pkgExample)
beforeAll(function () {
return server
.putPackage(PKG_NAME, pkgExample)
.status(HTTP_STATUS.CREATED)
.body_ok(API_MESSAGE.PKG_CREATED);
});
describe('should check sha integrity', () => {
const matchTarBallSha = (server) => {
return server.getTarball(PKG_NAME, `${PKG_NAME}-${PKG_VERSION}.tgz`)
return server
.getTarball(PKG_NAME, `${PKG_NAME}-${PKG_VERSION}.tgz`)
.status(HTTP_STATUS.OK)
.then(function(body) {
.then(function (body) {
// not real sha due to utf8 conversion
expect(generateSha(body)).toBe(pkgExample.versions[PKG_VERSION].dist.shasum);
});
};
test('server1 should match with sha key from published package', () => matchTarBallSha(server));
test('server2 should match with sha key from published package', () => matchTarBallSha(server2));
test('server1 should match with sha key from published package', () =>
matchTarBallSha(server));
test('server2 should match with sha key from published package', () =>
matchTarBallSha(server2));
});
describe('should match dist-tags', () => {
const matchDisTags = (verdaccioServer, port) => {
return verdaccioServer.getPackage(PKG_NAME)
return verdaccioServer
.getPackage(PKG_NAME)
.status(HTTP_STATUS.OK)
.then(function(body) {
.then(function (body) {
expect(body.name).toBe(PKG_NAME);
expect(body.time).toBeDefined();
expect(body.time[PKG_VERSION]).toBeDefined();
expect(body.time).toBeDefined();
expect(body.versions[PKG_VERSION].name).toBe(PKG_NAME);
expect(body.versions[PKG_VERSION].dist.tarball).toBe(`http://${DOMAIN_SERVERS}:${port}/${PKG_NAME}/-/${PKG_NAME}-${PKG_VERSION}.tgz`);
expect(body[DIST_TAGS]).toEqual({foo: PKG_VERSION, latest: PKG_VERSION});
expect(body.versions[PKG_VERSION].dist.tarball).toBe(
`http://${DOMAIN_SERVERS}:${port}/${PKG_NAME}/-/${PKG_NAME}-${PKG_VERSION}.tgz`
);
expect(body[DIST_TAGS]).toEqual({ foo: PKG_VERSION, latest: PKG_VERSION });
});
};

View file

@ -1,31 +1,35 @@
import _ from 'lodash';
import {readFile} from '../lib/test.utils';
import { readFile } from '../lib/test.utils';
import { HTTP_STATUS } from '../../../src/lib/constants';
const readTags = () => readFile('../fixtures/tags.json');
export default function(server, express) {
export default function (server, express) {
test('tags - testing for 404', () => {
return server.getPackage('testexp_tags')
// shouldn't exist yet
.status(HTTP_STATUS.NOT_FOUND)
.body_error(/no such package/);
return (
server
.getPackage('testexp_tags')
// shouldn't exist yet
.status(HTTP_STATUS.NOT_FOUND)
.body_error(/no such package/)
);
});
describe('tags', () => {
beforeAll(function() {
express.get('/testexp_tags', function(req, res) {
let f = readTags().toString().replace(/__NAME__/g, 'testexp_tags');
beforeAll(function () {
express.get('/testexp_tags', function (req, res) {
let f = readTags()
.toString()
.replace(/__NAME__/g, 'testexp_tags');
res.send(JSON.parse(f));
});
});
test('fetching package again', () => {
return server.getPackage('testexp_tags')
return server
.getPackage('testexp_tags')
.status(200)
.then(function(body) {
.then(function (body) {
expect(_.isObject(body.versions['1.1.0'])).toBe(true);
// note: 5.4.3 is invalid tag, 0.1.3alpha is highest semver
expect(body['dist-tags'].latest).toEqual('1.1.0');
@ -35,11 +39,12 @@ export default function(server, express) {
const versions = ['0.1.1alpha', '0.1.1-alpha', '0000.00001.001-alpha'];
versions.forEach(function(ver) {
test('fetching '+ver, () => {
return server.request({uri: '/testexp_tags/'+ver})
versions.forEach(function (ver) {
test('fetching ' + ver, () => {
return server
.request({ uri: '/testexp_tags/' + ver })
.status(200)
.then(function(body) {
.then(function (body) {
expect(body.version).toEqual('0.1.1alpha');
});
});
@ -47,117 +52,146 @@ export default function(server, express) {
});
describe('dist-tags methods', () => {
beforeAll(function() {
express.get('/testexp_tags2', function(req, res) {
let f = readTags().toString().replace(/__NAME__/g, 'testexp_tags2');
beforeAll(function () {
express.get('/testexp_tags2', function (req, res) {
let f = readTags()
.toString()
.replace(/__NAME__/g, 'testexp_tags2');
res.send(JSON.parse(f));
});
});
// populate cache
beforeAll(function() {
beforeAll(function () {
return server.getPackage('testexp_tags2').status(200);
});
test('fetching tags', () => {
return server.request({
method: 'GET',
uri: '/-/package/testexp_tags2/dist-tags',
}).status(200).then(function(body) {
const expected = {
latest: "1.1.0"
};
expect(body).toEqual(expected);
});
});
test('merging tags', () => {
return server.request({
method: 'POST',
uri: '/-/package/testexp_tags2/dist-tags',
json: {
foo: '0.1.2',
quux: '0.1.0',
},
}).status(201).body_ok(/updated/).then(function() {
return server.request({
return server
.request({
method: 'GET',
uri: '/-/package/testexp_tags2/dist-tags',
}).status(200).then(function(body) {
uri: '/-/package/testexp_tags2/dist-tags'
})
.status(200)
.then(function (body) {
const expected = {
"latest": "1.1.0",
"foo": "0.1.2",
"quux": "0.1.0"
};
expect(body).toEqual(expected);
});
});
});
test('should add a dist-tag called foo', () => {
return server.request({
method: 'PUT',
uri: '/-/package/testexp_tags2/dist-tags/foo',
json: '0.1.3alpha',
}).status(201).body_ok(/tagged/).then(function() {
return server.request({
method: 'GET',
uri: '/-/package/testexp_tags2/dist-tags',
}).status(200).then(function(body) {
const expected = {
foo: '0.1.3alpha',
quux: '0.1.0',
latest: '1.1.0'
};
expect(body).toEqual(expected);
});
});
});
test('merging tags', () => {
return server
.request({
method: 'POST',
uri: '/-/package/testexp_tags2/dist-tags',
json: {
foo: '0.1.2',
quux: '0.1.0'
}
})
.status(201)
.body_ok(/updated/)
.then(function () {
return server
.request({
method: 'GET',
uri: '/-/package/testexp_tags2/dist-tags'
})
.status(200)
.then(function (body) {
const expected = {
latest: '1.1.0',
foo: '0.1.2',
quux: '0.1.0'
};
expect(body).toEqual(expected);
});
});
});
test('should add a dist-tag called foo', () => {
return server
.request({
method: 'PUT',
uri: '/-/package/testexp_tags2/dist-tags/foo',
json: '0.1.3alpha'
})
.status(201)
.body_ok(/tagged/)
.then(function () {
return server
.request({
method: 'GET',
uri: '/-/package/testexp_tags2/dist-tags'
})
.status(200)
.then(function (body) {
const expected = {
foo: '0.1.3alpha',
quux: '0.1.0',
latest: '1.1.0'
};
expect(body).toEqual(expected);
});
});
});
test('should remove a dis-tag called quux', () => {
return server.request({
method: 'DELETE',
uri: '/-/package/testexp_tags2/dist-tags/latest',
}).status(201).body_ok(/removed/).then(function() {
return server.request({
method: 'GET',
uri: '/-/package/testexp_tags2/dist-tags',
}).status(200).then(function(body) {
const expected = {
latest: '1.1.0',
"quux": "0.1.0",
foo: "0.1.3alpha"
};
return server
.request({
method: 'DELETE',
uri: '/-/package/testexp_tags2/dist-tags/latest'
})
.status(201)
.body_ok(/removed/)
.then(function () {
return server
.request({
method: 'GET',
uri: '/-/package/testexp_tags2/dist-tags'
})
.status(200)
.then(function (body) {
const expected = {
latest: '1.1.0',
quux: '0.1.0',
foo: '0.1.3alpha'
};
expect(body).toEqual(expected);
expect(body).toEqual(expected);
});
});
});
});
test('should remove a dis-tag called foo', () => {
return server.request({
method: 'DELETE',
uri: '/-/package/testexp_tags2/dist-tags/foo',
}).status(201).body_ok(/removed/).then(function() {
return server.request({
method: 'GET',
uri: '/-/package/testexp_tags2/dist-tags',
}).status(200).then(function(body) {
const expected = {
latest: '1.1.0',
"quux": "0.1.0"
};
return server
.request({
method: 'DELETE',
uri: '/-/package/testexp_tags2/dist-tags/foo'
})
.status(201)
.body_ok(/removed/)
.then(function () {
return server
.request({
method: 'GET',
uri: '/-/package/testexp_tags2/dist-tags'
})
.status(200)
.then(function (body) {
const expected = {
latest: '1.1.0',
quux: '0.1.0'
};
expect(body).toEqual(expected);
expect(body).toEqual(expected);
});
});
});
});
});
}

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