1
0
mirror of https://github.com/verdaccio/verdaccio.git synced 2024-11-13 03:35:52 +01:00

refactor: enable pragma prettify to api modules (#1019)

no logic changes, just formatting

Co-Authored-By: Priscila <priscilawebdev@users.noreply.github.com>
Co-Authored-By: Ayush Sharma <ayush.aceit@gmail.com>
This commit is contained in:
Juan Picado @jotadeveloper 2018-09-28 11:35:21 +02:00 committed by Ayush Sharma
parent 353860d0ba
commit 44144b40ce
40 changed files with 1057 additions and 979 deletions

@ -1,3 +1,7 @@
/**
* @prettier
*/
// @flow
import _ from 'lodash';
@ -5,20 +9,20 @@ import type {$Application} from 'express';
import type {$ResponseExtend, $RequestExtend, $NextFunctionVer} from '../../../types';
export default (app: $Application, selfPath: string) => {
// Hook for tests only
app.get('/-/_debug', function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer) {
const doGarbabeCollector = _.isNil(global.gc) === false;
// Hook for tests only
app.get('/-/_debug', function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer) {
const doGarbabeCollector = _.isNil(global.gc) === false;
if (doGarbabeCollector) {
global.gc();
}
if (doGarbabeCollector) {
global.gc();
}
next({
pid: process.pid,
main: process.mainModule.filename,
conf: selfPath,
mem: process.memoryUsage(),
gc: doGarbabeCollector,
});
next({
pid: process.pid,
main: process.mainModule.filename,
conf: selfPath,
mem: process.memoryUsage(),
gc: doGarbabeCollector,
});
});
};

@ -1,3 +1,7 @@
/**
* @prettier
*/
// @flow
import mime from 'mime';
@ -62,14 +66,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) {
storage.mergeTags(req.params.package, req.body, function(err) {
if (err) {
return next(err);
}
res.status(HTTP_STATUS.CREATED);
return next({
ok: API_MESSAGE.TAG_UPDATED,
});
storage.mergeTags(req.params.package, req.body, function(err) {
if (err) {
return next(err);
}
res.status(HTTP_STATUS.CREATED);
return next({
ok: API_MESSAGE.TAG_UPDATED,
});
});
});
}

@ -1,3 +1,7 @@
/**
* @prettier
*/
// @flow
import _ from 'lodash';

@ -1,3 +1,7 @@
/**
* @prettier
*/
// @flow
import type {Router} from 'express';

@ -1,3 +1,7 @@
/**
* @prettier
*/
// @flow
import _ from 'lodash';
@ -18,8 +22,11 @@ export default function(router: Router, auth: IAuth, storage: IStorageHandler, c
const can = allow(auth);
// publishing a package
router.put('/:package/:_rev?/:revision?', can('publish'),
media(mime.getType('json')), expectJson, function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer) {
router.put('/:package/:_rev?/:revision?', can('publish'), media(mime.getType('json')), expectJson, function(
req: $RequestExtend,
res: $ResponseExtend,
next: $NextFunctionVer
) {
const name = req.params.package;
let metadata;
const create_tarball = function(filename: string, data, cb: Callback) {
@ -61,10 +68,12 @@ export default function(router: Router, auth: IAuth, storage: IStorageHandler, c
// https://github.com/isaacs/npm-registry-client/commit/e9fbeb8b67f249394f735c74ef11fe4720d46ca0
// issue https://github.com/rlidwka/sinopia/issues/31, dealing with it here:
if (typeof(metadata._attachments) !== 'object'
|| Object.keys(metadata._attachments).length !== 1
|| typeof(metadata.versions) !== 'object'
|| Object.keys(metadata.versions).length !== 1) {
if (
typeof metadata._attachments !== 'object' ||
Object.keys(metadata._attachments).length !== 1 ||
typeof metadata.versions !== 'object' ||
Object.keys(metadata.versions).length !== 1
) {
// npm is doing something strange again
// if this happens in normal circumstances, report it as a bug
return next(ErrorCode.getBadRequest('unsupported registry call'));
@ -140,8 +149,7 @@ export default function(router: Router, auth: IAuth, storage: IStorageHandler, c
});
// removing a tarball
router.delete('/:package/-/:filename/-rev/:revision', can('publish'),
function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer) {
router.delete('/:package/-/:filename/-rev/:revision', can('publish'), function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer) {
storage.removeTarball(req.params.package, req.params.filename, req.params.revision, function(err) {
if (err) {
return next(err);
@ -152,8 +160,11 @@ export default function(router: Router, auth: IAuth, storage: IStorageHandler, c
});
// uploading package tarball
router.put('/:package/-/:filename/*', can('publish'), media(HEADERS.OCTET_STREAM),
function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer) {
router.put('/:package/-/:filename/*', can('publish'), media(HEADERS.OCTET_STREAM), function(
req: $RequestExtend,
res: $ResponseExtend,
next: $NextFunctionVer
) {
const name = req.params.package;
const stream = storage.addTarball(name, req.params.filename);
req.pipe(stream);
@ -184,8 +195,11 @@ export default function(router: Router, auth: IAuth, storage: IStorageHandler, c
});
// adding a version
router.put('/:package/:version/-tag/:tag', can('publish'),
media(mime.getType('json')), expectJson, function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer) {
router.put('/:package/:version/-tag/:tag', can('publish'), media(mime.getType('json')), expectJson, function(
req: $RequestExtend,
res: $ResponseExtend,
next: $NextFunctionVer
) {
const {version, tag} = req.params;
const name = req.params.package;

@ -1,6 +1,10 @@
/**
* @prettier
*/
export default function(route, auth, storage) {
// searching packages
route.get('/-/all(\/since)?', function(req, res) {
route.get('/-/all(/since)?', function(req, res) {
let received_end = false;
let response_finished = false;
let processing_pkgs = 0;

@ -1,3 +1,7 @@
/**
* @prettier
*/
// @flow
import _ from 'lodash';
@ -23,7 +27,7 @@ export default function(route: Router, auth: IAuth, config: Config) {
const {name, password} = req.body;
if (_.isNil(req.remote_user.name) === false) {
const token = (name && password) ? await getApiToken(auth, config, req.remote_user, password) : undefined;
const token = name && password ? await getApiToken(auth, config, req.remote_user, password) : undefined;
res.status(HTTP_STATUS.CREATED);
@ -38,17 +42,17 @@ export default function(route: Router, auth: IAuth, config: Config) {
// 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);
return next({
ok: `user '${req.body.name }' created`,
ok: `user '${req.body.name}' created`,
token,
});
});
@ -62,7 +66,6 @@ export default function(route: Router, auth: IAuth, config: Config) {
});
});
// placeholder 'cause npm require to be authenticated to publish
// we do not do any real authentication yet
route.post('/_session', Cookies.express(), function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer) {

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

@ -1,3 +1,7 @@
/**
* @prettier
*/
// @flow
import type {IAuth, IStorageHandler} from '../../../types';

@ -1,3 +1,7 @@
/**
* @prettier
*/
// @flow
import _ from 'lodash';
@ -16,16 +20,8 @@ import webAPI from './web/api';
import web from './web';
import type {$Application} from 'express';
import type {
$ResponseExtend,
$RequestExtend,
$NextFunctionVer,
IStorageHandler,
IAuth} from '../../types';
import type {
Config as IConfig,
IPluginMiddleware,
} from '@verdaccio/types';
import type {$ResponseExtend, $RequestExtend, $NextFunctionVer, IStorageHandler, IAuth} from '../../types';
import type {Config as IConfig, IPluginMiddleware} from '@verdaccio/types';
const LoggerApp = require('../lib/logger');
const Middleware = require('./middleware');
@ -67,7 +63,7 @@ const defineAPI = function(config: IConfig, storage: IStorageHandler) {
const plugins = loadPlugin(config, config.middlewares, plugin_params, function(plugin: IPluginMiddleware) {
return plugin.register_middlewares;
});
plugins.forEach((plugin) => {
plugins.forEach(plugin => {
plugin.register_middlewares(app, auth, storage);
});
@ -111,11 +107,11 @@ const defineAPI = function(config: IConfig, storage: IStorageHandler) {
return app;
};
export default async function(configHash: any) {
export default (async function(configHash: any) {
LoggerApp.setup(configHash.logs);
const config: IConfig = new AppConfig(configHash);
const storage: IStorageHandler = new Storage(config);
// waits until init calls have been intialized
await storage.init(config);
return defineAPI(config, storage);
}
});

@ -1,12 +1,12 @@
/**
* @prettier
*/
// @flow
import _ from 'lodash';
import {
validateName as utilValidateName,
validatePackage as utilValidatePackage,
isObject,
ErrorCode} from '../lib/utils';
import {validateName as utilValidateName, validatePackage as utilValidatePackage, isObject, ErrorCode} from '../lib/utils';
import {API_ERROR, HEADER_TYPE, HEADERS, HTTP_STATUS, TOKEN_BASIC, TOKEN_BEARER} from '../lib/constants';
import {stringToMD5} from '../lib/crypto-utils';
import type {$ResponseExtend, $RequestExtend, $NextFunctionVer, IAuth} from '../../types';
@ -39,7 +39,7 @@ export function validateName(req: $RequestExtend, res: $ResponseExtend, next: $N
} else if (utilValidateName(value)) {
next();
} else {
next( ErrorCode.getForbidden('invalid ' + name));
next(ErrorCode.getForbidden('invalid ' + name));
}
}
@ -52,15 +52,14 @@ export function validatePackage(req: $RequestExtend, res: $ResponseExtend, next:
} else if (utilValidatePackage(value)) {
next();
} else {
next( ErrorCode.getForbidden('invalid ' + name) );
next(ErrorCode.getForbidden('invalid ' + name));
}
}
export function media(expect: string) {
return function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer) {
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();
}
@ -77,7 +76,7 @@ export function encodeScopePackage(req: $RequestExtend, res: $ResponseExtend, ne
export function expectJson(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer) {
if (!isObject(req.body)) {
return next( ErrorCode.getBadRequest('can\'t parse incoming json') );
return next(ErrorCode.getBadRequest("can't parse incoming json"));
}
next();
}
@ -87,10 +86,10 @@ export function anti_loop(config: Config) {
if (req.headers.via != null) {
let arr = req.headers.via.split(',');
for (let i=0; i<arr.length; i++) {
for (let i = 0; i < arr.length; i++) {
const m = arr[i].match(/\s*(\S+)\s+(\S+)/);
if (m && m[2] === config.server_id) {
return next( ErrorCode.getCode(HTTP_STATUS.LOOP_DETECTED, 'loop detected') );
return next(ErrorCode.getCode(HTTP_STATUS.LOOP_DETECTED, 'loop detected'));
}
}
}
@ -123,7 +122,7 @@ export function allow(auth: IAuth) {
};
}
export function final(body: any, req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer) {
export function final(body: any, req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer) {
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}`);
@ -135,8 +134,8 @@ export function allow(auth: IAuth) {
res.header(HEADERS.CONTENT_TYPE, HEADERS.JSON);
}
if (typeof(body) === 'object' && _.isNil(body) === false) {
if (typeof(body.error) === 'string') {
if (typeof body === 'object' && _.isNil(body) === false) {
if (typeof body.error === 'string') {
res._verdaccio_error = body.error;
}
body = JSON.stringify(body, undefined, ' ') + '\n';
@ -181,8 +180,7 @@ export function log(req: $RequestExtend, res: $ResponseExtend, next: $NextFuncti
}
req.url = req.originalUrl;
req.log.info( {req: req, ip: req.ip}
, '@{ip} requested \'@{req.method} @{req.url}\'' );
req.log.info({req: req, ip: req.ip}, "@{ip} requested '@{req.method} @{req.url}'");
req.originalUrl = req.url;
if (_.isNil(_auth) === false) {
@ -210,7 +208,7 @@ export function log(req: $RequestExtend, res: $ResponseExtend, next: $NextFuncti
let forwardedFor = req.headers['x-forwarded-for'];
let remoteAddress = req.connection.remoteAddress;
let remoteIP = forwardedFor ? `${forwardedFor} via ${remoteAddress}` : remoteAddress;
let message = '@{status}, user: @{user}(@{remoteIP}), req: \'@{request.method} @{request.url}\'';
let message = "@{status}, user: @{user}(@{remoteIP}), req: '@{request.method} @{request.url}'";
if (res._verdaccio_error) {
message += ', error: @{!error}';
} else {
@ -218,21 +216,24 @@ export function log(req: $RequestExtend, res: $ResponseExtend, next: $NextFuncti
}
req.url = req.originalUrl;
req.log.warn({
request: {
method: req.method,
url: req.url,
req.log.warn(
{
request: {
method: req.method,
url: req.url,
},
level: 35, // http
user: (req.remote_user && req.remote_user.name) || null,
remoteIP,
status: res.statusCode,
error: res._verdaccio_error,
bytes: {
in: bytesin,
out: bytesout,
},
},
level: 35, // http
user: req.remote_user && req.remote_user.name || null,
remoteIP,
status: res.statusCode,
error: res._verdaccio_error,
bytes: {
in: bytesin,
out: bytesout,
},
}, message);
message
);
req.originalUrl = req.url;
};
@ -254,25 +255,27 @@ export function log(req: $RequestExtend, res: $ResponseExtend, next: $NextFuncti
// Middleware
export function errorReportingMiddleware(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer) {
res.report_error = res.report_error || function(err) {
if (err.status && err.status >= HTTP_STATUS.BAD_REQUEST && err.status < 600) {
if (_.isNil(res.headersSent) === false) {
res.status(err.status);
next({error: err.message || API_ERROR.UNKNOWN_ERROR});
}
} else {
Logger.logger.error( {err: err}, 'unexpected error: @{!err.message}\n@{err.stack}');
if (!res.status || !res.send) {
Logger.logger.error('this is an error in express.js, please report this');
res.destroy();
} else if (!res.headersSent) {
res.status(HTTP_STATUS.INTERNAL_ERROR);
next({error: API_ERROR.INTERNAL_SERVER_ERROR});
res.report_error =
res.report_error ||
function(err) {
if (err.status && err.status >= HTTP_STATUS.BAD_REQUEST && err.status < 600) {
if (_.isNil(res.headersSent) === false) {
res.status(err.status);
next({error: err.message || API_ERROR.UNKNOWN_ERROR});
}
} else {
// socket should be already closed
Logger.logger.error({err: err}, 'unexpected error: @{!err.message}\n@{err.stack}');
if (!res.status || !res.send) {
Logger.logger.error('this is an error in express.js, please report this');
res.destroy();
} else if (!res.headersSent) {
res.status(HTTP_STATUS.INTERNAL_ERROR);
next({error: API_ERROR.INTERNAL_SERVER_ERROR});
} else {
// socket should be already closed
}
}
}
};
};
next();
}

@ -1,3 +1,7 @@
/**
* @prettier
*/
// @flow
import {Router} from 'express';

@ -1,34 +1,32 @@
/**
* @prettier
*/
// @flow
import _ from 'lodash';
import {addScope, addGravatarSupport, deleteProperties, sortByName, DIST_TAGS, parseReadme} from '../../../lib/utils';
import {allow} from '../../middleware';
import type {Router} from 'express';
import type {
IAuth,
$ResponseExtend,
$RequestExtend,
$NextFunctionVer,
IStorageHandler,
$SidebarPackage} from '../../../../types';
import type {IAuth, $ResponseExtend, $RequestExtend, $NextFunctionVer, IStorageHandler, $SidebarPackage} from '../../../../types';
function addPackageWebApi(route: Router, storage: IStorageHandler, auth: IAuth) {
const can = allow(auth);
const checkAllow = (name, remoteUser) => new Promise((resolve, reject) => {
try {
auth.allow_access(name, remoteUser, (err, allowed) => {
if (err) {
resolve(false);
} else {
resolve(allowed);
}
});
} catch (err) {
reject(err);
}
});
const checkAllow = (name, remoteUser) =>
new Promise((resolve, reject) => {
try {
auth.allow_access(name, remoteUser, (err, allowed) => {
if (err) {
resolve(false);
} else {
resolve(allowed);
}
});
} catch (err) {
reject(err);
}
});
// Get list of all visible package
route.get('/packages', function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer) {
@ -57,8 +55,7 @@ function addPackageWebApi(route: Router, storage: IStorageHandler, auth: IAuth)
});
// Get package readme
route.get('/package/readme/(@:scope/)?:package/:version?', can('access'),
function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer) {
route.get('/package/readme/(@:scope/)?:package/:version?', can('access'), function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer) {
const packageName = req.params.scope ? addScope(req.params.scope, req.params.package) : req.params.package;
storage.getPackage({
@ -75,8 +72,7 @@ function addPackageWebApi(route: Router, storage: IStorageHandler, auth: IAuth)
});
});
route.get('/sidebar/(@:scope/)?:package',
function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer) {
route.get('/sidebar/(@:scope/)?:package', function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer) {
const packageName: string = req.params.scope ? addScope(req.params.scope, req.params.package) : req.params.package;
storage.getPackage({

@ -1,3 +1,7 @@
/**
* @prettier
*/
// @flow
import Search from '../../../lib/search';

@ -1,3 +1,7 @@
/**
* @prettier
*/
// @flow
import {HTTP_STATUS} from '../../../lib/constants';

@ -1,3 +1,7 @@
/**
* @prettier
*/
import _ from 'lodash';
import fs from 'fs';
import path from 'path';
@ -16,10 +20,10 @@ const templatePath = path.join(env.DIST_PATH, '/index.html');
const existTemplate = fs.existsSync(templatePath);
if (!existTemplate) {
const err = new VError('missing file: "%s", run `yarn build:webui`', templatePath);
/* eslint no-console:off */
const err = new VError('missing file: "%s", run `yarn build:webui`', templatePath);
/* eslint no-console:off */
console.error(chalk.red(err.message));
/* eslint no-console:off */
/* eslint no-console:off */
process.exit(2);
}
@ -56,9 +60,7 @@ module.exports = function(config, auth, storage) {
});
router.get('/', function(req, res) {
const base = Utils.combineBaseUrl(
Utils.getWebProtocol(req.get(HEADERS.FORWARDED_PROTO), req.protocol),
req.get('host'), config.url_prefix);
const base = Utils.combineBaseUrl(Utils.getWebProtocol(req.get(HEADERS.FORWARDED_PROTO), req.protocol), req.get('host'), config.url_prefix);
let webPage = template
.replace(/ToReplaceByVerdaccio/g, base)

@ -1,3 +1,9 @@
/**
* @prettier
*/
// @flow
const path = require('path');
const APP_ROOT = path.resolve(__dirname, '../../');

@ -1,34 +1,24 @@
/**
* @prettier
*/
// @flow
import _ from 'lodash';
import {convertPayloadToBase64, ErrorCode} from './utils';
import {API_ERROR, HTTP_STATUS, ROLES, TIME_EXPIRATION_7D, TOKEN_BASIC, TOKEN_BEARER, CHARACTER_ENCODING} from './constants';
import type {
RemoteUser,
Package,
Callback,
Config,
Security,
APITokenOptions,
JWTOptions} from '@verdaccio/types';
import type {
CookieSessionToken, IAuthWebUI, AuthMiddlewarePayload, AuthTokenHeader, BasicPayload,
} from '../../types';
import type {RemoteUser, Package, Callback, Config, Security, APITokenOptions, JWTOptions} from '@verdaccio/types';
import type {CookieSessionToken, IAuthWebUI, AuthMiddlewarePayload, AuthTokenHeader, BasicPayload} from '../../types';
import {aesDecrypt, verifyPayload} from './crypto-utils';
/**
* Create a RemoteUser object
* @return {Object} { name: xx, pluginGroups: [], real_groups: [] }
*/
export function createRemoteUser(name: string, pluginGroups: Array<string>): RemoteUser {
const isGroupValid: boolean = Array.isArray(pluginGroups);
const groups = (isGroupValid ? pluginGroups : []).concat([
ROLES.$ALL,
ROLES.$AUTH,
ROLES.DEPRECATED_ALL,
ROLES.DEPRECATED_AUTH,
ROLES.ALL]);
const groups = (isGroupValid ? pluginGroups : []).concat([ROLES.$ALL, ROLES.$AUTH, ROLES.DEPRECATED_ALL, ROLES.DEPRECATED_AUTH, ROLES.ALL]);
return {
name,
@ -37,7 +27,6 @@ export function createRemoteUser(name: string, pluginGroups: Array<string>): Rem
};
}
/**
* Builds an anonymous remote user in case none is logged in.
* @return {Object} { name: xx, groups: [], real_groups: [] }
@ -46,12 +35,7 @@ export function createAnonymousRemoteUser(): RemoteUser {
return {
name: undefined,
// groups without '$' are going to be deprecated eventually
groups: [
ROLES.$ALL,
ROLES.$ANONYMOUS,
ROLES.DEPRECATED_ALL,
ROLES.DEPRECATED_ANONYMOUS,
],
groups: [ROLES.$ALL, ROLES.$ANONYMOUS, ROLES.DEPRECATED_ALL, ROLES.DEPRECATED_ANONYMOUS],
real_groups: [],
};
}
@ -59,7 +43,7 @@ export function createAnonymousRemoteUser(): RemoteUser {
export function allow_action(action: string) {
return function(user: RemoteUser, pkg: Package, callback: Callback) {
const {name, groups} = user;
const hasPermission = pkg[action].some((group) => name === group || groups.includes(group));
const hasPermission = pkg[action].some(group => name === group || groups.includes(group));
if (hasPermission) {
return callback(null, true);
@ -105,8 +89,8 @@ const defaultWebTokenOptions: JWTOptions = {
};
const defaultApiTokenConf: APITokenOptions = {
legacy: true,
sign: {},
legacy: true,
sign: {},
};
export function getSecurity(config: Config): Security {
@ -133,32 +117,28 @@ export function buildUserBuffer(name: string, password: string) {
export function isAESLegacy(security: Security): boolean {
const {legacy, jwt} = security.api;
return _.isNil(legacy) === false &&_.isNil(jwt) && legacy === true;
return _.isNil(legacy) === false && _.isNil(jwt) && legacy === true;
}
export async function getApiToken(
auth: IAuthWebUI,
config: Config,
remoteUser: RemoteUser,
aesPassword: string): Promise<string> {
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) => {
// fallback all goes to AES encryption
return await new Promise(resolve => {
resolve(auth.aesEncrypt(buildUserBuffer((remoteUser: any).name, aesPassword)).toString('base64'));
});
} else {
// i am wiling to use here _.isNil but flow does not like it yet.
// i am wiling to use here _.isNil but flow does not like it yet.
const {jwt} = security.api;
if (jwt && jwt.sign) {
return await auth.jwtEncrypt(remoteUser, jwt.sign);
} else {
return await new Promise((resolve) => {
return await new Promise(resolve => {
resolve(auth.aesEncrypt(buildUserBuffer((remoteUser: any).name, aesPassword)).toString('base64'));
});
}
}
}
}
@ -181,8 +161,7 @@ export function parseBasicPayload(credentials: string): BasicPayload {
return {user, password};
}
export function parseAESCredentials(
authorizationHeader: string, secret: string) {
export function parseAESCredentials(authorizationHeader: string, secret: string) {
const {scheme, token} = parseAuthTokenHeader(authorizationHeader);
// basic is deprecated and should not be enforced
@ -222,11 +201,7 @@ export function isAuthHeaderValid(authorization: string): boolean {
return authorization.split(' ').length === 2;
}
export function getMiddlewareCredentials(
security: Security,
secret: string,
authorizationHeader: string
): AuthMiddlewarePayload {
export function getMiddlewareCredentials(security: Security, secret: string, authorizationHeader: string): AuthMiddlewarePayload {
if (isAESLegacy(security)) {
const credentials = parseAESCredentials(authorizationHeader, secret);
if (!credentials) {
@ -243,7 +218,7 @@ export function getMiddlewareCredentials(
const {scheme, token} = parseAuthTokenHeader(authorizationHeader);
if (_.isString(token) && scheme.toUpperCase() === TOKEN_BEARER.toUpperCase()) {
return verifyJWTPayload(token, secret);
return verifyJWTPayload(token, secret);
}
}
}

@ -1,3 +1,7 @@
/**
* @prettier
*/
// @flow
import _ from 'lodash';
@ -12,14 +16,15 @@ import {
createAnonymousRemoteUser,
isAuthHeaderValid,
getSecurity,
isAESLegacy, parseAuthTokenHeader, parseBasicPayload, createRemoteUser,
isAESLegacy,
parseAuthTokenHeader,
parseBasicPayload,
createRemoteUser,
} from './auth-utils';
import {convertPayloadToBase64, ErrorCode} from './utils';
import {getMatchedPackagesSpec} from './config-utils';
import type {
Config, Logger, Callback, IPluginAuth, RemoteUser, JWTSignOptions, Security,
} from '@verdaccio/types';
import type {Config, Logger, Callback, IPluginAuth, RemoteUser, JWTSignOptions, Security} from '@verdaccio/types';
import type {$Response, NextFunction} from 'express';
import type {$RequestExtend, IAuth} from '../../types';
@ -66,10 +71,10 @@ class Auth implements IAuth {
return next();
}
self.logger.trace( {username}, 'authenticating @{username}');
self.logger.trace({username}, 'authenticating @{username}');
plugin.authenticate(username, password, function(err, groups) {
if (err) {
self.logger.trace( {username, err}, 'authenticating for user @{username} failed. Error: @{err.message}');
self.logger.trace({username, err}, 'authenticating for user @{username} failed. Error: @{err.message}');
return cb(err);
}
@ -90,7 +95,7 @@ class Auth implements IAuth {
throw new TypeError(API_ERROR.BAD_FORMAT_USER_GROUP);
}
self.logger.trace( {username, groups}, 'authentication for user @{username} was successfully. Groups: @{groups}');
self.logger.trace({username, groups}, 'authentication for user @{username} was successfully. Groups: @{groups}');
return cb(err, createRemoteUser(username, groups));
}
next();
@ -101,7 +106,7 @@ class Auth implements IAuth {
add_user(user: string, password: string, cb: Callback) {
let self = this;
let plugins = this.plugins.slice(0);
this.logger.trace( {user}, 'add user @{user}');
this.logger.trace({user}, 'add user @{user}');
(function next() {
let plugin = plugins.shift();
@ -115,11 +120,11 @@ class Auth implements IAuth {
// p.add_user() execution
plugin[method](user, password, function(err, ok) {
if (err) {
self.logger.trace( {user, err}, 'the user @{user} could not being added. Error: @{err}');
self.logger.trace({user, err}, 'the user @{user} could not being added. Error: @{err}');
return cb(err);
}
if (ok) {
self.logger.trace( {user}, 'the user @{user} has been added');
self.logger.trace({user}, 'the user @{user} has been added');
return self.authenticate(user, password, cb);
}
next();
@ -136,7 +141,7 @@ class Auth implements IAuth {
// $FlowFixMe
let pkg = Object.assign({name: packageName}, getMatchedPackagesSpec(packageName, this.config.packages));
const self = this;
this.logger.trace( {packageName}, 'allow access for @{packageName}');
this.logger.trace({packageName}, 'allow access for @{packageName}');
(function next() {
const plugin = plugins.shift();
@ -147,12 +152,12 @@ class Auth implements IAuth {
plugin.allow_access(user, pkg, function(err, ok: boolean) {
if (err) {
self.logger.trace( {packageName, err}, 'forbidden access for @{packageName}. Error: @{err.message}');
self.logger.trace({packageName, err}, 'forbidden access for @{packageName}. Error: @{err.message}');
return callback(err);
}
if (ok) {
self.logger.trace( {packageName}, 'allowed access for @{packageName}');
self.logger.trace({packageName}, 'allowed access for @{packageName}');
return callback(null, ok);
}
@ -169,7 +174,7 @@ class Auth implements IAuth {
const self = this;
// $FlowFixMe
let pkg = Object.assign({name: packageName}, getMatchedPackagesSpec(packageName, this.config.packages));
this.logger.trace( {packageName}, 'allow publish for @{packageName}');
this.logger.trace({packageName}, 'allow publish for @{packageName}');
(function next() {
const plugin = plugins.shift();
@ -180,12 +185,12 @@ class Auth implements IAuth {
plugin.allow_publish(user, pkg, (err, ok: boolean) => {
if (err) {
self.logger.trace( {packageName}, 'forbidden publish for @{packageName}');
self.logger.trace({packageName}, 'forbidden publish for @{packageName}');
return callback(err);
}
if (ok) {
self.logger.trace( {packageName}, 'allowed publish for @{packageName}');
self.logger.trace({packageName}, 'allowed publish for @{packageName}');
return callback(null, ok);
}
next(); // cb(null, false) causes next plugin to roll
@ -223,7 +228,7 @@ class Auth implements IAuth {
if (!isAuthHeaderValid(authorization)) {
this.logger.trace('api middleware auth heather is not valid');
return next( ErrorCode.getBadRequest(API_ERROR.BAD_AUTH_HEADER) );
return next(ErrorCode.getBadRequest(API_ERROR.BAD_AUTH_HEADER));
}
const security: Security = getSecurity(this.config);
@ -239,12 +244,7 @@ class Auth implements IAuth {
};
}
_handleJWTAPIMiddleware(
req: $RequestExtend,
security: Security,
secret: string,
authorization: string,
next: Function) {
_handleJWTAPIMiddleware(req: $RequestExtend, security: Security, secret: string, authorization: string, next: Function) {
const {scheme, token} = parseAuthTokenHeader(authorization);
if (scheme.toUpperCase() === TOKEN_BASIC.toUpperCase()) {
// this should happen when client tries to login with an existing user
@ -273,11 +273,7 @@ class Auth implements IAuth {
}
}
_handleAESMiddleware(req: $RequestExtend,
security: Security,
secret: string,
authorization: string,
next: Function) {
_handleAESMiddleware(req: $RequestExtend, security: Security, secret: string, authorization: string, next: Function) {
const credentials: any = getMiddlewareCredentials(security, secret, authorization);
if (credentials) {
const {user, password} = credentials;
@ -297,8 +293,7 @@ class Auth implements IAuth {
}
_isRemoteUserMissing(remote_user: RemoteUser): boolean {
return _.isUndefined(remote_user) === false &&
(_.isUndefined(remote_user.name) === false);
return _.isUndefined(remote_user) === false && _.isUndefined(remote_user.name) === false;
}
/**
@ -307,11 +302,11 @@ class Auth implements IAuth {
webUIJWTmiddleware() {
return (req: $RequestExtend, res: $Response, _next: NextFunction) => {
if (this._isRemoteUserMissing(req.remote_user)) {
return _next();
return _next();
}
req.pause();
const next = (err) => {
const next = err => {
req.resume();
if (err) {
// req.remote_user.error = err.message;
@ -327,7 +322,7 @@ class Auth implements IAuth {
}
if (!isAuthHeaderValid(authorization)) {
return next( ErrorCode.getBadRequest(API_ERROR.BAD_AUTH_HEADER) );
return next(ErrorCode.getBadRequest(API_ERROR.BAD_AUTH_HEADER));
}
const token = (authorization || '').replace(`${TOKEN_BEARER} `, '');
@ -339,7 +334,7 @@ class Auth implements IAuth {
try {
credentials = verifyJWTPayload(token, this.config.secret);
} catch (err) {
// FIXME: intended behaviour, do we want it?
// FIXME: intended behaviour, do we want it?
}
if (credentials) {

121
src/lib/bootstrap.js vendored

@ -1,3 +1,7 @@
/**
* @prettier
*/
// @flow
import {assign, isObject, isFunction} from 'lodash';
@ -24,17 +28,12 @@ const logger = require('./logger');
* @param {String} pkgVersion
* @param {String} pkgName
*/
function startVerdaccio(config: any,
cliListen: string,
configPath: string,
pkgVersion: string,
pkgName: string,
callback: Callback) {
function startVerdaccio(config: any, cliListen: string, configPath: string, pkgVersion: string, pkgName: string, callback: Callback) {
if (isObject(config) === false) {
throw new Error(API_ERROR.CONFIG_BAD_FORMAT);
}
endPointAPI(config).then((app)=> {
endPointAPI(config).then(app => {
const addresses = getListListenAddresses(cliListen, config.listen);
addresses.forEach(function(addr) {
@ -46,7 +45,8 @@ function startVerdaccio(config: any,
}
webServer = handleHTTPS(app, configPath, config);
} else { // http
} else {
// http
webServer = http.createServer(app);
}
@ -64,25 +64,31 @@ function unlinkAddressPath(addr) {
}
function logHTTPSWarning(storageLocation) {
logger.logger.fatal([
'You have enabled HTTPS and need to specify either ',
' "https.key", "https.cert" and "https.ca" or ',
' "https.pfx" and optionally "https.passphrase" ',
'to run https server',
'',
// commands are borrowed from node.js docs
'To quickly create self-signed certificate, use:',
' $ openssl genrsa -out ' + resolveConfigPath(storageLocation, keyPem) + ' 2048',
' $ openssl req -new -sha256 -key ' + resolveConfigPath(storageLocation, keyPem) + ' -out ' + resolveConfigPath(storageLocation, csrPem),
' $ openssl x509 -req -in ' + resolveConfigPath(storageLocation, csrPem) +
' -signkey ' + resolveConfigPath(storageLocation, keyPem) + ' -out ' + resolveConfigPath(storageLocation, certPem),
'',
'And then add to config file (' + storageLocation + '):',
' https:',
` key: ${resolveConfigPath(storageLocation, keyPem)}`,
` cert: ${resolveConfigPath(storageLocation, certPem)}`,
` ca: ${resolveConfigPath(storageLocation, csrPem)}`,
].join('\n'));
logger.logger.fatal(
[
'You have enabled HTTPS and need to specify either ',
' "https.key", "https.cert" and "https.ca" or ',
' "https.pfx" and optionally "https.passphrase" ',
'to run https server',
'',
// commands are borrowed from node.js docs
'To quickly create self-signed certificate, use:',
' $ openssl genrsa -out ' + resolveConfigPath(storageLocation, keyPem) + ' 2048',
' $ openssl req -new -sha256 -key ' + resolveConfigPath(storageLocation, keyPem) + ' -out ' + resolveConfigPath(storageLocation, csrPem),
' $ openssl x509 -req -in ' +
resolveConfigPath(storageLocation, csrPem) +
' -signkey ' +
resolveConfigPath(storageLocation, keyPem) +
' -out ' +
resolveConfigPath(storageLocation, certPem),
'',
'And then add to config file (' + storageLocation + '):',
' https:',
` key: ${resolveConfigPath(storageLocation, keyPem)}`,
` cert: ${resolveConfigPath(storageLocation, certPem)}`,
` ca: ${resolveConfigPath(storageLocation, csrPem)}`,
].join('\n')
);
process.exit(2);
}
@ -106,41 +112,46 @@ function handleHTTPS(app, configPath, config) {
});
}
return https.createServer(httpsOptions, app);
} catch (err) { // catch errors related to certificate loading
} catch (err) {
// catch errors related to certificate loading
logger.logger.fatal({err: err}, 'cannot create server: @{err.message}');
process.exit(2);
}
}
function listenDefaultCallback(webServer: $Application, addr: any, pkgName: string, pkgVersion: string) {
webServer.listen(addr.port || addr.path, addr.host, () => {
// send a message for tests
if (isFunction(process.send)) {
process.send({
verdaccio_started: true,
});
}
// $FlowFixMe
}).on('error', function(err) {
logger.logger.fatal({err: err}, 'cannot create server: @{err.message}');
process.exit(2);
});
webServer
.listen(addr.port || addr.path, addr.host, () => {
// send a message for tests
if (isFunction(process.send)) {
process.send({
verdaccio_started: true,
});
}
})
// $FlowFixMe
.on('error', function(err) {
logger.logger.fatal({err: err}, 'cannot create server: @{err.message}');
process.exit(2);
});
logger.logger.warn({
addr: ( addr.path
? URL.format({
protocol: 'unix',
pathname: addr.path,
})
: URL.format({
protocol: addr.proto,
hostname: addr.host,
port: addr.port,
pathname: '/',
})
),
version: pkgName + '/' + pkgVersion,
}, 'http address - @{addr} - @{version}');
logger.logger.warn(
{
addr: addr.path
? URL.format({
protocol: 'unix',
pathname: addr.path,
})
: URL.format({
protocol: addr.proto,
hostname: addr.host,
port: addr.port,
pathname: '/',
}),
version: pkgName + '/' + pkgVersion,
},
'http address - @{addr} - @{version}'
);
}
export {startVerdaccio, listenDefaultCallback};

@ -2,6 +2,9 @@
/* eslint no-sync:0 */
/* eslint no-empty:0 */
/**
* @prettier
*/
import path from 'path';
import semver from 'semver';

@ -1,3 +1,7 @@
/**
* @prettier
*/
// @flow
import path from 'path';
@ -34,18 +38,20 @@ export function getListListenAddresses(argListen: string, configListen: mixed) {
} else {
addresses = [DEFAULT_PORT];
}
addresses = addresses.map(function(addr) {
const parsedAddr = parseAddress(addr);
addresses = addresses
.map(function(addr) {
const parsedAddr = parseAddress(addr);
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/")');
}
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/")'
);
}
return parsedAddr;
}).filter(Boolean);
return parsedAddr;
})
.filter(Boolean);
return addresses;
}

@ -1,3 +1,7 @@
/**
* @prettier
*/
// @flow
import fs from 'fs';
@ -58,29 +62,28 @@ function createConfigFolder(configLocation) {
}
function updateStorageLinks(configLocation, defaultConfig) {
if (configLocation.type !== XDG) {
return defaultConfig;
}
if (configLocation.type !== XDG) {
return defaultConfig;
}
// $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, '.local', 'share');
if (folderExists(dataDir)) {
dataDir = Path.resolve(Path.join(dataDir, pkgJSON.name, 'storage'));
return defaultConfig.replace(/^storage: .\/storage$/m, `storage: ${dataDir}`);
} else {
return defaultConfig;
}
// $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, '.local', 'share');
if (folderExists(dataDir)) {
dataDir = Path.resolve(Path.join(dataDir, pkgJSON.name, 'storage'));
return defaultConfig.replace(/^storage: .\/storage$/m, `storage: ${dataDir}`);
} else {
return defaultConfig;
}
}
function getConfigPaths() {
return [getXDGDirectory(), getWindowsDirectory(), getRelativeDefaultDirectory(), getOldDirectory()].filter((path) => !!path);
return [getXDGDirectory(), getWindowsDirectory(), getRelativeDefaultDirectory(), getOldDirectory()].filter(path => !!path);
}
const getXDGDirectory = () => {
const XDGConfig = getXDGHome() ||
process.env.HOME && Path.join(process.env.HOME, '.config');
const XDGConfig = getXDGHome() || (process.env.HOME && Path.join(process.env.HOME, '.config'));
if (XDGConfig && folderExists(XDGConfig)) {
return {

@ -1,3 +1,7 @@
/**
* @prettier
*/
// @flow
import _ from 'lodash';
import assert from 'assert';
@ -24,7 +28,7 @@ export function normalizeUserlist(oldFormat: any, newFormat: any) {
const result = [];
/* eslint prefer-rest-params: "off" */
for (let i=0; i < arguments.length; i++) {
for (let i = 0; i < arguments.length; i++) {
if (arguments[i] == null) {
continue;
}
@ -72,7 +76,7 @@ export function sanityCheckUplinksProps(configUpLinks: any) {
for (let uplink in uplinks) {
if (Object.prototype.hasOwnProperty.call(uplinks, uplink)) {
assert(uplinks[uplink].url, 'CONFIG: no url for uplink: ' + uplink);
assert( _.isString(uplinks[uplink].url), 'CONFIG: wrong url format for uplink: ' + uplink);
assert(_.isString(uplinks[uplink].url), 'CONFIG: wrong url format for uplink: ' + uplink);
uplinks[uplink].url = uplinks[uplink].url.replace(/\/$/, '');
}
}
@ -87,7 +91,7 @@ export function hasProxyTo(pkg: string, upLink: string, packages: PackageList):
const matchedPkg: MatchedPackage = (getMatchedPackagesSpec(pkg, packages): MatchedPackage);
const proxyList = typeof matchedPkg !== 'undefined' ? matchedPkg.proxy : [];
if (proxyList) {
return proxyList.some((curr) => upLink === curr);
return proxyList.some(curr => upLink === curr);
}
return false;
@ -112,8 +116,7 @@ export function normalisePackageAccess(packages: PackageList): PackageList {
for (let pkg in packages) {
if (Object.prototype.hasOwnProperty.call(packages, pkg)) {
assert(_.isObject(packages[pkg]) && _.isArray(packages[pkg]) === false,
`CONFIG: bad "'${pkg}'" package description (object expected)`);
assert(_.isObject(packages[pkg]) && _.isArray(packages[pkg]) === false, `CONFIG: bad "'${pkg}'" package description (object expected)`);
normalizedPkgs[pkg].access = normalizeUserlist(packages[pkg].allow_access, packages[pkg].access);
delete normalizedPkgs[pkg].allow_access;
normalizedPkgs[pkg].publish = normalizeUserlist(packages[pkg].allow_publish, packages[pkg].publish);
@ -125,4 +128,3 @@ export function normalisePackageAccess(packages: PackageList): PackageList {
return normalizedPkgs;
}

@ -1,23 +1,18 @@
/**
* @prettier
*/
// @flow
import _ from 'lodash';
import assert from 'assert';
import {generateRandomHexString} from './crypto-utils';
import {
getMatchedPackagesSpec,
normalisePackageAccess,
sanityCheckUplinksProps,
uplinkSanityCheck} from './config-utils';
import {getMatchedPackagesSpec, normalisePackageAccess, sanityCheckUplinksProps, uplinkSanityCheck} from './config-utils';
import {getUserAgent, isObject} from './utils';
import {APP_ERROR} from './constants';
import type {
PackageList,
Config as AppConfig,
Security,
Logger,
} from '@verdaccio/types';
import type {PackageList, Config as AppConfig, Security, Logger} from '@verdaccio/types';
import type {MatchedPackage, StartUpConfig} from '../../types';
@ -82,7 +77,7 @@ class Config implements AppConfig {
this.packages = normalisePackageAccess(self.packages);
// loading these from ENV if aren't in config
allowedEnvConfig.forEach((envConf) => {
allowedEnvConfig.forEach(envConf => {
if (!(envConf in self)) {
self[envConf] = process.env[envConf] || process.env[envConf.toUpperCase()];
}

@ -1,9 +1,13 @@
/**
* @prettier
*/
// @flow
export const DEFAULT_PORT: string = '4873';
export const DEFAULT_PROTOCOL: string = 'http';
export const DEFAULT_DOMAIN: string = 'localhost';
export const TIME_EXPIRATION_24H: string ='24h';
export const TIME_EXPIRATION_24H: string = '24h';
export const TIME_EXPIRATION_7D: string = '7d';
export const DIST_TAGS = 'dist-tags';
@ -24,8 +28,8 @@ export const HEADERS = {
};
export const CHARACTER_ENCODING = {
UTF8: 'utf8'
}
UTF8: 'utf8',
};
export const HEADER_TYPE = {
CONTENT_ENCODING: 'content-encoding',
@ -95,9 +99,9 @@ export const API_ERROR = {
UPLINK_OFFLINE_PUBLISH: 'one of the uplinks is down, refuse to publish',
UPLINK_OFFLINE: 'uplink is offline',
CONTENT_MISMATCH: 'content length mismatch',
NOT_FILE_UPLINK: 'file doesn\'t exist on uplink',
NOT_FILE_UPLINK: "file doesn't exist on uplink",
MAX_USERS_REACHED: 'maximum amount of users reached',
VERSION_NOT_EXIST: 'this version doesn\'t exist',
VERSION_NOT_EXIST: "this version doesn't exist",
FILE_NOT_FOUND: 'File not found',
BAD_STATUS_CODE: 'bad status code',
PACKAGE_EXIST: 'this package is already present',
@ -123,5 +127,5 @@ export const PACKAGE_ACCESS = {
};
export const UPDATE_BANNER = {
CHANGELOG_URL: 'https://github.com/verdaccio/verdaccio/releases/tag/'
}
CHANGELOG_URL: 'https://github.com/verdaccio/verdaccio/releases/tag/',
};

@ -1,11 +1,10 @@
/**
* @prettier
*/
// @flow
import {
createDecipher,
createCipher,
createHash,
pseudoRandomBytes,
} from 'crypto';
import {createDecipher, createCipher, createHash, pseudoRandomBytes} from 'crypto';
import jwt from 'jsonwebtoken';
import type {JWTSignOptions, RemoteUser} from '@verdaccio/types';
@ -22,7 +21,6 @@ export function aesEncrypt(buf: Buffer, secret: string): Buffer {
return Buffer.concat([b1, b2]);
}
export function aesDecrypt(buf: Buffer, secret: string) {
try {
// deprecated
@ -48,22 +46,26 @@ export function createTarballHash() {
* @return {String}
*/
export function stringToMD5(data: Buffer | string) {
return createHash('md5').update(data).digest('hex');
return createHash('md5')
.update(data)
.digest('hex');
}
export function generateRandomHexString(length: number = 8) {
return pseudoRandomBytes(length).toString('hex');
}
export async function signPayload(
payload: RemoteUser,
secretOrPrivateKey: string,
options: JWTSignOptions): Promise<string> {
export async function signPayload(payload: RemoteUser, secretOrPrivateKey: string, options: JWTSignOptions): Promise<string> {
return new Promise(function(resolve, reject) {
return jwt.sign(payload, secretOrPrivateKey, {
notBefore: '1', // Make sure the time will not rollback :)
...options,
}, (error, token) => error ? reject(error) : resolve(token));
return jwt.sign(
payload,
secretOrPrivateKey,
{
notBefore: '1', // Make sure the time will not rollback :)
...options,
},
(error, token) => (error ? reject(error) : resolve(token))
);
});
}

@ -1,3 +1,7 @@
/**
* @prettier
*/
// @flow
/* eslint prefer-rest-params: 0 */
@ -8,31 +12,25 @@ import _ from 'lodash';
// $FlowFixMe
import {ErrorCode, isObject, getLatestVersion, tagVersion, validateName, DIST_TAGS} from './utils';
import {
generatePackageTemplate, normalizePackage, generateRevision, getLatestReadme, cleanUpReadme, normalizeContributors,
fileExist, noSuchFile, DEFAULT_REVISION, pkgFileName,
generatePackageTemplate,
normalizePackage,
generateRevision,
getLatestReadme,
cleanUpReadme,
normalizeContributors,
fileExist,
noSuchFile,
DEFAULT_REVISION,
pkgFileName,
} from './storage-utils';
import {createTarballHash} from './crypto-utils';
import {prepareSearchPackage} from './storage-utils';
import loadPlugin from '../lib/plugin-loader';
import LocalDatabase from '@verdaccio/local-storage';
import {UploadTarball, ReadTarball} from '@verdaccio/streams';
import type {
Package,
Config,
MergeTags,
Version,
DistFile,
Callback,
Logger,
} from '@verdaccio/types';
import type {
ILocalData,
IPackageStorage,
} from '@verdaccio/local-storage';
import type {
IUploadTarball,
IReadTarball,
} from '@verdaccio/streams';
import type {Package, Config, MergeTags, Version, DistFile, Callback, Logger} from '@verdaccio/types';
import type {ILocalData, IPackageStorage} from '@verdaccio/local-storage';
import type {IUploadTarball, IReadTarball} from '@verdaccio/streams';
import type {IStorage, StringValue} from '../../types';
import {API_ERROR} from './constants';
@ -54,12 +52,12 @@ class LocalStorage implements IStorage {
const storage: any = this._getLocalStorage(name);
if (_.isNil(storage)) {
return callback( ErrorCode.getNotFound('this package cannot be added'));
return callback(ErrorCode.getNotFound('this package cannot be added'));
}
storage.createPackage(name, generatePackageTemplate(name), (err) => {
storage.createPackage(name, generatePackageTemplate(name), err => {
if (_.isNull(err) === false && err.code === fileExist) {
return callback( ErrorCode.getConflict());
return callback(ErrorCode.getConflict());
}
const latest = getLatestVersion(pkg);
@ -81,13 +79,13 @@ class LocalStorage implements IStorage {
let storage: any = this._getLocalStorage(name);
if (_.isNil(storage)) {
return callback( ErrorCode.getNotFound());
return callback(ErrorCode.getNotFound());
}
storage.readPackage(name, (err, data) => {
if (_.isNil(err) === false) {
if (err.code === noSuchFile) {
return callback( ErrorCode.getNotFound());
return callback(ErrorCode.getNotFound());
} else {
return callback(err);
}
@ -95,13 +93,13 @@ class LocalStorage implements IStorage {
data = normalizePackage(data);
this.localData.remove(name, (removeFailed) => {
this.localData.remove(name, removeFailed => {
if (removeFailed) {
// This will happen when database is locked
return callback(ErrorCode.getBadData(removeFailed.message));
}
storage.deletePackage(pkgFileName, (err) => {
storage.deletePackage(pkgFileName, err => {
if (err) {
return callback(err);
}
@ -149,10 +147,10 @@ class LocalStorage implements IStorage {
// we do NOT overwrite any existing records
if (_.isNil(packageLocalJson._distfiles[filename])) {
let hash: DistFile = packageLocalJson._distfiles[filename] = {
let hash: DistFile = (packageLocalJson._distfiles[filename] = {
url: version.dist.tarball,
sha: version.dist.shasum,
};
});
/* eslint spaced-comment: 0 */
// $FlowFixMe
const upLink: string = version[Symbol.for('__verdaccio_uplink')];
@ -174,9 +172,10 @@ class LocalStorage implements IStorage {
for (let up in packageInfo._uplinks) {
if (Object.prototype.hasOwnProperty.call(packageInfo._uplinks, up)) {
const need_change = !isObject(packageLocalJson._uplinks[up])
|| packageInfo._uplinks[up].etag !== packageLocalJson._uplinks[up].etag
|| packageInfo._uplinks[up].fetched !== packageLocalJson._uplinks[up].fetched;
const need_change =
!isObject(packageLocalJson._uplinks[up]) ||
packageInfo._uplinks[up].etag !== packageLocalJson._uplinks[up].etag ||
packageInfo._uplinks[up].fetched !== packageLocalJson._uplinks[up].fetched;
if (need_change) {
change = true;
@ -209,64 +208,64 @@ class LocalStorage implements IStorage {
* @param {*} tag
* @param {*} callback
*/
addVersion(name: string,
version: string,
metadata: Version,
tag: StringValue,
callback: Callback) {
this._updatePackage(name, (data, cb) => {
// keep only one readme per package
data.readme = metadata.readme;
addVersion(name: string, version: string, metadata: Version, tag: StringValue, callback: Callback) {
this._updatePackage(
name,
(data, cb) => {
// keep only one readme per package
data.readme = metadata.readme;
// TODO: lodash remove
metadata = cleanUpReadme(metadata);
metadata.contributors = normalizeContributors(metadata.contributors);
// TODO: lodash remove
metadata = cleanUpReadme(metadata);
metadata.contributors = normalizeContributors(metadata.contributors);
if (data.versions[version] != null) {
return cb( ErrorCode.getConflict() );
}
if (data.versions[version] != null) {
return cb(ErrorCode.getConflict());
}
// if uploaded tarball has a different shasum, it's very likely that we have some kind of error
if (isObject(metadata.dist) && _.isString(metadata.dist.tarball)) {
let tarball = metadata.dist.tarball.replace(/.*\//, '');
// if uploaded tarball has a different shasum, it's very likely that we have some kind of error
if (isObject(metadata.dist) && _.isString(metadata.dist.tarball)) {
let tarball = metadata.dist.tarball.replace(/.*\//, '');
if (isObject(data._attachments[tarball])) {
if (_.isNil(data._attachments[tarball].shasum) === false && _.isNil(metadata.dist.shasum) === false) {
if (data._attachments[tarball].shasum != metadata.dist.shasum) {
const errorMessage = `shasum error, ${data._attachments[tarball].shasum} != ${metadata.dist.shasum}`;
return cb( ErrorCode.getBadRequest(errorMessage) );
if (isObject(data._attachments[tarball])) {
if (_.isNil(data._attachments[tarball].shasum) === false && _.isNil(metadata.dist.shasum) === false) {
if (data._attachments[tarball].shasum != metadata.dist.shasum) {
const errorMessage = `shasum error, ${data._attachments[tarball].shasum} != ${metadata.dist.shasum}`;
return cb(ErrorCode.getBadRequest(errorMessage));
}
}
let currentDate = new Date().toISOString();
// some old storage do not have this field #740
if (_.isNil(data.time)) {
data.time = {};
}
data.time['modified'] = currentDate;
if ('created' in data.time === false) {
data.time.created = currentDate;
}
data.time[version] = currentDate;
data._attachments[tarball].version = version;
}
let currentDate = new Date().toISOString();
// some old storage do not have this field #740
if (_.isNil(data.time)) {
data.time = {};
}
data.time['modified'] = currentDate;
if (('created' in data.time) === false) {
data.time.created = currentDate;
}
data.time[version] = currentDate;
data._attachments[tarball].version = version;
}
}
data.versions[version] = metadata;
tagVersion(data, version, tag);
this.localData.add(name, (addFailed) => {
if (addFailed) {
return cb(ErrorCode.getBadData(addFailed.message));
}
cb();
});
}, callback);
data.versions[version] = metadata;
tagVersion(data, version, tag);
this.localData.add(name, addFailed => {
if (addFailed) {
return cb(ErrorCode.getBadData(addFailed.message));
}
cb();
});
},
callback
);
}
/**
@ -276,23 +275,27 @@ class LocalStorage implements IStorage {
* @param {*} callback
*/
mergeTags(pkgName: string, tags: MergeTags, callback: Callback) {
this._updatePackage(pkgName, (data, cb) => {
/* eslint guard-for-in: 0 */
for (let tag: string in tags) {
// this handle dist-tag rm command
if (_.isNull(tags[tag])) {
delete data[DIST_TAGS][tag];
continue;
}
this._updatePackage(
pkgName,
(data, cb) => {
/* eslint guard-for-in: 0 */
for (let tag: string in tags) {
// this handle dist-tag rm command
if (_.isNull(tags[tag])) {
delete data[DIST_TAGS][tag];
continue;
}
if (_.isNil(data.versions[tags[tag]])) {
return cb( this._getVersionNotFound() );
if (_.isNil(data.versions[tags[tag]])) {
return cb(this._getVersionNotFound());
}
const version: string = tags[tag];
tagVersion(data, version, tag);
}
const version: string = tags[tag];
tagVersion(data, version, tag);
}
cb();
}, callback);
cb();
},
callback
);
}
/**
@ -322,37 +325,39 @@ class LocalStorage implements IStorage {
* @param {*} callback
* @return {Function}
*/
changePackage(name: string,
incomingPkg: Package,
revision?: string, callback: Callback) {
changePackage(name: string, incomingPkg: Package, revision?: string, callback: Callback) {
if (!isObject(incomingPkg.versions) || !isObject(incomingPkg[DIST_TAGS])) {
return callback( ErrorCode.getBadData());
return callback(ErrorCode.getBadData());
}
this._updatePackage(name, (localData, cb) => {
for (let version in localData.versions) {
if (_.isNil(incomingPkg.versions[version])) {
this.logger.info( {name: name, version: version}, 'unpublishing @{name}@@{version}');
this._updatePackage(
name,
(localData, cb) => {
for (let version in localData.versions) {
if (_.isNil(incomingPkg.versions[version])) {
this.logger.info({name: name, version: version}, 'unpublishing @{name}@@{version}');
delete localData.versions[version];
delete localData.time[version];
delete localData.versions[version];
delete localData.time[version];
for (let file in localData._attachments) {
if (localData._attachments[file].version === version) {
delete localData._attachments[file].version;
for (let file in localData._attachments) {
if (localData._attachments[file].version === version) {
delete localData._attachments[file].version;
}
}
}
}
}
localData[DIST_TAGS] = incomingPkg[DIST_TAGS];
cb();
}, function(err) {
if (err) {
return callback(err);
localData[DIST_TAGS] = incomingPkg[DIST_TAGS];
cb();
},
function(err) {
if (err) {
return callback(err);
}
callback();
}
callback();
});
);
}
/**
* Remove a tarball.
@ -361,27 +366,30 @@ class LocalStorage implements IStorage {
* @param {*} revision
* @param {*} callback
*/
removeTarball(name: string, filename: string,
revision: string, callback: Callback) {
removeTarball(name: string, filename: string, revision: string, callback: Callback) {
assert(validateName(filename));
this._updatePackage(name, (data, cb) => {
if (data._attachments[filename]) {
delete data._attachments[filename];
cb();
} else {
cb(this._getFileNotAvailable());
}
}, (err) => {
if (err) {
return callback(err);
}
const storage = this._getLocalStorage(name);
this._updatePackage(
name,
(data, cb) => {
if (data._attachments[filename]) {
delete data._attachments[filename];
cb();
} else {
cb(this._getFileNotAvailable());
}
},
err => {
if (err) {
return callback(err);
}
const storage = this._getLocalStorage(name);
if (storage) {
storage.deletePackage(filename, callback);
if (storage) {
storage.deletePackage(filename, callback);
}
}
});
);
}
/**
@ -418,14 +426,14 @@ class LocalStorage implements IStorage {
if (!storage) {
process.nextTick(() => {
uploadStream.emit('error', ('can\'t upload this package'));
uploadStream.emit('error', "can't upload this package");
});
return uploadStream;
}
const writeStream: IUploadTarball = storage.writeTarball(filename);
writeStream.on('error', (err) => {
writeStream.on('error', err => {
if (err.code === fileExist) {
uploadStream.emit('error', ErrorCode.getConflict());
uploadStream.abort();
@ -449,18 +457,22 @@ class LocalStorage implements IStorage {
});
writeStream.on('success', () => {
this._updatePackage(name, function updater(data, cb) {
data._attachments[filename] = {
shasum: shaOneHash.digest('hex'),
};
cb();
}, function(err) {
if (err) {
uploadStream.emit('error', err);
} else {
uploadStream.emit('success');
this._updatePackage(
name,
function updater(data, cb) {
data._attachments[filename] = {
shasum: shaOneHash.digest('hex'),
};
cb();
},
function(err) {
if (err) {
uploadStream.emit('error', err);
} else {
uploadStream.emit('success');
}
}
});
);
});
(uploadStream: any).abort = function() {
@ -561,7 +573,7 @@ class LocalStorage implements IStorage {
getPackageMetadata(name: string, callback?: Callback = () => {}): void {
const storage: IPackageStorage = this._getLocalStorage(name);
if (_.isNil(storage)) {
return callback( ErrorCode.getNotFound() );
return callback(ErrorCode.getNotFound());
}
this._readPackage(name, storage, callback);
@ -576,28 +588,31 @@ class LocalStorage implements IStorage {
search(startKey: string, options: any) {
const stream = new ReadTarball({objectMode: true});
this._searchEachPackage((item, cb) => {
if (item.time > parseInt(startKey, 10)) {
this.getPackageMetadata(item.name, (err: Error, data: Package) => {
if (err) {
return cb(err);
}
const time = new Date(item.time).toISOString();
const result = prepareSearchPackage(data, time);
if (_.isNil(result) === false) {
stream.push(result);
}
this._searchEachPackage(
(item, cb) => {
if (item.time > parseInt(startKey, 10)) {
this.getPackageMetadata(item.name, (err: Error, data: Package) => {
if (err) {
return cb(err);
}
const time = new Date(item.time).toISOString();
const result = prepareSearchPackage(data, time);
if (_.isNil(result) === false) {
stream.push(result);
}
cb();
});
} else {
cb();
});
} else {
cb();
}
},
function onEnd(err) {
if (err) {
return stream.emit('error', err);
}
stream.end();
}
}, function onEnd(err) {
if (err) {
return stream.emit('error', err);
}
stream.end();
});
);
return stream;
}
@ -620,7 +635,7 @@ class LocalStorage implements IStorage {
storage.readPackage(name, (err, result) => {
if (err) {
if (err.code === noSuchFile) {
return callback( ErrorCode.getNotFound() );
return callback(ErrorCode.getNotFound());
} else {
return callback(this._internalError(err, pkgFileName, 'error reading'));
}
@ -643,7 +658,7 @@ class LocalStorage implements IStorage {
if (packages) {
const listPackagesConf = Object.keys(packages || {});
listPackagesConf.map( (pkg) => {
listPackagesConf.map(pkg => {
if (packages[pkg].storage) {
storages[packages[pkg].storage] = true;
}
@ -706,7 +721,7 @@ class LocalStorage implements IStorage {
* @return {Object} Error instance
*/
_internalError(err: string, file: string, message: string) {
this.logger.error( {err: err, file: file}, `${message} @{file}: @{!err.message}` );
this.logger.error({err: err, file: file}, `${message} @{file}: @{!err.message}`);
return ErrorCode.getInternalError();
}
@ -721,11 +736,10 @@ class LocalStorage implements IStorage {
const storage: IPackageStorage = this._getLocalStorage(name);
if (!storage) {
return callback( ErrorCode.getNotFound() );
return callback(ErrorCode.getNotFound());
}
storage.updatePackage(name, updateHandler, this._writePackage.bind(this), normalizePackage,
callback);
storage.updatePackage(name, updateHandler, this._writePackage.bind(this), normalizePackage, callback);
}
/**
@ -821,9 +835,11 @@ class LocalStorage implements IStorage {
logger: this.logger,
};
return _.head(loadPlugin(this.config, this.config.store, plugin_params, (plugin) => {
return plugin.getPackageStorage;
}));
return _.head(
loadPlugin(this.config, this.config.store, plugin_params, plugin => {
return plugin.getPackageStorage;
})
);
}
}

@ -1,3 +1,7 @@
/**
* @prettier
*/
const cluster = require('cluster');
const Logger = require('bunyan');
const Error = require('http-errors');
@ -15,20 +19,28 @@ const {format} = require('date-fns');
*/
function getlvl(x) {
switch (true) {
case x < 15: return 'trace';
case x < 25: return 'debug';
case x < 35: return 'info';
case x == 35: return 'http';
case x < 45: return 'warn';
case x < 55: return 'error';
default: return 'fatal';
case x < 15:
return 'trace';
case x < 25:
return 'debug';
case x < 35:
return 'info';
case x == 35:
return 'http';
case x < 45:
return 'warn';
case x < 55:
return 'error';
default:
return 'fatal';
}
}
/**
* A RotatingFileStream that modifes the message first
*/
class VerdaccioRotatingFileStream extends Logger.RotatingFileStream { // We depend on mv so that this is there
class VerdaccioRotatingFileStream extends Logger.RotatingFileStream {
// We depend on mv so that this is there
write(obj) {
const msg = fillInMsgTemplate(obj.msg, obj, false);
super.write(JSON.stringify({...obj, msg}, Logger.safeCycles()) + '\n');
@ -62,19 +74,19 @@ function setup(logs) {
}
const stream = new VerdaccioRotatingFileStream(
_.merge({},
_.merge(
{},
// Defaults can be found here: https://github.com/trentm/node-bunyan#stream-type-rotating-file
target.options || {},
{path: target.path, level})
{path: target.path, level}
)
);
streams.push(
{
type: 'raw',
level,
stream,
}
);
streams.push({
type: 'raw',
level,
stream,
});
} else {
const stream = new Stream();
stream.writable = true;
@ -96,16 +108,16 @@ function setup(logs) {
if (target.format === 'pretty') {
// making fake stream for prettypritting
stream.write = (obj) => {
stream.write = obj => {
destination.write(`${print(obj.level, obj.msg, obj, destinationIsTTY)}\n`);
};
} else if (target.format === 'pretty-timestamped') {
// making fake stream for pretty pritting
stream.write = (obj) => {
stream.write = obj => {
destination.write(`[${format(obj.time, 'YYYY-MM-DD HH:mm:ss')}] ${print(obj.level, obj.msg, obj, destinationIsTTY)}\n`);
};
} else {
stream.write = (obj) => {
stream.write = obj => {
const msg = fillInMsgTemplate(obj.msg, obj, destinationIsTTY);
destination.write(`${JSON.stringify({...obj, msg}, Logger.safeCycles())}\n`);
};
@ -130,9 +142,9 @@ function setup(logs) {
},
});
process.on('SIGUSR2', function() {
Logger.reopenFileStreams();
});
process.on('SIGUSR2', function() {
Logger.reopenFileStreams();
});
module.exports.logger = logger;
}
@ -190,7 +202,7 @@ function fillInMsgTemplate(msg, obj, colors) {
}
}
if (typeof(str) === 'string') {
if (typeof str === 'string') {
if (!colors || str.includes('\n')) {
return str;
} else if (is_error) {
@ -218,23 +230,26 @@ function print(type, msg, obj, colors) {
}
const finalmsg = fillInMsgTemplate(msg, obj, colors);
const subsystems = [{
in: chalk.green('<--'),
out: chalk.yellow('-->'),
fs: chalk.black('-=-'),
default: chalk.blue('---'),
}, {
in: '<--',
out: '-->',
fs: '-=-',
default: '---',
}];
const subsystems = [
{
in: chalk.green('<--'),
out: chalk.yellow('-->'),
fs: chalk.black('-=-'),
default: chalk.blue('---'),
},
{
in: '<--',
out: '-->',
fs: '-=-',
default: '---',
},
];
const sub = subsystems[colors ? 0 : 1][obj.sub] || subsystems[+!colors].default;
if (colors) {
return ` ${levels[type]((pad(type)))}${chalk.white(`${sub} ${finalmsg}`)}`;
return ` ${levels[type](pad(type))}${chalk.white(`${sub} ${finalmsg}`)}`;
} else {
return ` ${(pad(type))}${sub} ${finalmsg}`;
return ` ${pad(type)}${sub} ${finalmsg}`;
}
}

@ -1,3 +1,7 @@
/**
* @prettier
*/
// @flow
import semver from 'semver';
@ -14,24 +18,24 @@ import type {Package} from '@verdaccio/types';
* @param {*} config
* @static
*/
export function mergeVersions(local: Package, up: Package) {
// copy new versions to a cache
// NOTE: if a certain version was updated, we can't refresh it reliably
for (let i in up.versions) {
if (_.isNil(local.versions[i])) {
local.versions[i] = up.versions[i];
}
export function mergeVersions(local: Package, up: Package) {
// copy new versions to a cache
// NOTE: if a certain version was updated, we can't refresh it reliably
for (let i in up.versions) {
if (_.isNil(local.versions[i])) {
local.versions[i] = up.versions[i];
}
}
for (let i in up[DIST_TAGS]) {
if (local[DIST_TAGS][i] !== up[DIST_TAGS][i]) {
if (!local[DIST_TAGS][i] || semver.lte(local[DIST_TAGS][i], up[DIST_TAGS][i])) {
local[DIST_TAGS][i] = up[DIST_TAGS][i];
}
if (i === 'latest' && local[DIST_TAGS][i] === up[DIST_TAGS][i]) {
// if remote has more fresh package, we should borrow its readme
local.readme = up.readme;
}
for (let i in up[DIST_TAGS]) {
if (local[DIST_TAGS][i] !== up[DIST_TAGS][i]) {
if (!local[DIST_TAGS][i] || semver.lte(local[DIST_TAGS][i], up[DIST_TAGS][i])) {
local[DIST_TAGS][i] = up[DIST_TAGS][i];
}
if (i === 'latest' && local[DIST_TAGS][i] === up[DIST_TAGS][i]) {
// if remote has more fresh package, we should borrow its readme
local.readme = up.readme;
}
}
}
}

@ -1,3 +1,7 @@
/**
* @prettier
*/
import Handlebars from 'handlebars';
import _ from 'lodash';
@ -60,7 +64,7 @@ export function notify(metadata, config, ...moreMedatata) {
return sendNotification(metadata, config.notify, ...moreMedatata);
} else {
// multiple notifications endpoints PR #108
return Promise.all(_.map(config.notify, (key) => sendNotification(metadata, key, ...moreMedatata)));
return Promise.all(_.map(config.notify, key => sendNotification(metadata, key, ...moreMedatata)));
}
}

@ -1,3 +1,7 @@
/**
* @prettier
*/
import isNil from 'lodash/isNil';
import logger from '../logger';
import request from 'request';

@ -1,3 +1,7 @@
/**
* @prettier
*/
// @flow
import Path from 'path';
@ -27,7 +31,7 @@ function mergeConfig(appConfig, pluginConfig) {
}
function isValid(plugin) {
return (_.isFunction(plugin) || _.isFunction(plugin.default));
return _.isFunction(plugin) || _.isFunction(plugin.default);
}
function isES6(plugin) {
@ -46,11 +50,7 @@ function isES6(plugin) {
* @param {*} sanityCheck callback that check the shape that should fulfill the plugin
* @return {Array} list of plugins
*/
export default function loadPlugin<T>(
config: Config,
pluginConfigs: any = {},
params: any,
sanityCheck: Function): T[] {
export default function loadPlugin<T>(config: Config, pluginConfigs: any = {}, params: any, sanityCheck: Function): T[] {
return Object.keys(pluginConfigs).map((pluginId: string) => {
let plugin;
@ -96,17 +96,15 @@ export default function loadPlugin<T>(
}
if (!isValid(plugin)) {
logger.logger.error({content: pluginId}, '@{content} doesn\'t look like a valid plugin');
logger.logger.error({content: pluginId}, "@{content} doesn't look like a valid plugin");
throw Error('"' + pluginId + '" doesn\'t look like a valid plugin');
}
/* eslint new-cap:off */
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);
/* eslint new-cap:off */
if (plugin === null || !sanityCheck(plugin)) {
logger.logger.error({content: pluginId}, '@{content} doesn\'t look like a valid plugin');
logger.logger.error({content: pluginId}, "@{content} doesn't look like a valid plugin");
throw Error('"' + pluginId + '" doesn\'t look like a valid plugin');
}
logger.logger.warn({content: pluginId}, 'Plugin successfully loaded: @{content}');

@ -1,3 +1,7 @@
/**
* @prettier
*/
// @flow
import lunrMutable from 'lunr-mutable-indexes';
@ -14,8 +18,8 @@ class Search implements IWebSearch {
* Constructor.
*/
constructor() {
/* eslint no-invalid-this: "off" */
this.index = lunrMutable(function() {
/* eslint no-invalid-this: "off" */
this.index = lunrMutable(function() {
this.field('name', {boost: 10});
this.field('description', {boost: 4});
this.field('author', {boost: 6});
@ -25,7 +29,7 @@ class Search implements IWebSearch {
});
}
/**
/**
* Performs a query to the indexer.
* If the keyword is a * it returns all local elements
* otherwise performs a search
@ -34,14 +38,15 @@ class Search implements IWebSearch {
*/
query(query: string) {
return query === '*'
? this.storage.localStorage.localData.get((items) => {
items.map( function( pkg ) {
return {ref: pkg, score: 1};
});
}) : this.index.search(`*${query}*`);
}
? this.storage.localStorage.localData.get(items => {
items.map(function(pkg) {
return {ref: pkg, score: 1};
});
})
: this.index.search(`*${query}*`);
}
/**
/**
* Add a new element to index
* @param {*} pkg the package
*/

@ -1,3 +1,7 @@
/**
* @prettier
*/
// @flow
import _ from 'lodash';
@ -33,15 +37,9 @@ const generatePackageTemplate = function(name: string): Package {
* @param {Object} pkg package reference.
*/
function normalizePackage(pkg: Package) {
const pkgProperties = [
'versions',
'dist-tags',
'_distfiles',
'_attachments',
'_uplinks',
'time'];
const pkgProperties = ['versions', 'dist-tags', '_distfiles', '_attachments', '_uplinks', 'time'];
pkgProperties.forEach((key) => {
pkgProperties.forEach(key => {
const pkgProp = pkg[key];
if (_.isNil(pkgProp) || isObject(pkgProp) === false) {
@ -62,7 +60,7 @@ function normalizePackage(pkg: Package) {
function generateRevision(rev: string): string {
const _rev = rev.split('-');
return ((+_rev[0] || 0) + 1) + '-' + generateRandomHexString();
return (+_rev[0] || 0) + 1 + '-' + generateRandomHexString();
}
function getLatestReadme(pkg: Package): string {
@ -74,13 +72,7 @@ function getLatestReadme(pkg: Package): string {
return readme;
}
// In case of empty readme - trying to get ANY readme in the following order: 'next','beta','alpha','test','dev','canary'
const readmeDistTagsPriority = [
'next',
'beta',
'alpha',
'test',
'dev',
'canary'];
const readmeDistTagsPriority = ['next', 'beta', 'alpha', 'test', 'dev', 'canary'];
readmeDistTagsPriority.map(function(tag) {
if (readme) {
return readme;
@ -100,8 +92,8 @@ function cleanUpReadme(version: Version): Version {
}
export function normalizeContributors(contributors: Array<Author>): Array<Author> {
if (isObject(contributors) || _.isString(contributors)) {
return [((contributors): any)];
if (isObject(contributors) || _.isString(contributors)) {
return [(contributors: any)];
}
return contributors;
@ -111,17 +103,18 @@ export const WHITELIST = ['_rev', 'name', 'versions', 'dist-tags', 'readme', 'ti
export function cleanUpLinksRef(keepUpLinkData: boolean, result: Package): Package {
const propertyToKeep = [...WHITELIST];
if (keepUpLinkData === true) {
propertyToKeep.push('_uplinks');
}
if (keepUpLinkData === true) {
propertyToKeep.push('_uplinks');
}
for (let i in result) {
if (propertyToKeep.indexOf(i) === -1) { // Remove sections like '_uplinks' from response
delete result[i];
}
for (let i in result) {
if (propertyToKeep.indexOf(i) === -1) {
// Remove sections like '_uplinks' from response
delete result[i];
}
}
return result;
return result;
}
/**
@ -226,14 +219,4 @@ export function prepareSearchPackage(data: Package, time: mixed) {
}
}
export {
generatePackageTemplate,
normalizePackage,
generateRevision,
getLatestReadme,
cleanUpReadme,
fileExist,
noSuchFile,
pkgFileName,
};
export {generatePackageTemplate, normalizePackage, generateRevision, getLatestReadme, cleanUpReadme, fileExist, noSuchFile, pkgFileName};

@ -1,3 +1,7 @@
/**
* @prettier
*/
// @flow
import _ from 'lodash';
@ -9,22 +13,12 @@ import Search from './search';
import {API_ERROR, HTTP_STATUS} from './constants';
import LocalStorage from './local-storage';
import {ReadTarball} from '@verdaccio/streams';
import {checkPackageLocal, publishPackage, checkPackageRemote, cleanUpLinksRef,
mergeUplinkTimeIntoLocal, generatePackageTemplate} from './storage-utils';
import {checkPackageLocal, publishPackage, checkPackageRemote, cleanUpLinksRef, mergeUplinkTimeIntoLocal, generatePackageTemplate} from './storage-utils';
import {setupUpLinks, updateVersionsHiddenUpLink} from './uplink-util';
import {mergeVersions} from './metadata-utils';
import {ErrorCode, normalizeDistTags, validateMetadata, isObject, DIST_TAGS} from './utils';
import type {IStorage, IProxy, IStorageHandler, ProxyList, StringValue} from '../../types';
import type {
Versions,
Package,
Config,
MergeTags,
Version,
DistFile,
Callback,
Logger,
} from '@verdaccio/types';
import type {Versions, Package, Config, MergeTags, Version, DistFile, Callback, Logger} from '@verdaccio/types';
import type {IReadTarball, IUploadTarball} from '@verdaccio/streams';
import {hasProxyTo} from './config-utils';
@ -66,9 +60,7 @@ class Storage implements IStorageHandler {
}
_isAllowPublishOffline(): boolean {
return typeof this.config.publish !== 'undefined'
&& _.isBoolean(this.config.publish.allow_offline)
&& this.config.publish.allow_offline;
return typeof this.config.publish !== 'undefined' && _.isBoolean(this.config.publish.allow_offline) && this.config.publish.allow_offline;
}
/**
@ -147,7 +139,7 @@ class Storage implements IStorageHandler {
// flow: should be IReadTarball
let localStream: any = self.localStorage.getTarball(name, filename);
let isOpen = false;
localStream.on('error', (err) => {
localStream.on('error', err => {
if (isOpen || err.status !== HTTP_STATUS.NOT_FOUND) {
return readStream.emit('error', err);
}
@ -199,11 +191,14 @@ class Storage implements IStorageHandler {
}
if (uplink == null) {
uplink = new ProxyStorage({
url: file.url,
cache: true,
_autogenerated: true,
}, self.config);
uplink = new ProxyStorage(
{
url: file.url,
cache: true,
_autogenerated: true,
},
self.config
);
}
let savestream = null;
@ -246,8 +241,7 @@ class Storage implements IStorageHandler {
});
savestream.on('error', function(err) {
self.logger.warn( {err: err, fileName: file}
, 'error saving file @{fileName}: @{err.message}\n@{err.stack}' );
self.logger.warn({err: err, fileName: file}, 'error saving file @{fileName}: @{err.message}\n@{err.stack}');
if (savestream) {
savestream.abort();
}
@ -280,19 +274,18 @@ class Storage implements IStorageHandler {
return options.callback(err);
}
this._syncUplinksMetadata(options.name, data, {req: options.req},
function getPackageSynUpLinksCallback(err, result: Package, uplinkErrors) {
if (err) {
return options.callback(err);
}
this._syncUplinksMetadata(options.name, data, {req: options.req}, function getPackageSynUpLinksCallback(err, result: Package, uplinkErrors) {
if (err) {
return options.callback(err);
}
normalizeDistTags(cleanUpLinksRef(options.keepUpLinkData, result));
normalizeDistTags(cleanUpLinksRef(options.keepUpLinkData, result));
// npm can throw if this field doesn't exist
result._attachments = {};
// npm can throw if this field doesn't exist
result._attachments = {};
options.callback(null, result, uplinkErrors);
});
options.callback(null, result, uplinkErrors);
});
});
}
@ -313,7 +306,9 @@ class Storage implements IStorageHandler {
// stream to write a tarball
let stream: any = new Stream.PassThrough({objectMode: true});
async.eachSeries(Object.keys(this.uplinks), function(up_name, cb) {
async.eachSeries(
Object.keys(this.uplinks),
function(up_name, cb) {
// shortcut: if `local=1` is supplied, don't call uplinks
if (options.req.query.local !== undefined) {
return cb();
@ -321,20 +316,23 @@ class Storage implements IStorageHandler {
// search by keyword for each uplink
let lstream: IUploadTarball = self.uplinks[up_name].search(options);
// join streams
lstream.pipe(stream, {end: false});
lstream.pipe(
stream,
{end: false}
);
lstream.on('error', function(err) {
self.logger.error({err: err}, 'uplink error: @{err.message}');
cb(), cb = function() {};
cb(), (cb = function() {});
});
lstream.on('end', function() {
cb(), cb = function() {};
cb(), (cb = function() {});
});
stream.abort = function() {
if (lstream.abort) {
lstream.abort();
}
cb(), cb = function() {};
cb(), (cb = function() {});
};
},
// executed after all series
@ -344,12 +342,16 @@ class Storage implements IStorageHandler {
stream.abort = function() {
lstream.abort();
};
lstream.pipe(stream, {end: true});
lstream.pipe(
stream,
{end: true}
);
lstream.on('error', function(err) {
self.logger.error({err: err}, 'search error: @{err.message}');
stream.end();
});
});
}
);
return stream;
}
@ -378,7 +380,7 @@ class Storage implements IStorageHandler {
packages.push(version);
} else {
self.logger.warn( {package: locals[itemPkg]}, 'package @{package} does not have a "latest" tag?' );
self.logger.warn({package: locals[itemPkg]}, 'package @{package} does not have a "latest" tag?');
}
}
@ -419,79 +421,87 @@ class Storage implements IStorageHandler {
}
}
async.map(upLinks, (upLink, cb) => {
const _options = Object.assign({}, options);
let upLinkMeta = packageInfo._uplinks[upLink.upname];
async.map(
upLinks,
(upLink, cb) => {
const _options = Object.assign({}, options);
let upLinkMeta = packageInfo._uplinks[upLink.upname];
if (isObject(upLinkMeta)) {
const fetched = upLinkMeta.fetched;
if (isObject(upLinkMeta)) {
const fetched = upLinkMeta.fetched;
if (fetched && (Date.now() - fetched) < upLink.maxage) {
return cb();
if (fetched && Date.now() - fetched < upLink.maxage) {
return cb();
}
_options.etag = upLinkMeta.etag;
}
_options.etag = upLinkMeta.etag;
upLink.getRemoteMetadata(name, _options, (err, upLinkResponse, eTag) => {
if (err && err.remoteStatus === 304) {
upLinkMeta.fetched = Date.now();
}
if (err || !upLinkResponse) {
// $FlowFixMe
return cb(null, [err || ErrorCode.getInternalError('no data')]);
}
try {
validateMetadata(upLinkResponse, name);
} catch (err) {
self.logger.error(
{
sub: 'out',
err: err,
},
'package.json validating error @{!err.message}\n@{err.stack}'
);
return cb(null, [err]);
}
packageInfo._uplinks[upLink.upname] = {
etag: eTag,
fetched: Date.now(),
};
packageInfo.time = mergeUplinkTimeIntoLocal(packageInfo, upLinkResponse);
updateVersionsHiddenUpLink(upLinkResponse.versions, upLink);
try {
mergeVersions(packageInfo, upLinkResponse);
} catch (err) {
self.logger.error(
{
sub: 'out',
err: err,
},
'package.json parsing error @{!err.message}\n@{err.stack}'
);
return cb(null, [err]);
}
// if we got to this point, assume that the correct package exists
// on the uplink
exists = true;
cb();
});
},
(err: Error, upLinksErrors: any) => {
assert(!err && Array.isArray(upLinksErrors));
if (!exists) {
return callback(ErrorCode.getNotFound(API_ERROR.NO_PACKAGE), null, upLinksErrors);
}
self.localStorage.updateVersions(name, packageInfo, function(err, packageJsonLocal: Package) {
if (err) {
return callback(err);
}
return callback(null, packageJsonLocal, upLinksErrors);
});
}
upLink.getRemoteMetadata(name, _options, (err, upLinkResponse, eTag) => {
if (err && err.remoteStatus === 304) {
upLinkMeta.fetched = Date.now();
}
if (err || !upLinkResponse) {
// $FlowFixMe
return cb(null, [err || ErrorCode.getInternalError('no data')]);
}
try {
validateMetadata(upLinkResponse, name);
} catch (err) {
self.logger.error({
sub: 'out',
err: err,
}, 'package.json validating error @{!err.message}\n@{err.stack}');
return cb(null, [err]);
}
packageInfo._uplinks[upLink.upname] = {
etag: eTag,
fetched: Date.now(),
};
packageInfo.time = mergeUplinkTimeIntoLocal(packageInfo, upLinkResponse);
updateVersionsHiddenUpLink(upLinkResponse.versions, upLink);
try {
mergeVersions(packageInfo, upLinkResponse);
} catch (err) {
self.logger.error({
sub: 'out',
err: err,
}, 'package.json parsing error @{!err.message}\n@{err.stack}');
return cb(null, [err]);
}
// if we got to this point, assume that the correct package exists
// on the uplink
exists = true;
cb();
});
}, (err: Error, upLinksErrors: any) => {
assert(!err && Array.isArray(upLinksErrors));
if (!exists) {
return callback( ErrorCode.getNotFound(API_ERROR.NO_PACKAGE)
, null
, upLinksErrors );
}
self.localStorage.updateVersions(name, packageInfo, function(err, packageJsonLocal: Package) {
if (err) {
return callback(err);
}
return callback(null, packageJsonLocal, upLinksErrors);
});
});
);
}
/**

@ -1,3 +1,7 @@
/**
* @prettier
*/
// @flow
import zlib from 'zlib';
@ -9,13 +13,7 @@ import URL from 'url';
import {parseInterval, isObject, ErrorCode, buildToken} from './utils';
import {ReadTarball} from '@verdaccio/streams';
import {ERROR_CODE, TOKEN_BASIC, TOKEN_BEARER, HEADERS, HTTP_STATUS, API_ERROR, HEADER_TYPE, CHARACTER_ENCODING} from './constants';
import type {
Config,
UpLinkConf,
Callback,
Headers,
Logger,
} from '@verdaccio/types';
import type {Config, UpLinkConf, Callback, Headers, Logger} from '@verdaccio/types';
import type {IProxy} from '../../types';
const LoggerApi = require('./logger');
@ -77,17 +75,21 @@ class ProxyStorage implements IProxy {
this.config.url = this.config.url.replace(/\/$/, '');
if (this.config.timeout && Number(this.config.timeout) >= 1000) {
this.logger.warn(['Too big timeout value: ' + this.config.timeout,
'We changed time format to nginx-like one',
'(see http://nginx.org/en/docs/syntax.html)',
'so please update your config accordingly'].join('\n'));
this.logger.warn(
[
'Too big timeout value: ' + this.config.timeout,
'We changed time format to nginx-like one',
'(see http://nginx.org/en/docs/syntax.html)',
'so please update your config accordingly',
].join('\n')
);
}
// a bunch of different configurable timers
this.maxage = parseInterval(setConfig(this.config, 'maxage', '2m' ));
this.maxage = parseInterval(setConfig(this.config, 'maxage', '2m'));
this.timeout = parseInterval(setConfig(this.config, 'timeout', '30s'));
this.max_fails = Number(setConfig(this.config, 'max_fails', 2 ));
this.fail_timeout = parseInterval(setConfig(this.config, 'fail_timeout', '5m' ));
this.max_fails = Number(setConfig(this.config, 'max_fails', 2));
this.fail_timeout = parseInterval(setConfig(this.config, 'fail_timeout', '5m'));
this.strict_ssl = Boolean(setConfig(this.config, 'strict_ssl', true));
}
@ -124,88 +126,97 @@ class ProxyStorage implements IProxy {
this._overrideWithUplinkConfigHeaders(headers);
const method = options.method || 'GET';
const uri = options.uri_full || (this.config.url + options.uri);
const uri = options.uri_full || this.config.url + options.uri;
self.logger.info({
method: method,
headers: headers,
uri: uri,
}, 'making request: \'@{method} @{uri}\'');
self.logger.info(
{
method: method,
headers: headers,
uri: uri,
},
"making request: '@{method} @{uri}'"
);
if (isObject(options.json)) {
json = JSON.stringify(options.json);
headers['Content-Type'] = headers['Content-Type'] || HEADERS.JSON;
}
let requestCallback = cb ? (function(err, res, body) {
let error;
const responseLength = err ? 0 : body.length;
// $FlowFixMe
processBody(err, body);
logActivity();
// $FlowFixMe
cb(err, res, body);
let requestCallback = cb
? function(err, res, body) {
let error;
const responseLength = err ? 0 : body.length;
// $FlowFixMe
processBody(err, body);
logActivity();
// $FlowFixMe
cb(err, res, body);
/**
* Perform a decode.
*/
function processBody() {
if (err) {
error = err.message;
return;
}
/**
* Perform a decode.
*/
function processBody() {
if (err) {
error = err.message;
return;
}
if (options.json && res.statusCode < 300) {
try {
// $FlowFixMe
body = JSON.parse(body.toString(CHARACTER_ENCODING.UTF8));
} catch (_err) {
body = {};
err = _err;
error = err.message;
if (options.json && res.statusCode < 300) {
try {
// $FlowFixMe
body = JSON.parse(body.toString(CHARACTER_ENCODING.UTF8));
} catch (_err) {
body = {};
err = _err;
error = err.message;
}
}
if (!err && isObject(body)) {
if (_.isString(body.error)) {
error = body.error;
}
}
}
/**
* Perform a log.
*/
function logActivity() {
let message = "@{!status}, req: '@{request.method} @{request.url}'";
message += error ? ', error: @{!error}' : ', bytes: @{bytes.in}/@{bytes.out}';
self.logger.warn(
{
err: err || undefined, // if error is null/false change this to undefined so it wont log
request: {method: method, url: uri},
level: 35, // http
status: res != null ? res.statusCode : 'ERR',
error: error,
bytes: {
in: json ? json.length : 0,
out: responseLength || 0,
},
},
message
);
}
}
: undefined;
if (!err && isObject(body)) {
if (_.isString(body.error)) {
error = body.error;
}
}
}
/**
* Perform a log.
*/
function logActivity() {
let message = '@{!status}, req: \'@{request.method} @{request.url}\'';
message += error
? ', error: @{!error}'
: ', bytes: @{bytes.in}/@{bytes.out}';
self.logger.warn({
err: err || undefined, // if error is null/false change this to undefined so it wont log
request: {method: method, url: uri},
level: 35, // http
status: res != null ? res.statusCode : 'ERR',
error: error,
bytes: {
in: json ? json.length : 0,
out: responseLength || 0,
},
}, message);
}
}) : undefined;
const req = request({
url: uri,
method: method,
headers: headers,
body: json,
ca: this.ca,
proxy: this.proxy,
encoding: null,
gzip: true,
timeout: this.timeout,
strictSSL: this.strict_ssl,
}, requestCallback);
const req = request(
{
url: uri,
method: method,
headers: headers,
body: json,
ca: this.ca,
proxy: this.proxy,
encoding: null,
gzip: true,
timeout: this.timeout,
strictSSL: this.strict_ssl,
},
requestCallback
);
let statusCalled = false;
req.on('response', function(res) {
@ -216,15 +227,18 @@ class ProxyStorage implements IProxy {
if (_.isNil(requestCallback) === false) {
(function do_log() {
const message = '@{!status}, req: \'@{request.method} @{request.url}\' (streaming)';
self.logger.warn({
request: {
method: method,
url: uri,
const message = "@{!status}, req: '@{request.method} @{request.url}' (streaming)";
self.logger.warn(
{
request: {
method: method,
url: uri,
},
level: 35, // http
status: _.isNull(res) === false ? res.statusCode : 'ERR',
},
level: 35, // http
status: _.isNull(res) === false ? res.statusCode : 'ERR',
}, message);
message
);
})();
}
});
@ -363,7 +377,7 @@ class ProxyStorage implements IProxy {
// add/override headers specified in the config
/* eslint guard-for-in: 0 */
for (let key in this.config.headers) {
headers[key] = this.config.headers[key];
headers[key] = this.config.headers[key];
}
}
@ -375,8 +389,8 @@ class ProxyStorage implements IProxy {
isUplinkValid(url: string) {
// $FlowFixMe
const urlParsed: Url = URL.parse(url);
const isHTTPS = (urlDomainParsed) => urlDomainParsed.protocol === 'https:' && (urlParsed.port === null || urlParsed.port === '443');
const getHost = (urlDomainParsed) => isHTTPS(urlDomainParsed) ? urlDomainParsed.hostname : urlDomainParsed.host;
const isHTTPS = urlDomainParsed => urlDomainParsed.protocol === 'https:' && (urlParsed.port === null || urlParsed.port === '443');
const getHost = urlDomainParsed => (isHTTPS(urlDomainParsed) ? urlDomainParsed.hostname : urlDomainParsed.host);
const isMatchProtocol: boolean = urlParsed.protocol === this.url.protocol;
const isMatchHost: boolean = getHost(urlParsed) === getHost(this.url);
// $FlowFixMe
@ -398,26 +412,29 @@ class ProxyStorage implements IProxy {
headers['Accept'] = contenTypeAccept;
}
this.request({
uri: `/${encode(name)}`,
json: true,
headers: headers,
req: options.req,
}, (err, res, body) => {
if (err) {
return callback(err);
this.request(
{
uri: `/${encode(name)}`,
json: true,
headers: headers,
req: options.req,
},
(err, res, body) => {
if (err) {
return callback(err);
}
if (res.statusCode === HTTP_STATUS.NOT_FOUND) {
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}`);
// $FlowFixMe
error.remoteStatus = res.statusCode;
return callback(error);
}
callback(null, body, res.headers.etag);
}
if (res.statusCode === HTTP_STATUS.NOT_FOUND) {
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}`);
// $FlowFixMe
error.remoteStatus = res.statusCode;
return callback(error);
}
callback(null, body, res.headers.etag);
});
);
}
/**
@ -486,13 +503,13 @@ class ProxyStorage implements IProxy {
},
});
let parsePackage = (pkg) => {
let parsePackage = pkg => {
if (isObject(pkg)) {
transformStream.emit('data', pkg);
}
};
requestStream.on('response', (res) => {
requestStream.on('response', res => {
if (!String(res.statusCode).match(/^2\d\d$/)) {
return transformStream.emit('error', ErrorCode.getInternalError(`bad status code ${res.statusCode} from uplink`));
}
@ -511,7 +528,7 @@ class ProxyStorage implements IProxy {
});
});
requestStream.on('error', (err) => {
requestStream.on('error', err => {
transformStream.emit('error', err);
});
@ -539,19 +556,12 @@ class ProxyStorage implements IProxy {
// https://github.com/rlidwka/sinopia/issues/254
//
if (this.proxy === false) {
headers['X-Forwarded-For'] = (
req && req.headers['x-forwarded-for']
? req.headers['x-forwarded-for'] + ', '
: ''
) + req.connection.remoteAddress;
headers['X-Forwarded-For'] = (req && req.headers['x-forwarded-for'] ? req.headers['x-forwarded-for'] + ', ' : '') + req.connection.remoteAddress;
}
}
// always attach Via header to avoid loops, even if we're not proxying
headers['Via'] =
req && req.headers['via']
? req.headers['via'] + ', '
: '';
headers['Via'] = req && req.headers['via'] ? req.headers['via'] + ', ' : '';
headers['Via'] += '1.1 ' + this.server_id + ' (Verdaccio)';
}
@ -567,17 +577,23 @@ class ProxyStorage implements IProxy {
} else {
if (alive) {
if (this.failed_requests >= this.max_fails) {
this.logger.warn({
host: this.url.host,
}, 'host @{host} is back online');
this.logger.warn(
{
host: this.url.host,
},
'host @{host} is back online'
);
}
this.failed_requests = 0;
} else {
this.failed_requests ++;
this.failed_requests++;
if (this.failed_requests === this.max_fails) {
this.logger.warn({
host: this.url.host,
}, 'host @{host} is now offline');
this.logger.warn(
{
host: this.url.host,
},
'host @{host} is now offline'
);
}
}
this.last_request_time = Date.now();
@ -634,8 +650,7 @@ class ProxyStorage implements IProxy {
if (noProxyItem[0] !== '.') noProxyItem = '.' + noProxyItem;
if (hostname.lastIndexOf(noProxyItem) === hostname.length - noProxyItem.length) {
if (this.proxy) {
this.logger.debug({url: this.url.href, rule: noProxyItem},
'not using proxy for @{url}, excluded by @{rule} rule');
this.logger.debug({url: this.url.href, rule: noProxyItem}, 'not using proxy for @{url}, excluded by @{rule} rule');
// $FlowFixMe
this.proxy = false;
}
@ -648,7 +663,7 @@ 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}');
}
}
}

@ -1,4 +1,7 @@
// @prettier
/**
* @prettier
*/
// @flow
import request from 'request';
@ -6,7 +9,7 @@ import semver from 'semver';
import chalk from 'chalk';
import _ from 'lodash';
import { UPDATE_BANNER, DEFAULT_REGISTRY, HTTP_STATUS } from './constants';
import {UPDATE_BANNER, DEFAULT_REGISTRY, HTTP_STATUS} from './constants';
const VERDACCIO_LATEST_REGISTRY_URL = `${DEFAULT_REGISTRY}/verdaccio/latest`;
@ -14,48 +17,48 @@ const VERDACCIO_LATEST_REGISTRY_URL = `${DEFAULT_REGISTRY}/verdaccio/latest`;
* Creates NPM update banner using chalk
*/
export function createBanner(currentVersion: string, newVersion: string, releaseType: string): string {
const changelog = `${UPDATE_BANNER.CHANGELOG_URL}v${newVersion}`
const versionUpdate = `${chalk.bold.red(currentVersion)}${chalk.bold.green(newVersion)}`
const banner = chalk`
const changelog = `${UPDATE_BANNER.CHANGELOG_URL}v${newVersion}`;
const versionUpdate = `${chalk.bold.red(currentVersion)}${chalk.bold.green(newVersion)}`;
const banner = chalk`
{white.bold A new ${_.upperCase(releaseType)} version of Verdaccio is available. ${versionUpdate} }
{white.bold Run ${chalk.green.bold('npm install -g verdaccio')} to update}.
{white.bold Registry: ${DEFAULT_REGISTRY}}
{blue.bold Changelog: ${changelog}}
`;
return banner;
return banner;
}
/**
* creates error banner
*/
export function createErrorBanner(message: string): string {
const banner = chalk`
const banner = chalk`
{red.bold Unable to check verdaccio version on ${DEFAULT_REGISTRY}}
{red.bold Error: ${message}}
`;
return banner;
return banner;
}
/**
* Show verdaccio update banner on start
*/
* Show verdaccio update banner on start
*/
export function verdaccioUpdateBanner(pkgVersion: string) {
request(VERDACCIO_LATEST_REGISTRY_URL, function (error: ?Object = null, response: Object = {}) {
if (!error && response.statusCode === HTTP_STATUS.OK && response.body) {
// In case, NPM does not returns version, keeping version equals to
// verdaccio version.
const { version = pkgVersion } = JSON.parse(response.body);
const releaseType = semver.diff(version, pkgVersion);
request(VERDACCIO_LATEST_REGISTRY_URL, function(error: ?Object = null, response: Object = {}) {
if (!error && response.statusCode === HTTP_STATUS.OK && response.body) {
// In case, NPM does not returns version, keeping version equals to
// verdaccio version.
const {version = pkgVersion} = JSON.parse(response.body);
const releaseType = semver.diff(version, pkgVersion);
if (releaseType && semver.gt(version, pkgVersion)) {
const banner = createBanner(pkgVersion, version, releaseType);
/* eslint-disable-next-line */
console.log(banner);
}
} else {
const errorBanner = createErrorBanner(JSON.stringify(error));
/* eslint-disable-next-line */
console.log(errorBanner);
}
});
}
if (releaseType && semver.gt(version, pkgVersion)) {
const banner = createBanner(pkgVersion, version, releaseType);
/* eslint-disable-next-line */
console.log(banner);
}
} else {
const errorBanner = createErrorBanner(JSON.stringify(error));
/* eslint-disable-next-line */
console.log(errorBanner);
}
});
}

@ -1,12 +1,16 @@
/**
* @prettier
*/
// @flow
import ProxyStorage from './up-storage';
import type {Versions, Config} from '@verdaccio/types';
import type {IProxy, ProxyList} from '../../types';
/**
* Set up the Up Storage for each link.
*/
/**
* Set up the Up Storage for each link.
*/
export function setupUpLinks(config: Config): ProxyList {
const uplinks: ProxyList = {};

@ -1,5 +1,9 @@
/**
* @prettier
*/
// @flow
// @prettier
import _ from 'lodash';
import fs from 'fs';
import assert from 'assert';
@ -9,14 +13,7 @@ import URL from 'url';
import createError from 'http-errors';
import marked from 'marked';
import {
HTTP_STATUS,
API_ERROR,
DEFAULT_PORT,
DEFAULT_DOMAIN,
DEFAULT_PROTOCOL,
CHARACTER_ENCODING, HEADERS
} from './constants';
import {HTTP_STATUS, API_ERROR, DEFAULT_PORT, DEFAULT_DOMAIN, DEFAULT_PROTOCOL, CHARACTER_ENCODING, HEADERS} from './constants';
import {generateGravatarUrl, GRAVATAR_DEFAULT} from '../utils/user';
import type {Package} from '@verdaccio/types';
@ -52,11 +49,7 @@ export function validatePackage(name: string): boolean {
return validateName(nameList[0]);
} else {
// scoped package
return (
nameList[0][0] === '@' &&
validateName(nameList[0].slice(1)) &&
validateName(nameList[1])
);
return nameList[0][0] === '@' && validateName(nameList[0].slice(1)) && validateName(nameList[1]);
}
}
@ -123,11 +116,7 @@ export function validateMetadata(object: Package, name: string): Object {
* Create base url for registry.
* @return {String} base registry url
*/
export function combineBaseUrl(
protocol: string,
host: string,
prefix?: string
): string {
export function combineBaseUrl(protocol: string, host: string, prefix?: string): string {
let result = `${protocol}://${host}`;
if (prefix) {
@ -151,25 +140,13 @@ export function extractTarballFromUrl(url: string) {
* @param {*} config
* @return {String} a filtered package
*/
export function convertDistRemoteToLocalTarballUrls(
pkg: Package,
req: $Request,
urlPrefix: string | void
) {
export function convertDistRemoteToLocalTarballUrls(pkg: Package, req: $Request, urlPrefix: string | void) {
for (let ver in pkg.versions) {
if (Object.prototype.hasOwnProperty.call(pkg.versions, ver)) {
const distName = pkg.versions[ver].dist;
if (
_.isNull(distName) === false &&
_.isNull(distName.tarball) === false
) {
distName.tarball = getLocalRegistryTarballUri(
distName.tarball,
pkg.name,
req,
urlPrefix
);
if (_.isNull(distName) === false && _.isNull(distName.tarball) === false) {
distName.tarball = getLocalRegistryTarballUri(distName.tarball, pkg.name, req, urlPrefix);
}
}
}
@ -181,23 +158,14 @@ export function convertDistRemoteToLocalTarballUrls(
* @param {*} uri
* @return {String} a parsed url
*/
export function getLocalRegistryTarballUri(
uri: string,
pkgName: string,
req: $Request,
urlPrefix: string | void
) {
export function getLocalRegistryTarballUri(uri: string, pkgName: string, req: $Request, urlPrefix: string | void) {
const currentHost = req.headers.host;
if (!currentHost) {
return uri;
}
const tarballName = extractTarballFromUrl(uri);
const domainRegistry = combineBaseUrl(
getWebProtocol(req.get(HEADERS.FORWARDED_PROTO), req.protocol),
req.headers.host,
urlPrefix
);
const domainRegistry = combineBaseUrl(getWebProtocol(req.get(HEADERS.FORWARDED_PROTO), req.protocol), req.headers.host, urlPrefix);
return `${domainRegistry}/${pkgName.replace(/\//g, '%2f')}/-/${tarballName}`;
}
@ -260,9 +228,7 @@ export function parseAddress(urlAddress: any) {
// TODO: refactor it to something more reasonable?
//
// protocol : // ( host )|( ipv6 ): port /
let urlPattern = /^((https?):(\/\/)?)?((([^\/:]*)|\[([^\[\]]+)\]):)?(\d+)\/?$/.exec(
urlAddress
);
let urlPattern = /^((https?):(\/\/)?)?((([^\/:]*)|\[([^\[\]]+)\]):)?(\d+)\/?$/.exec(urlAddress);
if (urlPattern) {
return {
@ -363,11 +329,7 @@ export function parseInterval(interval: any): number {
interval.split(/\s+/).forEach(function(x) {
if (!x) return;
let m = x.match(/^((0|[1-9][0-9]*)(\.[0-9]+)?)(ms|s|m|h|d|w|M|y|)$/);
if (
!m ||
parseIntervalTable[m[4]] >= last_suffix ||
(m[4] === '' && last_suffix !== Infinity)
) {
if (!m || parseIntervalTable[m[4]] >= last_suffix || (m[4] === '' && last_suffix !== Infinity)) {
throw Error('invalid interval: ' + interval);
}
last_suffix = parseIntervalTable[m[4]];
@ -380,7 +342,7 @@ export function parseInterval(interval: any): number {
* Detect running protocol (http or https)
*/
export function getWebProtocol(headerProtocol: string | void, protocol: string): string {
if (typeof(headerProtocol) === 'string' && headerProtocol !== '') {
if (typeof headerProtocol === 'string' && headerProtocol !== '') {
const commaIndex = headerProtocol.indexOf(',');
return commaIndex > 0 ? headerProtocol.substr(0, commaIndex) : headerProtocol;
}
@ -390,7 +352,7 @@ export function getWebProtocol(headerProtocol: string | void, protocol: string):
export function getLatestVersion(pkgInfo: Package): string {
return pkgInfo[DIST_TAGS].latest;
};
}
export const ErrorCode = {
getConflict: (message: string = API_ERROR.PACKAGE_EXIST) => {
@ -403,30 +365,23 @@ export const ErrorCode = {
return createError(HTTP_STATUS.BAD_REQUEST, customMessage);
},
getInternalError: (customMessage?: string) => {
return customMessage
? createError(HTTP_STATUS.INTERNAL_ERROR, customMessage)
: createError(HTTP_STATUS.INTERNAL_ERROR);
return customMessage ? createError(HTTP_STATUS.INTERNAL_ERROR, customMessage) : createError(HTTP_STATUS.INTERNAL_ERROR);
},
getForbidden: (message: string = 'can\'t use this filename') => {
getForbidden: (message: string = "can't use this filename") => {
return createError(HTTP_STATUS.FORBIDDEN, message);
},
getServiceUnavailable: (
message: string = API_ERROR.RESOURCE_UNAVAILABLE
) => {
getServiceUnavailable: (message: string = API_ERROR.RESOURCE_UNAVAILABLE) => {
return createError(HTTP_STATUS.SERVICE_UNAVAILABLE, message);
},
getNotFound: (customMessage?: string) => {
return createError(
HTTP_STATUS.NOT_FOUND,
customMessage || API_ERROR.NO_PACKAGE
);
return createError(HTTP_STATUS.NOT_FOUND, customMessage || API_ERROR.NO_PACKAGE);
},
getCode: (statusCode: number, customMessage: string) => {
return createError(statusCode, customMessage);
},
};
export function parseConfigFile (configPath: string): Object {
export function parseConfigFile(configPath: string): Object {
return YAML.safeLoad(fs.readFileSync(configPath, CHARACTER_ENCODING.UTF8));
}
@ -473,7 +428,7 @@ export function addScope(scope: string, packageName: string) {
}
export function deleteProperties(propertiesToDelete: Array<string>, objectItem: any) {
_.forEach(propertiesToDelete, (property) => {
_.forEach(propertiesToDelete, property => {
delete objectItem[property];
});
@ -501,7 +456,7 @@ export function addGravatarSupport(pkgInfo: Object): Object {
// for contributors
if (_.isEmpty(contributors) === false) {
pkgInfoCopy.latest.contributors = contributors.map((contributor) => {
pkgInfoCopy.latest.contributors = contributors.map(contributor => {
if (isObject(contributor)) {
// $FlowFixMe
contributor.avatar = generateGravatarUrl(contributor.email);
@ -519,7 +474,7 @@ export function addGravatarSupport(pkgInfo: Object): Object {
// for maintainers
if (_.isEmpty(maintainers) === false) {
pkgInfoCopy.latest.maintainers = maintainers.map((maintainer) => {
pkgInfoCopy.latest.maintainers = maintainers.map(maintainer => {
maintainer.avatar = generateGravatarUrl(maintainer.email);
return maintainer;
});