0
Fork 0
mirror of https://github.com/verdaccio/verdaccio.git synced 2024-12-16 21:56:25 -05:00

refactor: max-len and printWidth to 100 (#1941)

* refactor: max-len and printWidth to 100

* chore: ci specific pnpm version

* fix: add types peer package

literally get rid of types package
This commit is contained in:
Juan Picado 2020-09-17 06:48:16 +02:00
parent 1e48f1c077
commit 8c730c0694
172 changed files with 3044 additions and 949 deletions

View file

@ -21,6 +21,7 @@
# do not copy over node_modules we will run `npm install` anyway
node_modules
website
# output from test runs and similar things
*.log

View file

@ -58,7 +58,7 @@
"camelcase": "off",
"guard-for-in": "error",
"new-cap": "error",
"max-len": ["warn", 160],
"max-len": ["warn", 100],
"no-console": ["error", { "allow": ["warn"] }],
"no-constant-condition": "error",
"no-debugger": "error",

View file

@ -33,7 +33,7 @@ jobs:
node-version: 14
- name: install pnpm
run: npm install pnpm@5.5.2 -g
run: npm i pnpm@5.5.2 -g
- name: setup pnpm config
run: pnpm config set store-dir $PNPM_CACHE_FOLDER

View file

@ -32,7 +32,7 @@ jobs:
with:
node_version: ${{ matrix.node_version }}
- name: Install pnpm
run: npm i -g pnpm
run: npm i pnpm@5.5.12 -g
- name: Install
run: pnpm recursive install
- name: Format

View file

@ -1,7 +1,7 @@
{
"endOfLine": "lf",
"useTabs": false,
"printWidth": 160,
"printWidth": 100,
"tabWidth": 2,
"singleQuote": true,
"bracketSpacing": true,

View file

@ -14,10 +14,10 @@ COPY . .
RUN npm -g i pnpm@latest && \
pnpm config set registry $VERDACCIO_BUILD_REGISTRY && \
pnpm recursive install --frozen-lockfile && \
pnpm recursive install --frozen-lockfile --ignore-scripts && \
pnpm run build && \
pnpm run lint && \
pnpm install --prod
pnpm install --prod --ignore-scripts
FROM node:12.18.3-alpine
LABEL maintainer="https://github.com/verdaccio/verdaccio"

View file

@ -93,7 +93,7 @@
"selfsigned": "1.10.7",
"standard-version": "8.0.0",
"supertest": "4.0.2",
"typescript": ">=3.3.1 <3.10.0",
"typescript": "^3.9.7",
"verdaccio": "latest",
"verdaccio-audit": "latest",
"verdaccio-auth-memory": "latest",
@ -101,8 +101,6 @@
"verdaccio-memory": "latest"
},
"scripts": {
"start": "node packages/verdaccio/debug/bootstrap.js",
"debug": "node --inspect packages/verdaccio/debug/bootstrap.js",
"dev": "cross-env BABEL_ENV=registry babel-node --extensions \".ts,.tsx\" packages/cli/src",
"clean": "pnpm recursive run clean",
"build": "pnpm recursive run build",

View file

@ -40,7 +40,6 @@
},
"devDependencies": {
"@verdaccio/config": "workspace:5.0.0-alpha.0",
"@verdaccio/dev-types": "workspace:5.0.0-alpha.0",
"@verdaccio/server": "workspace:5.0.0-alpha.0",
"@verdaccio/types": "workspace:*",
"body-parser": "1.19.0",

View file

@ -6,13 +6,17 @@ import { media, allow } from '@verdaccio/middleware';
import { API_MESSAGE, HTTP_STATUS, DIST_TAGS } from '@verdaccio/dev-commons';
import { VerdaccioError } from '@verdaccio/commons-api';
import { Package } from '@verdaccio/types';
// @ts-ignore
import { IAuth, $ResponseExtend, $RequestExtend, $NextFunctionVer, IStorageHandler } from '@verdaccio/dev-types';
import { IStorageHandler } from '@verdaccio/store';
import { IAuth } from '@verdaccio/auth';
import { $NextFunctionVer, $RequestExtend, $ResponseExtend } from '../types/custom';
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');
}
@ -31,11 +35,25 @@ export default function (route: Router, auth: IAuth, storage: IStorageHandler):
// 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 {
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 {
@ -49,7 +67,11 @@ export default function (route: Router, auth: IAuth, storage: IStorageHandler):
});
});
route.get('/-/package/:package/dist-tags', can('access'), function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
route.get('/-/package/:package/dist-tags', can('access'), function (
req: $RequestExtend,
res: $ResponseExtend,
next: $NextFunctionVer
): void {
storage.getPackage({
name: req.params.package,
uplinksLook: true,
@ -64,8 +86,14 @@ export default function (route: Router, auth: IAuth, storage: IStorageHandler):
});
});
route.post('/-/package/:package/dist-tags', can('publish'), function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
storage.mergeTags(req.params.package, req.body, function (err: VerdaccioError): $NextFunctionVer {
route.post('/-/package/:package/dist-tags', can('publish'), function (
req: $RequestExtend,
res: $ResponseExtend,
next: $NextFunctionVer
): void {
storage.mergeTags(req.params.package, req.body, function (
err: VerdaccioError
): $NextFunctionVer {
if (err) {
return next(err);
}

View file

@ -1,8 +1,15 @@
import _ from 'lodash';
import express, { Express } from 'express';
import { match, validateName, validatePackage, encodeScopePackage, antiLoop } from '@verdaccio/middleware';
import { IAuth, IStorageHandler } from '@verdaccio/dev-types';
import {
match,
validateName,
validatePackage,
encodeScopePackage,
antiLoop,
} from '@verdaccio/middleware';
import { IAuth } from '@verdaccio/auth';
import { IStorageHandler } from '@verdaccio/store';
import { Config } from '@verdaccio/types';
import bodyParser from 'body-parser';
@ -18,7 +25,11 @@ import profile from './v1/profile';
import token from './v1/token';
import v1Search from './v1/search';
export default function (config: Config, auth: IAuth, storage: IStorageHandler): Express.Application {
export default function (
config: Config,
auth: IAuth,
storage: IStorageHandler
): Express.Application {
/* eslint new-cap:off */
const app = express.Router();
/* eslint new-cap:off */

View file

@ -6,11 +6,19 @@ import { allow } from '@verdaccio/middleware';
import { convertDistRemoteToLocalTarballUrls, getVersion, ErrorCode } from '@verdaccio/utils';
import { HEADERS, DIST_TAGS, API_ERROR } from '@verdaccio/dev-commons';
import { Config, Package } from '@verdaccio/types';
import { IAuth, $ResponseExtend, $RequestExtend, $NextFunctionVer, IStorageHandler } from '@verdaccio/dev-types';
import { IAuth } from '@verdaccio/auth';
import { IStorageHandler } from '@verdaccio/store';
import { $RequestExtend, $ResponseExtend, $NextFunctionVer } from '../types/custom';
const debug = buildDebug('verdaccio:api:package');
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 {
@ -25,10 +33,19 @@ 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 {
route.get('/:package/:version?', can('access'), function (
req: $RequestExtend,
res: $ResponseExtend,
next: $NextFunctionVer
): void {
debug('init package by version');
const name = req.params.package;
const getPackageMetaCallback = function (err, metadata: Package): void {
@ -81,13 +98,19 @@ export default function (route: Router, auth: IAuth, storage: IStorageHandler, c
});
});
route.get('/:scopedPackage/-/:scope/:filename', can('access'), function (req: $RequestExtend, res: $ResponseExtend): void {
route.get('/:scopedPackage/-/:scope/:filename', can('access'), function (
req: $RequestExtend,
res: $ResponseExtend
): void {
const { scopedPackage, filename } = req.params;
downloadStream(scopedPackage, filename, storage, req, res);
});
route.get('/:package/-/:filename', can('access'), function (req: $RequestExtend, res: $ResponseExtend): void {
route.get('/:package/-/:filename', can('access'), function (
req: $RequestExtend,
res: $ResponseExtend
): void {
downloadStream(req.params.package, req.params.filename, storage, req, res);
});
}

View file

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

View file

@ -4,20 +4,33 @@ import mime from 'mime';
import { Router } from 'express';
import buildDebug from 'debug';
import { IAuth, $ResponseExtend, $RequestExtend, $NextFunctionVer, IStorageHandler } from '@verdaccio/dev-types';
import { API_MESSAGE, HEADERS, DIST_TAGS, API_ERROR, HTTP_STATUS } from '@verdaccio/dev-commons';
import { validateMetadata, isObject, ErrorCode, hasDiffOneKey, isRelatedToDeprecation } from '@verdaccio/utils';
import {
validateMetadata,
isObject,
ErrorCode,
hasDiffOneKey,
isRelatedToDeprecation,
} from '@verdaccio/utils';
import { media, expectJson, allow } from '@verdaccio/middleware';
import { notify } from '@verdaccio/hooks';
import { Config, Callback, MergeTags, Version, Package } from '@verdaccio/types';
import { logger } from '@verdaccio/logger';
import { IAuth } from '@verdaccio/auth';
import { IStorageHandler } from '@verdaccio/store';
import { $RequestExtend, $ResponseExtend, $NextFunctionVer } from '../types/custom';
import star from './star';
import { isPublishablePackage } from './utils';
const debug = buildDebug('verdaccio:api:publish');
export default function publish(router: Router, auth: IAuth, storage: IStorageHandler, config: Config): void {
export default function publish(
router: Router,
auth: IAuth,
storage: IStorageHandler,
config: Config
): void {
const can = allow(auth);
/**
@ -79,7 +92,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.
@ -92,13 +111,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)
);
}
/**
@ -117,7 +152,12 @@ export function publishPackage(storage: IStorageHandler, config: Config, auth: I
const createTarball = function (filename: string, data, cb: Callback): void {
const stream = storage.addTarball(packageName, filename);
stream.on('error', function (err) {
debug('error on stream a tarball %o for %o with error %o', filename, packageName, err.message);
debug(
'error on stream a tarball %o for %o with error %o',
filename,
packageName,
err.message
);
cb(err);
});
stream.on('success', function () {
@ -172,7 +212,11 @@ export function publishPackage(storage: IStorageHandler, config: Config, auth: I
// npm-registry-client 0.3+ embeds tarball into the json upload
// https://github.com/isaacs/npm-registry-client/commit/e9fbeb8b67f249394f735c74ef11fe4720d46ca0
// issue https://github.com/rlidwka/sinopia/issues/31, dealing with it here:
const isInvalidBodyFormat = isObject(_attachments) === false || hasDiffOneKey(_attachments) || isObject(versions) === false || hasDiffOneKey(versions);
const isInvalidBodyFormat =
isObject(_attachments) === false ||
hasDiffOneKey(_attachments) ||
isObject(versions) === false ||
hasDiffOneKey(versions);
if (isInvalidBodyFormat) {
// npm is doing something strange again
@ -190,7 +234,9 @@ export function publishPackage(storage: IStorageHandler, config: Config, auth: I
// at this point document is either created or existed before
const [firstAttachmentKey] = Object.keys(_attachments);
createTarball(Path.basename(firstAttachmentKey), _attachments[firstAttachmentKey], function (error) {
createTarball(Path.basename(firstAttachmentKey), _attachments[firstAttachmentKey], function (
error
) {
debug('creating a tarball %o', firstAttachmentKey);
if (error) {
debug('error on create a tarball for %o with error %o', packageName, error.message);
@ -199,7 +245,8 @@ export function publishPackage(storage: IStorageHandler, config: Config, auth: I
const versionToPublish = Object.keys(versions)[0];
versions[versionToPublish].readme = _.isNil(metadataCopy.readme) === false ? String(metadataCopy.readme) : '';
versions[versionToPublish].readme =
_.isNil(metadataCopy.readme) === false ? String(metadataCopy.readme) : '';
createVersion(versionToPublish, versions[versionToPublish], function (error) {
if (error) {
@ -214,7 +261,12 @@ export function publishPackage(storage: IStorageHandler, config: Config, auth: I
}
try {
await notify(metadataCopy, config, req.remote_user, `${metadataCopy.name}@${versionToPublish}`);
await notify(
metadataCopy,
config,
req.remote_user,
`${metadataCopy.name}@${versionToPublish}`
);
} catch (error) {
debug('error on notify add a new tag %o', `${metadataCopy.name}@${versionToPublish}`);
logger.error({ error }, 'notify batch service has failed: @{error}');
@ -295,14 +347,20 @@ export function removeTarball(storage: IStorageHandler) {
const packageName = req.params.package;
const { filename, revision } = req.params;
logger.debug({ packageName, filename, revision }, `removing a tarball for @{packageName}-@{tarballName}-@{revision}`);
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 });
});
};

View file

@ -1,14 +1,16 @@
import { USERS, HTTP_STATUS } from '@verdaccio/dev-commons';
import { Response } from 'express';
import _ from 'lodash';
import { logger } from '@verdaccio/logger';
import buildDebug from 'debug';
import { $RequestExtend, $NextFunctionVer, IStorageHandler } from '@verdaccio/dev-types';
import { IStorageHandler } from '@verdaccio/store';
import { $RequestExtend, $ResponseExtend, $NextFunctionVer } from '../types/custom';
const debug = buildDebug('verdaccio:api:publish:star');
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]) {
@ -52,7 +54,10 @@ export default function (storage: IStorageHandler): (req: $RequestExtend, res: R
// Check is star or unstar
const isStar = Object.keys(newStarUser).includes(remoteUsername);
debug('is start? %o', isStar);
if (_.isNil(localStarUsers) === false && validateInputs(newStarUser, localStarUsers, remoteUsername, isStar)) {
if (
_.isNil(localStarUsers) === false &&
validateInputs(newStarUser, localStarUsers, remoteUsername, isStar)
) {
return afterChangePackage();
}
const users = isStar

View file

@ -4,27 +4,33 @@ import { Response, Router } from 'express';
import { USERS, HTTP_STATUS } from '@verdaccio/dev-commons';
import { Package } from '@verdaccio/types';
import { $RequestExtend, $NextFunctionVer, IStorageHandler } from '@verdaccio/dev-types';
import { IStorageHandler } from '@verdaccio/store';
import { $RequestExtend, $NextFunctionVer } from '../types/custom';
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;
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,30 +1,50 @@
import _ from 'lodash';
import { Response, Router } from 'express';
import { createRemoteUser, getAuthenticatedMessage, validatePassword, ErrorCode } from '@verdaccio/utils';
import {
createRemoteUser,
getAuthenticatedMessage,
validatePassword,
ErrorCode,
} from '@verdaccio/utils';
import { getApiToken } from '@verdaccio/auth';
import { logger } from '@verdaccio/logger';
import { Config, RemoteUser } from '@verdaccio/types';
import { $RequestExtend, $NextFunctionVer, IAuth } from '@verdaccio/dev-types';
import { IAuth } from '@verdaccio/auth';
import { API_ERROR, API_MESSAGE, HTTP_STATUS } from '@verdaccio/dev-commons';
import { $RequestExtend, $NextFunctionVer } from '../types/custom';
export default function (route: Router, auth: IAuth, config: Config): void {
route.get('/-/user/:org_couchdb_user', function (req: $RequestExtend, res: Response, next: $NextFunctionVer): void {
route.get('/-/user/:org_couchdb_user', function (
req: $RequestExtend,
res: Response,
next: $NextFunctionVer
): void {
res.status(HTTP_STATUS.OK);
next({
ok: getAuthenticatedMessage(req.remote_user.name),
});
});
route.put('/-/user/:org_couchdb_user/:_rev?/:revision?', function (req: $RequestExtend, res: Response, next: $NextFunctionVer): void {
route.put('/-/user/:org_couchdb_user/:_rev?/:revision?', function (
req: $RequestExtend,
res: Response,
next: $NextFunctionVer
): void {
const { name, password } = req.body;
const remoteName = req.remote_user.name;
if (_.isNil(remoteName) === false && _.isNil(name) === false && remoteName === name) {
auth.authenticate(name, password, async function callbackAuthenticate(err, user): Promise<void> {
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}');
logger.trace(
{ name, err },
'authenticating for user @{username} failed. Error: @{err.message}'
);
return next(ErrorCode.getCode(HTTP_STATUS.UNAUTHORIZED, API_ERROR.BAD_USERNAME_PASSWORD));
}
@ -50,12 +70,15 @@ export default function (route: Router, auth: IAuth, config: Config): void {
// 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(
ErrorCode.getCode(err.status, err.message) || ErrorCode.getConflict(err.message)
);
}
return next(err);
}
const token = name && password ? await getApiToken(auth, config, user, password) : undefined;
const token =
name && password ? await getApiToken(auth, config, user, password) : undefined;
req.remote_user = user;
res.status(HTTP_STATUS.CREATED);
@ -67,7 +90,11 @@ export default function (route: Router, auth: IAuth, config: Config): void {
}
});
route.delete('/-/user/token/*', function (req: $RequestExtend, res: Response, next: $NextFunctionVer): void {
route.delete('/-/user/token/*', function (
req: $RequestExtend,
res: Response,
next: $NextFunctionVer
): void {
res.status(HTTP_STATUS.OK);
next({
ok: API_MESSAGE.LOGGED_OUT,

View file

@ -3,7 +3,8 @@ import { Response, Router } from 'express';
import { API_ERROR, APP_ERROR, HTTP_STATUS, SUPPORT_ERRORS } from '@verdaccio/dev-commons';
import { ErrorCode, validatePassword } from '@verdaccio/utils';
import { $NextFunctionVer, $RequestExtend, IAuth } from '@verdaccio/dev-types';
import { IAuth } from '@verdaccio/auth';
import { $RequestExtend, $NextFunctionVer } from '../../types/custom';
export interface Profile {
tfa: boolean;
@ -30,7 +31,11 @@ export default function (route: Router, auth: IAuth): void {
};
}
route.get('/-/npm/v1/user', function (req: $RequestExtend, res: Response, next: $NextFunctionVer): void {
route.get('/-/npm/v1/user', function (
req: $RequestExtend,
res: Response,
next: $NextFunctionVer
): void {
if (_.isNil(req.remote_user.name) === false) {
return next(buildProfile(req.remote_user.name));
}
@ -41,7 +46,11 @@ export default function (route: Router, auth: IAuth): void {
});
});
route.post('/-/npm/v1/user', function (req: $RequestExtend, res: Response, next: $NextFunctionVer): void {
route.post('/-/npm/v1/user', function (
req: $RequestExtend,
res: Response,
next: $NextFunctionVer
): void {
if (_.isNil(req.remote_user.name)) {
res.status(HTTP_STATUS.UNAUTHORIZED);
return next({
@ -65,7 +74,9 @@ export default function (route: Router, auth: IAuth): void {
password.new,
(err, isUpdated): $NextFunctionVer => {
if (_.isNull(err) === false) {
return next(ErrorCode.getCode(err.status, err.message) || ErrorCode.getConflict(err.message));
return next(
ErrorCode.getCode(err.status, err.message) || ErrorCode.getConflict(err.message)
);
}
if (isUpdated) {

View file

@ -40,9 +40,11 @@ function compileTextSearch(textSearch: string): (pkg: Package) => boolean {
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]
);
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;
@ -61,7 +63,9 @@ export default function (route, auth, storage): void {
return {
package: pkg,
flags: {
unstable: Object.keys(pkg.versions).some((v) => semver.satisfies(v, '^1.0.0')) ? undefined : true,
unstable: Object.keys(pkg.versions).some((v) => semver.satisfies(v, '^1.0.0'))
? undefined
: true,
},
score: {
final: 1,

View file

@ -6,7 +6,9 @@ import { logger } from '@verdaccio/logger';
import { Response, Router } from 'express';
import { Config, RemoteUser, Token } from '@verdaccio/types';
import { $NextFunctionVer, $RequestExtend, IAuth, IStorageHandler } from '../../../types';
import { IAuth } from '@verdaccio/auth';
import { IStorageHandler } from '@verdaccio/store';
import { $RequestExtend, $NextFunctionVer } from '../../types/custom';
export type NormalizeToken = Token & {
created: string;
@ -20,8 +22,17 @@ function normalizeToken(token: Token): NormalizeToken {
}
// 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) {
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) {
@ -45,7 +56,11 @@ export default function (route: Router, auth: IAuth, storage: IStorageHandler, c
return next(ErrorCode.getUnauthorized());
});
route.post('/-/npm/v1/tokens', function (req: $RequestExtend, res: Response, next: $NextFunctionVer) {
route.post('/-/npm/v1/tokens', function (
req: $RequestExtend,
res: Response,
next: $NextFunctionVer
) {
const { password, readonly, cidr_whitelist } = req.body;
const { name } = req.remote_user;
@ -62,7 +77,9 @@ export default function (route: Router, auth: IAuth, storage: IStorageHandler, c
req.remote_user = user;
if (!_.isFunction(storage.saveToken)) {
return next(ErrorCode.getCode(HTTP_STATUS.NOT_IMPLEMENTED, SUPPORT_ERRORS.STORAGE_NOT_IMPLEMENT));
return next(
ErrorCode.getCode(HTTP_STATUS.NOT_IMPLEMENTED, SUPPORT_ERRORS.STORAGE_NOT_IMPLEMENT)
);
}
try {
@ -104,23 +121,26 @@ export default function (route: Router, auth: IAuth, storage: IStorageHandler, c
});
});
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));
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());
}
return next(ErrorCode.getUnauthorized());
});
);
}

View file

@ -1,5 +1,5 @@
import { Response, Router } from 'express';
import { $RequestExtend, $NextFunctionVer } from '@verdaccio/dev-types';
import { $RequestExtend, $NextFunctionVer } from '../types/custom';
export default function (route: Router): void {
route.get('/whoami', (req: $RequestExtend, res: Response, next: $NextFunctionVer): void => {

View file

@ -7,8 +7,7 @@ import { parseConfigFile } from '@verdaccio/utils';
import { Config } from '@verdaccio/config';
import { Storage } from '@verdaccio/store';
import { final, handleError, errorReportingMiddleware } from '@verdaccio/middleware';
import { Auth } from '@verdaccio/auth';
import { IAuth } from '@verdaccio/dev-types';
import { Auth, IAuth } from '@verdaccio/auth';
import { HEADER_TYPE, HTTP_STATUS, generatePackageMetadata } from '@verdaccio/dev-commons';
import { HEADERS } from '@verdaccio/commons-api';
import apiEndpoints from '../../src';
@ -56,13 +55,23 @@ export function publishVersion(app, configFile, pkgName, version): supertest.Tes
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON);
}
export async function publishTaggedVersion(app, configFile, pkgName: string, version: string, tag: string) {
export async function publishTaggedVersion(
app,
configFile,
pkgName: string,
version: string,
tag: string
) {
const pkgMetadata = generatePackageMetadata(pkgName, version, {
[tag]: version,
});
return supertest(app)
.put(`/${encodeURIComponent(pkgName)}/${encodeURIComponent(version)}/-tag/${encodeURIComponent(tag)}`)
.put(
`/${encodeURIComponent(pkgName)}/${encodeURIComponent(version)}/-tag/${encodeURIComponent(
tag
)}`
)
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.send(JSON.stringify(pkgMetadata))
.expect(HTTP_STATUS.CREATED)

View file

@ -2,13 +2,15 @@ import supertest from 'supertest';
import { HTTP_STATUS } from '@verdaccio/commons-api';
import { HEADER_TYPE, HEADERS } from '@verdaccio/dev-commons';
import { $RequestExtend, $ResponseExtend } from '@verdaccio/dev-types';
import { $ResponseExtend, $RequestExtend } from '../../types/custom';
import { initializeServer, publishTaggedVersion, publishVersion } from './_helper';
const mockApiJWTmiddleware = jest.fn(() => (req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: 'foo', groups: [], real_groups: [] };
_next();
});
const mockApiJWTmiddleware = jest.fn(
() => (req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: 'foo', groups: [], real_groups: [] };
_next();
}
);
jest.mock('@verdaccio/auth', () => ({
Auth: class {

View file

@ -1,13 +1,21 @@
import { HTTP_STATUS } from '@verdaccio/commons-api';
import { API_ERROR, API_MESSAGE, generatePackageMetadata, HEADER_TYPE, HEADERS } from '@verdaccio/dev-commons';
import { $RequestExtend, $ResponseExtend } from '@verdaccio/dev-types';
import supertest from 'supertest';
import {
API_ERROR,
API_MESSAGE,
generatePackageMetadata,
HEADER_TYPE,
HEADERS,
} from '@verdaccio/dev-commons';
import { $ResponseExtend, $RequestExtend } from '../../types/custom';
import { initializeServer, publishVersion } from './_helper';
const mockApiJWTmiddleware = jest.fn(() => (req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: 'foo', groups: [], real_groups: [] };
_next();
});
const mockApiJWTmiddleware = jest.fn(
() => (req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: 'foo', groups: [], real_groups: [] };
_next();
}
);
jest.setTimeout(50000000);

View file

@ -1,16 +1,18 @@
import supertest from 'supertest';
import _ from 'lodash';
import { HTTP_STATUS, API_ERROR } from '@verdaccio/commons-api';
import { HEADERS, HEADER_TYPE, API_MESSAGE } from '@verdaccio/dev-commons';
import { $RequestExtend, $ResponseExtend } from '@verdaccio/dev-types';
import { getBadRequest, getConflict, getUnauthorized } from '@verdaccio/commons-api';
import _ from 'lodash';
import { $RequestExtend, $ResponseExtend } from '../../types/custom';
import { initializeServer } from './_helper';
const mockApiJWTmiddleware = jest.fn(() => (req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: 'test', groups: [], real_groups: [] };
_next();
});
const mockApiJWTmiddleware = jest.fn(
() => (req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: 'test', groups: [], real_groups: [] };
_next();
}
);
const mockAuthenticate = jest.fn(() => (_name, _password, callback): void => {
return callback(null, ['all']);
@ -42,10 +44,12 @@ describe('user', () => {
const credentials = { name: 'test', password: 'test' };
test('should test add a new user', async (done) => {
mockApiJWTmiddleware.mockImplementationOnce(() => (req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: undefined };
_next();
});
mockApiJWTmiddleware.mockImplementationOnce(
() => (req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: undefined };
_next();
}
);
mockAddUser.mockImplementationOnce(() => (_name, _password, callback): void => {
return callback(null, true);
@ -72,10 +76,12 @@ describe('user', () => {
});
test('should test fails on add a existing user with login', async (done) => {
mockApiJWTmiddleware.mockImplementationOnce(() => (req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: undefined };
_next();
});
mockApiJWTmiddleware.mockImplementationOnce(
() => (req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: undefined };
_next();
}
);
supertest(await initializeServer('user.yaml'))
.put('/-/user/org.couchdb.user:jotaNew')
.send(credentials)
@ -109,10 +115,12 @@ describe('user', () => {
});
test('should test fails add a new user with missing name', async (done) => {
mockApiJWTmiddleware.mockImplementationOnce(() => (req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: undefined };
_next();
});
mockApiJWTmiddleware.mockImplementationOnce(
() => (req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: undefined };
_next();
}
);
mockAddUser.mockImplementationOnce(() => (_name, _password, callback): void => {
return callback(getBadRequest(API_ERROR.USERNAME_PASSWORD_REQUIRED));
});
@ -136,10 +144,12 @@ describe('user', () => {
});
test('should test fails add a new user with missing password', async (done) => {
mockApiJWTmiddleware.mockImplementationOnce(() => (req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: undefined };
_next();
});
mockApiJWTmiddleware.mockImplementationOnce(
() => (req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: undefined };
_next();
}
);
const credentialsShort = _.cloneDeep(credentials);
delete credentialsShort.password;
@ -162,10 +172,12 @@ describe('user', () => {
});
test('should test fails add a new user with wrong password', async (done) => {
mockApiJWTmiddleware.mockImplementationOnce(() => (req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: 'test' };
_next();
});
mockApiJWTmiddleware.mockImplementationOnce(
() => (req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: 'test' };
_next();
}
);
mockAuthenticate.mockImplementationOnce(() => (_name, _password, callback): void => {
return callback(getUnauthorized(API_ERROR.BAD_USERNAME_PASSWORD));
});
@ -189,10 +201,12 @@ describe('user', () => {
});
test('should be able to logout an user', async (done) => {
mockApiJWTmiddleware.mockImplementationOnce(() => (req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: 'test' };
_next();
});
mockApiJWTmiddleware.mockImplementationOnce(
() => (req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: 'test' };
_next();
}
);
mockAuthenticate.mockImplementationOnce(() => (_name, _password, callback): void => {
return callback(getUnauthorized(API_ERROR.BAD_USERNAME_PASSWORD));
});

View file

@ -2,13 +2,16 @@ import supertest from 'supertest';
import { HTTP_STATUS } from '@verdaccio/commons-api';
import { HEADERS } from '@verdaccio/dev-commons';
import { $RequestExtend, $ResponseExtend } from '@verdaccio/dev-types';
import { $RequestExtend, $ResponseExtend } from '../../types/custom';
import { initializeServer } from './_helper';
const mockApiJWTmiddleware = jest.fn(() => (req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: 'foo', groups: [], real_groups: [] };
_next();
});
const mockApiJWTmiddleware = jest.fn(
() => (req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: 'foo', groups: [], real_groups: [] };
_next();
}
);
jest.mock('@verdaccio/auth', () => ({
Auth: class {

View file

@ -1,5 +1,11 @@
import { HTTP_STATUS, API_ERROR } from '@verdaccio/dev-commons';
import { addVersion, uploadPackageTarball, removeTarball, unPublishPackage, publishPackage } from '../../src/publish';
import {
addVersion,
uploadPackageTarball,
removeTarball,
unPublishPackage,
publishPackage,
} from '../../src/publish';
const REVISION_MOCK = '15-e53a77096b0ee33e';

View file

@ -22,9 +22,6 @@
{
"path": "../store"
},
{
"path": "../types"
},
{
"path": "../middleware"
},

View file

@ -1,4 +1,9 @@
import { Logger, RemoteUser } from '@verdaccio/types';
import { NextFunction, Request, Response } from 'express';
export type $RequestExtend = Request & { remote_user?: any; log: Logger };
export type $ResponseExtend = Response & { cookies?: any };
export type $NextFunctionVer = NextFunction & any;
declare global {
namespace Express {

View file

@ -34,7 +34,6 @@
},
"devDependencies": {
"@verdaccio/config": "workspace:5.0.0-alpha.0",
"@verdaccio/dev-types": "workspace:5.0.0-alpha.0",
"@verdaccio/mock": "workspace:5.0.0-alpha.0",
"@verdaccio/types": "workspace:*"
},

View file

@ -1,8 +1,13 @@
import _ from 'lodash';
import { NextFunction } from 'express';
import { NextFunction, Request, Response } from 'express';
import buildDebug from 'debug';
import { VerdaccioError, getBadRequest, getInternalError, getForbidden } from '@verdaccio/commons-api';
import {
VerdaccioError,
getBadRequest,
getInternalError,
getForbidden,
} from '@verdaccio/commons-api';
import { API_ERROR, SUPPORT_ERRORS, TOKEN_BASIC, TOKEN_BEARER } from '@verdaccio/dev-commons';
import { loadPlugin } from '@verdaccio/loaders';
import {
@ -17,15 +22,62 @@ import {
createRemoteUser,
} from '@verdaccio/utils';
import { Config, Logger, Callback, IPluginAuth, RemoteUser, JWTSignOptions, Security, AuthPluginPackage, AllowAccess, PackageAccess } from '@verdaccio/types';
import { $RequestExtend, $ResponseExtend, IAuth, AESPayload } from '@verdaccio/dev-types';
import { getMiddlewareCredentials, getSecurity, verifyJWTPayload, parseBasicPayload, parseAuthTokenHeader, isAuthHeaderValid, isAESLegacy } from './utils';
import {
Config,
Logger,
Callback,
IPluginAuth,
RemoteUser,
IBasicAuth,
JWTSignOptions,
Security,
AuthPluginPackage,
AllowAccess,
PackageAccess,
} from '@verdaccio/types';
import {
getMiddlewareCredentials,
getSecurity,
verifyJWTPayload,
parseBasicPayload,
parseAuthTokenHeader,
isAuthHeaderValid,
isAESLegacy,
} from './utils';
/* eslint-disable @typescript-eslint/no-var-requires */
const LoggerApi = require('@verdaccio/logger');
const debug = buildDebug('verdaccio:auth');
export interface IAuthWebUI {
jwtEncrypt(user: RemoteUser, signOptions: JWTSignOptions): Promise<string>;
aesEncrypt(buf: Buffer): Buffer;
}
export interface AESPayload {
user: string;
password: string;
}
export type $RequestExtend = Request & { remote_user?: any; log: Logger };
export type $ResponseExtend = Response & { cookies?: any };
export type $NextFunctionVer = NextFunction & any;
export interface IAuthMiddleware {
apiJWTmiddleware(): $NextFunctionVer;
webUIJWTmiddleware(): $NextFunctionVer;
}
export interface IAuth extends IBasicAuth<Config>, IAuthMiddleware, IAuthWebUI {
config: Config;
logger: Logger;
secret: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
plugins: any[];
allow_unpublish(pkg: AuthPluginPackage, user: RemoteUser, callback: Callback): void;
}
class Auth implements IAuth {
public config: Config;
public logger: Logger;
@ -46,19 +98,29 @@ class Auth implements IAuth {
logger: this.logger,
};
return loadPlugin<IPluginAuth<Config>>(config, config.auth, pluginOptions, (plugin: IPluginAuth<Config>): boolean => {
const { authenticate, allow_access, allow_publish } = plugin;
return loadPlugin<IPluginAuth<Config>>(
config,
config.auth,
pluginOptions,
(plugin: IPluginAuth<Config>): boolean => {
const { authenticate, allow_access, allow_publish } = plugin;
// @ts-ignore
return authenticate || allow_access || allow_publish;
});
// @ts-ignore
return authenticate || allow_access || allow_publish;
}
);
}
private _applyDefaultPlugins(): void {
this.plugins.push(getDefaultPlugins(this.logger));
}
public changePassword(username: string, password: string, newPassword: string, cb: Callback): void {
public changePassword(
username: string,
password: string,
newPassword: string,
cb: Callback
): void {
const validPlugins = _.filter(this.plugins, (plugin) => isFunction(plugin.changePassword));
if (_.isEmpty(validPlugins)) {
@ -140,7 +202,9 @@ 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) {
@ -165,10 +229,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;
debug('allow access for %o', packageName);
@ -195,8 +267,15 @@ 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)
);
debug('allow unpublish for %o', packageName);
for (const plugin of this.plugins) {
@ -206,7 +285,10 @@ class Auth implements IAuth {
} else {
plugin.allow_unpublish!(user, pkg, (err, ok: boolean): void => {
if (err) {
debug('forbidden publish for %o, it will fallback on unpublish permissions', packageName);
debug(
'forbidden publish for %o, it will fallback on unpublish permissions',
packageName
);
return callback(err);
}
@ -229,10 +311,17 @@ 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));
const pkg = Object.assign(
{ name: packageName, version: packageVersion },
getMatchedPackagesSpec(packageName, this.config.packages)
);
debug('allow publish for %o init | plugins: %o', packageName, plugins.length);
(function next(): void {
@ -320,7 +409,13 @@ 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
@ -349,7 +444,13 @@ 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;

View file

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

View file

@ -1,8 +1,25 @@
import { Config, RemoteUser, Security } from '@verdaccio/types';
import { AuthMiddlewarePayload, AuthTokenHeader, BasicPayload, IAuthWebUI } from '@verdaccio/dev-types';
import _ from 'lodash';
import { Config, RemoteUser, Security } from '@verdaccio/types';
import { HTTP_STATUS, TOKEN_BASIC, TOKEN_BEARER } from '@verdaccio/dev-commons';
import { aesDecrypt, buildUserBuffer, convertPayloadToBase64, createAnonymousRemoteUser, defaultSecurity, ErrorCode, verifyPayload } from '@verdaccio/utils';
import {
aesDecrypt,
buildUserBuffer,
convertPayloadToBase64,
createAnonymousRemoteUser,
defaultSecurity,
ErrorCode,
verifyPayload,
} from '@verdaccio/utils';
import { IAuthWebUI, AESPayload } from './auth';
export type BasicPayload = AESPayload | void;
export type AuthMiddlewarePayload = RemoteUser | BasicPayload;
export interface AuthTokenHeader {
scheme: string;
token: string;
}
export function parseAuthTokenHeader(authorizationHeader: string): AuthTokenHeader {
const parts = authorizationHeader.split(' ');
@ -27,7 +44,11 @@ export function parseAESCredentials(authorizationHeader: string, secret: string)
}
}
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) {
@ -54,13 +75,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.
@ -70,7 +98,9 @@ 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')
);
});
}

View file

@ -19,9 +19,8 @@ import {
signPayload,
} from '@verdaccio/utils';
import { IAuth } from '@verdaccio/dev-types';
import { Config, Security, RemoteUser } from '@verdaccio/types';
import { Auth } from '../src';
import { Auth, IAuth } from '../src';
import { getMiddlewareCredentials, getApiToken, verifyJWTPayload, getSecurity } from '../src';
setup([]);
@ -86,7 +85,9 @@ describe('Auth utilities', () => {
};
const verifyAES = (token: string, user: string, password: string, secret: string) => {
const payload = aesDecrypt(convertPayloadToBase64(token), secret).toString(CHARACTER_ENCODING.UTF8);
const payload = aesDecrypt(convertPayloadToBase64(token), secret).toString(
CHARACTER_ENCODING.UTF8
);
const content = payload.split(':');
expect(content[0]).toBe(user);
@ -95,49 +96,98 @@ describe('Auth utilities', () => {
describe('getApiToken test', () => {
test('should sign token with aes and security missing', async () => {
const token = await signCredentials('security-missing', 'test', 'test', '1234567', 'aesEncrypt', 'jwtEncrypt');
const token = await signCredentials(
'security-missing',
'test',
'test',
'1234567',
'aesEncrypt',
'jwtEncrypt'
);
verifyAES(token, 'test', 'test', '1234567');
expect(_.isString(token)).toBeTruthy();
});
test('should sign token with aes and security empty', async () => {
const token = await signCredentials('security-empty', 'test', 'test', '123456', 'aesEncrypt', 'jwtEncrypt');
const token = await signCredentials(
'security-empty',
'test',
'test',
'123456',
'aesEncrypt',
'jwtEncrypt'
);
verifyAES(token, 'test', 'test', '123456');
expect(_.isString(token)).toBeTruthy();
});
test('should sign token with aes', async () => {
const token = await signCredentials('security-basic', 'test', 'test', '123456', 'aesEncrypt', 'jwtEncrypt');
const token = await signCredentials(
'security-basic',
'test',
'test',
'123456',
'aesEncrypt',
'jwtEncrypt'
);
verifyAES(token, 'test', 'test', '123456');
expect(_.isString(token)).toBeTruthy();
});
test('should sign token with legacy and jwt disabled', async () => {
const token = await signCredentials('security-no-legacy', 'test', 'test', 'x8T#ZCx=2t', 'aesEncrypt', 'jwtEncrypt');
const token = await signCredentials(
'security-no-legacy',
'test',
'test',
'x8T#ZCx=2t',
'aesEncrypt',
'jwtEncrypt'
);
expect(_.isString(token)).toBeTruthy();
verifyAES(token, 'test', 'test', 'x8T#ZCx=2t');
});
test('should sign token with legacy enabled and jwt enabled', async () => {
const token = await signCredentials('security-jwt-legacy-enabled', 'test', 'test', 'secret', 'jwtEncrypt', 'aesEncrypt');
const token = await signCredentials(
'security-jwt-legacy-enabled',
'test',
'test',
'secret',
'jwtEncrypt',
'aesEncrypt'
);
verifyJWT(token, 'test', 'test', 'secret');
expect(_.isString(token)).toBeTruthy();
});
test('should sign token with jwt enabled', async () => {
const token = await signCredentials('security-jwt', 'test', 'test', 'secret', 'jwtEncrypt', 'aesEncrypt');
const token = await signCredentials(
'security-jwt',
'test',
'test',
'secret',
'jwtEncrypt',
'aesEncrypt'
);
expect(_.isString(token)).toBeTruthy();
verifyJWT(token, 'test', 'test', 'secret');
});
test('should sign with jwt whether legacy is disabled', async () => {
const token = await signCredentials('security-legacy-disabled', 'test', 'test', 'secret', 'jwtEncrypt', 'aesEncrypt');
const token = await signCredentials(
'security-legacy-disabled',
'test',
'test',
'secret',
'jwtEncrypt',
'aesEncrypt'
);
expect(_.isString(token)).toBeTruthy();
verifyJWT(token, 'test', 'test', 'secret');
@ -156,7 +206,14 @@ describe('Auth utilities', () => {
const secret = 'secret';
const user = 'test';
const pass = 'test';
const token = await signCredentials('security-legacy', user, pass, secret, 'aesEncrypt', 'jwtEncrypt');
const token = await signCredentials(
'security-legacy',
user,
pass,
secret,
'aesEncrypt',
'jwtEncrypt'
);
const config: Config = getConfig('security-legacy', secret);
const security: Security = getSecurity(config);
const credentials = getMiddlewareCredentials(security, secret, `Bearer ${token}`);
@ -184,19 +241,41 @@ describe('Auth utilities', () => {
test.concurrent('should return empty credential wrong secret key', async () => {
const secret = 'secret';
const token = await signCredentials('security-legacy', 'test', 'test', secret, 'aesEncrypt', 'jwtEncrypt');
const token = await signCredentials(
'security-legacy',
'test',
'test',
secret,
'aesEncrypt',
'jwtEncrypt'
);
const config: Config = getConfig('security-legacy', secret);
const security: Security = getSecurity(config);
const credentials = getMiddlewareCredentials(security, 'BAD_SECRET', buildToken(TOKEN_BEARER, token));
const credentials = getMiddlewareCredentials(
security,
'BAD_SECRET',
buildToken(TOKEN_BEARER, token)
);
expect(credentials).not.toBeDefined();
});
test.concurrent('should return empty credential wrong scheme', async () => {
const secret = 'secret';
const token = await signCredentials('security-legacy', 'test', 'test', secret, 'aesEncrypt', 'jwtEncrypt');
const token = await signCredentials(
'security-legacy',
'test',
'test',
secret,
'aesEncrypt',
'jwtEncrypt'
);
const config: Config = getConfig('security-legacy', secret);
const security: Security = getSecurity(config);
const credentials = getMiddlewareCredentials(security, secret, buildToken('BAD_SCHEME', token));
const credentials = getMiddlewareCredentials(
security,
secret,
buildToken('BAD_SCHEME', token)
);
expect(credentials).not.toBeDefined();
});
@ -206,7 +285,11 @@ describe('Auth utilities', () => {
const auth: IAuth = new Auth(config);
const token = auth.aesEncrypt(Buffer.from(`corruptedBuffer`)).toString('base64');
const security: Security = getSecurity(config);
const credentials = getMiddlewareCredentials(security, secret, buildToken(TOKEN_BEARER, token));
const credentials = getMiddlewareCredentials(
security,
secret,
buildToken(TOKEN_BEARER, token)
);
expect(credentials).not.toBeDefined();
});
});
@ -229,7 +312,11 @@ describe('Auth utilities', () => {
test('should return anonymous whether token is corrupted', () => {
const config: Config = getConfig('security-jwt', '12345');
const security: Security = getSecurity(config);
const credentials = getMiddlewareCredentials(security, '12345', buildToken(TOKEN_BEARER, 'fakeToken'));
const credentials = getMiddlewareCredentials(
security,
'12345',
buildToken(TOKEN_BEARER, 'fakeToken')
);
expect(credentials).toBeDefined();
// @ts-ignore
@ -243,7 +330,11 @@ describe('Auth utilities', () => {
test('should return anonymous whether token and scheme are corrupted', () => {
const config: Config = getConfig('security-jwt', '12345');
const security: Security = getSecurity(config);
const credentials = getMiddlewareCredentials(security, '12345', buildToken('FakeScheme', 'fakeToken'));
const credentials = getMiddlewareCredentials(
security,
'12345',
buildToken('FakeScheme', 'fakeToken')
);
expect(credentials).not.toBeDefined();
});
@ -252,9 +343,20 @@ describe('Auth utilities', () => {
const secret = 'secret';
const user = 'test';
const config: Config = getConfig('security-jwt', secret);
const token = await signCredentials('security-jwt', user, 'secretTest', secret, 'jwtEncrypt', 'aesEncrypt');
const token = await signCredentials(
'security-jwt',
user,
'secretTest',
secret,
'jwtEncrypt',
'aesEncrypt'
);
const security: Security = getSecurity(config);
const credentials = getMiddlewareCredentials(security, secret, buildToken(TOKEN_BEARER, token));
const credentials = getMiddlewareCredentials(
security,
secret,
buildToken(TOKEN_BEARER, token)
);
expect(credentials).toBeDefined();
// @ts-ignore
expect(credentials.name).toEqual(user);

View file

@ -1,7 +1,7 @@
import _ from 'lodash';
import { Config as AppConfig } from '@verdaccio/config';
import { setup } from '@verdaccio/logger';
import { IAuth } from '@verdaccio/dev-types';
import { IAuth } from '@verdaccio/auth';
import { Config } from '@verdaccio/types';
import { ROLES } from '@verdaccio/dev-commons';
import { getInternalError } from '@verdaccio/commons-api';
@ -35,7 +35,14 @@ describe('AuthTest', () => {
expect(callback).toHaveBeenCalledTimes(1);
expect(callback).toHaveBeenCalledWith(null, {
groups: ['test', ROLES.$ALL, ROLES.$AUTH, ROLES.DEPRECATED_ALL, ROLES.DEPRECATED_AUTH, ROLES.ALL],
groups: [
'test',
ROLES.$ALL,
ROLES.$AUTH,
ROLES.DEPRECATED_ALL,
ROLES.DEPRECATED_AUTH,
ROLES.ALL,
],
name: 'foo',
real_groups: groups,
});

View file

@ -13,11 +13,19 @@ import { isVersionValid, MIN_NODE_VERSION } from './utils';
const isRootUser = process.getuid && process.getuid() === 0;
if (isRootUser) {
global.console.warn(bgYellow().red("*** WARNING: Verdaccio doesn't need superuser privileges. Don't run it under root! ***"));
global.console.warn(
bgYellow().red(
"*** WARNING: Verdaccio doesn't need superuser privileges. Don't run it under root! ***"
)
);
}
if (isVersionValid()) {
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);
}

View file

@ -36,7 +36,14 @@ export default function initProgram(commander, pkgVersion, pkgName) {
// initLogger.warn({file: configPathLocation}, 'config file - @{file}');
startVerdaccio(verdaccioConfiguration, cliListener, configPathLocation, pkgVersion, pkgName, listenDefaultCallback);
startVerdaccio(
verdaccioConfiguration,
cliListener,
configPathLocation,
pkgVersion,
pkgName,
listenDefaultCallback
);
} catch (err) {
// initLogger.fatal({file: configPathLocation, err: err}, 'cannot open config file @{file}: @{!err.message}');
process.exit(1);

View file

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

View file

@ -162,6 +162,7 @@ export const STORAGE = {
DEFAULT_REVISION: '0-0000000000000000'
};
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}`;

View file

@ -4,7 +4,11 @@ export interface DistTags {
[key: string]: string;
}
export function generatePackageMetadata(pkgName: string, version = '1.0.0', distTags: DistTags = { ['latest']: version }): Package {
export function generatePackageMetadata(
pkgName: string,
version = '1.0.0',
distTags: DistTags = { ['latest']: version }
): Package {
// @ts-ignore
return {
_id: pkgName,
@ -38,7 +42,8 @@ export function generatePackageMetadata(pkgName: string, version = '1.0.0', dist
name: 'foo',
},
dist: {
integrity: 'sha512-6gHiERpiDgtb3hjqpQH5/i7zRmvYi9pmCjQf2ZMy3QEa9wVk9RgdZaPWUt7ZOnWUPFjcr9cmE6dUBf+XoPoH4g==',
integrity:
'sha512-6gHiERpiDgtb3hjqpQH5/i7zRmvYi9pmCjQf2ZMy3QEa9wVk9RgdZaPWUt7ZOnWUPFjcr9cmE6dUBf+XoPoH4g==',
shasum: '2c03764f651a9f016ca0b7620421457b619151b9', // pragma: allowlist secret
tarball: `http:\/\/localhost:5555\/${pkgName}\/-\/${pkgName}-${version}.tgz`,
},

View file

@ -22,9 +22,6 @@
"build:js": "babel src/ --out-dir build/ --copy-files --extensions \".ts,.tsx\" --source-maps",
"build": "pnpm run build:js && pnpm run build:types"
},
"devDependencies": {
"@verdaccio/dev-types": "workspace:5.0.0-alpha.0"
},
"dependencies": {
"@verdaccio/dev-commons": "workspace:5.0.0-alpha.0",
"@verdaccio/logger": "workspace:5.0.0-alpha.0",

View file

@ -37,7 +37,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;
}
@ -75,7 +77,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}`);
@ -84,16 +87,17 @@ function updateStorageLinks(configLocation, defaultConfig): string {
}
function getConfigPaths(): SetupDirectory[] {
const listPaths: SetupDirectory[] = [getXDGDirectory(), getWindowsDirectory(), getRelativeDefaultDirectory(), getOldDirectory()].reduce(function (
acc,
currentValue: any
): 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[]);
}, [] as SetupDirectory[]);
return listPaths;
}

View file

@ -12,15 +12,27 @@ import {
} from '@verdaccio/utils';
import { APP_ERROR } from '@verdaccio/dev-commons';
import { PackageList, Config as AppConfig, Security, Logger } from '@verdaccio/types';
import { MatchedPackage, StartUpConfig } from '@verdaccio/dev-types';
import {
PackageList,
Config as AppConfig,
Security,
Logger,
PackageAccess,
} from '@verdaccio/types';
const LoggerApi = require('@verdaccio/logger');
const strategicConfigProps = ['uplinks', 'packages'];
const allowedEnvConfig = ['http_proxy', 'https_proxy', 'no_proxy'];
export type MatchedPackage = PackageAccess | void;
export interface StartUpConfig {
storage: string;
plugins?: string;
self_path: string;
}
/**
* Coordinates the application configuration
*/

View file

@ -113,7 +113,9 @@ export function getBadRequest(customMessage: string): VerdaccioError {
}
export function getInternalError(customMessage?: string): VerdaccioError {
return customMessage ? getError(HTTP_STATUS.INTERNAL_ERROR, customMessage) : getError(HTTP_STATUS.INTERNAL_ERROR, API_ERROR.UNKNOWN_ERROR);
return customMessage
? getError(HTTP_STATUS.INTERNAL_ERROR, customMessage)
: getError(HTTP_STATUS.INTERNAL_ERROR, API_ERROR.UNKNOWN_ERROR);
}
export function getUnauthorized(message = 'no credentials provided'): VerdaccioError {
@ -124,7 +126,9 @@ export function getForbidden(message = "can't use this filename"): VerdaccioErro
return getError(HTTP_STATUS.FORBIDDEN, message);
}
export function getServiceUnavailable(message: string = API_ERROR.RESOURCE_UNAVAILABLE): VerdaccioError {
export function getServiceUnavailable(
message: string = API_ERROR.RESOURCE_UNAVAILABLE
): VerdaccioError {
return getError(HTTP_STATUS.SERVICE_UNAVAILABLE, message);
}

View file

@ -19,7 +19,11 @@ export type ReadFileOptions = {
* @param {*} callback
*/
// eslint-disable-next-line @typescript-eslint/no-empty-function
function readFile(name: string, options: ReadFileOptions = {}, callback: Callback = (): void => {}): void {
function readFile(
name: string,
options: ReadFileOptions = {},
callback: Callback = (): void => {}
): void {
if (typeof options === 'function') {
callback = options;
options = {};

View file

@ -32,7 +32,9 @@ describe('testing locking', () => {
test('file should fail to be found to be locked', (done) => {
lockFile(getFilePath('package.fail.json'), (error: Error) => {
expect(error.message).toMatch(/ENOENT: no such file or directory, stat '(.*)package.fail.json'/);
expect(error.message).toMatch(
/ENOENT: no such file or directory, stat '(.*)package.fail.json'/
);
done();
});
});
@ -83,7 +85,9 @@ describe('testing locking', () => {
parse: true,
};
readFile(getFilePath('package.fail.json'), options, (error: Error) => {
expect(error.message).toMatch(/ENOENT: no such file or directory, open '(.*)package.fail.json'/);
expect(error.message).toMatch(
/ENOENT: no such file or directory, open '(.*)package.fail.json'/
);
done();
});
});

View file

@ -4,7 +4,14 @@ import Path from 'path';
import { Callback, AuthConf, Config, IPluginAuth } from '@verdaccio/types';
import { unlockFile } from '@verdaccio/file-locking';
import { verifyPassword, lockAndRead, parseHTPasswd, addUserToHTPasswd, changePasswordToHTPasswd, sanityCheck } from './utils';
import {
verifyPassword,
lockAndRead,
parseHTPasswd,
addUserToHTPasswd,
changePasswordToHTPasswd,
sanityCheck,
} from './utils';
export interface VerdaccioConfigApp extends Config {
file: string;
@ -197,7 +204,12 @@ export default class HTPasswd implements IPluginAuth<VerdaccioConfigApp> {
* @param {function} cd
* @returns {function}
*/
public changePassword(user: string, password: string, newPassword: string, realCb: Callback): void {
public changePassword(
user: string,
password: string,
newPassword: string,
realCb: Callback
): void {
lockAndRead(this.path, (err, res) => {
let locked = false;
const pathPassFile = this.path;

View file

@ -95,7 +95,13 @@ export function addUserToHTPasswd(body: string, user: string, passwd: string): s
* @param {number} maxUsers
* @returns {object}
*/
export function sanityCheck(user: string, password: string, verifyFn: Callback, users: {}, maxUsers: number): HttpError | null {
export function sanityCheck(
user: string,
password: string,
verifyFn: Callback,
users: {},
maxUsers: number
): HttpError | null {
let err;
// check for user or password
@ -144,7 +150,12 @@ export function getCryptoPassword(password: string): string {
* @param {string} newPasswd
* @returns {string}
*/
export function changePasswordToHTPasswd(body: string, user: string, passwd: string, newPasswd: string): string {
export function changePasswordToHTPasswd(
body: string,
user: string,
passwd: string,
newPasswd: string
): string {
let lines = body.split('\n');
lines = lines.map((line) => {
const [username, password] = line.split(':', 3);

View file

@ -1,6 +1,14 @@
import crypto from 'crypto';
import { verifyPassword, lockAndRead, parseHTPasswd, addUserToHTPasswd, sanityCheck, changePasswordToHTPasswd, getCryptoPassword } from '../src/utils';
import {
verifyPassword,
lockAndRead,
parseHTPasswd,
addUserToHTPasswd,
sanityCheck,
changePasswordToHTPasswd,
getCryptoPassword,
} from '../src/utils';
const mockReadFile = jest.fn();
const mockUnlockFile = jest.fn();
@ -69,7 +77,10 @@ describe('verifyPassword', () => {
expect(verifyPassword(input[0], input[1])).toBeTruthy();
});
it('should verify the bcrypt password with false', () => {
const input = ['testpasswordchanged', '$2y$04$Wqed4yN0OktGbiUdxSTwtOva1xfESfkNIZfcS9/vmHLsn3.lkFxJO'];
const input = [
'testpasswordchanged',
'$2y$04$Wqed4yN0OktGbiUdxSTwtOva1xfESfkNIZfcS9/vmHLsn3.lkFxJO',
];
expect(verifyPassword(input[0], input[1])).toBeFalsy();
});
});
@ -116,7 +127,9 @@ describe('addUserToHTPasswd - crypto', () => {
jest.doMock('../src/crypt3.ts', () => false);
const input = ['', 'username', 'password'];
const utils = require('../src/utils.ts');
expect(utils.addUserToHTPasswd(input[0], input[1], input[2])).toEqual('username:{SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g=:autocreated 2018-01-14T11:17:40.712Z\n');
expect(utils.addUserToHTPasswd(input[0], input[1], input[2])).toEqual(
'username:{SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g=:autocreated 2018-01-14T11:17:40.712Z\n'
);
});
});
@ -229,7 +242,9 @@ describe('changePasswordToHTPasswd', () => {
const body = 'root:$6qLTHoPfGLy2:autocreated 2018-08-20T13:38:12.164Z';
expect(changePasswordToHTPasswd(body, 'root', 'demo123', 'demo123')).toEqual('root:ABfaAAjDKIgfw:autocreated 2018-08-20T13:38:12.164Z');
expect(changePasswordToHTPasswd(body, 'root', 'demo123', 'demo123')).toEqual(
'root:ABfaAAjDKIgfw:autocreated 2018-08-20T13:38:12.164Z'
);
});
test('should change the password when crypt3 is not available', () => {

View file

@ -6,7 +6,17 @@ import buildDebug from 'debug';
import _ from 'lodash';
import async from 'async';
import mkdirp from 'mkdirp';
import { Callback, Config, IPackageStorage, IPluginStorage, LocalStorage, Logger, StorageList, Token, TokenFilter } from '@verdaccio/types';
import {
Callback,
Config,
IPackageStorage,
IPluginStorage,
LocalStorage,
Logger,
StorageList,
Token,
TokenFilter,
} from '@verdaccio/types';
import level from 'level';
import { getInternalError } from '@verdaccio/commons-api';
@ -82,7 +92,11 @@ class LocalDatabase implements IPluginStorage<{}> {
}
}
public search(onPackage: Callback, onEnd: Callback, validateName: (name: string) => boolean): void {
public search(
onPackage: Callback,
onEnd: Callback,
validateName: (name: string) => boolean
): void {
const storages = this._getCustomPackageLocalStorages();
debug(`search custom local packages: %o`, JSON.stringify(storages));
const base = Path.dirname(this.config.self_path);
@ -182,7 +196,10 @@ class LocalDatabase implements IPluginStorage<{}> {
this.get((err, data) => {
if (err) {
cb(getInternalError('error remove private package'));
this.logger.error({ err }, '[local-storage/remove]: remove the private package has failed @{err}');
this.logger.error(
{ err },
'[local-storage/remove]: remove the private package has failed @{err}'
);
debug('error on remove package %o', name);
}
@ -213,7 +230,9 @@ class LocalDatabase implements IPluginStorage<{}> {
public getPackageStorage(packageName: string): IPackageStorage {
const packageAccess = this.config.getMatchedPackagesSpec(packageName);
const packagePath: string = this._getLocalStoragePath(packageAccess ? packageAccess.storage : undefined);
const packagePath: string = this._getLocalStoragePath(
packageAccess ? packageAccess.storage : undefined
);
debug('storage path selected: ', packagePath);
if (_.isString(packagePath) === false) {
@ -221,7 +240,10 @@ class LocalDatabase implements IPluginStorage<{}> {
return;
}
const packageStoragePath: string = Path.join(Path.resolve(Path.dirname(this.config.self_path || ''), packagePath), packageName);
const packageStoragePath: string = Path.join(
Path.resolve(Path.dirname(this.config.self_path || ''), packagePath),
packageName
);
debug('storage absolute path: ', packageStoragePath);
@ -317,8 +339,12 @@ class LocalDatabase implements IPluginStorage<{}> {
debug('sync database started');
if (this.locked) {
this.logger.error('Database is locked, please check error message printed during startup to prevent data loss.');
return new Error('Verdaccio database is locked, please contact your administrator to checkout logs during verdaccio startup.');
this.logger.error(
'Database is locked, please check error message printed during startup to prevent data loss.'
);
return new Error(
'Verdaccio database is locked, please contact your administrator to checkout logs during verdaccio startup.'
);
}
// Uses sync to prevent ugly race condition
try {
@ -383,7 +409,9 @@ class LocalDatabase implements IPluginStorage<{}> {
}
private _dbGenPath(dbName: string, config: Config): string {
return Path.join(Path.resolve(Path.dirname(config.self_path || ''), config.storage as string, dbName));
return Path.join(
Path.resolve(Path.dirname(config.self_path || ''), config.storage as string, dbName)
);
}
/**
@ -404,7 +432,10 @@ class LocalDatabase implements IPluginStorage<{}> {
// Only recreate if file not found to prevent data loss
if (err.code !== noSuchFile) {
this.locked = true;
this.logger.error('Failed to read package database file, please check the error printed below:\n', `File Path: ${this.path}\n\n ${err.message}`);
this.logger.error(
'Failed to read package database file, please check the error printed below:\n',
`File Path: ${this.path}\n\n ${err.message}`
);
}
return emptyDatabase;

View file

@ -78,7 +78,13 @@ export default class LocalFS implements ILocalFSPackageManager {
* @param {*} transformPackage
* @param {*} onEnd
*/
public updatePackage(name: string, updateHandler: Callback, onWrite: Callback, transformPackage: Function, onEnd: Callback): void {
public updatePackage(
name: string,
updateHandler: Callback,
onWrite: Callback,
transformPackage: Function,
onEnd: Callback
): void {
this._lockAndReadJSON(pkgFileName, (err, json) => {
let locked = false;
const self = this;
@ -127,7 +133,10 @@ export default class LocalFS implements ILocalFSPackageManager {
});
}
public deletePackage(packageName: string, callback: (err: NodeJS.ErrnoException | null) => void): void {
public deletePackage(
packageName: string,
callback: (err: NodeJS.ErrnoException | null) => void
): void {
debug('delete a package %o', packageName);
return fs.unlink(this._getStorage(packageName), callback);
@ -190,7 +199,10 @@ export default class LocalFS implements ILocalFSPackageManager {
if (exists) {
uploadStream.emit('error', fSError(fileExist));
} else {
const temporalName = path.join(this.path, `${name}.tmp-${String(Math.random()).replace(/^0\./, '')}`);
const temporalName = path.join(
this.path,
`${name}.tmp-${String(Math.random()).replace(/^0\./, '')}`
);
debug('write a temporal name %o', temporalName);
const file = fs.createWriteStream(temporalName);
const removeTempFile = (): void => fs.unlink(temporalName, () => {});

View file

@ -17,7 +17,10 @@ export function loadPrivatePackages(path: string, logger: Logger): LocalStorage
try {
db = JSON.parse(data);
} catch (err) {
logger.error(`Package database file corrupted (invalid JSON), please check the error printed below.\nFile Path: ${path}`, err);
logger.error(
`Package database file corrupted (invalid JSON), please check the error printed below.\nFile Path: ${path}`,
err
);
throw Error('Package database file corrupted (invalid JSON)');
}

View file

@ -31,7 +31,10 @@ function hasScope(file: string): boolean {
}
/* eslint-disable no-async-promise-executor */
export async function findPackages(storagePath: string, validationHandler: Function): Promise<{ name: string; path: string }[]> {
export async function findPackages(
storagePath: string,
validationHandler: Function
): Promise<{ name: string; path: string }[]> {
const listPackages: { name: string; path: string }[] = [];
return new Promise(async (resolve, reject) => {
try {

View file

@ -30,7 +30,8 @@ const json = {
_nodeVersion: '8.7.0',
_npmUser: {},
dist: {
integrity: 'sha512-6gHiERpiDgtb3hjqpQH5/i7zRmvYi9pmCjQf2ZMy3QEa9wVk9RgdZaPWUt7ZOnWUPFjcr9cmE6dUBf+XoPoH4g==',
integrity:
'sha512-6gHiERpiDgtb3hjqpQH5/i7zRmvYi9pmCjQf2ZMy3QEa9wVk9RgdZaPWUt7ZOnWUPFjcr9cmE6dUBf+XoPoH4g==',
shasum: '2c03764f651a9f016ca0b7620421457b619151b9',
tarball: 'http://localhost:5555/@scope/pk1-test/-/@scope/pk1-test-1.0.6.tgz',
},

View file

@ -24,7 +24,9 @@ let loadPrivatePackages;
describe('Local Database', () => {
beforeEach(() => {
const writeMock = jest.spyOn(fs, 'writeFileSync').mockImplementation();
loadPrivatePackages = jest.spyOn(pkgUtils, 'loadPrivatePackages').mockReturnValue({ list: [], secret: '' });
loadPrivatePackages = jest
.spyOn(pkgUtils, 'loadPrivatePackages')
.mockReturnValue({ list: [], secret: '' });
locaDatabase = new LocalDatabase(optionsPlugin.config, optionsPlugin.logger);
(locaDatabase as LocalDatabase).clean();
writeMock.mockClear();
@ -77,7 +79,13 @@ describe('Local Database', () => {
if (storage) {
const storagePath = path.normalize((storage as ILocalFSPackageManager).path).toLowerCase();
expect(storagePath).toBe(path.normalize(path.join(__dirname, '__fixtures__', optionsPlugin.config.storage || '', pkgName)).toLowerCase());
expect(storagePath).toBe(
path
.normalize(
path.join(__dirname, '__fixtures__', optionsPlugin.config.storage || '', pkgName)
)
.toLowerCase()
);
}
});
@ -90,7 +98,17 @@ describe('Local Database', () => {
if (storage) {
const storagePath = path.normalize((storage as ILocalFSPackageManager).path).toLowerCase();
expect(storagePath).toBe(
path.normalize(path.join(__dirname, '__fixtures__', optionsPlugin.config.storage || '', 'private_folder', pkgName)).toLowerCase()
path
.normalize(
path.join(
__dirname,
'__fixtures__',
optionsPlugin.config.storage || '',
'private_folder',
pkgName
)
)
.toLowerCase()
);
}
});
@ -153,7 +171,11 @@ describe('Local Database', () => {
const scopedPackages = ['@pkg1/test'];
const stats = { mtime: new Date() };
jest.spyOn(fs, 'stat').mockImplementation((_, cb) => cb(null, stats as fs.Stats));
jest.spyOn(fs, 'readdir').mockImplementation((storePath, cb) => cb(null, storePath.match('test-storage') ? scopedPackages : []));
jest
.spyOn(fs, 'readdir')
.mockImplementation((storePath, cb) =>
cb(null, storePath.match('test-storage') ? scopedPackages : [])
);
callSearch(locaDatabase, 1, done);
});
@ -162,7 +184,11 @@ describe('Local Database', () => {
const nonScopedPackages = ['pkg1', 'pkg2'];
const stats = { mtime: new Date() };
jest.spyOn(fs, 'stat').mockImplementation((_, cb) => cb(null, stats as fs.Stats));
jest.spyOn(fs, 'readdir').mockImplementation((storePath, cb) => cb(null, storePath.match('test-storage') ? nonScopedPackages : []));
jest
.spyOn(fs, 'readdir')
.mockImplementation((storePath, cb) =>
cb(null, storePath.match('test-storage') ? nonScopedPackages : [])
);
const db = new LocalDatabase(
assign({}, optionsPlugin.config, {
@ -176,7 +202,9 @@ describe('Local Database', () => {
});
test('should fails on read the storage', (done) => {
const spyInstance = jest.spyOn(fs, 'readdir').mockImplementation((_, cb) => cb(Error('fails'), null));
const spyInstance = jest
.spyOn(fs, 'readdir')
.mockImplementation((_, cb) => cb(Error('fails'), null));
const db = new LocalDatabase(
assign({}, optionsPlugin.config, {
@ -267,7 +295,9 @@ describe('Local Database', () => {
db.createReadStream.mockImplementation(() => stream);
setTimeout(() => events.once['error'](new Error('Unexpected error!')));
await expect(locaDatabase.readTokens({ user: 'someUser' })).rejects.toThrow('Unexpected error!');
await expect(locaDatabase.readTokens({ user: 'someUser' })).rejects.toThrow(
'Unexpected error!'
);
});
});
});

View file

@ -42,7 +42,10 @@ describe('Local FS test', () => {
describe('readPackage() group', () => {
test('readPackage() success', (done) => {
const localFs: ILocalPackageManager = new LocalDriver(path.join(__dirname, '__fixtures__/readme-test'), logger);
const localFs: ILocalPackageManager = new LocalDriver(
path.join(__dirname, '__fixtures__/readme-test'),
logger
);
localFs.readPackage(pkgFileName, (err) => {
expect(err).toBeNull();
@ -51,7 +54,10 @@ describe('Local FS test', () => {
});
test('readPackage() fails', (done) => {
const localFs: ILocalPackageManager = new LocalDriver(path.join(__dirname, '__fixtures__/readme-testt'), logger);
const localFs: ILocalPackageManager = new LocalDriver(
path.join(__dirname, '__fixtures__/readme-testt'),
logger
);
localFs.readPackage(pkgFileName, (err) => {
expect(err).toBeTruthy();
@ -60,7 +66,10 @@ describe('Local FS test', () => {
});
test('readPackage() fails corrupt', (done) => {
const localFs: ILocalPackageManager = new LocalDriver(path.join(__dirname, '__fixtures__/readme-test-corrupt'), logger);
const localFs: ILocalPackageManager = new LocalDriver(
path.join(__dirname, '__fixtures__/readme-test-corrupt'),
logger
);
localFs.readPackage('corrupt.js', (err) => {
expect(err).toBeTruthy();
@ -108,7 +117,10 @@ describe('Local FS test', () => {
});
test('removePackage() success', (done) => {
const localFs: ILocalPackageManager = new LocalDriver(path.join(localTempStorage, '_toDelete'), logger);
const localFs: ILocalPackageManager = new LocalDriver(
path.join(localTempStorage, '_toDelete'),
logger
);
localFs.removePackage((error) => {
expect(error).toBeNull();
done();
@ -116,7 +128,10 @@ describe('Local FS test', () => {
});
test('removePackage() fails', (done) => {
const localFs: ILocalPackageManager = new LocalDriver(path.join(localTempStorage, '_toDelete_fake'), logger);
const localFs: ILocalPackageManager = new LocalDriver(
path.join(localTempStorage, '_toDelete_fake'),
logger
);
localFs.removePackage((error) => {
expect(error).toBeTruthy();
expect(error.code).toBe('ENOENT');
@ -127,7 +142,10 @@ describe('Local FS test', () => {
describe('readTarball() group', () => {
test('readTarball() success', (done) => {
const localFs: ILocalPackageManager = new LocalDriver(path.join(__dirname, '__fixtures__/readme-test'), logger);
const localFs: ILocalPackageManager = new LocalDriver(
path.join(__dirname, '__fixtures__/readme-test'),
logger
);
const readTarballStream = localFs.readTarball('test-readme-0.0.0.tgz');
readTarballStream.on('error', function (err) {
@ -148,7 +166,10 @@ describe('Local FS test', () => {
});
test('readTarball() fails', (done) => {
const localFs: ILocalPackageManager = new LocalDriver(path.join(__dirname, '__fixtures__/readme-test'), logger);
const localFs: ILocalPackageManager = new LocalDriver(
path.join(__dirname, '__fixtures__/readme-test'),
logger
);
const readTarballStream = localFs.readTarball('file-does-not-exist-0.0.0.tgz');
readTarballStream.on('error', function (err) {
@ -167,8 +188,14 @@ describe('Local FS test', () => {
test('writeTarball() success', (done) => {
const newFileName = 'new-readme-0.0.0.tgz';
const readmeStorage: ILocalPackageManager = new LocalDriver(path.join(__dirname, '__fixtures__/readme-test'), logger);
const writeStorage: ILocalPackageManager = new LocalDriver(path.join(__dirname, '../_storage'), logger);
const readmeStorage: ILocalPackageManager = new LocalDriver(
path.join(__dirname, '__fixtures__/readme-test'),
logger
);
const writeStorage: ILocalPackageManager = new LocalDriver(
path.join(__dirname, '../_storage'),
logger
);
const readTarballStream = readmeStorage.readTarball('test-readme-0.0.0.tgz');
const writeTarballStream = writeStorage.writeTarball(newFileName);
@ -207,7 +234,10 @@ describe('Local FS test', () => {
test('writeTarball() abort', (done) => {
const newFileLocationFolder: string = path.join(localTempStorage, '_writeTarball');
const newFileName = 'new-readme-abort-0.0.0.tgz';
const readmeStorage: ILocalPackageManager = new LocalDriver(path.join(__dirname, '__fixtures__/readme-test'), logger);
const readmeStorage: ILocalPackageManager = new LocalDriver(
path.join(__dirname, '__fixtures__/readme-test'),
logger
);
const writeStorage: ILocalPackageManager = new LocalDriver(newFileLocationFolder, logger);
const readTarballStream = readmeStorage.readTarball('test-readme-0.0.0.tgz');
const writeTarballStream = writeStorage.writeTarball(newFileName);
@ -249,7 +279,10 @@ describe('Local FS test', () => {
});
const LocalDriver = require('../src/local-fs').default;
const localFs: ILocalPackageManager = new LocalDriver(path.join(__dirname, '__fixtures__/update-package'), logger);
const localFs: ILocalPackageManager = new LocalDriver(
path.join(__dirname, '__fixtures__/update-package'),
logger
);
localFs.updatePackage('updatePackage', updateHandler, onWrite, transform, () => {
expect(transform).toHaveBeenCalledTimes(1);
@ -268,7 +301,10 @@ describe('Local FS test', () => {
};
});
require('../src/local-fs').default;
const localFs: ILocalPackageManager = new LocalDriver(path.join(__dirname, '__fixtures__/update-package'), logger);
const localFs: ILocalPackageManager = new LocalDriver(
path.join(__dirname, '__fixtures__/update-package'),
logger
);
localFs.updatePackage('updatePackage', updateHandler, onWrite, transform, (err) => {
expect(err).not.toBeNull();
@ -287,7 +323,10 @@ describe('Local FS test', () => {
};
});
const LocalDriver = require('../src/local-fs').default;
const localFs: ILocalPackageManager = new LocalDriver(path.join(__dirname, '__fixtures__/update-package'), logger);
const localFs: ILocalPackageManager = new LocalDriver(
path.join(__dirname, '__fixtures__/update-package'),
logger
);
localFs.updatePackage('updatePackage', updateHandler, onWrite, transform, (err) => {
expect(err).not.toBeNull();
@ -306,7 +345,10 @@ describe('Local FS test', () => {
};
});
const LocalDriver = require('../src/local-fs').default;
const localFs: ILocalPackageManager = new LocalDriver(path.join(__dirname, '__fixtures__/update-package'), logger);
const localFs: ILocalPackageManager = new LocalDriver(
path.join(__dirname, '__fixtures__/update-package'),
logger
);
localFs.updatePackage('updatePackage', updateHandler, onWrite, transform, (err) => {
expect(err).not.toBeNull();
@ -326,7 +368,10 @@ describe('Local FS test', () => {
});
const LocalDriver = require('../src/local-fs').default;
const localFs: ILocalPackageManager = new LocalDriver(path.join(__dirname, '__fixtures__/update-package'), logger);
const localFs: ILocalPackageManager = new LocalDriver(
path.join(__dirname, '__fixtures__/update-package'),
logger
);
const updateHandler = jest.fn((_name, cb) => {
cb(fSError('something wrong', 500));
});

View file

@ -42,25 +42,37 @@ describe('readme', () => {
});
test('should parse basic / local storage', () => {
expect(parseReadme('[Local Storage](javascript:alert(JSON.stringify(localStorage)))')).toEqual('<p><a>Local Storage</a></p>');
expect(
parseReadme('[Local Storage](javascript:alert(JSON.stringify(localStorage)))')
).toEqual('<p><a>Local Storage</a></p>');
});
test('should parse basic / case insensitive', () => {
expect(parseReadme("[CaseInsensitive](JaVaScRiPt:alert('CaseInsensitive'))")).toEqual('<p><a>CaseInsensitive</a></p>');
expect(parseReadme("[CaseInsensitive](JaVaScRiPt:alert('CaseInsensitive'))")).toEqual(
'<p><a>CaseInsensitive</a></p>'
);
});
test('should parse basic / url', () => {
expect(parseReadme("[URL](javascript://www.google.com%0Aalert('URL'))")).toEqual('<p><a>URL</a></p>');
expect(parseReadme("[URL](javascript://www.google.com%0Aalert('URL'))")).toEqual(
'<p><a>URL</a></p>'
);
});
test('should parse basic / in quotes', () => {
expect(parseReadme('[In Quotes](\'javascript:alert("InQuotes")\')')).toEqual('<p><a href="\'javascript:alert(%22InQuotes%22)\'">In Quotes</a></p>');
expect(parseReadme('[In Quotes](\'javascript:alert("InQuotes")\')')).toEqual(
'<p><a href="\'javascript:alert(%22InQuotes%22)\'">In Quotes</a></p>'
);
});
});
describe('should parse images', () => {
test('in quotes', () => {
expect(parseReadme('![Escape SRC - onload](https://www.example.com/image.png"onload="alert(\'ImageOnLoad\'))')).toEqual(
expect(
parseReadme(
'![Escape SRC - onload](https://www.example.com/image.png"onload="alert(\'ImageOnLoad\'))'
)
).toEqual(
'<p><img alt="Escape SRC - onload" src="https://www.example.com/image.png%22onload=%22alert(\'ImageOnLoad\')"></p>'
);
});
@ -78,17 +90,23 @@ describe('readme', () => {
});
test('xss / white space cookie', () => {
expect(parseReadme('[XSS](j a v a s c r i p t:prompt(document.cookie))')).toEqual(
'<p>[XSS](j a v a s c r i p t:prompt(document.cookie))</p>'
);
expect(
parseReadme('[XSS](j a v a s c r i p t:prompt(document.cookie))')
).toEqual('<p>[XSS](j a v a s c r i p t:prompt(document.cookie))</p>');
});
test('xss / data test/html', () => {
expect(parseReadme('[XSS](data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K)')).toEqual('<p><a>XSS</a></p>');
expect(
parseReadme('[XSS](data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K)')
).toEqual('<p><a>XSS</a></p>');
});
test('xss / data test/html encoded', () => {
expect(parseReadme('[XSS](&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x27&#x58&#x53&#x53&#x27&#x29)')).toEqual(
expect(
parseReadme(
'[XSS](&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x27&#x58&#x53&#x53&#x27&#x29)'
)
).toEqual(
'<p><a href="&amp;#x6A&amp;#x61&amp;#x76&amp;#x61&amp;#x73&amp;#x63&amp;#x72&amp;#x69&amp;#x70&amp;#x74&amp;#x3A&amp;#x61&amp;#x6C&amp;#x65&amp;#x72&amp;#x74&amp;#x28&amp;#x27&amp;#x58&amp;#x53&amp;#x53&amp;#x27&amp;#x29">XSS</a></p>'
);
});
@ -98,7 +116,9 @@ describe('readme', () => {
});
test('xss / js window error alert', () => {
expect(parseReadme('[XSS](javascript:window.onerror=alert;throw%20document.cookie)')).toEqual('<p><a>XSS</a></p>');
expect(parseReadme('[XSS](javascript:window.onerror=alert;throw%20document.cookie)')).toEqual(
'<p><a>XSS</a></p>'
);
});
test('xss / js window encoded prompt', () => {
@ -110,15 +130,21 @@ describe('readme', () => {
});
test('xss / js window encoded window error alert multiple statement', () => {
expect(parseReadme('[XSS](javascript:window.onerror=alert;throw%20document.cookie)')).toEqual('<p><a>XSS</a></p>');
expect(parseReadme('[XSS](javascript:window.onerror=alert;throw%20document.cookie)')).toEqual(
'<p><a>XSS</a></p>'
);
});
test('xss / js window encoded window error alert throw error', () => {
expect(parseReadme('[XSS](javascript://%0d%0awindow.onerror=alert;throw%20document.cookie)')).toEqual('<p><a>XSS</a></p>');
expect(
parseReadme('[XSS](javascript://%0d%0awindow.onerror=alert;throw%20document.cookie)')
).toEqual('<p><a>XSS</a></p>');
});
test('xss / js window encoded data text/html base 64', () => {
expect(parseReadme('[XSS](data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K)')).toEqual('<p><a>XSS</a></p>');
expect(
parseReadme('[XSS](data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K)')
).toEqual('<p><a>XSS</a></p>');
});
test('xss / js vbscript alert', () => {
@ -143,7 +169,9 @@ describe('readme', () => {
});
test('xss / js case #5', () => {
expect(parseReadme('[XSS](Javas%26%2399;ript:alert(1&#41;)')).toEqual('<p><a href="Javas%26%2399;ript:alert(1)">XSS</a></p>');
expect(parseReadme('[XSS](Javas%26%2399;ript:alert(1&#41;)')).toEqual(
'<p><a href="Javas%26%2399;ript:alert(1)">XSS</a></p>'
);
});
test('xss / js case #6', () => {
@ -157,34 +185,48 @@ describe('readme', () => {
describe('xss / js url', () => {
test('xss / case #1', () => {
expect(parseReadme('[XSS](javascript://www.google.com%0Aprompt(1))')).toEqual('<p><a>XSS</a></p>');
expect(parseReadme('[XSS](javascript://www.google.com%0Aprompt(1))')).toEqual(
'<p><a>XSS</a></p>'
);
});
test('xss / case #2', () => {
expect(parseReadme('[XSS](javascript://%0d%0aconfirm(1);com)')).toEqual('<p><a>XSS</a></p>');
expect(parseReadme('[XSS](javascript://%0d%0aconfirm(1);com)')).toEqual(
'<p><a>XSS</a></p>'
);
});
test('xss / case #3', () => {
expect(parseReadme('[XSS](javascript:window.onerror=confirm;throw%201)')).toEqual('<p><a>XSS</a></p>');
expect(parseReadme('[XSS](javascript:window.onerror=confirm;throw%201)')).toEqual(
'<p><a>XSS</a></p>'
);
});
test('xss / case #4', () => {
expect(parseReadme('[XSS](<28>javascript:alert(document.domain&#41;)')).toEqual('<p><a href="%EF%BF%BDjavascript:alert(document.domain)">XSS</a></p>');
expect(parseReadme('[XSS](<28>javascript:alert(document.domain&#41;)')).toEqual(
'<p><a href="%EF%BF%BDjavascript:alert(document.domain)">XSS</a></p>'
);
});
test('xss / case #5', () => {
expect(parseReadme('![XSS](javascript:prompt(document.cookie))\\')).toEqual('<p><img alt="XSS">\\</p>');
expect(parseReadme('![XSS](javascript:prompt(document.cookie))\\')).toEqual(
'<p><img alt="XSS">\\</p>'
);
});
test('xss / case #6', () => {
expect(parseReadme('![XSS](data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K)\\')).toEqual(
expect(
parseReadme('![XSS](data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K)\\')
).toEqual(
'<p><img alt="XSS" src="data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K">\\</p>'
);
});
// FIXME: requires proper parsing
test.skip('xss / case #7', () => {
expect(parseReadme(`![XSS'"\`onerror=prompt(document.cookie)](x)\\`)).toEqual('<p>![XSS\'\\"`onerror=prompt(document.cookie)](x)\\\\</p>');
expect(parseReadme(`![XSS'"\`onerror=prompt(document.cookie)](x)\\`)).toEqual(
'<p>![XSS\'\\"`onerror=prompt(document.cookie)](x)\\\\</p>'
);
});
});
});

View file

@ -410,7 +410,11 @@ declare module '@verdaccio/types' {
getSecret(): Promise<string>;
setSecret(secret: string): Promise<any>;
getPackageStorage(packageInfo: string): IPackageStorage;
search(onPackage: onSearchPackage, onEnd: onEndSearchPackage, validateName: onValidatePackage): void;
search(
onPackage: onSearchPackage,
onEnd: onEndSearchPackage,
validateName: onValidatePackage
): void;
}
type StorageUpdateCallback = (data: Package, cb: CallbackAction) => void;
@ -444,7 +448,13 @@ declare module '@verdaccio/types' {
}
interface StoragePackageActions extends TarballActions {
addVersion(name: string, version: string, metadata: Version, tag: StringValue, callback: Callback): void;
addVersion(
name: string,
version: string,
metadata: Version,
tag: StringValue,
callback: Callback
): void;
mergeTags(name: string, tags: MergeTags, callback: Callback): void;
removePackage(name: string, callback: Callback): void;
changePackage(name: string, metadata: Package, revision: string, callback: Callback): void;
@ -521,9 +531,17 @@ declare module '@verdaccio/types' {
allow_publish?(user: RemoteUser, pkg: T & PackageAccess, cb: AuthAccessCallback): void;
allow_access?(user: RemoteUser, pkg: T & PackageAccess, cb: AuthAccessCallback): void;
allow_unpublish?(user: RemoteUser, pkg: T & PackageAccess, cb: AuthAccessCallback): void;
allow_publish?(user: RemoteUser, pkg: AllowAccess & PackageAccess, cb: AuthAccessCallback): void;
allow_publish?(
user: RemoteUser,
pkg: AllowAccess & PackageAccess,
cb: AuthAccessCallback
): void;
allow_access?(user: RemoteUser, pkg: AllowAccess & PackageAccess, cb: AuthAccessCallback): void;
allow_unpublish?(user: RemoteUser, pkg: AllowAccess & PackageAccess, cb: AuthAccessCallback): void;
allow_unpublish?(
user: RemoteUser,
pkg: AllowAccess & PackageAccess,
cb: AuthAccessCallback
): void;
apiJWTmiddleware?(helpers: any): Function;
}

View file

@ -25,7 +25,6 @@
"@verdaccio/auth": "workspace:5.0.0-alpha.0",
"@verdaccio/config": "workspace:5.0.0-alpha.0",
"@verdaccio/dev-commons": "workspace:5.0.0-alpha.0",
"@verdaccio/dev-types": "workspace:5.0.0-alpha.0",
"@verdaccio/types": "workspace:*",
"@verdaccio/utils": "workspace:5.0.0-alpha.0"
},

View file

@ -7,7 +7,12 @@ 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 || '');
@ -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: Notification,
remoteUser: RemoteUser,
publishedPackage: string
): Promise<any> {
return handleNotify(metadata, notify, remoteUser, publishedPackage) as Promise<any>;
}
export function notify(metadata: Package, config: Config, remoteUser: RemoteUser, publishedPackage: string): Promise<any> | void {
export function notify(
metadata: Package,
config: Config,
remoteUser: RemoteUser,
publishedPackage: string
): Promise<any> | void {
if (config.notify) {
if (config.notify.content) {
return sendNotification(metadata, (config.notify as unknown) as Notification, remoteUser, publishedPackage);
return sendNotification(
metadata,
(config.notify as unknown) as Notification,
remoteUser,
publishedPackage
);
}
// multiple notifications endpoints PR #108
return Promise.all(_.map(config.notify, (key) => sendNotification(metadata, key, remoteUser, publishedPackage)));
return Promise.all(
_.map(config.notify, (key) => sendNotification(metadata, key, remoteUser, publishedPackage))
);
}
return Promise.resolve();

View file

@ -15,8 +15,12 @@ const parseConfigurationNotifyFile = (name) => {
return parseConfigurationFile(`notify/${name}`);
};
const singleNotificationConfig = parseConfigFile(parseConfigurationNotifyFile('single.notify'));
const singleHeaderNotificationConfig = parseConfigFile(parseConfigurationNotifyFile('single.header.notify'));
const packagePatternNotificationConfig = parseConfigFile(parseConfigurationNotifyFile('single.packagePattern.notify'));
const singleHeaderNotificationConfig = parseConfigFile(
parseConfigurationNotifyFile('single.header.notify')
);
const packagePatternNotificationConfig = parseConfigFile(
parseConfigurationNotifyFile('single.packagePattern.notify')
);
const multiNotificationConfig = parseConfigFile(parseConfigurationNotifyFile('multiple.notify'));
describe('Notifications:: Notify', () => {

View file

@ -38,7 +38,10 @@ describe('Notifications:: notifyRequest', () => {
});
const notification = require('../src/notify-request');
const args = [{ errorMessage: 'bad data' }, 'notify service has thrown an error: @{errorMessage}'];
const args = [
{ errorMessage: 'bad data' },
'notify service has thrown an error: @{errorMessage}',
];
await expect(notification.notifyRequest(options, content)).rejects.toEqual(API_ERROR.BAD_DATA);
expect(logger.logger.error).toHaveBeenCalledWith(...args);
@ -55,7 +58,10 @@ describe('Notifications:: notifyRequest', () => {
});
const notification = require('../src/notify-request');
const args = [{ errorMessage: 'bad data' }, 'notify service has thrown an error: @{errorMessage}'];
const args = [
{ errorMessage: 'bad data' },
'notify service has thrown an error: @{errorMessage}',
];
await expect(notification.notifyRequest(options, content)).rejects.toEqual(API_ERROR.BAD_DATA);
expect(logger.logger.error).toHaveBeenCalledWith(...args);
@ -75,7 +81,9 @@ describe('Notifications:: notifyRequest', () => {
const infoArgs = [{ content }, 'A notification has been shipped: @{content}'];
const debugArgs = [{ body: 'Successfully delivered' }, ' body: @{body}'];
await expect(notification.notifyRequest(options, content)).resolves.toEqual('Successfully delivered');
await expect(notification.notifyRequest(options, content)).resolves.toEqual(
'Successfully delivered'
);
expect(logger.logger.info).toHaveBeenCalledWith(...infoArgs);
expect(logger.logger.debug).toHaveBeenCalledWith(...debugArgs);
});

View file

@ -20,7 +20,6 @@
},
"devDependencies": {
"@verdaccio/mock": "workspace:5.0.0-alpha.0",
"@verdaccio/dev-types": "workspace:5.0.0-alpha.0",
"@verdaccio/config": "workspace:5.0.0-alpha.0",
"@verdaccio/commons-api": "workspace:*",
"@verdaccio/types": "workspace:*"

View file

@ -50,7 +50,13 @@ function isES6(plugin): boolean {
* @param {*} sanityCheck callback that check the shape that should fulfill the plugin
* @return {Array} list of plugins
*/
export function loadPlugin<T extends IPlugin<T>>(config: Config, pluginConfigs: any = {}, params: any, sanityCheck: any, prefix: string = 'verdaccio'): any[] {
export function loadPlugin<T extends IPlugin<T>>(
config: Config,
pluginConfigs: any = {},
params: any,
sanityCheck: any,
prefix: string = 'verdaccio'
): any[] {
return Object.keys(pluginConfigs).map(
(pluginId: string): IPlugin<T> => {
let plugin;
@ -94,19 +100,27 @@ export function loadPlugin<T extends IPlugin<T>>(config: Config, pluginConfigs:
}
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);
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}');
@ -114,7 +128,10 @@ export function loadPlugin<T extends IPlugin<T>>(config: Config, pluginConfigs:
/* 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`);
}

View file

@ -46,7 +46,9 @@ describe('plugin loader', () => {
return p.authenticate || p.allow_access || p.allow_publish;
});
} catch (e) {
expect(e.message).toEqual(`"${relativePath}/invalid-plugin" plugin does not have the right code structure`);
expect(e.message).toEqual(
`"${relativePath}/invalid-plugin" plugin does not have the right code structure`
);
}
});
@ -58,7 +60,9 @@ describe('plugin loader', () => {
return plugin.authenticate || plugin.allow_access || plugin.allow_publish;
});
} catch (err) {
expect(err.message).toEqual(`sanity check has failed, "${relativePath}/invalid-plugin-sanity" is not a valid plugin`);
expect(err.message).toEqual(
`sanity check has failed, "${relativePath}/invalid-plugin-sanity" is not a valid plugin`
);
}
});
@ -71,7 +75,9 @@ describe('plugin loader', () => {
});
} catch (e) {
expect(e.message).toMatch('plugin not found');
expect(e.message.replace(/\\/g, '/')).toMatch('/partials/test-plugin-storage/invalid-package');
expect(e.message.replace(/\\/g, '/')).toMatch(
'/partials/test-plugin-storage/invalid-package'
);
}
});

View file

@ -62,7 +62,9 @@ function getMessage(debugLevel, msg, sub, templateObjects, hasColors) {
const subSystemType = subSystemLevels.color[sub ?? 'default'];
if (hasColors) {
const logString = `${levelsColors[debugLevel](pad(debugLevel, LEVEL_VALUE_MAX))}${white(`${subSystemType} ${finalMessage}`)}`;
const logString = `${levelsColors[debugLevel](pad(debugLevel, LEVEL_VALUE_MAX))}${white(
`${subSystemType} ${finalMessage}`
)}`;
return padLeft(logString, logString.length + CUSTOM_PAD_LENGTH, ' ');
}
@ -71,7 +73,11 @@ function getMessage(debugLevel, msg, sub, templateObjects, hasColors) {
return padLeft(logString, logString.length + CUSTOM_PAD_LENGTH, ' ');
}
export function printMessage(templateObjects: ObjectTemplate, options: PrettyOptionsExtended, hasColors = true): string {
export function printMessage(
templateObjects: ObjectTemplate,
options: PrettyOptionsExtended,
hasColors = true
): string {
const { prettyStamp } = options;
const { level, msg, sub } = templateObjects;
const debugLevel = calculateLevel(level);

View file

@ -67,7 +67,8 @@ describe('formatter', () => {
status: 200,
error: undefined,
bytes: { in: 0, out: 150186 },
msg: "@{status}, user: @{user}(@{remoteIP}), req: '@{request.method} @{request.url}', bytes: @{bytes.in}/@{bytes.out}",
msg:
"@{status}, user: @{user}(@{remoteIP}), req: '@{request.method} @{request.url}', bytes: @{bytes.in}/@{bytes.out}",
};
expect(printMessage(log, prettyfierOptions)).toMatchSnapshot();
@ -84,7 +85,9 @@ describe('formatter', () => {
err: {
type: 'Error',
message: 'getaddrinfo ENOTFOUND registry.fake.org',
stack: 'Error: getaddrinfo ENOTFOUND registry.fake.org\n' + ' at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:60:26)',
stack:
'Error: getaddrinfo ENOTFOUND registry.fake.org\n' +
' at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:60:26)',
errno: -3008,
code: 'ENOTFOUND',
syscall: 'getaddrinfo',

View file

@ -18,7 +18,12 @@ export type LogPlugin = {
export type LogType = 'file' | 'stdout';
export type LogFormat = 'json' | 'pretty-timestamped' | 'pretty';
export function createLogger(options = {}, destination = pino.destination(1), format: LogFormat = DEFAULT_LOG_FORMAT, prettyPrintOptions = {}) {
export function createLogger(
options = {},
destination = pino.destination(1),
format: LogFormat = DEFAULT_LOG_FORMAT,
prettyPrintOptions = {}
) {
if (_.isNil(format)) {
format = DEFAULT_LOG_FORMAT;
}

View file

@ -26,10 +26,7 @@
"@verdaccio/dev-commons": "workspace:5.0.0-alpha.0",
"@verdaccio/logger": "workspace:5.0.0-alpha.0",
"@verdaccio/utils": "workspace:5.0.0-alpha.0",
"@verdaccio/auth": "workspace:5.0.0-alpha.0",
"lodash": "4.17.15"
},
"devDependencies": {
"@verdaccio/dev-types": "workspace:5.0.0-alpha.0"
},
"gitHead": "7c246ede52ff717707fcae66dd63fc4abd536982"
}
}

View file

@ -8,15 +8,34 @@ import {
stringToMD5,
ErrorCode,
} from '@verdaccio/utils';
import { API_ERROR, HEADER_TYPE, HEADERS, HTTP_STATUS, TOKEN_BASIC, TOKEN_BEARER } from '@verdaccio/dev-commons';
import { $ResponseExtend, $RequestExtend, $NextFunctionVer, IAuth } from '@verdaccio/dev-types';
import { Config, Package, RemoteUser } from '@verdaccio/types';
import {
API_ERROR,
HEADER_TYPE,
HEADERS,
HTTP_STATUS,
TOKEN_BASIC,
TOKEN_BEARER,
} from '@verdaccio/dev-commons';
import { NextFunction, Request, Response } from 'express';
import { Config, Package, RemoteUser, Logger } from '@verdaccio/types';
import { logger } from '@verdaccio/logger';
import { IAuth } from '@verdaccio/auth';
import { VerdaccioError } from '@verdaccio/commons-api';
import { HttpError } from 'http-errors';
export type $RequestExtend = Request & { remote_user?: any; log: Logger };
export type $ResponseExtend = Response & { cookies?: any };
export type $NextFunctionVer = NextFunction & any;
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 {
@ -25,7 +44,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
@ -39,7 +62,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');
@ -52,7 +81,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');
@ -66,14 +101,26 @@ export function validatePackage(req: $RequestExtend, res: $ResponseExtend, next:
export function media(expect: string | null): any {
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');
@ -81,7 +128,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"));
}
@ -108,11 +159,21 @@ export function allow(auth: IAuth): Function {
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}`);
auth['allow_' + action]({ packageName, packageVersion }, remote, function (error, allowed): void {
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);
@ -134,7 +195,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}`);
@ -155,7 +221,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 {
@ -178,7 +247,8 @@ export function final(body: FinalBody, req: $RequestExtend, res: $ResponseExtend
}
// FIXME: deprecated, moved to @verdaccio/dev-commons
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}`;
@ -274,7 +344,12 @@ export function log(req: $RequestExtend, res: $ResponseExtend, next: $NextFuncti
next();
}
export function handleError(err: HttpError, req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer) {
export function handleError(
err: HttpError,
req: $RequestExtend,
res: $ResponseExtend,
next: $NextFunctionVer
) {
if (_.isError(err)) {
if (err.code === 'ECONNABORT' && res.statusCode === HTTP_STATUS.NOT_MODIFIED) {
return next();
@ -292,7 +367,11 @@ export function handleError(err: HttpError, req: $RequestExtend, res: $ResponseE
}
// Middleware
export function errorReportingMiddleware(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
export function errorReportingMiddleware(
req: $RequestExtend,
res: $ResponseExtend,
next: $NextFunctionVer
): void {
res.locals.report_error =
res.locals.report_error ||
function (err: VerdaccioError): void {

View file

@ -32,7 +32,6 @@
"verdaccio": "^4.8.1"
},
"devDependencies": {
"@verdaccio/dev-types": "workspace:5.0.0-alpha.0",
"@verdaccio/types": "workspace:*"
},
"gitHead": "7c246ede52ff717707fcae66dd63fc4abd536982"

View file

@ -7,7 +7,9 @@ import { parseConfigFile } from '@verdaccio/utils';
* Override the default.yaml configuration file with any new config provided.
*/
function configExample(externalConfig, configFile: string = 'default.yaml', location: string = '') {
const locationFile = location ? path.join(location, configFile) : path.join(__dirname, `./config/yaml/${configFile}`);
const locationFile = location
? path.join(location, configFile)
: path.join(__dirname, `./config/yaml/${configFile}`);
const config = parseConfigFile(locationFile);
return _.assign({}, _.cloneDeep(config), externalConfig);

View file

@ -1,7 +1,14 @@
import _ from 'lodash';
import request from 'supertest';
import { DIST_TAGS, LATEST, HEADER_TYPE, HEADERS, HTTP_STATUS, TOKEN_BEARER } from '@verdaccio/dev-commons';
import {
DIST_TAGS,
LATEST,
HEADER_TYPE,
HEADERS,
HTTP_STATUS,
TOKEN_BEARER,
} from '@verdaccio/dev-commons';
import { buildToken, encodeScopedUri } from '@verdaccio/utils';
import { generateRandomHexString } from '@verdaccio/utils';
import { Package } from '@verdaccio/types';
@ -29,9 +36,17 @@ export function getTaggedVersionFromPackage(pkg, pkgName, tag: string = LATEST,
return latestPkg;
}
export function putPackage(request: any, pkgName: string, publishMetadata: Package, token?: string): Promise<any[]> {
export function putPackage(
request: any,
pkgName: string,
publishMetadata: Package,
token?: string
): Promise<any[]> {
return new Promise((resolve) => {
const put = request.put(pkgName).set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON).send(JSON.stringify(publishMetadata));
const put = request
.put(pkgName)
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.send(JSON.stringify(publishMetadata));
if (_.isEmpty(token) === false) {
expect(token).toBeDefined();
@ -50,7 +65,9 @@ export function putPackage(request: any, pkgName: string, publishMetadata: Packa
export function deletePackage(request: any, pkgName: string, token?: string): Promise<any[]> {
return new Promise((resolve) => {
const del = request.put(`/${encodeScopedUri(pkgName)}/-rev/${generateRandomHexString(8)}`).set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON);
const del = request
.put(`/${encodeScopedUri(pkgName)}/-rev/${generateRandomHexString(8)}`)
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON);
if (_.isNil(token) === false) {
del.set(HEADERS.AUTHORIZATION, buildToken(TOKEN_BEARER, token as string));
@ -65,7 +82,12 @@ export function deletePackage(request: any, pkgName: string, token?: string): Pr
});
}
export function getPackage(request: any, token: string, pkgName: string, statusCode: number = HTTP_STATUS.OK): Promise<any[]> {
export function getPackage(
request: any,
token: string,
pkgName: string,
statusCode: number = HTTP_STATUS.OK
): Promise<any[]> {
return new Promise((resolve) => {
const getRequest = request.get(`/${pkgName}`);
@ -82,7 +104,13 @@ export function getPackage(request: any, token: string, pkgName: string, statusC
});
}
export function loginUserToken(request: any, user: string, credentials: any, token: string, statusCode: number = HTTP_STATUS.CREATED): Promise<any[]> {
export function loginUserToken(
request: any,
user: string,
credentials: any,
token: string,
statusCode: number = HTTP_STATUS.CREATED
): Promise<any[]> {
// $FlowFixMe
return new Promise((resolve) => {
request
@ -97,7 +125,12 @@ export function loginUserToken(request: any, user: string, credentials: any, tok
});
}
export function addUser(request: any, user: string, credentials: any, statusCode: number = HTTP_STATUS.CREATED): Promise<any[]> {
export function addUser(
request: any,
user: string,
credentials: any,
statusCode: number = HTTP_STATUS.CREATED
): Promise<any[]> {
// $FlowFixMe
return new Promise((resolve) => {
request
@ -124,7 +157,11 @@ export async function getNewToken(request: any, credentials: any): Promise<strin
});
}
export function getProfile(request: any, token: string, statusCode: number = HTTP_STATUS.OK): Promise<any[]> {
export function getProfile(
request: any,
token: string,
statusCode: number = HTTP_STATUS.OK
): Promise<any[]> {
// $FlowFixMe
return new Promise((resolve) => {
request
@ -138,7 +175,12 @@ export function getProfile(request: any, token: string, statusCode: number = HTT
});
}
export function postProfile(request: any, body: any, token: string, statusCode: number = HTTP_STATUS.OK): Promise<any[]> {
export function postProfile(
request: any,
body: any,
token: string,
statusCode: number = HTTP_STATUS.OK
): Promise<any[]> {
// $FlowFixMe
return new Promise((resolve) => {
request
@ -153,7 +195,13 @@ export function postProfile(request: any, body: any, token: string, statusCode:
});
}
export async function fetchPackageByVersionAndTag(app, encodedPkgName, pkgName, version, tag = 'latest') {
export async function fetchPackageByVersionAndTag(
app,
encodedPkgName,
pkgName,
version,
tag = 'latest'
) {
// we retrieve the package to verify
const [err, resp] = await getPackage(request(app), '', encodedPkgName);
@ -170,7 +218,12 @@ export async function isExistPackage(app, packageName) {
}
export async function verifyPackageVersionDoesExist(app, packageName, version, token?: string) {
const [, res] = await getPackage(request(app), token as string, encodeScopedUri(packageName), HTTP_STATUS.OK);
const [, res] = await getPackage(
request(app),
token as string,
encodeScopedUri(packageName),
HTTP_STATUS.OK
);
const { versions } = res.body;
const versionsKeys = Object.keys(versions);

View file

@ -81,7 +81,12 @@ export function mockServer(port: number, options: MockRegistryOptions = {}) {
fs.copyFileSync(finalOptions.configPath!, tempConfigFile);
fsExtra.copySync(finalOptions.storePath!, storePath);
const verdaccioConfig = new VerdaccioConfig(storePath, tempConfigFile, `http://${DOMAIN_SERVERS}:${port}/`, port);
const verdaccioConfig = new VerdaccioConfig(
storePath,
tempConfigFile,
`http://${DOMAIN_SERVERS}:${port}/`,
port
);
const server: IServerBridge = new Server(verdaccioConfig.domainPath);
return new VerdaccioProcess(verdaccioConfig, server, finalOptions.silence, finalOptions.debug);

View file

@ -147,7 +147,12 @@ export default class Server implements IServerBridge {
}).send(JSON.stringify(version));
}
public putTarballIncomplete(pkgName: string, filename: string, data: any, headerContentSize: number): Promise<any> {
public putTarballIncomplete(
pkgName: string,
filename: string,
data: any,
headerContentSize: number
): Promise<any> {
const promise = this.request({
uri: `/${encodeURIComponent(pkgName)}/-/${encodeURIComponent(filename)}/whatever`,
method: 'PUT',
@ -183,7 +188,9 @@ export default class Server implements IServerBridge {
}
public addPackage(name: string) {
return this.putPackage(name, getPackage(name)).status(HTTP_STATUS.CREATED).body_ok(API_MESSAGE.PKG_CREATED);
return this.putPackage(name, getPackage(name))
.status(HTTP_STATUS.CREATED)
.body_ok(API_MESSAGE.PKG_CREATED);
}
public whoami() {

View file

@ -14,7 +14,12 @@ export default class VerdaccioProcess implements IServerProcess {
private isDebug: boolean;
private silence: boolean;
public constructor(config: IVerdaccioConfig, bridge: IServerBridge, silence = true, isDebug = false) {
public constructor(
config: IVerdaccioConfig,
bridge: IServerBridge,
silence = true,
isDebug = false
) {
this.config = config;
this.bridge = bridge;
this.silence = silence;

View file

@ -11,7 +11,11 @@ export function generateRamdonStorage() {
return path.join(tempRoot, tempStorage);
}
export function generateNewVersion(pkgName: string, version: string, shashum = '238e7641e59508dc9c20eb4ad37a8aa57ab777b4'): Version {
export function generateNewVersion(
pkgName: string,
version: string,
shashum = '238e7641e59508dc9c20eb4ad37a8aa57ab777b4'
): Version {
// $FlowFixMe
return {
name: pkgName,
@ -30,7 +34,8 @@ export function generateNewVersion(pkgName: string, version: string, shashum = '
name: 'Foo',
},
dist: {
integrity: 'sha512-zVEqt1JUCOPsash9q4wMkJEDPD+QCx95TRhQII+JnoS31uBUKoZxhzvvUJCcLVy2CQG4QdwXARU7dYWPnrwhGg==',
integrity:
'sha512-zVEqt1JUCOPsash9q4wMkJEDPD+QCx95TRhQII+JnoS31uBUKoZxhzvvUJCcLVy2CQG4QdwXARU7dYWPnrwhGg==',
shasum: shashum,
tarball: `http:\/\/localhost:4873\/${pkgName}\/-\/${pkgName}-${version}.tgz`,
},

View file

@ -8,6 +8,18 @@ import { Callback } from '@verdaccio/types';
* @param {String} pkgVersion
* @param {String} pkgName
*/
declare function startVerdaccio(config: any, cliListen: string, configPath: string, pkgVersion: string, pkgName: string, callback: Callback): void;
declare function listenDefaultCallback(webServer: Application, addr: any, pkgName: string, pkgVersion: string): void;
declare function startVerdaccio(
config: any,
cliListen: string,
configPath: string,
pkgVersion: string,
pkgName: string,
callback: Callback
): void;
declare function listenDefaultCallback(
webServer: Application,
addr: any,
pkgName: string,
pkgVersion: string
): void;
export { startVerdaccio, listenDefaultCallback };

View file

@ -14,7 +14,15 @@ import { logger } from '@verdaccio/logger';
import { getListListenAddresses, resolveConfigPath } from './cli-utils';
import { displayExperimentsInfoBox } from './experiments';
function launchServer(app, addr, config, configPath: string, pkgVersion: string, pkgName: string, callback: Callback): void {
function launchServer(
app,
addr,
config,
configPath: string,
pkgVersion: string,
pkgName: string,
callback: Callback
): void {
let webServer;
if (addr.proto === 'https') {
webServer = handleHTTPS(app, configPath, config);
@ -22,7 +30,11 @@ function launchServer(app, addr, config, configPath: string, pkgVersion: string,
// http
webServer = http.createServer(app);
}
if (config.server && typeof config.server.keepAliveTimeout !== 'undefined' && config.server.keepAliveTimeout !== 'null') {
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;
}
@ -39,7 +51,14 @@ function launchServer(app, addr, config, configPath: string, pkgVersion: string,
* @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);
}
@ -50,7 +69,9 @@ function startVerdaccio(config: any, cliListen: string, configPath: string, pkgV
displayExperimentsInfoBox(config.experiments);
}
addresses.forEach((addr) => launchServer(app, addr, config, configPath, pkgVersion, pkgName, callback));
addresses.forEach((addr) =>
launchServer(app, addr, config, configPath, pkgVersion, pkgName, callback)
);
});
}
@ -71,7 +92,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 ' +
@ -126,7 +150,12 @@ function handleHTTPS(app: Application, configPath: string, config: ConfigWithHtt
}
}
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

View file

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

View file

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

View file

@ -15,7 +15,10 @@ export 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 ' +

View file

@ -35,18 +35,25 @@ describe('startServer via API', () => {
const version = '1.0.0';
const port = '6000';
await startVerdaccio(configExample(), port, store, version, serverName, (webServer, addrs, pkgName, pkgVersion) => {
expect(webServer).toBeDefined();
expect(addrs).toBeDefined();
expect(addrs.proto).toBe(DEFAULT_PROTOCOL);
expect(addrs.host).toBe(DEFAULT_DOMAIN);
expect(addrs.port).toBe(port);
expect(pkgName).toBeDefined();
expect(pkgVersion).toBeDefined();
expect(pkgVersion).toBe(version);
expect(pkgName).toBe(serverName);
done();
});
await startVerdaccio(
configExample(),
port,
store,
version,
serverName,
(webServer, addrs, pkgName, pkgVersion) => {
expect(webServer).toBeDefined();
expect(addrs).toBeDefined();
expect(addrs.proto).toBe(DEFAULT_PROTOCOL);
expect(addrs.host).toBe(DEFAULT_DOMAIN);
expect(addrs.port).toBe(port);
expect(pkgName).toBeDefined();
expect(pkgVersion).toBeDefined();
expect(pkgVersion).toBe(version);
expect(pkgName).toBe(serverName);
done();
}
);
});
test('should set keepAliveTimeout to 0 seconds', async (done) => {

View file

@ -33,7 +33,6 @@
"request": "2.87.0"
},
"devDependencies": {
"@verdaccio/dev-types": "workspace:5.0.0-alpha.0",
"@verdaccio/types": "workspace:*"
},
"gitHead": "7c246ede52ff717707fcae66dd63fc4abd536982"

View file

@ -6,9 +6,25 @@ import _ from 'lodash';
import request from 'request';
import { parseInterval, isObject, ErrorCode, buildToken } from '@verdaccio/utils';
import { ReadTarball } from '@verdaccio/streams';
import { ERROR_CODE, TOKEN_BASIC, TOKEN_BEARER, HEADERS, HTTP_STATUS, API_ERROR, HEADER_TYPE, CHARACTER_ENCODING } from '@verdaccio/dev-commons';
import { Config, Callback, Headers, Logger, Package } from '@verdaccio/types';
import { IProxy, UpLinkConfLocal } from '@verdaccio/dev-types';
import {
ERROR_CODE,
TOKEN_BASIC,
TOKEN_BEARER,
HEADERS,
HTTP_STATUS,
API_ERROR,
HEADER_TYPE,
CHARACTER_ENCODING,
} from '@verdaccio/dev-commons';
import {
Config,
Callback,
Headers,
Logger,
UpLinkConf,
Package,
IReadTarball,
} from '@verdaccio/types';
const LoggerApi = require('@verdaccio/logger');
const encode = function (thing): string {
@ -25,6 +41,33 @@ const setConfig = (config, key, def): string => {
return _.isNil(config[key]) === false ? config[key] : def;
};
export type UpLinkConfLocal = UpLinkConf & {
no_proxy?: string;
};
export interface ProxyList {
[key: string]: IProxy;
}
export interface IProxy {
config: UpLinkConfLocal;
failed_requests: number;
userAgent: string;
ca?: string | void;
logger: Logger;
server_id: string;
url: any;
maxage: number;
timeout: number;
max_fails: number;
fail_timeout: number;
upname: string;
fetchTarball(url: string): IReadTarball;
isUplinkValid(url: string): boolean;
search(options: any);
getRemoteMetadata(name: string, options: any, callback: Callback): void;
}
/**
* Implements Storage interface
* (same for storage.js, local-storage.js, up-storage.js)
@ -401,8 +444,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
@ -439,7 +485,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);
@ -473,7 +521,10 @@ class ProxyStorage implements IProxy {
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];
@ -523,7 +574,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
@ -572,7 +626,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;
}
}
@ -622,7 +678,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
);
}
/**
@ -632,7 +691,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';
@ -667,7 +731,10 @@ class ProxyStorage implements IProxy {
}
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;
}
@ -680,7 +747,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,7 +1,6 @@
import { Versions, Config } from '@verdaccio/types';
import { IProxy, ProxyList } from '@verdaccio/dev-types';
import { ProxyStorage } from './up-storage';
import { ProxyStorage, IProxy, ProxyList } from './up-storage';
/**
* Set up the Up Storage for each link.

View file

@ -1,6 +1,12 @@
import { buildToken } from '@verdaccio/utils';
import { ERROR_CODE, TOKEN_BASIC, TOKEN_BEARER, DEFAULT_REGISTRY, HEADERS } from '@verdaccio/dev-commons';
import {
ERROR_CODE,
TOKEN_BASIC,
TOKEN_BEARER,
DEFAULT_REGISTRY,
HEADERS,
} from '@verdaccio/dev-commons';
import { setup } from '@verdaccio/logger';
import { ProxyStorage } from '../src/up-storage';

View file

@ -32,7 +32,6 @@
},
"devDependencies": {
"@verdaccio/commons-api": "workspace:*",
"@verdaccio/dev-types": "workspace:5.0.0-alpha.0",
"@verdaccio/mock": "workspace:5.0.0-alpha.0",
"@verdaccio/proxy": "workspace:5.0.0-alpha.0",
"http-errors": "1.7.3",

View file

@ -1,10 +1,14 @@
import _ from 'lodash';
import { Application } from 'express';
import { $ResponseExtend, $RequestExtend, $NextFunctionVer } from '@verdaccio/dev-types';
import { $ResponseExtend, $RequestExtend, $NextFunctionVer } from '../../types/custom';
export default (app: Application, selfPath: string): void => {
// Hook for tests only
app.get('/-/_debug', function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
app.get('/-/_debug', function (
req: $RequestExtend,
res: $ResponseExtend,
next: $NextFunctionVer
): void {
const doGarbabeCollector = _.isNil(global.gc) === false;
if (doGarbabeCollector) {

View file

@ -14,10 +14,12 @@ import { Config as AppConfig } from '@verdaccio/config';
import { webAPI, renderWebMiddleware } from '@verdaccio/web';
import { $ResponseExtend, $RequestExtend, $NextFunctionVer, IStorageHandler, IAuth } from '@verdaccio/dev-types';
import { IAuth } from '@verdaccio/auth';
import { IStorageHandler } from '@verdaccio/store';
import { Config as IConfig, IPluginMiddleware, IPluginStorageFilter } from '@verdaccio/types';
import { setup, logger } from '@verdaccio/logger';
import { log, final, errorReportingMiddleware } from '@verdaccio/middleware';
import { $ResponseExtend, $RequestExtend, $NextFunctionVer } from '../types/custom';
import hookDebug from './debug';
@ -39,7 +41,11 @@ const defineAPI = function (config: IConfig, storage: IStorageHandler): any {
app.use(compression());
app.get('/favicon.ico', function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
app.get('/favicon.ico', function (
req: $RequestExtend,
res: $ResponseExtend,
next: $NextFunctionVer
): void {
req.url = '/-/static/favicon.png';
next();
});
@ -55,14 +61,20 @@ const defineAPI = function (config: IConfig, storage: IStorageHandler): any {
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);
});
// For npm request
// @ts-ignore
app.use(apiEndpoint(config, auth, storage));
// For WebUI & WebUI API
@ -80,7 +92,12 @@ const defineAPI = function (config: IConfig, storage: IStorageHandler): any {
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();
@ -110,7 +127,12 @@ export default (async function (configHash: any): Promise<any> {
config: config,
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

@ -30,7 +30,8 @@ const json = {
_nodeVersion: '8.7.0',
_npmUser: {},
dist: {
integrity: 'sha512-6gHiERpiDgtb3hjqpQH5/i7zRmvYi9pmCjQf2ZMy3QEa9wVk9RgdZaPWUt7ZOnWUPFjcr9cmE6dUBf+XoPoH4g==',
integrity:
'sha512-6gHiERpiDgtb3hjqpQH5/i7zRmvYi9pmCjQf2ZMy3QEa9wVk9RgdZaPWUt7ZOnWUPFjcr9cmE6dUBf+XoPoH4g==',
shasum: '2c03764f651a9f016ca0b7620421457b619151b9',
tarball: 'http://localhost:5555/@scope/pk1-test/-/@scope/pk1-test-1.0.6.tgz',
},

View file

@ -26,7 +26,8 @@ export function generateVersion(pkgName, version) {
name: 'foo',
},
dist: {
integrity: 'sha512-6gHiERpiDgtb3hjqpQH5/i7zRmvYi9pmCjQf2ZMy3QEa9wVk9RgdZaPWUt7ZOnWUPFjcr9cmE6dUBf+XoPoH4g==',
integrity:
'sha512-6gHiERpiDgtb3hjqpQH5/i7zRmvYi9pmCjQf2ZMy3QEa9wVk9RgdZaPWUt7ZOnWUPFjcr9cmE6dUBf+XoPoH4g==',
shasum: '2c03764f651a9f016ca0b7620421457b619151b9', // pragma: allowlist secret
tarball: `http:\/\/localhost:5555\/${pkgName}\/-\/${pkgName}-${version}.tgz`,
},
@ -39,7 +40,10 @@ export function generateVersion(pkgName, version) {
* @param pkgName
* @param _versions
*/
export function generatePackageUnpublish(pkgName: string, _versions: string[] = ['1.0.0']): Package {
export function generatePackageUnpublish(
pkgName: string,
_versions: string[] = ['1.0.0']
): Package {
const latest: string = _versions[_versions.length - 1];
const versions = _versions.reduce((cat, version) => {
cat[version] = generateVersion(pkgName, version);
@ -102,7 +106,8 @@ export function generatePackageMetadata(pkgName: string, version = '1.0.0'): Pac
name: 'foo',
},
dist: {
integrity: 'sha512-6gHiERpiDgtb3hjqpQH5/i7zRmvYi9pmCjQf2ZMy3QEa9wVk9RgdZaPWUt7ZOnWUPFjcr9cmE6dUBf+XoPoH4g==',
integrity:
'sha512-6gHiERpiDgtb3hjqpQH5/i7zRmvYi9pmCjQf2ZMy3QEa9wVk9RgdZaPWUt7ZOnWUPFjcr9cmE6dUBf+XoPoH4g==',
shasum: '2c03764f651a9f016ca0b7620421457b619151b9', // pragma: allowlist secret
tarball: `http:\/\/localhost:5555\/${pkgName}\/-\/${pkgName}-${version}.tgz`,
},
@ -120,7 +125,11 @@ export function generatePackageMetadata(pkgName: string, version = '1.0.0'): Pac
};
}
export function generateDeprecateMetadata(pkgName: string, version = '1.0.0', deprecated: string = ''): Package {
export function generateDeprecateMetadata(
pkgName: string,
version = '1.0.0',
deprecated: string = ''
): Package {
const res = {
...generatePackageMetadata(pkgName, version),
_attachments: {},

View file

@ -2,7 +2,13 @@ import path from 'path';
import request from 'supertest';
import _ from 'lodash';
import { HEADERS, HTTP_STATUS, HEADER_TYPE, API_MESSAGE, TOKEN_BEARER } from '@verdaccio/dev-commons';
import {
HEADERS,
HTTP_STATUS,
HEADER_TYPE,
API_MESSAGE,
TOKEN_BEARER,
} from '@verdaccio/dev-commons';
import { buildToken, encodeScopedUri } from '@verdaccio/utils';
import { setup, logger } from '@verdaccio/logger';
@ -21,7 +27,13 @@ import {
import endPointAPI from '../../src';
import publishMetadata from './helpers/publish-api';
import { generateDeprecateMetadata, generatePackageMetadata, generatePackageUnpublish, generateStarMedatada, generateVersion } from './helpers/utils';
import {
generateDeprecateMetadata,
generatePackageMetadata,
generatePackageUnpublish,
generateStarMedatada,
generateVersion,
} from './helpers/utils';
setup([]);
@ -91,7 +103,9 @@ describe('endpoint unit test', () => {
.expect(HTTP_STATUS.FORBIDDEN)
.end(function (err, res) {
expect(res.body.error).toBeDefined();
expect(res.body.error).toMatch(/authorization required to access package auth-package/);
expect(res.body.error).toMatch(
/authorization required to access package auth-package/
);
done();
});
});
@ -104,7 +118,9 @@ describe('endpoint unit test', () => {
.expect(HTTP_STATUS.FORBIDDEN)
.end(function (err, res) {
expect(res.body.error).toBeDefined();
expect(res.body.error).toMatch(/authorization required to access package auth-package/);
expect(res.body.error).toMatch(
/authorization required to access package auth-package/
);
done();
});
});
@ -117,7 +133,9 @@ describe('endpoint unit test', () => {
.expect(HTTP_STATUS.FORBIDDEN)
.end(function (err, res) {
expect(res.body.error).toBeDefined();
expect(res.body.error).toMatch(/authorization required to access package auth-package/);
expect(res.body.error).toMatch(
/authorization required to access package auth-package/
);
done();
});
});
@ -470,14 +488,24 @@ describe('endpoint unit test', () => {
}
const newVersion = '2.0.1';
const [newErr] = await putPackage(request(app), `/${encodeScopedUri(pkgName)}`, generatePackageMetadata(pkgName, newVersion), token);
const [newErr] = await putPackage(
request(app),
`/${encodeScopedUri(pkgName)}`,
generatePackageMetadata(pkgName, newVersion),
token
);
if (newErr) {
expect(newErr).toBeNull();
return done(newErr);
}
const deletePayload = generatePackageUnpublish(pkgName, ['2.0.0']);
const [err2, res2] = await putPackage(request(app), generateUnPublishURI(pkgName), deletePayload, token);
const [err2, res2] = await putPackage(
request(app),
generateUnPublishURI(pkgName),
deletePayload,
token
);
expect(err2).toBeNull();
expect(res2.body.ok).toMatch(API_MESSAGE.PKG_CHANGED);
@ -526,17 +554,29 @@ describe('endpoint unit test', () => {
const newVersion = '1.0.0';
const token = await getNewToken(request(app), credentials);
const [newErr] = await putPackage(request(app), `/${encodeScopedUri(pkgName)}`, generatePackageMetadata(pkgName, newVersion), token);
const [newErr] = await putPackage(
request(app),
`/${encodeScopedUri(pkgName)}`,
generatePackageMetadata(pkgName, newVersion),
token
);
if (newErr) {
expect(newErr).toBeNull();
return done(newErr);
}
const deletePayload = generatePackageUnpublish(pkgName, ['2.0.0']);
const [err2, res2] = await putPackage(request(app), generateUnPublishURI(pkgName), deletePayload, token);
const [err2, res2] = await putPackage(
request(app),
generateUnPublishURI(pkgName),
deletePayload,
token
);
expect(err2).not.toBeNull();
expect(res2.body.error).toMatch(/user jota_unpublish_fail is not allowed to unpublish package non-unpublish/);
expect(res2.body.error).toMatch(
/user jota_unpublish_fail is not allowed to unpublish package non-unpublish/
);
done();
});
@ -562,10 +602,17 @@ describe('endpoint unit test', () => {
const newVersion = '1.0.0';
const token = await getNewToken(request(app), credentials);
const [newErr, resp] = await putPackage(request(app), `/${encodeScopedUri(pkgName)}`, generatePackageMetadata(pkgName, newVersion), token);
const [newErr, resp] = await putPackage(
request(app),
`/${encodeScopedUri(pkgName)}`,
generatePackageMetadata(pkgName, newVersion),
token
);
expect(newErr).not.toBeNull();
expect(resp.body.error).toMatch(/user jota_only_unpublish_fail is not allowed to publish package only-unpublish/);
expect(resp.body.error).toMatch(
/user jota_only_unpublish_fail is not allowed to publish package only-unpublish/
);
done();
});
});
@ -741,7 +788,12 @@ describe('endpoint unit test', () => {
let token = '';
beforeAll(async (done) => {
token = await getNewToken(request(app), credentials);
await putPackage(request(app), `/${pkgName}`, generatePackageMetadata(pkgName, version), token);
await putPackage(
request(app),
`/${pkgName}`,
generatePackageMetadata(pkgName, version),
token
);
done();
});
@ -775,20 +827,39 @@ describe('endpoint unit test', () => {
let credentials = { name: 'only_publish', password: 'secretPass' };
let token = await getNewToken(request(app), credentials);
const pkg = generateDeprecateMetadata(pkgName, version, 'get deprecated');
const [err, res] = await putPackage(request(app), `/${encodeScopedUri(pkgName)}`, pkg, token);
const [err, res] = await putPackage(
request(app),
`/${encodeScopedUri(pkgName)}`,
pkg,
token
);
expect(err).not.toBeNull();
expect(res.body.error).toBeDefined();
expect(res.body.error).toMatch(/user only_publish is not allowed to unpublish package @scope\/deprecate/);
expect(res.body.error).toMatch(
/user only_publish is not allowed to unpublish package @scope\/deprecate/
);
credentials = { name: 'only_unpublish', password: 'secretPass' };
token = await getNewToken(request(app), credentials);
const [err2, res2] = await putPackage(request(app), `/${encodeScopedUri(pkgName)}`, pkg, token);
const [err2, res2] = await putPackage(
request(app),
`/${encodeScopedUri(pkgName)}`,
pkg,
token
);
expect(err2).not.toBeNull();
expect(res2.body.error).toBeDefined();
expect(res2.body.error).toMatch(/user only_unpublish is not allowed to publish package @scope\/deprecate/);
expect(res2.body.error).toMatch(
/user only_unpublish is not allowed to publish package @scope\/deprecate/
);
});
test('should deprecate multiple packages', async (done) => {
await putPackage(request(app), `/${pkgName}`, generatePackageMetadata(pkgName, '1.0.1'), token);
await putPackage(
request(app),
`/${pkgName}`,
generatePackageMetadata(pkgName, '1.0.1'),
token
);
const pkg = generateDeprecateMetadata(pkgName, version, 'get deprecated');
pkg.versions['1.0.1'] = {
...generateVersion(pkgName, '1.0.1'),

View file

@ -1,10 +1,23 @@
import path from 'path';
import request from 'supertest';
import { HEADERS, HTTP_STATUS, HEADER_TYPE, TOKEN_BEARER, TOKEN_BASIC, API_ERROR } from '@verdaccio/dev-commons';
import {
HEADERS,
HTTP_STATUS,
HEADER_TYPE,
TOKEN_BEARER,
TOKEN_BASIC,
API_ERROR,
} from '@verdaccio/dev-commons';
import { mockServer, generateRamdonStorage } from '@verdaccio/mock';
import { buildUserBuffer, buildToken } from '@verdaccio/utils';
import { configExample, DOMAIN_SERVERS, addUser, getPackage, loginUserToken } from '@verdaccio/mock';
import {
configExample,
DOMAIN_SERVERS,
addUser,
getPackage,
loginUserToken,
} from '@verdaccio/mock';
import { setup, logger } from '@verdaccio/logger';
@ -72,7 +85,12 @@ describe('endpoint user auth JWT unit test', () => {
expect(resp1.body).toBeDefined();
expect(resp1.body.name).toMatch('vue');
const [err2, resp2] = await getPackage(request(app), FAKE_TOKEN, 'vue', HTTP_STATUS.UNAUTHORIZED);
const [err2, resp2] = await getPackage(
request(app),
FAKE_TOKEN,
'vue',
HTTP_STATUS.UNAUTHORIZED
);
expect(err2).toBeNull();
expect(resp2.statusCode).toBe(HTTP_STATUS.UNAUTHORIZED);
expect(resp2.body.error).toMatch(FORBIDDEN_VUE);
@ -106,7 +124,12 @@ describe('endpoint user auth JWT unit test', () => {
});
test('should fails on try to access with corrupted token', async (done) => {
const [err2, resp2] = await getPackage(request(app), FAKE_TOKEN, 'vue', HTTP_STATUS.UNAUTHORIZED);
const [err2, resp2] = await getPackage(
request(app),
FAKE_TOKEN,
'vue',
HTTP_STATUS.UNAUTHORIZED
);
expect(err2).toBeNull();
expect(resp2.statusCode).toBe(HTTP_STATUS.UNAUTHORIZED);
expect(resp2.body.error).toMatch(FORBIDDEN_VUE);
@ -126,7 +149,13 @@ describe('endpoint user auth JWT unit test', () => {
// we login when token is valid
const newCredentials = { name: 'newFailsUser', password: 'BAD_PASSWORD' };
const [err2, resp2] = await loginUserToken(request(app), newCredentials.name, newCredentials, token, HTTP_STATUS.UNAUTHORIZED);
const [err2, resp2] = await loginUserToken(
request(app),
newCredentials.name,
newCredentials,
token,
HTTP_STATUS.UNAUTHORIZED
);
expect(err2).toBeNull();
expect(resp2.statusCode).toBe(HTTP_STATUS.UNAUTHORIZED);
expect(resp2.body.error).toMatch(API_ERROR.BAD_USERNAME_PASSWORD);

View file

@ -5,7 +5,14 @@ import { mockServer } from '@verdaccio/mock';
import { API_ERROR, HTTP_STATUS, SUPPORT_ERRORS } from '@verdaccio/dev-commons';
import { setup, logger } from '@verdaccio/logger';
import { generateRamdonStorage, getNewToken, getProfile, postProfile, configExample, DOMAIN_SERVERS } from '@verdaccio/mock';
import {
generateRamdonStorage,
getNewToken,
getProfile,
postProfile,
configExample,
DOMAIN_SERVERS,
} from '@verdaccio/mock';
import endPointAPI from '../../src';
@ -99,7 +106,12 @@ describe('endpoint user profile', () => {
tfa: {},
};
const token = await getNewToken(request(app), credentials);
const [, resp] = await postProfile(request(app), body, token, HTTP_STATUS.SERVICE_UNAVAILABLE);
const [, resp] = await postProfile(
request(app),
body,
token,
HTTP_STATUS.SERVICE_UNAVAILABLE
);
expect(resp.error).not.toBeNull();
expect(resp.error.text).toMatch(SUPPORT_ERRORS.TFA_DISABLED);

View file

@ -4,7 +4,7 @@ import _ from 'lodash';
import { Config as AppConfig } from '@verdaccio/config';
import { Config, UpLinkConf } from '@verdaccio/types';
import { VerdaccioError } from '@verdaccio/commons-api';
import { IProxy } from '@verdaccio/dev-types';
import { IProxy } from '@verdaccio/proxy';
import { API_ERROR, HTTP_STATUS } from '@verdaccio/dev-commons';
import { mockServer, configExample, DOMAIN_SERVERS } from '@verdaccio/mock';
import { ProxyStorage } from '@verdaccio/proxy';
@ -154,7 +154,10 @@ describe('UpStorge', () => {
describe('UpStorge::isUplinkValid', () => {
describe('valid use cases', () => {
const validateUpLink = (url: string, tarBallUrl = `${url}/artifactory/api/npm/npm/pk1-juan/-/pk1-juan-1.0.7.tgz`) => {
const validateUpLink = (
url: string,
tarBallUrl = `${url}/artifactory/api/npm/npm/pk1-juan/-/pk1-juan-1.0.7.tgz`
) => {
const uplinkConf = { url };
const proxy: IProxy = generateProxy(uplinkConf);
@ -184,7 +187,12 @@ describe('UpStorge', () => {
// corner case https://github.com/verdaccio/verdaccio/issues/571
test('should validate tarball path against uplink case#6', () => {
// same protocol, same domain, port === 443 which is also the standard for https
expect(validateUpLink('https://my.domain.test', `https://my.domain.test:443/artifactory/api/npm/npm/pk1-juan/-/pk1-juan-1.0.7.tgz`)).toBe(true);
expect(
validateUpLink(
'https://my.domain.test',
`https://my.domain.test:443/artifactory/api/npm/npm/pk1-juan/-/pk1-juan-1.0.7.tgz`
)
).toBe(true);
});
test('should validate tarball path against uplink case#7', () => {
@ -229,7 +237,8 @@ describe('UpStorge', () => {
test('should fails on validate tarball path against uplink case#4', () => {
// same domain, same protocol, different port
const url = 'https://subdomain.domain:5001';
const tarBallUrl = 'https://subdomain.domain:4000/api/npm/npm/pk1-juan/-/pk1-juan-1.0.7.tgz';
const tarBallUrl =
'https://subdomain.domain:4000/api/npm/npm/pk1-juan/-/pk1-juan-1.0.7.tgz';
const uplinkConf = { url };
const proxy: IProxy = generateProxy(uplinkConf);

View file

@ -3,7 +3,7 @@ import fs from 'fs';
import { Writable } from 'stream';
import { Config as AppConfig } from '@verdaccio/config';
import { Storage } from '@verdaccio/store';
import { IStorageHandler } from '@verdaccio/dev-types';
import { IStorageHandler } from '@verdaccio/store';
import { Config } from '@verdaccio/types';
import { API_ERROR, HTTP_STATUS } from '@verdaccio/dev-commons';
@ -139,7 +139,9 @@ describe('StorageTest', () => {
reader.on('end', () => {
expect(cachedSpy).toHaveBeenCalledTimes(0);
expect(notcachedSpy).toHaveBeenCalledTimes(1);
expect(notcachedSpy).toHaveBeenCalledWith('http://0.0.0.0:55548/@jquery%2fjquery/-/jquery-1.5.1.tgz');
expect(notcachedSpy).toHaveBeenCalledWith(
'http://0.0.0.0:55548/@jquery%2fjquery/-/jquery-1.5.1.tgz'
);
res();
});
reader.on('error', (err) => {

View file

@ -2,10 +2,23 @@ import path from 'path';
import request from 'supertest';
import _ from 'lodash';
import { HEADERS, HTTP_STATUS, HEADER_TYPE, TOKEN_BEARER, API_ERROR, SUPPORT_ERRORS } from '@verdaccio/dev-commons';
import {
HEADERS,
HTTP_STATUS,
HEADER_TYPE,
TOKEN_BEARER,
API_ERROR,
SUPPORT_ERRORS,
} from '@verdaccio/dev-commons';
import { buildToken } from '@verdaccio/utils';
import { generateRamdonStorage, DOMAIN_SERVERS, mockServer, getNewToken, configExample } from '@verdaccio/mock';
import {
generateRamdonStorage,
DOMAIN_SERVERS,
mockServer,
getNewToken,
configExample,
} from '@verdaccio/mock';
import { setup, logger } from '@verdaccio/logger';

View file

@ -3,7 +3,13 @@ import request from 'supertest';
import { HEADERS, API_ERROR, HTTP_STATUS, HEADER_TYPE, DIST_TAGS } from '@verdaccio/dev-commons';
import { addUser, mockServer, DOMAIN_SERVERS, configExample, generateRamdonStorage } from '@verdaccio/mock';
import {
addUser,
mockServer,
DOMAIN_SERVERS,
configExample,
generateRamdonStorage,
} from '@verdaccio/mock';
import { setup, logger } from '@verdaccio/logger';
import endPointAPI from '../../src';
@ -58,7 +64,11 @@ describe('endpoint web unit test', () => {
.send(JSON.stringify(publishMetadata))
.expect(HTTP_STATUS.CREATED);
await request(app).put('/forbidden-place').set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON).send(JSON.stringify(forbiddenPlace)).expect(HTTP_STATUS.CREATED);
await request(app)
.put('/forbidden-place')
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.send(JSON.stringify(forbiddenPlace))
.expect(HTTP_STATUS.CREATED);
});
describe('Packages', () => {

View file

@ -30,7 +30,8 @@ const json = {
_nodeVersion: '8.7.0',
_npmUser: {},
dist: {
integrity: 'sha512-6gHiERpiDgtb3hjqpQH5/i7zRmvYi9pmCjQf2ZMy3QEa9wVk9RgdZaPWUt7ZOnWUPFjcr9cmE6dUBf+XoPoH4g==',
integrity:
'sha512-6gHiERpiDgtb3hjqpQH5/i7zRmvYi9pmCjQf2ZMy3QEa9wVk9RgdZaPWUt7ZOnWUPFjcr9cmE6dUBf+XoPoH4g==',
shasum: '2c03764f651a9f016ca0b7620421457b619151b9',
tarball: 'http://localhost:5555/forbidden-place/-/forbidden-place-1.0.6.tgz',
},

View file

@ -30,7 +30,8 @@ const json = {
_nodeVersion: '8.7.0',
_npmUser: {},
dist: {
integrity: 'sha512-6gHiERpiDgtb3hjqpQH5/i7zRmvYi9pmCjQf2ZMy3QEa9wVk9RgdZaPWUt7ZOnWUPFjcr9cmE6dUBf+XoPoH4g==',
integrity:
'sha512-6gHiERpiDgtb3hjqpQH5/i7zRmvYi9pmCjQf2ZMy3QEa9wVk9RgdZaPWUt7ZOnWUPFjcr9cmE6dUBf+XoPoH4g==',
shasum: '2c03764f651a9f016ca0b7620421457b619151b9',
tarball: 'http://localhost:5555/@scope/pk1-test/-/@scope/pk1-test-1.0.6.tgz',
},

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