Apply partially new eslint rules, upgrade es6 and replace octal literals by chalk colors

This commit is contained in:
Juan Picado 2017-04-23 20:02:26 +02:00
parent e3e1485369
commit f282941075
No known key found for this signature in database
GPG Key ID: 18AC54485952D158
40 changed files with 2430 additions and 2386 deletions

View File

@ -1,3 +1,4 @@
node_modules node_modules
lib/static lib/static
coverage/ coverage/
lib/GUI/

View File

@ -9,8 +9,11 @@
# Created to work with eslint@0.18.0 # Created to work with eslint@0.18.0
# #
extends: ["eslint:recommended", "google"]
env: env:
node: true node: true
browser: true
es6: true es6: true
rules: rules:
@ -43,3 +46,22 @@ rules:
# useful for code clean-up # useful for code clean-up
no-unused-vars: [1, {"vars": "all", "args": "none"}] no-unused-vars: [1, {"vars": "all", "args": "none"}]
max-len: [1, 160]
# camelcase is standard, but this should be 1 and then 2 soon
camelcase: 0
# configuration that should be upgraded progresivelly
require-jsdoc: 1
valid-jsdoc: 1
prefer-spread: 1
no-constant-condition: 1
no-var: 1
no-empty: 1
guard-for-in: 1
no-invalid-this: 1
new-cap: 1
one-var: 1
no-redeclare: 1
prefer-rest-params: 1
no-console: [1, {"allow": ["log", "warn"]}]

View File

@ -4,36 +4,36 @@ module.exports = function(grunt) {
browserify: { browserify: {
dist: { dist: {
files: { files: {
'lib/static/main.js': [ 'lib/GUI/js/main.js' ] 'lib/static/main.js': ['lib/GUI/js/main.js'],
}, },
options: { options: {
debug: true, debug: true,
transform: [ 'browserify-handlebars' ] transform: ['browserify-handlebars'],
} },
} },
}, },
less: { less: {
dist: { dist: {
files: { files: {
'lib/static/main.css': [ 'lib/GUI/css/main.less' ] 'lib/static/main.css': ['lib/GUI/css/main.less'],
}, },
options: { options: {
sourceMap: false sourceMap: false,
} },
} },
}, },
watch: { watch: {
files: [ 'lib/GUI/**/*' ], files: ['lib/GUI/**/*'],
tasks: [ 'default' ] tasks: ['default'],
} },
}) });
grunt.loadNpmTasks('grunt-browserify') grunt.loadNpmTasks('grunt-browserify');
grunt.loadNpmTasks('grunt-contrib-watch') grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-less') grunt.loadNpmTasks('grunt-contrib-less');
grunt.registerTask('default', [ grunt.registerTask('default', [
'browserify', 'browserify',
'less' 'less',
]) ]);
} };

View File

@ -1,3 +1,3 @@
#!/usr/bin/env node #!/usr/bin/env node
require('../lib/cli') require('../lib/cli');

View File

@ -1,6 +1,6 @@
module.exports = require('./lib') module.exports = require('./lib');
/**package /** package
{ "name": "verdaccio", { "name": "verdaccio",
"version": "0.0.0", "version": "0.0.0",
"dependencies": {"js-yaml": "*"}, "dependencies": {"js-yaml": "*"},

View File

@ -7,275 +7,272 @@
* ======================================================================== */ * ======================================================================== */
+function ($) { +function($) {
'use strict'; 'use strict';
// MODAL CLASS DEFINITION // MODAL CLASS DEFINITION
// ====================== // ======================
var Modal = function (element, options) { let Modal = function(element, options) {
this.options = options this.options = options;
this.$body = $(document.body) this.$body = $(document.body);
this.$element = $(element) this.$element = $(element);
this.$backdrop = this.$backdrop =
this.isShown = null this.isShown = null;
this.scrollbarWidth = 0 this.scrollbarWidth = 0;
if (this.options.remote) { if (this.options.remote) {
this.$element this.$element
.find('.modal-content') .find('.modal-content')
.load(this.options.remote, $.proxy(function () { .load(this.options.remote, $.proxy(function() {
this.$element.trigger('loaded.bs.modal') this.$element.trigger('loaded.bs.modal');
}, this)) }, this));
} }
} };
Modal.VERSION = '3.3.0' Modal.VERSION = '3.3.0';
Modal.TRANSITION_DURATION = 300 Modal.TRANSITION_DURATION = 300;
Modal.BACKDROP_TRANSITION_DURATION = 150 Modal.BACKDROP_TRANSITION_DURATION = 150;
Modal.DEFAULTS = { Modal.DEFAULTS = {
backdrop: true, backdrop: true,
keyboard: true, keyboard: true,
show: true show: true,
} };
Modal.prototype.toggle = function (_relatedTarget) { Modal.prototype.toggle = function(_relatedTarget) {
return this.isShown ? this.hide() : this.show(_relatedTarget) return this.isShown ? this.hide() : this.show(_relatedTarget);
} };
Modal.prototype.show = function (_relatedTarget) { Modal.prototype.show = function(_relatedTarget) {
var that = this let that = this;
var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget }) let e = $.Event('show.bs.modal', {relatedTarget: _relatedTarget});
this.$element.trigger(e) this.$element.trigger(e);
if (this.isShown || e.isDefaultPrevented()) return if (this.isShown || e.isDefaultPrevented()) return;
this.isShown = true this.isShown = true;
this.checkScrollbar() this.checkScrollbar();
this.$body.addClass('modal-open') this.$body.addClass('modal-open');
this.setScrollbar() this.setScrollbar();
this.escape() this.escape();
this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this)) this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this));
this.backdrop(function () { this.backdrop(function() {
var transition = $.support.transition && that.$element.hasClass('fade') let transition = $.support.transition && that.$element.hasClass('fade');
if (!that.$element.parent().length) { if (!that.$element.parent().length) {
that.$element.appendTo(that.$body) // don't move modals dom position that.$element.appendTo(that.$body); // don't move modals dom position
} }
that.$element that.$element
.show() .show()
.scrollTop(0) .scrollTop(0);
if (transition) { if (transition) {
that.$element[0].offsetWidth // force reflow that.$element[0].offsetWidth; // force reflow
} }
that.$element that.$element
.addClass('in') .addClass('in')
.attr('aria-hidden', false) .attr('aria-hidden', false);
that.enforceFocus() that.enforceFocus();
var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget }) let e = $.Event('shown.bs.modal', {relatedTarget: _relatedTarget});
transition ? transition ?
that.$element.find('.modal-dialog') // wait for modal to slide in that.$element.find('.modal-dialog') // wait for modal to slide in
.one('bsTransitionEnd', function () { .one('bsTransitionEnd', function() {
that.$element.trigger('focus').trigger(e) that.$element.trigger('focus').trigger(e);
}) })
.emulateTransitionEnd(Modal.TRANSITION_DURATION) : .emulateTransitionEnd(Modal.TRANSITION_DURATION) :
that.$element.trigger('focus').trigger(e) that.$element.trigger('focus').trigger(e);
}) });
} };
Modal.prototype.hide = function (e) { Modal.prototype.hide = function(e) {
if (e) e.preventDefault() if (e) e.preventDefault();
e = $.Event('hide.bs.modal') e = $.Event('hide.bs.modal');
this.$element.trigger(e) this.$element.trigger(e);
if (!this.isShown || e.isDefaultPrevented()) return if (!this.isShown || e.isDefaultPrevented()) return;
this.isShown = false this.isShown = false;
this.escape() this.escape();
$(document).off('focusin.bs.modal') $(document).off('focusin.bs.modal');
this.$element this.$element
.removeClass('in') .removeClass('in')
.attr('aria-hidden', true) .attr('aria-hidden', true)
.off('click.dismiss.bs.modal') .off('click.dismiss.bs.modal');
$.support.transition && this.$element.hasClass('fade') ? $.support.transition && this.$element.hasClass('fade') ?
this.$element this.$element
.one('bsTransitionEnd', $.proxy(this.hideModal, this)) .one('bsTransitionEnd', $.proxy(this.hideModal, this))
.emulateTransitionEnd(Modal.TRANSITION_DURATION) : .emulateTransitionEnd(Modal.TRANSITION_DURATION) :
this.hideModal() this.hideModal();
} };
Modal.prototype.enforceFocus = function () { Modal.prototype.enforceFocus = function() {
$(document) $(document)
.off('focusin.bs.modal') // guard against infinite focus loop .off('focusin.bs.modal') // guard against infinite focus loop
.on('focusin.bs.modal', $.proxy(function (e) { .on('focusin.bs.modal', $.proxy(function(e) {
if (this.$element[0] !== e.target && !this.$element.has(e.target).length) { if (this.$element[0] !== e.target && !this.$element.has(e.target).length) {
this.$element.trigger('focus') this.$element.trigger('focus');
} }
}, this)) }, this));
} };
Modal.prototype.escape = function () { Modal.prototype.escape = function() {
if (this.isShown && this.options.keyboard) { if (this.isShown && this.options.keyboard) {
this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) { this.$element.on('keydown.dismiss.bs.modal', $.proxy(function(e) {
e.which == 27 && this.hide() e.which == 27 && this.hide();
}, this)) }, this));
} else if (!this.isShown) { } else if (!this.isShown) {
this.$element.off('keydown.dismiss.bs.modal') this.$element.off('keydown.dismiss.bs.modal');
} }
} };
Modal.prototype.hideModal = function () { Modal.prototype.hideModal = function() {
var that = this let that = this;
this.$element.hide() this.$element.hide();
this.backdrop(function () { this.backdrop(function() {
that.$body.removeClass('modal-open') that.$body.removeClass('modal-open');
that.resetScrollbar() that.resetScrollbar();
that.$element.trigger('hidden.bs.modal') that.$element.trigger('hidden.bs.modal');
}) });
} };
Modal.prototype.removeBackdrop = function () { Modal.prototype.removeBackdrop = function() {
this.$backdrop && this.$backdrop.remove() this.$backdrop && this.$backdrop.remove();
this.$backdrop = null this.$backdrop = null;
} };
Modal.prototype.backdrop = function (callback) { Modal.prototype.backdrop = function(callback) {
var that = this let that = this;
var animate = this.$element.hasClass('fade') ? 'fade' : '' let animate = this.$element.hasClass('fade') ? 'fade' : '';
if (this.isShown && this.options.backdrop) { if (this.isShown && this.options.backdrop) {
var doAnimate = $.support.transition && animate let doAnimate = $.support.transition && animate;
this.$backdrop = $('<div class="modal-backdrop ' + animate + '" />') this.$backdrop = $('<div class="modal-backdrop ' + animate + '" />')
.prependTo(this.$element) .prependTo(this.$element)
.on('click.dismiss.bs.modal', $.proxy(function (e) { .on('click.dismiss.bs.modal', $.proxy(function(e) {
if (e.target !== e.currentTarget) return if (e.target !== e.currentTarget) return;
this.options.backdrop == 'static' this.options.backdrop == 'static'
? this.$element[0].focus.call(this.$element[0]) ? this.$element[0].focus.call(this.$element[0])
: this.hide.call(this) : this.hide.call(this);
}, this)) }, this));
if (doAnimate) this.$backdrop[0].offsetWidth // force reflow if (doAnimate) this.$backdrop[0].offsetWidth; // force reflow
this.$backdrop.addClass('in') this.$backdrop.addClass('in');
if (!callback) return if (!callback) return;
doAnimate ? doAnimate ?
this.$backdrop this.$backdrop
.one('bsTransitionEnd', callback) .one('bsTransitionEnd', callback)
.emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :
callback() callback();
} else if (!this.isShown && this.$backdrop) { } else if (!this.isShown && this.$backdrop) {
this.$backdrop.removeClass('in') this.$backdrop.removeClass('in');
var callbackRemove = function () { let callbackRemove = function() {
that.removeBackdrop() that.removeBackdrop();
callback && callback() callback && callback();
} };
$.support.transition && this.$element.hasClass('fade') ? $.support.transition && this.$element.hasClass('fade') ?
this.$backdrop this.$backdrop
.one('bsTransitionEnd', callbackRemove) .one('bsTransitionEnd', callbackRemove)
.emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :
callbackRemove() callbackRemove();
} else if (callback) { } else if (callback) {
callback() callback();
} }
} };
Modal.prototype.checkScrollbar = function () { Modal.prototype.checkScrollbar = function() {
this.scrollbarWidth = this.measureScrollbar() this.scrollbarWidth = this.measureScrollbar();
} };
Modal.prototype.setScrollbar = function () { Modal.prototype.setScrollbar = function() {
var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10) let bodyPad = parseInt((this.$body.css('padding-right') || 0), 10);
if (this.scrollbarWidth) this.$body.css('padding-right', bodyPad + this.scrollbarWidth) if (this.scrollbarWidth) this.$body.css('padding-right', bodyPad + this.scrollbarWidth);
} };
Modal.prototype.resetScrollbar = function () { Modal.prototype.resetScrollbar = function() {
this.$body.css('padding-right', '') this.$body.css('padding-right', '');
} };
Modal.prototype.measureScrollbar = function () { // thx walsh Modal.prototype.measureScrollbar = function() { // thx walsh
if (document.body.clientWidth >= window.innerWidth) return 0 if (document.body.clientWidth >= window.innerWidth) return 0;
var scrollDiv = document.createElement('div') let scrollDiv = document.createElement('div');
scrollDiv.className = 'modal-scrollbar-measure' scrollDiv.className = 'modal-scrollbar-measure';
this.$body.append(scrollDiv) this.$body.append(scrollDiv);
var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth let scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth;
this.$body[0].removeChild(scrollDiv) this.$body[0].removeChild(scrollDiv);
return scrollbarWidth return scrollbarWidth;
} };
// MODAL PLUGIN DEFINITION // MODAL PLUGIN DEFINITION
// ======================= // =======================
function Plugin(option, _relatedTarget) { function Plugin(option, _relatedTarget) {
return this.each(function () { return this.each(function() {
var $this = $(this) let $this = $(this);
var data = $this.data('bs.modal') let data = $this.data('bs.modal');
var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option) let options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option);
if (!data) $this.data('bs.modal', (data = new Modal(this, options))) if (!data) $this.data('bs.modal', (data = new Modal(this, options)));
if (typeof option == 'string') data[option](_relatedTarget) if (typeof option == 'string') data[option](_relatedTarget);
else if (options.show) data.show(_relatedTarget) else if (options.show) data.show(_relatedTarget);
}) });
} }
var old = $.fn.modal let old = $.fn.modal;
$.fn.modal = Plugin $.fn.modal = Plugin;
$.fn.modal.Constructor = Modal $.fn.modal.Constructor = Modal;
// MODAL NO CONFLICT // MODAL NO CONFLICT
// ================= // =================
$.fn.modal.noConflict = function () { $.fn.modal.noConflict = function() {
$.fn.modal = old $.fn.modal = old;
return this return this;
} };
// MODAL DATA-API // MODAL DATA-API
// ============== // ==============
$(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) { $(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function(e) {
var $this = $(this) let $this = $(this);
var href = $this.attr('href') let href = $this.attr('href');
var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) // strip for ie7 let $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))); // strip for ie7
var option = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data()) let option = $target.data('bs.modal') ? 'toggle' : $.extend({remote: !/#/.test(href) && href}, $target.data(), $this.data());
if ($this.is('a')) e.preventDefault() if ($this.is('a')) e.preventDefault();
$target.one('show.bs.modal', function (showEvent) {
if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown
$target.one('hidden.bs.modal', function () {
$this.is(':visible') && $this.trigger('focus')
})
})
Plugin.call($target, option, this)
})
$target.one('show.bs.modal', function(showEvent) {
if (showEvent.isDefaultPrevented()) return; // only register focus restorer if modal will actually get shown
$target.one('hidden.bs.modal', function() {
$this.is(':visible') && $this.trigger('focus');
});
});
Plugin.call($target, option, this);
});
}(jQuery); }(jQuery);

View File

@ -1,54 +1,53 @@
var $ = require('unopinionate').selector let $ = require('unopinionate').selector;
var onClick = require('onclick') let onClick = require('onclick');
var transitionComplete = require('transition-complete') let transitionComplete = require('transition-complete');
$(function() { $(function() {
onClick('.entry .name', function() { onClick('.entry .name', function() {
var $this = $(this) let $this = $(this);
var $entry = $this.closest('.entry') let $entry = $this.closest('.entry');
if ($entry.hasClass('open')) { if ($entry.hasClass('open')) {
// Close entry // Close entry
$entry $entry
.height($entry.outerHeight()) .height($entry.outerHeight())
.removeClass('open') .removeClass('open');
setTimeout(function() { setTimeout(function() {
$entry.css('height', $entry.attr('data-height') + 'px') $entry.css('height', $entry.attr('data-height') + 'px');
}, 0) }, 0);
transitionComplete(function() { transitionComplete(function() {
$entry.find('.readme').remove() $entry.find('.readme').remove();
$entry.css('height', 'auto') $entry.css('height', 'auto');
}) });
} else { } else {
// Open entry // Open entry
$('.entry.open').each(function() { $('.entry.open').each(function() {
// Close open entries // Close open entries
var $entry = $(this) let $entry = $(this);
$entry $entry
.height($entry.outerHeight()) .height($entry.outerHeight())
.removeClass('open') .removeClass('open');
setTimeout(function() { setTimeout(function() {
$entry.css('height', $entry.attr('data-height') + 'px') $entry.css('height', $entry.attr('data-height') + 'px');
}, 0) }, 0);
transitionComplete(function() { transitionComplete(function() {
$entry.find('.readme').remove() $entry.find('.readme').remove();
$entry.css('height', 'auto') $entry.css('height', 'auto');
}) });
}) });
// Add the open class // Add the open class
$entry.addClass('open') $entry.addClass('open');
// Explicitly set heights for transitions // Explicitly set heights for transitions
var height = $entry.outerHeight() let height = $entry.outerHeight();
$entry $entry
.attr('data-height', height) .attr('data-height', height)
.css('height', height) .css('height', height);
// Get the data // Get the data
$.ajax({ $.ajax({
@ -57,17 +56,17 @@ $(function() {
+ encodeURIComponent($entry.attr('data-version')), + encodeURIComponent($entry.attr('data-version')),
dataType: 'text', dataType: 'text',
success: function(html) { success: function(html) {
var $readme = $("<div class='readme'>") let $readme = $('<div class=\'readme\'>')
.html(html) .html(html)
.appendTo($entry) .appendTo($entry);
$entry.height(height + $readme.outerHeight()) $entry.height(height + $readme.outerHeight());
transitionComplete(function() { transitionComplete(function() {
$entry.css('height', 'auto') $entry.css('height', 'auto');
}) });
} },
}) });
} }
}) });
}) });

View File

@ -1,13 +1,13 @@
// twitter bootstrap stuff; // twitter bootstrap stuff;
// not in static 'cause I want it to be bundled with the rest of javascripts // not in static 'cause I want it to be bundled with the rest of javascripts
require('./bootstrap-modal') require('./bootstrap-modal');
// our own files // our own files
require('./search') require('./search');
require('./entry') require('./entry');
var $ = require('unopinionate').selector let $ = require('unopinionate').selector;
$(document).on('click', '.js-userLogoutBtn', function() { $(document).on('click', '.js-userLogoutBtn', function() {
$('#userLogoutForm').submit() $('#userLogoutForm').submit();
return false return false;
}) });

View File

@ -1,5 +1,5 @@
var $ = require('unopinionate').selector let $ = require('unopinionate').selector;
var template = require('../entry.hbs') let template = require('../entry.hbs');
$(function() { $(function() {
;(function(window, document) { ;(function(window, document) {
@ -22,7 +22,8 @@ $(function() {
$form.bind('submit keyup', function(e) { $form.bind('submit keyup', function(e) {
var query, isValidQuery var query, isValidQuery
e.preventDefault()
e.preventDefault();
query = $input.val() query = $input.val()
isValidQuery = (query !== '') isValidQuery = (query !== '')
@ -31,35 +32,35 @@ $(function() {
if (!isValidQuery) { if (!isValidQuery) {
if (request && typeof request.abort === 'function') { if (request && typeof request.abort === 'function') {
request.abort() request.abort();
} }
$searchResults.html('') $searchResults.html('')
return return;
} }
if (request && typeof request.abort === 'function') { if (request && typeof request.abort === 'function') {
request.abort() request.abort();
} }
if (query !== lastQuery) { if (query !== lastQuery) {
lastQuery = query lastQuery = query
$searchResults.html( $searchResults.html(
"<img class='search-ajax' src='-/static/ajax.gif' alt='Spinner'/>") '<img class=\'search-ajax\' src=\'-/static/ajax.gif\' alt=\'Spinner\'/>');
} }
request = $.getJSON('-/search/' + query, function( results ) { request = $.getJSON('-/search/' + query, function( results ) {
if (results.length > 0) { if (results.length > 0) {
var html = '' let html = '';
$.each(results, function(i, entry) { $.each(results, function(i, entry) {
html += template(entry) html += template(entry);
}) });
$searchResults.html(html) $searchResults.html(html);
} else { } else {
$searchResults.html( $searchResults.html(
"<div class='no-results'><big>No Results</big></div>") '<div class=\'no-results\'><big>No Results</big></div>');
} }
}).fail(function () { }).fail(function () {
$searchResults.html( $searchResults.html(
@ -68,10 +69,9 @@ $(function() {
}) })
$(document).on('click', '.icon-cancel', function(e) { $(document).on('click', '.icon-cancel', function(e) {
e.preventDefault() e.preventDefault();
$input.val('') $input.val('');
$form.keyup() $form.keyup();
}) });
})(window, window.document);
})(window, window.document) });
})

View File

@ -1,33 +1,37 @@
const Crypto = require('crypto') /* eslint prefer-spread: "off" */
const jju = require('jju')
const Error = require('http-errors') 'use strict';
const Logger = require('./logger')
const Crypto = require('crypto');
const jju = require('jju');
const Error = require('http-errors');
const Logger = require('./logger');
const load_plugins = require('./plugin-loader').load_plugins; const load_plugins = require('./plugin-loader').load_plugins;
module.exports = Auth module.exports = Auth;
function Auth(config) { function Auth(config) {
var self = Object.create(Auth.prototype) let self = Object.create(Auth.prototype);
self.config = config self.config = config;
self.logger = Logger.logger.child({ sub: 'auth' }) self.logger = Logger.logger.child({sub: 'auth'});
self.secret = config.secret self.secret = config.secret;
var plugin_params = { let plugin_params = {
config: config, config: config,
logger: self.logger logger: self.logger,
} };
if (config.users_file) { if (config.users_file) {
if (!config.auth || !config.auth.htpasswd) { if (!config.auth || !config.auth.htpasswd) {
// b/w compat // b/w compat
config.auth = config.auth || {} config.auth = config.auth || {};
config.auth.htpasswd = { file: config.users_file } config.auth.htpasswd = {file: config.users_file};
} }
} }
self.plugins = load_plugins(config, config.auth, plugin_params, function (p) { self.plugins = load_plugins(config, config.auth, plugin_params, function(p) {
return p.authenticate || p.allow_access || p.allow_publish return p.authenticate || p.allow_access || p.allow_publish;
}) });
self.plugins.unshift({ self.plugins.unshift({
verdaccio_version: '1.1.0', verdaccio_version: '1.1.0',
@ -38,342 +42,342 @@ function Auth(config) {
&& (Crypto.createHash('sha1').update(password).digest('hex') && (Crypto.createHash('sha1').update(password).digest('hex')
=== config.users[user].password) === config.users[user].password)
) { ) {
return cb(null, [ user ]) return cb(null, [user]);
} }
return cb() return cb();
}, },
adduser: function(user, password, cb) { adduser: function(user, password, cb) {
if (config.users && config.users[user]) if (config.users && config.users[user])
return cb( Error[403]('this user already exists') ) return cb( Error[403]('this user already exists') );
return cb() return cb();
}, },
}) });
function allow_action(action) { function allow_action(action) {
return function(user, package, cb) { return function(user, pkg, cb) {
var ok = package[action].reduce(function(prev, curr) { let ok = pkg[action].reduce(function(prev, curr) {
if (user.groups.indexOf(curr) !== -1) return true if (user.groups.indexOf(curr) !== -1) return true;
return prev return prev;
}, false) }, false);
if (ok) return cb(null, true) if (ok) return cb(null, true);
if (user.name) { if (user.name) {
cb( Error[403]('user ' + user.name + ' is not allowed to ' + action + ' package ' + package.name) ) cb( Error[403]('user ' + user.name + ' is not allowed to ' + action + ' package ' + pkg.name) );
} else { } else {
cb( Error[403]('unregistered users are not allowed to ' + action + ' package ' + package.name) ) cb( Error[403]('unregistered users are not allowed to ' + action + ' package ' + pkg.name) );
} }
} };
} }
self.plugins.push({ self.plugins.push({
authenticate: function(user, password, cb) { authenticate: function(user, password, cb) {
return cb( Error[403]('bad username/password, access denied') ) return cb( Error[403]('bad username/password, access denied') );
}, },
add_user: function(user, password, cb) { add_user: function(user, password, cb) {
return cb( Error[409]('registration is disabled') ) return cb( Error[409]('registration is disabled') );
}, },
allow_access: allow_action('access'), allow_access: allow_action('access'),
allow_publish: allow_action('publish'), allow_publish: allow_action('publish'),
}) });
return self return self;
} }
Auth.prototype.authenticate = function(user, password, cb) { Auth.prototype.authenticate = function(user, password, cb) {
var plugins = this.plugins.slice(0) let plugins = this.plugins.slice(0)
;(function next() { ;(function next() {
var p = plugins.shift() let p = plugins.shift();
if (typeof(p.authenticate) !== 'function') { if (typeof(p.authenticate) !== 'function') {
return next() return next();
} }
p.authenticate(user, password, function(err, groups) { p.authenticate(user, password, function(err, groups) {
if (err) return cb(err) if (err) return cb(err);
if (groups != null && groups != false) if (groups != null && groups != false)
return cb(err, AuthenticatedUser(user, groups)) return cb(err, authenticatedUser(user, groups));
next() next();
}) });
})() })();
} };
Auth.prototype.add_user = function(user, password, cb) { Auth.prototype.add_user = function(user, password, cb) {
var self = this let self = this;
var plugins = this.plugins.slice(0) let plugins = this.plugins.slice(0)
;(function next() { ;(function next() {
var p = plugins.shift() let p = plugins.shift();
var n = 'adduser' let n = 'adduser';
if (typeof(p[n]) !== 'function') { if (typeof(p[n]) !== 'function') {
n = 'add_user' n = 'add_user';
} }
if (typeof(p[n]) !== 'function') { if (typeof(p[n]) !== 'function') {
next() next();
} else { } else {
p[n](user, password, function(err, ok) { p[n](user, password, function(err, ok) {
if (err) return cb(err) if (err) return cb(err);
if (ok) return self.authenticate(user, password, cb) if (ok) return self.authenticate(user, password, cb);
next() next();
}) });
} }
})() })();
} };
Auth.prototype.allow_access = function(package_name, user, callback) { Auth.prototype.allow_access = function(package_name, user, callback) {
var plugins = this.plugins.slice(0) let plugins = this.plugins.slice(0);
var package = Object.assign({ name: package_name }, let pkg = Object.assign({name: package_name},
this.config.get_package_spec(package_name)) this.config.get_package_spec(package_name))
;(function next() { ;(function next() {
var p = plugins.shift() let p = plugins.shift();
if (typeof(p.allow_access) !== 'function') { if (typeof(p.allow_access) !== 'function') {
return next() return next();
} }
p.allow_access(user, package, function(err, ok) { p.allow_access(user, pkg, function(err, ok) {
if (err) return callback(err) if (err) return callback(err);
if (ok) return callback(null, ok) if (ok) return callback(null, ok);
next() // cb(null, false) causes next plugin to roll next(); // cb(null, false) causes next plugin to roll
}) });
})() })();
} };
Auth.prototype.allow_publish = function(package_name, user, callback) { Auth.prototype.allow_publish = function(package_name, user, callback) {
var plugins = this.plugins.slice(0) let plugins = this.plugins.slice(0);
var package = Object.assign({ name: package_name }, let pkg = Object.assign({name: package_name},
this.config.get_package_spec(package_name)) this.config.get_package_spec(package_name))
;(function next() { ;(function next() {
var p = plugins.shift() let p = plugins.shift();
if (typeof(p.allow_publish) !== 'function') { if (typeof(p.allow_publish) !== 'function') {
return next() return next();
} }
p.allow_publish(user, package, function(err, ok) { p.allow_publish(user, pkg, function(err, ok) {
if (err) return callback(err) if (err) return callback(err);
if (ok) return callback(null, ok) if (ok) return callback(null, ok);
next() // cb(null, false) causes next plugin to roll next(); // cb(null, false) causes next plugin to roll
}) });
})() })();
} };
Auth.prototype.basic_middleware = function() { Auth.prototype.basic_middleware = function() {
var self = this let self = this;
return function(req, res, _next) { return function(req, res, _next) {
req.pause() req.pause();
function next(err) { function next(err) {
req.resume() req.resume();
// uncomment this to reject users with bad auth headers // uncomment this to reject users with bad auth headers
//return _next.apply(null, arguments) // return _next.apply(null, arguments)
// swallow error, user remains unauthorized // swallow error, user remains unauthorized
// set remoteUserError to indicate that user was attempting authentication // set remoteUserError to indicate that user was attempting authentication
if (err) req.remote_user.error = err.message if (err) req.remote_user.error = err.message;
return _next() return _next();
} }
if (req.remote_user != null && req.remote_user.name !== undefined) if (req.remote_user != null && req.remote_user.name !== undefined)
return next() return next();
req.remote_user = AnonymousUser() req.remote_user = buildAnonymousUser();
var authorization = req.headers.authorization let authorization = req.headers.authorization;
if (authorization == null) return next() if (authorization == null) return next();
var parts = authorization.split(' ') let parts = authorization.split(' ');
if (parts.length !== 2) if (parts.length !== 2)
return next( Error[400]('bad authorization header') ) return next( Error[400]('bad authorization header') );
var scheme = parts[0] let scheme = parts[0];
if (scheme === 'Basic') { if (scheme === 'Basic') {
var credentials = new Buffer(parts[1], 'base64').toString() var credentials = new Buffer(parts[1], 'base64').toString();
} else if (scheme === 'Bearer') { } else if (scheme === 'Bearer') {
var credentials = self.aes_decrypt(new Buffer(parts[1], 'base64')).toString('utf8') var credentials = self.aes_decrypt(new Buffer(parts[1], 'base64')).toString('utf8');
if (!credentials) return next() if (!credentials) return next();
} else { } else {
return next() return next();
} }
var index = credentials.indexOf(':') let index = credentials.indexOf(':');
if (index < 0) return next() if (index < 0) return next();
var user = credentials.slice(0, index) let user = credentials.slice(0, index);
var pass = credentials.slice(index + 1) let pass = credentials.slice(index + 1);
self.authenticate(user, pass, function(err, user) { self.authenticate(user, pass, function(err, user) {
if (!err) { if (!err) {
req.remote_user = user req.remote_user = user;
next() next();
} else { } else {
req.remote_user = AnonymousUser() req.remote_user = buildAnonymousUser();
next(err) next(err);
} }
}) });
} };
} };
Auth.prototype.bearer_middleware = function() { Auth.prototype.bearer_middleware = function() {
var self = this let self = this;
return function(req, res, _next) { return function(req, res, _next) {
req.pause() req.pause();
function next(_err) { function next(_err) {
req.resume() req.resume();
return _next.apply(null, arguments) return _next.apply(null, arguments);
} }
if (req.remote_user != null && req.remote_user.name !== undefined) if (req.remote_user != null && req.remote_user.name !== undefined)
return next() return next();
req.remote_user = AnonymousUser() req.remote_user = buildAnonymousUser();
var authorization = req.headers.authorization let authorization = req.headers.authorization;
if (authorization == null) return next() if (authorization == null) return next();
var parts = authorization.split(' ') let parts = authorization.split(' ');
if (parts.length !== 2) if (parts.length !== 2)
return next( Error[400]('bad authorization header') ) return next( Error[400]('bad authorization header') );
var scheme = parts[0] let scheme = parts[0];
var token = parts[1] let token = parts[1];
if (scheme !== 'Bearer') if (scheme !== 'Bearer')
return next() return next();
try { try {
var user = self.decode_token(token) var user = self.decode_token(token);
} catch(err) { } catch(err) {
return next(err) return next(err);
} }
req.remote_user = AuthenticatedUser(user.u, user.g) req.remote_user = authenticatedUser(user.u, user.g);
req.remote_user.token = token req.remote_user.token = token;
next() next();
} };
} };
Auth.prototype.cookie_middleware = function() { Auth.prototype.cookie_middleware = function() {
var self = this let self = this;
return function(req, res, _next) { return function(req, res, _next) {
req.pause() req.pause();
function next(_err) { function next(_err) {
req.resume() req.resume();
return _next() return _next();
} }
if (req.remote_user != null && req.remote_user.name !== undefined) if (req.remote_user != null && req.remote_user.name !== undefined)
return next() return next();
req.remote_user = AnonymousUser() req.remote_user = buildAnonymousUser();
var token = req.cookies.get('token') let token = req.cookies.get('token');
if (token == null) return next() if (token == null) return next();
/*try { /* try {
var user = self.decode_token(token, 60*60) var user = self.decode_token(token, 60*60)
} catch(err) { } catch(err) {
return next() return next()
} }
req.remote_user = AuthenticatedUser(user.u, user.g) req.remote_user = authenticatedUser(user.u, user.g)
req.remote_user.token = token req.remote_user.token = token
next()*/ next()*/
var credentials = self.aes_decrypt(new Buffer(token, 'base64')).toString('utf8') let credentials = self.aes_decrypt(new Buffer(token, 'base64')).toString('utf8');
if (!credentials) return next() if (!credentials) return next();
var index = credentials.indexOf(':') let index = credentials.indexOf(':');
if (index < 0) return next() if (index < 0) return next();
var user = credentials.slice(0, index) let user = credentials.slice(0, index);
var pass = credentials.slice(index + 1) let pass = credentials.slice(index + 1);
self.authenticate(user, pass, function(err, user) { self.authenticate(user, pass, function(err, user) {
if (!err) { if (!err) {
req.remote_user = user req.remote_user = user;
next() next();
} else { } else {
req.remote_user = AnonymousUser() req.remote_user = buildAnonymousUser();
next(err) next(err);
} }
}) });
} };
} };
Auth.prototype.issue_token = function(user) { Auth.prototype.issue_token = function(user) {
var data = jju.stringify({ let data = jju.stringify({
u: user.name, u: user.name,
g: user.real_groups && user.real_groups.length ? user.real_groups : undefined, g: user.real_groups && user.real_groups.length ? user.real_groups : undefined,
t: ~~(Date.now()/1000), t: ~~(Date.now()/1000),
}, { indent: false }) }, {indent: false});
data = new Buffer(data, 'utf8') data = new Buffer(data, 'utf8');
var mac = Crypto.createHmac('sha256', this.secret).update(data).digest() let mac = Crypto.createHmac('sha256', this.secret).update(data).digest();
return Buffer.concat([ data, mac ]).toString('base64') return Buffer.concat([data, mac]).toString('base64');
} };
Auth.prototype.decode_token = function(str, expire_time) { Auth.prototype.decode_token = function(str, expire_time) {
var buf = new Buffer(str, 'base64') let buf = new Buffer(str, 'base64');
if (buf.length <= 32) throw Error[401]('invalid token') if (buf.length <= 32) throw Error[401]('invalid token');
var data = buf.slice(0, buf.length - 32) let data = buf.slice(0, buf.length - 32);
var their_mac = buf.slice(buf.length - 32) let their_mac = buf.slice(buf.length - 32);
var good_mac = Crypto.createHmac('sha256', this.secret).update(data).digest() let good_mac = Crypto.createHmac('sha256', this.secret).update(data).digest();
their_mac = Crypto.createHash('sha512').update(their_mac).digest('hex') their_mac = Crypto.createHash('sha512').update(their_mac).digest('hex');
good_mac = Crypto.createHash('sha512').update(good_mac).digest('hex') good_mac = Crypto.createHash('sha512').update(good_mac).digest('hex');
if (their_mac !== good_mac) throw Error[401]('bad signature') if (their_mac !== good_mac) throw Error[401]('bad signature');
// make token expire in 24 hours // make token expire in 24 hours
// TODO: make configurable? // TODO: make configurable?
expire_time = expire_time || 24*60*60 expire_time = expire_time || 24*60*60;
data = jju.parse(data.toString('utf8')) data = jju.parse(data.toString('utf8'));
if (Math.abs(data.t - ~~(Date.now()/1000)) > expire_time) if (Math.abs(data.t - ~~(Date.now()/1000)) > expire_time)
throw Error[401]('token expired') throw Error[401]('token expired');
return data return data;
} };
Auth.prototype.aes_encrypt = function(buf) { Auth.prototype.aes_encrypt = function(buf) {
var c = Crypto.createCipher('aes192', this.secret) let c = Crypto.createCipher('aes192', this.secret);
var b1 = c.update(buf) let b1 = c.update(buf);
var b2 = c.final() let b2 = c.final();
return Buffer.concat([ b1, b2 ]) return Buffer.concat([b1, b2]);
} };
Auth.prototype.aes_decrypt = function(buf) { Auth.prototype.aes_decrypt = function(buf) {
try { try {
var c = Crypto.createDecipher('aes192', this.secret) let c = Crypto.createDecipher('aes192', this.secret);
var b1 = c.update(buf) let b1 = c.update(buf);
var b2 = c.final() let b2 = c.final();
return Buffer.concat([b1, b2]);
} catch(_) { } catch(_) {
return new Buffer(0) return new Buffer(0);
} }
return Buffer.concat([ b1, b2 ]) };
}
function AnonymousUser() { function buildAnonymousUser() {
return { return {
name: undefined, name: undefined,
// groups without '$' are going to be deprecated eventually // groups without '$' are going to be deprecated eventually
groups: [ '$all', '$anonymous', '@all', '@anonymous', 'all', 'undefined', 'anonymous' ], groups: ['$all', '$anonymous', '@all', '@anonymous', 'all', 'undefined', 'anonymous'],
real_groups: [], real_groups: [],
} };
} }
function AuthenticatedUser(name, groups) { function authenticatedUser(name, groups) {
var _groups = (groups || []).concat([ '$all', '$authenticated', '@all', '@authenticated', 'all' ]) let _groups = (groups || []).concat(['$all', '$authenticated', '@all', '@authenticated', 'all']);
return { return {
name: name, name: name,
groups: _groups, groups: _groups,
real_groups: groups, real_groups: groups,
} };
} }

View File

@ -1,113 +1,113 @@
#!/usr/bin/env node #!/usr/bin/env node
/*eslint no-sync:0*/ /* eslint no-sync:0*/
'use strict';
if (process.getuid && process.getuid() === 0) { if (process.getuid && process.getuid() === 0) {
global.console.error("Verdaccio doesn't need superuser privileges. Don't run it under root.") global.console.error('Verdaccio doesn\'t need superuser privileges. Don\'t run it under root.');
} }
process.title = 'verdaccio' process.title = 'verdaccio';
try { try {
// for debugging memory leaks // for debugging memory leaks
// totally optional // totally optional
require('heapdump') require('heapdump');
} catch(err) {} } catch(err) {}
var logger = require('./logger') let logger = require('./logger');
logger.setup() // default setup logger.setup(); // default setup
var commander = require('commander') let commander = require('commander');
var constants = require('constants') let constants = require('constants');
var fs = require('fs') let fs = require('fs');
var http = require('http') let http = require('http');
var https = require('https') let https = require('https');
var YAML = require('js-yaml') let YAML = require('js-yaml');
var Path = require('path') let Path = require('path');
var URL = require('url') let URL = require('url');
var server = require('./index') let server = require('./index');
var Utils = require('./utils') let Utils = require('./utils');
var pkginfo = require('pkginfo')(module); // eslint-disable-line no-unused-vars let pkginfo = require('pkginfo')(module); // eslint-disable-line no-unused-vars
var pkgVersion = module.exports.version let pkgVersion = module.exports.version;
var pkgName = module.exports.name let pkgName = module.exports.name;
commander commander
.option('-l, --listen <[host:]port>', 'host:port number to listen on (default: localhost:4873)') .option('-l, --listen <[host:]port>', 'host:port number to listen on (default: localhost:4873)')
.option('-c, --config <config.yaml>', 'use this configuration file (default: ./config.yaml)') .option('-c, --config <config.yaml>', 'use this configuration file (default: ./config.yaml)')
.version(pkgVersion) .version(pkgVersion)
.parse(process.argv) .parse(process.argv);
if (commander.args.length == 1 && !commander.config) { if (commander.args.length == 1 && !commander.config) {
// handling "verdaccio [config]" case if "-c" is missing in commandline // handling "verdaccio [config]" case if "-c" is missing in commandline
commander.config = commander.args.pop() commander.config = commander.args.pop();
} }
if (commander.args.length != 0) { if (commander.args.length != 0) {
commander.help() commander.help();
} }
var config, config_path let config, config_path;
try { try {
if (commander.config) { if (commander.config) {
config_path = Path.resolve(commander.config) config_path = Path.resolve(commander.config);
} else { } else {
config_path = require('./config-path')() config_path = require('./config-path')();
} }
config = YAML.safeLoad(fs.readFileSync(config_path, 'utf8')) config = YAML.safeLoad(fs.readFileSync(config_path, 'utf8'));
logger.logger.warn({ file: config_path }, 'config file - @{file}') logger.logger.warn({file: config_path}, 'config file - @{file}');
} catch (err) { } catch (err) {
logger.logger.fatal({ file: config_path, err: err }, 'cannot open config file @{file}: @{!err.message}') logger.logger.fatal({file: config_path, err: err}, 'cannot open config file @{file}: @{!err.message}');
process.exit(1) process.exit(1);
} }
afterConfigLoad() afterConfigLoad();
function get_listen_addresses() { function get_listen_addresses() {
// command line || config file || default // command line || config file || default
var addresses let addresses;
if (commander.listen) { if (commander.listen) {
addresses = [ commander.listen ] addresses = [commander.listen];
} else if (Array.isArray(config.listen)) { } else if (Array.isArray(config.listen)) {
addresses = config.listen addresses = config.listen;
} else if (config.listen) { } else if (config.listen) {
addresses = [ config.listen ] addresses = [config.listen];
} else { } else {
addresses = [ '4873' ] addresses = ['4873'];
} }
addresses = addresses.map(function(addr) { addresses = addresses.map(function(addr) {
var parsed_addr = Utils.parse_address(addr) let parsed_addr = Utils.parse_address(addr);
if (!parsed_addr) { if (!parsed_addr) {
logger.logger.warn({ addr: addr }, logger.logger.warn({addr: addr},
'invalid address - @{addr}, we expect a port (e.g. "4873"),' 'invalid address - @{addr}, we expect a port (e.g. "4873"),'
+ ' host:port (e.g. "localhost:4873") or full url' + ' host:port (e.g. "localhost:4873") or full url'
+ ' (e.g. "http://localhost:4873/")') + ' (e.g. "http://localhost:4873/")');
} }
return parsed_addr return parsed_addr;
}).filter(Boolean);
}).filter(Boolean) return addresses;
return addresses
} }
function afterConfigLoad() { function afterConfigLoad() {
if (!config.self_path) config.self_path = Path.resolve(config_path) if (!config.self_path) config.self_path = Path.resolve(config_path);
if (!config.https) config.https = { enable: false }; if (!config.https) config.https = {enable: false};
var app = server(config) let app = server(config);
get_listen_addresses().forEach(function(addr) { get_listen_addresses().forEach(function(addr) {
var webServer let webServer;
if (addr.proto === 'https') { // https if (addr.proto === 'https') { // https
if (!config.https || !config.https.key || !config.https.cert) { if (!config.https || !config.https.key || !config.https.cert) {
var conf_path = function(file) { let conf_path = function(file) {
if (!file) return config_path if (!file) return config_path;
return Path.resolve(Path.dirname(config_path), file) return Path.resolve(Path.dirname(config_path), file);
} };
logger.logger.fatal([ logger.logger.fatal([
'You need to specify "https.key" and "https.cert" to run https server', 'You need to specify "https.key" and "https.cert" to run https server',
@ -122,8 +122,8 @@ function afterConfigLoad() {
' https:', ' https:',
' key: verdaccio-key.pem', ' key: verdaccio-key.pem',
' cert: verdaccio-cert.pem', ' cert: verdaccio-cert.pem',
].join('\n')) ].join('\n'));
process.exit(2) process.exit(2);
} }
try { try {
@ -132,12 +132,11 @@ function afterConfigLoad() {
secureOptions: constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3, secureOptions: constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3,
key: fs.readFileSync(config.https.key), key: fs.readFileSync(config.https.key),
cert: fs.readFileSync(config.https.cert), cert: fs.readFileSync(config.https.cert),
ca: fs.readFileSync(config.https.ca) ca: fs.readFileSync(config.https.ca),
}, app);
}, 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}') logger.logger.fatal({err: err}, 'cannot create server: @{err.message}');
process.exit(2) process.exit(2);
} }
} else { // http } else { // http
webServer = http.createServer(app); webServer = http.createServer(app);
@ -146,9 +145,9 @@ function afterConfigLoad() {
webServer webServer
.listen(addr.port || addr.path, addr.host) .listen(addr.port || addr.path, addr.host)
.on('error', function(err) { .on('error', function(err) {
logger.logger.fatal({ err: err }, 'cannot create server: @{err.message}') logger.logger.fatal({err: err}, 'cannot create server: @{err.message}');
process.exit(2) process.exit(2);
}) });
logger.logger.warn({ logger.logger.warn({
addr: ( addr.path addr: ( addr.path
@ -164,17 +163,17 @@ function afterConfigLoad() {
}) })
), ),
version: pkgName + '/' + pkgVersion, version: pkgName + '/' + pkgVersion,
}, 'http address - @{addr}') }, 'http address - @{addr}');
}) });
// undocumented stuff for tests // undocumented stuff for tests
if (typeof(process.send) === 'function') { if (typeof(process.send) === 'function') {
process.send({ verdaccio_started: true }) process.send({verdaccio_started: true});
} }
} }
process.on('uncaughtException', function(err) { process.on('uncaughtException', function(err) {
logger.logger.fatal( { err: err } logger.logger.fatal( {err: err}
, 'uncaught exception, please report this\n@{err.stack}' ) , 'uncaught exception, please report this\n@{err.stack}' );
process.exit(255) process.exit(255);
}) });

View File

@ -1,47 +1,49 @@
var fs = require('fs') 'use strict';
var Path = require('path')
var logger = require('./logger')
module.exports = find_config_file let fs = require('fs');
let Path = require('path');
let logger = require('./logger');
module.exports = find_config_file;
function find_config_file() { function find_config_file() {
var paths = get_paths() let paths = get_paths();
for (var i=0; i<paths.length; i++) { for (let i=0; i<paths.length; i++) {
if (file_exists(paths[i].path)) return paths[i].path if (file_exists(paths[i].path)) return paths[i].path;
} }
create_config_file(paths[0]) create_config_file(paths[0]);
return paths[0].path return paths[0].path;
} }
function create_config_file(config_path) { function create_config_file(config_path) {
require('mkdirp').sync(Path.dirname(config_path.path)) require('mkdirp').sync(Path.dirname(config_path.path));
logger.logger.info({ file: config_path.path }, 'Creating default config file in @{file}') logger.logger.info({file: config_path.path}, 'Creating default config file in @{file}');
var created_config = fs.readFileSync(require.resolve('../conf/default.yaml'), 'utf8') let created_config = fs.readFileSync(require.resolve('../conf/default.yaml'), 'utf8');
if (config_path.type === 'xdg') { if (config_path.type === 'xdg') {
var data_dir = process.env.XDG_DATA_HOME let data_dir = process.env.XDG_DATA_HOME
|| Path.join(process.env.HOME, '.local', 'share') || Path.join(process.env.HOME, '.local', 'share');
if (folder_exists(data_dir)) { if (folder_exists(data_dir)) {
data_dir = Path.resolve(Path.join(data_dir, 'verdaccio', 'storage')) data_dir = Path.resolve(Path.join(data_dir, 'verdaccio', 'storage'));
created_config = created_config.replace(/^storage: .\/storage$/m, 'storage: ' + data_dir) created_config = created_config.replace(/^storage: .\/storage$/m, 'storage: ' + data_dir);
} }
} }
fs.writeFileSync(config_path.path, created_config) fs.writeFileSync(config_path.path, created_config);
} }
function get_paths() { function get_paths() {
var try_paths = [] let try_paths = [];
var xdg_config = process.env.XDG_CONFIG_HOME let xdg_config = process.env.XDG_CONFIG_HOME
|| process.env.HOME && Path.join(process.env.HOME, '.config') || process.env.HOME && Path.join(process.env.HOME, '.config');
if (xdg_config && folder_exists(xdg_config)) { if (xdg_config && folder_exists(xdg_config)) {
try_paths.push({ try_paths.push({
path: Path.join(xdg_config, 'verdaccio', 'config.yaml'), path: Path.join(xdg_config, 'verdaccio', 'config.yaml'),
type: 'xdg', type: 'xdg',
}) });
} }
if (process.platform === 'win32' if (process.platform === 'win32'
@ -50,40 +52,40 @@ function get_paths() {
try_paths.push({ try_paths.push({
path: Path.resolve(Path.join(process.env.APPDATA, 'verdaccio', 'config.yaml')), path: Path.resolve(Path.join(process.env.APPDATA, 'verdaccio', 'config.yaml')),
type: 'win', type: 'win',
}) });
} }
try_paths.push({ try_paths.push({
path: Path.resolve(Path.join('.', 'verdaccio', 'config.yaml')), path: Path.resolve(Path.join('.', 'verdaccio', 'config.yaml')),
type: 'def', type: 'def',
}) });
// backward compatibility // backward compatibility
try_paths.push({ try_paths.push({
path: Path.resolve(Path.join('.', 'config.yaml')), path: Path.resolve(Path.join('.', 'config.yaml')),
type: 'old', type: 'old',
}) });
return try_paths return try_paths;
} }
function folder_exists(path) { function folder_exists(path) {
try { try {
var stat = fs.statSync(path) var stat = fs.statSync(path);
} catch(_) { } catch(_) {
return false return false;
} }
return stat.isDirectory() return stat.isDirectory();
} }
function file_exists(path) { function file_exists(path) {
try { try {
var stat = fs.statSync(path) var stat = fs.statSync(path);
} catch(_) { } catch(_) {
return false return false;
} }
return stat.isFile() return stat.isFile();
} }

View File

@ -1,32 +1,34 @@
var assert = require('assert') 'use strict';
var Crypto = require('crypto')
var Error = require('http-errors') let assert = require('assert');
var minimatch = require('minimatch') let Crypto = require('crypto');
var Path = require('path') let Error = require('http-errors');
var LocalData = require('./local-data') let minimatch = require('minimatch');
var Utils = require('./utils') let Path = require('path');
var Utils = require('./utils') let LocalData = require('./local-data');
var pkginfo = require('pkginfo')(module) // eslint-disable-line no-unused-vars var Utils = require('./utils');
var pkgVersion = module.exports.version var Utils = require('./utils');
var pkgName = module.exports.name let pkginfo = require('pkginfo')(module); // eslint-disable-line no-unused-vars
let pkgVersion = module.exports.version;
let pkgName = module.exports.name;
// [[a, [b, c]], d] -> [a, b, c, d] // [[a, [b, c]], d] -> [a, b, c, d]
function flatten(array) { function flatten(array) {
var result = [] let result = [];
for (var i=0; i<array.length; i++) { for (let i=0; i<array.length; i++) {
if (Array.isArray(array[i])) { if (Array.isArray(array[i])) {
result.push.apply(result, flatten(array[i])) result.push.apply(result, flatten(array[i]));
} else { } else {
result.push(array[i]) result.push(array[i]);
} }
} }
return result return result;
} }
function Config(config) { function Config(config) {
var self = Object.create(Config.prototype) let self = Object.create(Config.prototype);
for (var i in config) { for (var i in config) {
if (self[i] == null) self[i] = config[i] if (self[i] == null) self[i] = config[i];
} }
if (!self.user_agent) { if (!self.user_agent) {
@ -34,44 +36,44 @@ function Config(config) {
} }
// some weird shell scripts are valid yaml files parsed as string // some weird shell scripts are valid yaml files parsed as string
assert.equal(typeof(config), 'object', 'CONFIG: it doesn\'t look like a valid config file') assert.equal(typeof(config), 'object', 'CONFIG: it doesn\'t look like a valid config file');
assert(self.storage, 'CONFIG: storage path not defined') assert(self.storage, 'CONFIG: storage path not defined');
self.localList = new LocalData( self.localList = new LocalData(
Path.join( Path.join(
Path.resolve(Path.dirname(self.self_path || ''), self.storage), Path.resolve(Path.dirname(self.self_path || ''), self.storage),
'.sinopia-db.json' '.sinopia-db.json'
) )
) );
if (!self.secret) { if (!self.secret) {
self.secret = self.localList.data.secret self.secret = self.localList.data.secret;
if (!self.secret) { if (!self.secret) {
self.secret = Crypto.pseudoRandomBytes(32).toString('hex') self.secret = Crypto.pseudoRandomBytes(32).toString('hex');
self.localList.data.secret = self.secret self.localList.data.secret = self.secret;
self.localList.sync() self.localList.sync();
} }
} }
var users = { let users = {
all: true, 'all': true,
anonymous: true, 'anonymous': true,
'undefined': true, 'undefined': true,
owner: true, 'owner': true,
none: true 'none': true,
}; };
var check_user_or_uplink = function(arg) { let check_user_or_uplink = function(arg) {
assert(arg !== 'all' && arg !== 'owner' && arg !== 'anonymous' && arg !== 'undefined' && arg !== 'none', 'CONFIG: reserved user/uplink name: ' + arg) assert(arg !== 'all' && arg !== 'owner' && arg !== 'anonymous' && arg !== 'undefined' && arg !== 'none', 'CONFIG: reserved user/uplink name: ' + arg);
assert(!arg.match(/\s/), 'CONFIG: invalid user name: ' + arg) assert(!arg.match(/\s/), 'CONFIG: invalid user name: ' + arg);
assert(users[arg] == null, 'CONFIG: duplicate user/uplink name: ' + arg) assert(users[arg] == null, 'CONFIG: duplicate user/uplink name: ' + arg);
users[arg] = true users[arg] = true;
} }
;[ 'users', 'uplinks', 'packages' ].forEach(function(x) { ;['users', 'uplinks', 'packages'].forEach(function(x) {
if (self[x] == null) self[x] = {} if (self[x] == null) self[x] = {};
assert(Utils.is_object(self[x]), 'CONFIG: bad "'+x+'" value (object expected)') assert(Utils.is_object(self[x]), 'CONFIG: bad "'+x+'" value (object expected)');
}) });
for (var i in self.users) { for (var i in self.users) {
check_user_or_uplink(i); check_user_or_uplink(i);
@ -81,127 +83,127 @@ function Config(config) {
} }
for (var i in self.users) { for (var i in self.users) {
assert(self.users[i].password, 'CONFIG: no password for user: ' + i) assert(self.users[i].password, 'CONFIG: no password for user: ' + i);
assert(typeof(self.users[i].password) === 'string' && assert(typeof(self.users[i].password) === 'string' &&
self.users[i].password.match(/^[a-f0-9]{40}$/) self.users[i].password.match(/^[a-f0-9]{40}$/)
, 'CONFIG: wrong password format for user: ' + i + ', sha1 expected') , 'CONFIG: wrong password format for user: ' + i + ', sha1 expected');
} }
for (var i in self.uplinks) { for (var i in self.uplinks) {
assert(self.uplinks[i].url, 'CONFIG: no url for uplink: ' + i) assert(self.uplinks[i].url, 'CONFIG: no url for uplink: ' + i);
assert( typeof(self.uplinks[i].url) === 'string' assert( typeof(self.uplinks[i].url) === 'string'
, 'CONFIG: wrong url format for uplink: ' + i) , 'CONFIG: wrong url format for uplink: ' + i);
self.uplinks[i].url = self.uplinks[i].url.replace(/\/$/, '') self.uplinks[i].url = self.uplinks[i].url.replace(/\/$/, '');
} }
function normalize_userlist() { function normalize_userlist() {
var result = [] let result = [];
for (var i=0; i<arguments.length; i++) { for (let i=0; i<arguments.length; i++) {
if (arguments[i] == null) continue if (arguments[i] == null) continue;
// if it's a string, split it to array // if it's a string, split it to array
if (typeof(arguments[i]) === 'string') { if (typeof(arguments[i]) === 'string') {
result.push(arguments[i].split(/\s+/)) result.push(arguments[i].split(/\s+/));
} else if (Array.isArray(arguments[i])) { } else if (Array.isArray(arguments[i])) {
result.push(arguments[i]) result.push(arguments[i]);
} else { } else {
throw Error('CONFIG: bad package acl (array or string expected): ' + JSON.stringify(arguments[i])) throw Error('CONFIG: bad package acl (array or string expected): ' + JSON.stringify(arguments[i]));
} }
} }
return flatten(result) return flatten(result);
} }
// add a default rule for all packages to make writing plugins easier // add a default rule for all packages to make writing plugins easier
if (self.packages['**'] == null) { if (self.packages['**'] == null) {
self.packages['**'] = {} self.packages['**'] = {};
} }
for (var i in self.packages) { for (var i in self.packages) {
assert( assert(
typeof(self.packages[i]) === 'object' && typeof(self.packages[i]) === 'object' &&
!Array.isArray(self.packages[i]) !Array.isArray(self.packages[i])
, 'CONFIG: bad "'+i+'" package description (object expected)') , 'CONFIG: bad "'+i+'" package description (object expected)');
self.packages[i].access = normalize_userlist( self.packages[i].access = normalize_userlist(
self.packages[i].allow_access, self.packages[i].allow_access,
self.packages[i].access self.packages[i].access
); );
delete self.packages[i].allow_access delete self.packages[i].allow_access;
self.packages[i].publish = normalize_userlist( self.packages[i].publish = normalize_userlist(
self.packages[i].allow_publish, self.packages[i].allow_publish,
self.packages[i].publish self.packages[i].publish
); );
delete self.packages[i].allow_publish delete self.packages[i].allow_publish;
self.packages[i].proxy = normalize_userlist( self.packages[i].proxy = normalize_userlist(
self.packages[i].proxy_access, self.packages[i].proxy_access,
self.packages[i].proxy self.packages[i].proxy
); );
delete self.packages[i].proxy_access delete self.packages[i].proxy_access;
} }
// loading these from ENV if aren't in config // loading these from ENV if aren't in config
;[ 'http_proxy', 'https_proxy', 'no_proxy' ].forEach((function(v) { ['http_proxy', 'https_proxy', 'no_proxy'].forEach((function(v) {
if (!(v in self)) { if (!(v in self)) {
self[v] = process.env[v] || process.env[v.toUpperCase()] self[v] = process.env[v] || process.env[v.toUpperCase()];
} }
}).bind(self)) }));
// unique identifier of self server (or a cluster), used to avoid loops // unique identifier of self server (or a cluster), used to avoid loops
if (!self.server_id) { if (!self.server_id) {
self.server_id = Crypto.pseudoRandomBytes(6).toString('hex') self.server_id = Crypto.pseudoRandomBytes(6).toString('hex');
} }
return self return self;
} }
Config.prototype.can_proxy_to = function(package, uplink) { Config.prototype.can_proxy_to = function(pkg, uplink) {
return (this.get_package_spec(package).proxy || []).reduce(function(prev, curr) { return (this.get_package_spec(pkg).proxy || []).reduce(function(prev, curr) {
if (uplink === curr) return true if (uplink === curr) return true;
return prev return prev;
}, false) }, false);
} };
Config.prototype.get_package_spec = function(package) { Config.prototype.get_package_spec = function(pkg) {
for (var i in this.packages) { for (let i in this.packages) {
if (minimatch.makeRe(i).exec(package)) { if (minimatch.makeRe(i).exec(pkg)) {
return this.packages[i] return this.packages[i];
} }
} }
return {} return {};
} };
module.exports = Config module.exports = Config;
var parse_interval_table = { let parse_interval_table = {
'': 1000, '': 1000,
ms: 1, 'ms': 1,
s: 1000, 's': 1000,
m: 60*1000, 'm': 60*1000,
h: 60*60*1000, 'h': 60*60*1000,
d: 86400000, 'd': 86400000,
w: 7*86400000, 'w': 7*86400000,
M: 30*86400000, 'M': 30*86400000,
y: 365*86400000, 'y': 365*86400000,
} };
module.exports.parse_interval = function(interval) { module.exports.parse_interval = function(interval) {
if (typeof(interval) === 'number') return interval * 1000 if (typeof(interval) === 'number') return interval * 1000;
var result = 0 let result = 0;
var last_suffix = Infinity let last_suffix = Infinity;
interval.split(/\s+/).forEach(function(x) { interval.split(/\s+/).forEach(function(x) {
if (!x) return if (!x) return;
var m = x.match(/^((0|[1-9][0-9]*)(\.[0-9]+)?)(ms|s|m|h|d|w|M|y|)$/) let m = x.match(/^((0|[1-9][0-9]*)(\.[0-9]+)?)(ms|s|m|h|d|w|M|y|)$/);
if (!m if (!m
|| parse_interval_table[m[4]] >= last_suffix || parse_interval_table[m[4]] >= last_suffix
|| (m[4] === '' && last_suffix !== Infinity)) { || (m[4] === '' && last_suffix !== Infinity)) {
throw Error('invalid interval: ' + interval) throw Error('invalid interval: ' + interval);
} }
last_suffix = parse_interval_table[m[4]] last_suffix = parse_interval_table[m[4]];
result += Number(m[1]) * parse_interval_table[m[4]] result += Number(m[1]) * parse_interval_table[m[4]];
}) });
return result return result;
} };

View File

@ -1,15 +1,17 @@
'use strict';
/** /**
* file-locking.js - file system locking (replaces fs-ext) * file-locking.js - file system locking (replaces fs-ext)
*/ */
var async = require('async'), let async = require('async'),
locker = require('lockfile'), locker = require('lockfile'),
fs = require('fs'), fs = require('fs'),
path = require('path') path = require('path');
// locks a file by creating a lock file // locks a file by creating a lock file
function lockFile(name, next) { function lockFile(name, next) {
var lockFileName = name + '.lock', let lockFileName = name + '.lock',
lockOpts = { lockOpts = {
wait: 1000, // time (ms) to wait when checking for stale locks wait: 1000, // time (ms) to wait when checking for stale locks
pollPeriod: 100, // how often (ms) to re-check stale locks pollPeriod: 100, // how often (ms) to re-check stale locks
@ -17,65 +19,64 @@ function lockFile(name, next) {
stale: 5 * 60 * 1000, // locks are considered stale after 5 minutes stale: 5 * 60 * 1000, // locks are considered stale after 5 minutes
retries: 100, // number of times to attempt to create a lock retries: 100, // number of times to attempt to create a lock
retryWait: 100 // time (ms) between tries retryWait: 100, // time (ms) between tries
} };
async.series({ async.series({
statdir: function (callback) { statdir: function(callback) {
// test to see if the directory exists // test to see if the directory exists
fs.stat(path.dirname(name), function (err, stats) { fs.stat(path.dirname(name), function(err, stats) {
if (err) { if (err) {
callback(err) callback(err);
} else if (!stats.isDirectory()) { } else if (!stats.isDirectory()) {
callback(new Error(path.dirname(name) + ' is not a directory')) callback(new Error(path.dirname(name) + ' is not a directory'));
} else { } else {
callback(null) callback(null);
}
})
},
statfile: function (callback) {
// test to see if the file to lock exists
fs.stat(name, function (err, stats) {
if (err) {
callback(err)
} else if (!stats.isFile()) {
callback(new Error(path.dirname(name) + ' is not a file'))
} else {
callback(null)
} }
}); });
}, },
lockfile: function (callback) { statfile: function(callback) {
// try to lock the file // test to see if the file to lock exists
locker.lock(lockFileName, lockOpts, callback) fs.stat(name, function(err, stats) {
} if (err) {
callback(err);
} else if (!stats.isFile()) {
callback(new Error(path.dirname(name) + ' is not a file'));
} else {
callback(null);
}
});
},
}, function (err) { lockfile: function(callback) {
// try to lock the file
locker.lock(lockFileName, lockOpts, callback);
},
}, function(err) {
if (err) { if (err) {
// lock failed // lock failed
return next(err) return next(err);
} }
// lock succeeded // lock succeeded
return next(null); return next(null);
}) });
} }
// unlocks file by removing existing lock file // unlocks file by removing existing lock file
function unlockFile(name, next) { function unlockFile(name, next) {
var lockFileName = name + '.lock' let lockFileName = name + '.lock';
locker.unlock(lockFileName, function (err) { locker.unlock(lockFileName, function(err) {
if (err) { if (err) {
return next(err) return next(err);
} }
return next(null) return next(null);
}) });
} }
/** /**
@ -87,63 +88,62 @@ function unlockFile(name, next) {
function readFile(name, options, next) { function readFile(name, options, next) {
if (typeof options === 'function' && next === null) { if (typeof options === 'function' && next === null) {
next = options; next = options;
options = {} options = {};
} }
options = options || {} options = options || {};
options.lock = options.lock || false options.lock = options.lock || false;
options.parse = options.parse || false options.parse = options.parse || false;
function lock(callback) { function lock(callback) {
if (!options.lock) { if (!options.lock) {
return callback(null) return callback(null);
} }
lockFile(name, function (err) { lockFile(name, function(err) {
if (err) { if (err) {
return callback(err) return callback(err);
} }
return callback(null) return callback(null);
}) });
} }
function read(callback) { function read(callback) {
fs.readFile(name, 'utf8', function (err, contents) { fs.readFile(name, 'utf8', function(err, contents) {
if (err) { if (err) {
return callback(err) return callback(err);
} }
callback(null, contents) callback(null, contents);
});
})
} }
function parseJSON(contents, callback) { function parseJSON(contents, callback) {
if (!options.parse) { if (!options.parse) {
return callback(null, contents) return callback(null, contents);
} }
try { try {
contents = JSON.parse(contents) contents = JSON.parse(contents);
return callback(null, contents) return callback(null, contents);
} catch (err) { } catch (err) {
return callback(err) return callback(err);
} }
} }
async.waterfall([ async.waterfall([
lock, lock,
read, read,
parseJSON parseJSON,
], ],
function (err, result) { function(err, result) {
if (err) { if (err) {
return next(err) return next(err);
} else { } else {
return next(null, result) return next(null, result);
} }
}) });
} }
exports.lockFile = lockFile; exports.lockFile = lockFile;

View File

@ -1,99 +1,102 @@
var Cookies = require('cookies') 'use strict';
var express = require('express')
var bodyParser = require('body-parser') let Cookies = require('cookies');
var Error = require('http-errors') let express = require('express');
var Path = require('path') let bodyParser = require('body-parser');
var Middleware = require('./middleware') let Error = require('http-errors');
var Notify = require('./notify') let Path = require('path');
var Utils = require('./utils') let Middleware = require('./middleware');
var expect_json = Middleware.expect_json let Notify = require('./notify');
var match = Middleware.match let Utils = require('./utils');
var media = Middleware.media let expect_json = Middleware.expect_json;
var validate_name = Middleware.validate_name let match = Middleware.match;
var validate_pkg = Middleware.validate_package let media = Middleware.media;
let validate_name = Middleware.validate_name;
let validate_pkg = Middleware.validate_package;
module.exports = function(config, auth, storage) { module.exports = function(config, auth, storage) {
var app = express.Router() let app = express.Router();
var can = Middleware.allow(auth) let can = Middleware.allow(auth);
var notify = Notify.notify; let notify = Notify.notify;
// validate all of these params as a package name // validate all of these params as a package name
// this might be too harsh, so ask if it causes trouble // this might be too harsh, so ask if it causes trouble
app.param('package', validate_pkg) app.param('package', validate_pkg);
app.param('filename', validate_name) app.param('filename', validate_name);
app.param('tag', validate_name) app.param('tag', validate_name);
app.param('version', validate_name) app.param('version', validate_name);
app.param('revision', validate_name) app.param('revision', validate_name);
app.param('token', validate_name) app.param('token', validate_name);
// these can't be safely put into express url for some reason // these can't be safely put into express url for some reason
app.param('_rev', match(/^-rev$/)) app.param('_rev', match(/^-rev$/));
app.param('org_couchdb_user', match(/^org\.couchdb\.user:/)) app.param('org_couchdb_user', match(/^org\.couchdb\.user:/));
app.param('anything', match(/.*/)) app.param('anything', match(/.*/));
app.use(auth.basic_middleware()) app.use(auth.basic_middleware());
//app.use(auth.bearer_middleware()) // app.use(auth.bearer_middleware())
app.use(bodyParser.json({ strict: false, limit: config.max_body_size || '10mb' })) app.use(bodyParser.json({strict: false, limit: config.max_body_size || '10mb'}));
app.use(Middleware.anti_loop(config)) app.use(Middleware.anti_loop(config));
// encode / in a scoped package name to be matched as a single parameter in routes // encode / in a scoped package name to be matched as a single parameter in routes
app.use(function(req, res, next) { app.use(function(req, res, next) {
if (req.url.indexOf('@') != -1) { if (req.url.indexOf('@') != -1) {
// e.g.: /@org/pkg/1.2.3 -> /@org%2Fpkg/1.2.3, /@org%2Fpkg/1.2.3 -> /@org%2Fpkg/1.2.3 // e.g.: /@org/pkg/1.2.3 -> /@org%2Fpkg/1.2.3, /@org%2Fpkg/1.2.3 -> /@org%2Fpkg/1.2.3
req.url = req.url.replace(/^(\/@[^\/%]+)\/(?!$)/, '$1%2F') req.url = req.url.replace(/^(\/@[^\/%]+)\/(?!$)/, '$1%2F');
} }
next() next();
}) });
// for "npm whoami" // for "npm whoami"
app.get('/whoami', function(req, res, next) { app.get('/whoami', function(req, res, next) {
if (req.headers.referer === 'whoami') { if (req.headers.referer === 'whoami') {
next({ username: req.remote_user.name }) next({username: req.remote_user.name});
} else { } else {
next('route') next('route');
} }
}) });
app.get('/-/whoami', function(req, res, next) { app.get('/-/whoami', function(req, res, next) {
next({ username: req.remote_user.name }) next({username: req.remote_user.name});
}) });
// TODO: anonymous user? // TODO: anonymous user?
app.get('/:package/:version?', can('access'), function(req, res, next) { app.get('/:package/:version?', can('access'), function(req, res, next) {
storage.get_package(req.params.package, { req: req }, function(err, info) { storage.get_package(req.params.package, {req: req}, function(err, info) {
if (err) return next(err) if (err) return next(err);
info = Utils.filter_tarball_urls(info, req, config) info = Utils.filter_tarball_urls(info, req, config);
var version = req.params.version let version = req.params.version;
if (!version) return next(info) if (!version) return next(info);
var t = Utils.get_version(info, version) let t = Utils.get_version(info, version);
if (t != null) return next(t) if (t != null) return next(t);
if (info['dist-tags'] != null) { if (info['dist-tags'] != null) {
if (info['dist-tags'][version] != null) { if (info['dist-tags'][version] != null) {
version = info['dist-tags'][version] version = info['dist-tags'][version];
t = Utils.get_version(info, version) t = Utils.get_version(info, version);
if (t != null) return next(t) if (t != null) return next(t);
} }
} }
return next( Error[404]('version not found: ' + req.params.version) ) return next( Error[404]('version not found: ' + req.params.version) );
}) });
}) });
app.get('/:package/-/:filename', can('access'), function(req, res, next) { app.get('/:package/-/:filename', can('access'), function(req, res, next) {
var stream = storage.get_tarball(req.params.package, req.params.filename) let stream = storage.get_tarball(req.params.package, req.params.filename);
stream.on('content-length', function(v) { stream.on('content-length', function(v) {
res.header('Content-Length', v) res.header('Content-Length', v);
}) });
stream.on('error', function(err) { stream.on('error', function(err) {
return res.report_error(err) return res.report_error(err);
}) });
res.header('Content-Type', 'application/octet-stream') res.header('Content-Type', 'application/octet-stream');
stream.pipe(res) stream.pipe(res);
}) });
// searching packages // searching packages
<<<<<<< HEAD
app.get('/-/all(\/since)?', function(req, res, next) { app.get('/-/all(\/since)?', function(req, res, next) {
var received_end = false var received_end = false
var response_finished = false var response_finished = false
@ -128,26 +131,36 @@ module.exports = function(config, auth, storage) {
} else { } else {
res.write('{"_updated":' + 99999); res.write('{"_updated":' + 99999);
} }
=======
app.get('/-/all/:anything?', function(req, res, next) {
let received_end = false;
let response_finished = false;
let processing_pkgs = 0;
var stream = storage.search(req.query.startkey || 0, { req: req }) res.status(200);
res.write('{"_updated":' + Date.now());
>>>>>>> Apply partially new eslint rules, upgrade es6 and replace octal literals by chalk colors
let stream = storage.search(req.query.startkey || 0, {req: req});
stream.on('data', function each(pkg) { stream.on('data', function each(pkg) {
processing_pkgs++ processing_pkgs++;
auth.allow_access(pkg.name, req.remote_user, function(err, allowed) { auth.allow_access(pkg.name, req.remote_user, function(err, allowed) {
processing_pkgs-- processing_pkgs--;
if (err) { if (err) {
if (err.status && String(err.status).match(/^4\d\d$/)) { if (err.status && String(err.status).match(/^4\d\d$/)) {
// auth plugin returns 4xx user error, // auth plugin returns 4xx user error,
// that's equivalent of !allowed basically // that's equivalent of !allowed basically
allowed = false allowed = false;
} else { } else {
stream.abort(err) stream.abort(err);
} }
} }
if (allowed) { if (allowed) {
<<<<<<< HEAD
if (respShouldBeArray) { if (respShouldBeArray) {
res.write(`${firstPackage ? '' : ','}${JSON.stringify(pkg)}\n`) res.write(`${firstPackage ? '' : ','}${JSON.stringify(pkg)}\n`)
if (firstPackage) { if (firstPackage) {
@ -156,69 +169,77 @@ module.exports = function(config, auth, storage) {
} else { } else {
res.write(',\n' + JSON.stringify(pkg.name) + ':' + JSON.stringify(pkg)) res.write(',\n' + JSON.stringify(pkg.name) + ':' + JSON.stringify(pkg))
} }
=======
res.write(',\n' + JSON.stringify(pkg.name) + ':' + JSON.stringify(pkg));
>>>>>>> Apply partially new eslint rules, upgrade es6 and replace octal literals by chalk colors
} }
check_finish() check_finish();
}) });
}) });
stream.on('error', function (_err) { stream.on('error', function(_err) {
res.socket.destroy() res.socket.destroy();
}) });
stream.on('end', function () { stream.on('end', function() {
received_end = true received_end = true;
check_finish() check_finish();
}) });
function check_finish() { function check_finish() {
if (!received_end) return if (!received_end) return;
if (processing_pkgs) return if (processing_pkgs) return;
if (response_finished) return if (response_finished) return;
<<<<<<< HEAD
response_finished = true response_finished = true
if (respShouldBeArray) { if (respShouldBeArray) {
res.end(']\n') res.end(']\n')
} else { } else {
res.end('}\n') res.end('}\n')
} }
=======
response_finished = true;
res.end('}\n');
>>>>>>> Apply partially new eslint rules, upgrade es6 and replace octal literals by chalk colors
} }
}) });
// placeholder 'cause npm require to be authenticated to publish // placeholder 'cause npm require to be authenticated to publish
// we do not do any real authentication yet // we do not do any real authentication yet
app.post('/_session', Cookies.express(), function(req, res, next) { app.post('/_session', Cookies.express(), function(req, res, next) {
res.cookies.set('AuthSession', String(Math.random()), { res.cookies.set('AuthSession', String(Math.random()), {
// npmjs.org sets 10h expire // npmjs.org sets 10h expire
expires: new Date(Date.now() + 10*60*60*1000) expires: new Date(Date.now() + 10*60*60*1000),
}) });
next({ ok: true, name: 'somebody', roles: [] }) next({ok: true, name: 'somebody', roles: []});
}) });
app.get('/-/user/:org_couchdb_user', function(req, res, next) { app.get('/-/user/:org_couchdb_user', function(req, res, next) {
res.status(200) res.status(200);
next({ next({
ok: 'you are authenticated as "' + req.remote_user.name + '"', ok: 'you are authenticated as "' + req.remote_user.name + '"',
}) });
}) });
app.put('/-/user/:org_couchdb_user/:_rev?/:revision?', function(req, res, next) { app.put('/-/user/:org_couchdb_user/:_rev?/:revision?', function(req, res, next) {
var token = (req.body.name && req.body.password) let token = (req.body.name && req.body.password)
? auth.aes_encrypt(req.body.name + ':' + req.body.password).toString('base64') ? auth.aes_encrypt(req.body.name + ':' + req.body.password).toString('base64')
: undefined : undefined;
if (req.remote_user.name != null) { if (req.remote_user.name != null) {
res.status(201) res.status(201);
return next({ return next({
ok: "you are authenticated as '" + req.remote_user.name + "'", ok: 'you are authenticated as \'' + req.remote_user.name + '\'',
//token: auth.issue_token(req.remote_user), // token: auth.issue_token(req.remote_user),
token: token, token: token,
}) });
} else { } else {
if (typeof(req.body.name) !== 'string' || typeof(req.body.password) !== 'string') { if (typeof(req.body.name) !== 'string' || typeof(req.body.password) !== 'string') {
if (typeof(req.body.password_sha)) { if (typeof(req.body.password_sha)) {
return next( Error[422]('your npm version is outdated\nPlease update to npm@1.4.5 or greater.\nSee https://github.com/rlidwka/sinopia/issues/93 for details.') ) return next( Error[422]('your npm version is outdated\nPlease update to npm@1.4.5 or greater.\nSee https://github.com/rlidwka/sinopia/issues/93 for details.') );
} else { } else {
return next( Error[422]('user/password is not found in request (npm issue?)') ) return next( Error[422]('user/password is not found in request (npm issue?)') );
} }
} }
auth.add_user(req.body.name, req.body.password, function(err, user) { auth.add_user(req.body.name, req.body.password, function(err, user) {
@ -227,133 +248,130 @@ module.exports = function(config, auth, storage) {
// With npm registering is the same as logging in, // With npm registering is the same as logging in,
// and npm accepts only an 409 error. // and npm accepts only an 409 error.
// So, changing status code here. // So, changing status code here.
return next( Error[409](err.message) ) return next( Error[409](err.message) );
} }
return next(err) return next(err);
} }
req.remote_user = user req.remote_user = user;
res.status(201) res.status(201);
return next({ return next({
ok: "user '" + req.body.name + "' created", ok: 'user \'' + req.body.name + '\' created',
//token: auth.issue_token(req.remote_user), // token: auth.issue_token(req.remote_user),
token: token, token: token,
}) });
}) });
} }
}) });
app.delete('/-/user/token/*', function(req, res, next) { app.delete('/-/user/token/*', function(req, res, next) {
res.status(200) res.status(200);
next({ next({
ok: 'Logged out', ok: 'Logged out',
}) });
}) });
function tag_package_version(req, res, next) { function tag_package_version(req, res, next) {
if (typeof(req.body) !== 'string') return next('route') if (typeof(req.body) !== 'string') return next('route');
var tags = {} let tags = {};
tags[req.params.tag] = req.body tags[req.params.tag] = req.body;
storage.merge_tags(req.params.package, tags, function(err) { storage.merge_tags(req.params.package, tags, function(err) {
if (err) return next(err) if (err) return next(err);
res.status(201) res.status(201);
return next({ ok: 'package tagged' }) return next({ok: 'package tagged'});
}) });
} }
// tagging a package // tagging a package
app.put('/:package/:tag', app.put('/:package/:tag',
can('publish'), media('application/json'), tag_package_version) can('publish'), media('application/json'), tag_package_version);
app.post('/-/package/:package/dist-tags/:tag', app.post('/-/package/:package/dist-tags/:tag',
can('publish'), media('application/json'), tag_package_version) can('publish'), media('application/json'), tag_package_version);
app.put('/-/package/:package/dist-tags/:tag', app.put('/-/package/:package/dist-tags/:tag',
can('publish'), media('application/json'), tag_package_version) can('publish'), media('application/json'), tag_package_version);
app.delete('/-/package/:package/dist-tags/:tag', can('publish'), function (req, res, next) { app.delete('/-/package/:package/dist-tags/:tag', can('publish'), function(req, res, next) {
var tags = {} let tags = {};
tags[req.params.tag] = null tags[req.params.tag] = null;
storage.merge_tags(req.params.package, tags, function(err) { storage.merge_tags(req.params.package, tags, function(err) {
if (err) return next(err) if (err) return next(err);
res.status(201) res.status(201);
return next({ ok: 'tag removed' }) return next({ok: 'tag removed'});
}) });
}) });
app.get('/-/package/:package/dist-tags', can('access'), function(req, res, next) { app.get('/-/package/:package/dist-tags', can('access'), function(req, res, next) {
storage.get_package(req.params.package, { req: req }, function(err, info) { storage.get_package(req.params.package, {req: req}, function(err, info) {
if (err) return next(err) if (err) return next(err);
next(info['dist-tags']) next(info['dist-tags']);
}) });
}) });
app.post('/-/package/:package/dist-tags', app.post('/-/package/:package/dist-tags',
can('publish'), media('application/json'), expect_json, can('publish'), media('application/json'), expect_json,
function(req, res, next) { function(req, res, next) {
storage.merge_tags(req.params.package, req.body, function(err) { storage.merge_tags(req.params.package, req.body, function(err) {
if (err) return next(err) if (err) return next(err);
res.status(201) res.status(201);
return next({ ok: 'tags updated' }) return next({ok: 'tags updated'});
}) });
}) });
app.put('/-/package/:package/dist-tags', app.put('/-/package/:package/dist-tags',
can('publish'), media('application/json'), expect_json, can('publish'), media('application/json'), expect_json,
function(req, res, next) { function(req, res, next) {
storage.replace_tags(req.params.package, req.body, function(err) { storage.replace_tags(req.params.package, req.body, function(err) {
if (err) return next(err) if (err) return next(err);
res.status(201) res.status(201);
return next({ ok: 'tags updated' }) return next({ok: 'tags updated'});
}) });
}) });
app.delete('/-/package/:package/dist-tags', app.delete('/-/package/:package/dist-tags',
can('publish'), media('application/json'), can('publish'), media('application/json'),
function(req, res, next) { function(req, res, next) {
storage.replace_tags(req.params.package, {}, function(err) { storage.replace_tags(req.params.package, {}, function(err) {
if (err) return next(err) if (err) return next(err);
res.status(201) res.status(201);
return next({ ok: 'tags removed' }) return next({ok: 'tags removed'});
}) });
}) });
// publishing a package // publishing a package
app.put('/:package/:_rev?/:revision?', can('publish'), media('application/json'), expect_json, function(req, res, next) { app.put('/:package/:_rev?/:revision?', can('publish'), media('application/json'), expect_json, function(req, res, next) {
var name = req.params.package let name = req.params.package;
if (Object.keys(req.body).length == 1 && Utils.is_object(req.body.users)) { if (Object.keys(req.body).length == 1 && Utils.is_object(req.body.users)) {
// 501 status is more meaningful, but npm doesn't show error message for 5xx // 501 status is more meaningful, but npm doesn't show error message for 5xx
return next( Error[404]('npm star|unstar calls are not implemented') ) return next( Error[404]('npm star|unstar calls are not implemented') );
} }
try { try {
var metadata = Utils.validate_metadata(req.body, name) var metadata = Utils.validate_metadata(req.body, name);
} catch(err) { } catch(err) {
return next( Error[422]('bad incoming package data') ) return next( Error[422]('bad incoming package data') );
} }
if (req.params._rev) { if (req.params._rev) {
storage.change_package(name, metadata, req.params.revision, function(err) { storage.change_package(name, metadata, req.params.revision, function(err) {
after_change(err, 'package changed') after_change(err, 'package changed');
}) });
} else { } else {
storage.add_package(name, metadata, function(err) { storage.add_package(name, metadata, function(err) {
after_change(err, 'created new package') after_change(err, 'created new package');
}) });
} }
function after_change(err, ok_message) { function after_change(err, ok_message) {
// old npm behaviour // old npm behaviour
if (metadata._attachments == null) { if (metadata._attachments == null) {
if (err) return next(err) if (err) return next(err);
res.status(201) res.status(201);
return next({ ok: ok_message }) return next({ok: ok_message});
} }
// npm-registry-client 0.3+ embeds tarball into the json upload // npm-registry-client 0.3+ embeds tarball into the json upload
@ -361,121 +379,120 @@ module.exports = function(config, auth, storage) {
// issue #31, dealing with it here: // issue #31, dealing with it here:
if (typeof(metadata._attachments) !== 'object' if (typeof(metadata._attachments) !== 'object'
|| Object.keys(metadata._attachments).length !== 1 || Object.keys(metadata._attachments).length !== 1
|| typeof(metadata.versions) !== 'object' || typeof(metadata.versions) !== 'object'
|| Object.keys(metadata.versions).length !== 1) { || Object.keys(metadata.versions).length !== 1) {
// npm is doing something strange again // npm is doing something strange again
// if this happens in normal circumstances, report it as a bug // if this happens in normal circumstances, report it as a bug
return next( Error[400]('unsupported registry call') ) return next( Error[400]('unsupported registry call') );
} }
if (err && err.status != 409) return next(err) if (err && err.status != 409) return next(err);
// at this point document is either created or existed before // at this point document is either created or existed before
var t1 = Object.keys(metadata._attachments)[0] let t1 = Object.keys(metadata._attachments)[0];
create_tarball(Path.basename(t1), metadata._attachments[t1], function(err) { create_tarball(Path.basename(t1), metadata._attachments[t1], function(err) {
if (err) return next(err) if (err) return next(err);
var t2 = Object.keys(metadata.versions)[0] let t2 = Object.keys(metadata.versions)[0];
metadata.versions[t2].readme = metadata.readme != null ? String(metadata.readme) : '' metadata.versions[t2].readme = metadata.readme != null ? String(metadata.readme) : '';
create_version(t2, metadata.versions[t2], function(err) { create_version(t2, metadata.versions[t2], function(err) {
if (err) return next(err) if (err) return next(err);
add_tags(metadata['dist-tags'], function(err) { add_tags(metadata['dist-tags'], function(err) {
if (err) return next(err) if (err) return next(err);
notify(metadata, config) notify(metadata, config);
res.status(201) res.status(201);
return next({ ok: ok_message }) return next({ok: ok_message});
}) });
}) });
}) });
} }
function create_tarball(filename, data, cb) { function create_tarball(filename, data, cb) {
var stream = storage.add_tarball(name, filename) let stream = storage.add_tarball(name, filename);
stream.on('error', function(err) { stream.on('error', function(err) {
cb(err) cb(err);
}) });
stream.on('success', function() { stream.on('success', function() {
cb() cb();
}) });
// this is dumb and memory-consuming, but what choices do we have? // this is dumb and memory-consuming, but what choices do we have?
stream.end(new Buffer(data.data, 'base64')) stream.end(new Buffer(data.data, 'base64'));
stream.done() stream.done();
} }
function create_version(version, data, cb) { function create_version(version, data, cb) {
storage.add_version(name, version, data, null, cb) storage.add_version(name, version, data, null, cb);
} }
function add_tags(tags, cb) { function add_tags(tags, cb) {
storage.merge_tags(name, tags, cb) storage.merge_tags(name, tags, cb);
} }
}) });
// unpublishing an entire package // unpublishing an entire package
app.delete('/:package/-rev/*', can('publish'), function(req, res, next) { app.delete('/:package/-rev/*', can('publish'), function(req, res, next) {
storage.remove_package(req.params.package, function(err) { storage.remove_package(req.params.package, function(err) {
if (err) return next(err) if (err) return next(err);
res.status(201) res.status(201);
return next({ ok: 'package removed' }) return next({ok: 'package removed'});
}) });
}) });
// removing a tarball // removing a tarball
app.delete('/:package/-/:filename/-rev/:revision', can('publish'), function(req, res, next) { app.delete('/:package/-/:filename/-rev/:revision', can('publish'), function(req, res, next) {
storage.remove_tarball(req.params.package, req.params.filename, req.params.revision, function(err) { storage.remove_tarball(req.params.package, req.params.filename, req.params.revision, function(err) {
if (err) return next(err) if (err) return next(err);
res.status(201) res.status(201);
return next({ ok: 'tarball removed' }) return next({ok: 'tarball removed'});
}) });
}) });
// uploading package tarball // uploading package tarball
app.put('/:package/-/:filename/*', can('publish'), media('application/octet-stream'), function(req, res, next) { app.put('/:package/-/:filename/*', can('publish'), media('application/octet-stream'), function(req, res, next) {
var name = req.params.package let name = req.params.package;
var stream = storage.add_tarball(name, req.params.filename) let stream = storage.add_tarball(name, req.params.filename);
req.pipe(stream) req.pipe(stream);
// checking if end event came before closing // checking if end event came before closing
var complete = false let complete = false;
req.on('end', function() { req.on('end', function() {
complete = true complete = true;
stream.done() stream.done();
}) });
req.on('close', function() { req.on('close', function() {
if (!complete) { if (!complete) {
stream.abort() stream.abort();
} }
}) });
stream.on('error', function(err) { stream.on('error', function(err) {
return res.report_error(err) return res.report_error(err);
}) });
stream.on('success', function() { stream.on('success', function() {
res.status(201) res.status(201);
return next({ return next({
ok: 'tarball uploaded successfully' ok: 'tarball uploaded successfully',
}) });
}) });
}) });
// adding a version // adding a version
app.put('/:package/:version/-tag/:tag', can('publish'), media('application/json'), expect_json, function(req, res, next) { app.put('/:package/:version/-tag/:tag', can('publish'), media('application/json'), expect_json, function(req, res, next) {
var name = req.params.package let name = req.params.package;
var version = req.params.version let version = req.params.version;
var tag = req.params.tag let tag = req.params.tag;
storage.add_version(name, version, req.body, tag, function(err) { storage.add_version(name, version, req.body, tag, function(err) {
if (err) return next(err) if (err) return next(err);
res.status(201) res.status(201);
return next({ ok: 'package published' }) return next({ok: 'package published'});
}) });
}) });
return app return app;
} };

View File

@ -1,167 +1,167 @@
var async = require('async') 'use strict';
var bodyParser = require('body-parser')
var Cookies = require('cookies') let async = require('async');
var express = require('express') let bodyParser = require('body-parser');
var fs = require('fs') let Cookies = require('cookies');
var Handlebars = require('handlebars') let express = require('express');
var renderReadme = require('render-readme') let fs = require('fs');
var Search = require('./search') let Handlebars = require('handlebars');
var Middleware = require('./middleware') let renderReadme = require('render-readme');
var match = Middleware.match let Search = require('./search');
var validate_name = Middleware.validate_name let Middleware = require('./middleware');
var validate_pkg = Middleware.validate_package let match = Middleware.match;
let validate_name = Middleware.validate_name;
let validate_pkg = Middleware.validate_package;
module.exports = function(config, auth, storage) { module.exports = function(config, auth, storage) {
var app = express.Router() let app = express.Router();
var can = Middleware.allow(auth) let can = Middleware.allow(auth);
// validate all of these params as a package name // validate all of these params as a package name
// this might be too harsh, so ask if it causes trouble // this might be too harsh, so ask if it causes trouble
app.param('package', validate_pkg) app.param('package', validate_pkg);
app.param('filename', validate_name) app.param('filename', validate_name);
app.param('version', validate_name) app.param('version', validate_name);
app.param('anything', match(/.*/)) app.param('anything', match(/.*/));
app.use(Cookies.express()) app.use(Cookies.express());
app.use(bodyParser.urlencoded({ extended: false })) app.use(bodyParser.urlencoded({extended: false}));
app.use(auth.cookie_middleware()) app.use(auth.cookie_middleware());
app.use(function(req, res, next) { app.use(function(req, res, next) {
// disable loading in frames (clickjacking, etc.) // disable loading in frames (clickjacking, etc.)
res.header('X-Frame-Options', 'deny') res.header('X-Frame-Options', 'deny');
next() next();
}) });
Search.configureStorage(storage) Search.configureStorage(storage);
Handlebars.registerPartial('entry', fs.readFileSync(require.resolve('./GUI/entry.hbs'), 'utf8')) Handlebars.registerPartial('entry', fs.readFileSync(require.resolve('./GUI/entry.hbs'), 'utf8'));
let template;
if(config.web && config.web.template) { if (config.web && config.web.template) {
var template = Handlebars.compile(fs.readFileSync(config.web.template, 'utf8')); template = Handlebars.compile(fs.readFileSync(config.web.template, 'utf8'));
} } else {
else { template = Handlebars.compile(fs.readFileSync(require.resolve('./GUI/index.hbs'), 'utf8'));
var template = Handlebars.compile(fs.readFileSync(require.resolve('./GUI/index.hbs'), 'utf8'))
} }
app.get('/', function(req, res, next) { app.get('/', function(req, res, next) {
var base = config.url_prefix let base = config.url_prefix
? config.url_prefix.replace(/\/$/, '') ? config.url_prefix.replace(/\/$/, '')
: req.protocol + '://' + req.get('host') : req.protocol + '://' + req.get('host');
res.setHeader('Content-Type', 'text/html') res.setHeader('Content-Type', 'text/html');
storage.get_local(function(err, packages) { storage.get_local(function(err, packages) {
if (err) throw err // that function shouldn't produce any if (err) throw err; // that function shouldn't produce any
async.filterSeries(packages, function(package, cb) { async.filterSeries(packages, function(pkg, cb) {
auth.allow_access(package.name, req.remote_user, function(err, allowed) { auth.allow_access(pkg.name, req.remote_user, function(err, allowed) {
setImmediate(function () { setImmediate(function() {
if (err) { if (err) {
cb(null, false); cb(null, false);
} else { } else {
cb(err, allowed) cb(err, allowed);
} }
}) });
}) });
}, function(err, packages) { }, function(err, packages) {
if (err) throw err if (err) throw err;
packages.sort(function(p1, p2) { packages.sort(function(p1, p2) {
if (p1.name < p2.name) { if (p1.name < p2.name) {
return -1; return -1;
} } else {
else {
return 1; return 1;
} }
}); });
next(template({ next(template({
name: config.web && config.web.title ? config.web.title : 'Verdaccio', name: config.web && config.web.title ? config.web.title : 'Verdaccio',
tagline: config.web && config.web.tagline ? config.web.tagline : '', tagline: config.web && config.web.tagline ? config.web.tagline : '',
packages: packages, packages: packages,
baseUrl: base, baseUrl: base,
username: req.remote_user.name, username: req.remote_user.name,
})) }));
}) });
}) });
}) });
// Static // Static
app.get('/-/static/:filename', function(req, res, next) { app.get('/-/static/:filename', function(req, res, next) {
var file = __dirname + '/static/' + req.params.filename let file = __dirname + '/static/' + req.params.filename;
res.sendFile(file, function(err) { res.sendFile(file, function(err) {
if (!err) return if (!err) return;
if (err.status === 404) { if (err.status === 404) {
next() next();
} else { } else {
next(err) next(err);
} }
}) });
}) });
app.get('/-/logo', function(req, res, next) { app.get('/-/logo', function(req, res, next) {
res.sendFile( config.web && config.web.logo res.sendFile( config.web && config.web.logo
? config.web.logo ? config.web.logo
: __dirname + '/static/logo-sm.png' ) : __dirname + '/static/logo-sm.png' );
}) });
app.post('/-/login', function(req, res, next) { app.post('/-/login', function(req, res, next) {
auth.authenticate(req.body.user, req.body.pass, function(err, user) { auth.authenticate(req.body.user, req.body.pass, function(err, user) {
if (!err) { if (!err) {
req.remote_user = user req.remote_user = user;
//res.cookies.set('token', auth.issue_token(req.remote_user)) // res.cookies.set('token', auth.issue_token(req.remote_user))
var str = req.body.user + ':' + req.body.pass let str = req.body.user + ':' + req.body.pass;
res.cookies.set('token', auth.aes_encrypt(str).toString('base64')) res.cookies.set('token', auth.aes_encrypt(str).toString('base64'));
} }
var base = config.url_prefix let base = config.url_prefix
? config.url_prefix.replace(/\/$/, '') ? config.url_prefix.replace(/\/$/, '')
: req.protocol + '://' + req.get('host') : req.protocol + '://' + req.get('host');
res.redirect(base) res.redirect(base);
}) });
}) });
app.post('/-/logout', function(req, res, next) { app.post('/-/logout', function(req, res, next) {
var base = config.url_prefix let base = config.url_prefix
? config.url_prefix.replace(/\/$/, '') ? config.url_prefix.replace(/\/$/, '')
: req.protocol + '://' + req.get('host') : req.protocol + '://' + req.get('host');
res.cookies.set('token', '') res.cookies.set('token', '');
res.redirect(base) res.redirect(base);
}) });
// Search // Search
app.get('/-/search/:anything', function(req, res, next) { app.get('/-/search/:anything', function(req, res, next) {
var results = Search.query(req.params.anything) const results = Search.query(req.params.anything);
var packages = [] const packages = [];
var getData = function(i) { const getData = function(i) {
storage.get_package(results[i].ref, function(err, entry) { storage.get_package(results[i].ref, function(err, entry) {
if (!err && entry) { if (!err && entry) {
auth.allow_access(entry.name, req.remote_user, function(err, allowed) { // TODO: This may cause performance issue? auth.allow_access(entry.name, req.remote_user, function(err, allowed) { // TODO: This may cause performance issue?
if (err || !allowed) return if (err || !allowed) return;
packages.push(entry.versions[entry['dist-tags'].latest]) packages.push(entry.versions[entry['dist-tags'].latest]);
}) });
} }
if (i >= results.length - 1) { if (i >= results.length - 1) {
next(packages) next(packages);
} else { } else {
getData(i + 1) getData(i + 1);
} }
}) });
} };
if (results.length) { if (results.length) {
getData(0) getData(0);
} else { } else {
next([]) next([]);
} }
}) });
app.get('/-/readme(/@:scope?)?/:package/:version?', can('access'), function(req, res, next) { app.get('/-/readme(/@:scope?)?/:package/:version?', can('access'), function(req, res, next) {
var packageName = req.params.package; let packageName = req.params.package;
if (req.params.scope) packageName = "@"+ req.params.scope + "/" + packageName; if (req.params.scope) packageName = '@'+ req.params.scope + '/' + packageName;
storage.get_package(packageName, {req: req}, function(err, info) { storage.get_package(packageName, {req: req}, function(err, info) {
if (err) return next(err) if (err) return next(err);
next( renderReadme(info.readme || 'ERROR: No README data found!') ) next( renderReadme(info.readme || 'ERROR: No README data found!') );
}) });
}) });
return app return app;
} };

View File

@ -1,105 +1,107 @@
var express = require('express') 'use strict';
var Error = require('http-errors')
var compression = require('compression') let express = require('express');
var Auth = require('./auth') let Error = require('http-errors');
var Logger = require('./logger') let compression = require('compression');
var Config = require('./config') let Auth = require('./auth');
var Middleware = require('./middleware') let Logger = require('./logger');
var Cats = require('./status-cats') let Config = require('./config');
var Storage = require('./storage') let Middleware = require('./middleware');
let Cats = require('./status-cats');
let Storage = require('./storage');
module.exports = function(config_hash) { module.exports = function(config_hash) {
Logger.setup(config_hash.logs) Logger.setup(config_hash.logs);
var config = Config(config_hash); let config = Config(config_hash);
var storage = new Storage(config); let storage = new Storage(config);
var auth = Auth(config); let auth = Auth(config);
var app = express(); let app = express();
// run in production mode by default, just in case // run in production mode by default, just in case
// it shouldn't make any difference anyway // it shouldn't make any difference anyway
app.set('env', process.env.NODE_ENV || 'production') app.set('env', process.env.NODE_ENV || 'production');
function error_reporting_middleware(req, res, next) { function error_reporting_middleware(req, res, next) {
res.report_error = res.report_error || function(err) { res.report_error = res.report_error || function(err) {
if (err.status && err.status >= 400 && err.status < 600) { if (err.status && err.status >= 400 && err.status < 600) {
if (!res.headersSent) { if (!res.headersSent) {
res.status(err.status) res.status(err.status);
next({ error: err.message || 'unknown error' }) next({error: err.message || 'unknown error'});
} }
} else { } else {
Logger.logger.error( { err: err } Logger.logger.error( {err: err}
, 'unexpected error: @{!err.message}\n@{err.stack}') , 'unexpected error: @{!err.message}\n@{err.stack}');
if (!res.status || !res.send) { if (!res.status || !res.send) {
Logger.logger.error('this is an error in express.js, please report this') Logger.logger.error('this is an error in express.js, please report this');
res.destroy() res.destroy();
} else if (!res.headersSent) { } else if (!res.headersSent) {
res.status(500) res.status(500);
next({ error: 'internal server error' }) next({error: 'internal server error'});
} else { } else {
// socket should be already closed // socket should be already closed
} }
} }
} };
next() next();
} }
app.use(Middleware.log) app.use(Middleware.log);
app.use(error_reporting_middleware) app.use(error_reporting_middleware);
app.use(function(req, res, next) { app.use(function(req, res, next) {
res.setHeader('X-Powered-By', config.user_agent) res.setHeader('X-Powered-By', config.user_agent);
next() next();
}) });
app.use(Cats.middleware) app.use(Cats.middleware);
app.use(compression()) app.use(compression());
app.get('/favicon.ico', function(req, res, next) { app.get('/favicon.ico', function(req, res, next) {
req.url = '/-/static/favicon.png' req.url = '/-/static/favicon.png';
next() next();
}) });
// hook for tests only // hook for tests only
if (config._debug) { if (config._debug) {
app.get('/-/_debug', function(req, res, next) { app.get('/-/_debug', function(req, res, next) {
var do_gc = typeof(global.gc) !== 'undefined' let do_gc = typeof(global.gc) !== 'undefined';
if (do_gc) global.gc() if (do_gc) global.gc();
next({ next({
pid : process.pid, pid: process.pid,
main : process.mainModule.filename, main: process.mainModule.filename,
conf : config.self_path, conf: config.self_path,
mem : process.memoryUsage(), mem: process.memoryUsage(),
gc : do_gc, gc: do_gc,
}) });
}) });
} }
app.use(require('./index-api')(config, auth, storage)) app.use(require('./index-api')(config, auth, storage));
if (config.web && config.web.enable === false) { if (config.web && config.web.enable === false) {
app.get('/', function(req, res, next) { app.get('/', function(req, res, next) {
next( Error[404]('web interface is disabled in the config file') ) next( Error[404]('web interface is disabled in the config file') );
}) });
} else { } else {
app.use(require('./index-web')(config, auth, storage)) app.use(require('./index-web')(config, auth, storage));
} }
app.get('/*', function(req, res, next) { app.get('/*', function(req, res, next) {
next( Error[404]('file not found') ) next( Error[404]('file not found') );
}) });
app.use(function(err, req, res, next) { app.use(function(err, req, res, next) {
if (Object.prototype.toString.call(err) !== '[object Error]') return next(err) if (Object.prototype.toString.call(err) !== '[object Error]') return next(err);
if (err.code === 'ECONNABORT' && res.statusCode === 304) return next() if (err.code === 'ECONNABORT' && res.statusCode === 304) return next();
if (typeof(res.report_error) !== 'function') { if (typeof(res.report_error) !== 'function') {
// in case of very early error this middleware may not be loaded before error is generated // in case of very early error this middleware may not be loaded before error is generated
// fixing that // fixing that
error_reporting_middleware(req, res, function(){}) error_reporting_middleware(req, res, function() {});
} }
res.report_error(err) res.report_error(err);
}) });
app.use(Middleware.final) app.use(Middleware.final);
return app return app;
} };

View File

@ -1,44 +1,44 @@
"use strict"; 'use strict';
const fs = require('fs'); const fs = require('fs');
const Path = require('path'); const Path = require('path');
class LocalData { class LocalData {
constructor(path) { constructor(path) {
this.path = path this.path = path;
try { try {
this.data = JSON.parse(fs.readFileSync(this.path, 'utf8')) this.data = JSON.parse(fs.readFileSync(this.path, 'utf8'));
} catch(_) { } catch(_) {
this.data = { list: [] } this.data = {list: []};
} }
} }
add(name) { add(name) {
if (this.data.list.indexOf(name) === -1) { if (this.data.list.indexOf(name) === -1) {
this.data.list.push(name) this.data.list.push(name);
this.sync() this.sync();
} }
} }
remove(name) { remove(name) {
const i = this.data.list.indexOf(name) const i = this.data.list.indexOf(name);
if (i !== -1) { if (i !== -1) {
this.data.list.splice(i, 1) this.data.list.splice(i, 1);
} }
this.sync() this.sync();
} }
get() { get() {
return this.data.list return this.data.list;
} }
sync() { sync() {
// Uses sync to prevent ugly race condition // Uses sync to prevent ugly race condition
try { try {
require('mkdirp').sync(Path.dirname(this.path)) require('mkdirp').sync(Path.dirname(this.path));
} catch(err) {} } catch(err) {}
fs.writeFileSync(this.path, JSON.stringify(this.data)) fs.writeFileSync(this.path, JSON.stringify(this.data));
} }
} }

View File

@ -1,215 +1,215 @@
"use strict"; 'use strict';
const fs = require('fs') const fs = require('fs');
const Error = require('http-errors') const Error = require('http-errors');
const mkdirp = require('mkdirp') const mkdirp = require('mkdirp');
const Path = require('path') const Path = require('path');
const MyStreams = require('./streams') const MyStreams = require('./streams');
function FSError(code) { function FSError(code) {
var err = Error(code) let err = Error(code);
err.code = code err.code = code;
return err return err;
} }
var locker = require('./file-locking') let locker = require('./file-locking');
function tempFile(str) { function tempFile(str) {
return str + '.tmp' + String(Math.random()).substr(2) return str + '.tmp' + String(Math.random()).substr(2);
} }
function renameTmp(src, dst, _cb) { function renameTmp(src, dst, _cb) {
function cb(err) { function cb(err) {
if (err) fs.unlink(src, function() {}) if (err) fs.unlink(src, function() {});
_cb(err) _cb(err);
} }
if (process.platform !== 'win32') { if (process.platform !== 'win32') {
return fs.rename(src, dst, cb) return fs.rename(src, dst, cb);
} }
// windows can't remove opened file, // windows can't remove opened file,
// but it seem to be able to rename it // but it seem to be able to rename it
var tmp = tempFile(dst) let tmp = tempFile(dst);
fs.rename(dst, tmp, function(err) { fs.rename(dst, tmp, function(err) {
fs.rename(src, dst, cb) fs.rename(src, dst, cb);
if (!err) fs.unlink(tmp, function () {}) if (!err) fs.unlink(tmp, () => {});
}) });
} }
function write(dest, data, cb) { function write(dest, data, cb) {
var safe_write = function(cb) { let safe_write = function(cb) {
var tmpname = tempFile(dest) let tmpname = tempFile(dest);
fs.writeFile(tmpname, data, function(err) { fs.writeFile(tmpname, data, function(err) {
if (err) return cb(err) if (err) return cb(err);
renameTmp(tmpname, dest, cb) renameTmp(tmpname, dest, cb);
}) });
} };
safe_write(function(err) { safe_write(function(err) {
if (err && err.code === 'ENOENT') { if (err && err.code === 'ENOENT') {
mkdirp(Path.dirname(dest), function(err) { mkdirp(Path.dirname(dest), function(err) {
if (err) return cb(err) if (err) return cb(err);
safe_write(cb) safe_write(cb);
}) });
} else { } else {
cb(err) cb(err);
} }
}) });
} }
function write_stream(name) { function write_stream(name) {
var stream = MyStreams.UploadTarballStream() let stream = MyStreams.UploadTarballStream();
var _ended = 0 let _ended = 0;
stream.on('end', function() { stream.on('end', function() {
_ended = 1 _ended = 1;
}) });
fs.exists(name, function(exists) { fs.exists(name, function(exists) {
if (exists) return stream.emit('error', FSError('EEXISTS')) if (exists) return stream.emit('error', FSError('EEXISTS'));
var tmpname = name + '.tmp-'+String(Math.random()).replace(/^0\./, '') let tmpname = name + '.tmp-'+String(Math.random()).replace(/^0\./, '');
var file = fs.createWriteStream(tmpname) let file = fs.createWriteStream(tmpname);
var opened = false let opened = false;
stream.pipe(file) stream.pipe(file);
stream.done = function() { stream.done = function() {
function onend() { function onend() {
file.on('close', function() { file.on('close', function() {
renameTmp(tmpname, name, function(err) { renameTmp(tmpname, name, function(err) {
if (err) { if (err) {
stream.emit('error', err) stream.emit('error', err);
} else { } else {
stream.emit('success') stream.emit('success');
} }
}) });
}) });
file.destroySoon() file.destroySoon();
} }
if (_ended) { if (_ended) {
onend() onend();
} else { } else {
stream.on('end', onend) stream.on('end', onend);
} }
} };
stream.abort = function() { stream.abort = function() {
if (opened) { if (opened) {
opened = false opened = false;
file.on('close', function() { file.on('close', function() {
fs.unlink(tmpname, function(){}) fs.unlink(tmpname, function() {});
}) });
} }
file.destroySoon() file.destroySoon();
} };
file.on('open', function() { file.on('open', function() {
opened = true opened = true;
// re-emitting open because it's handled in storage.js // re-emitting open because it's handled in storage.js
stream.emit('open') stream.emit('open');
}) });
file.on('error', function(err) { file.on('error', function(err) {
stream.emit('error', err) stream.emit('error', err);
}) });
}) });
return stream return stream;
} }
function read_stream(name, stream, callback) { function read_stream(name, stream, callback) {
var rstream = fs.createReadStream(name) let rstream = fs.createReadStream(name);
rstream.on('error', function(err) { rstream.on('error', function(err) {
stream.emit('error', err) stream.emit('error', err);
}) });
rstream.on('open', function(fd) { rstream.on('open', function(fd) {
fs.fstat(fd, function(err, stats) { fs.fstat(fd, function(err, stats) {
if (err) return stream.emit('error', err) if (err) return stream.emit('error', err);
stream.emit('content-length', stats.size) stream.emit('content-length', stats.size);
stream.emit('open') stream.emit('open');
rstream.pipe(stream) rstream.pipe(stream);
}) });
}) });
stream = MyStreams.ReadTarballStream() stream = MyStreams.ReadTarballStream();
stream.abort = function() { stream.abort = function() {
rstream.close() rstream.close();
} };
return stream return stream;
} }
function create(name, contents, callback) { function create(name, contents, callback) {
fs.exists(name, function(exists) { fs.exists(name, function(exists) {
if (exists) return callback( FSError('EEXISTS') ) if (exists) return callback( FSError('EEXISTS') );
write(name, contents, callback) write(name, contents, callback);
}) });
} }
function update(name, contents, callback) { function update(name, contents, callback) {
fs.exists(name, function(exists) { fs.exists(name, function(exists) {
if (!exists) return callback( FSError('ENOENT') ) if (!exists) return callback( FSError('ENOENT') );
write(name, contents, callback) write(name, contents, callback);
}) });
} }
function read(name, callback) { function read(name, callback) {
fs.readFile(name, callback) fs.readFile(name, callback);
} }
module.exports.read = read module.exports.read = read;
module.exports.read_json = function(name, cb) { module.exports.read_json = function(name, cb) {
read(name, function(err, res) { read(name, function(err, res) {
if (err) return cb(err) if (err) return cb(err);
var args = [] let args = [];
try { try {
args = [ null, JSON.parse(res.toString('utf8')) ] args = [null, JSON.parse(res.toString('utf8'))];
} catch(err) { } catch(err) {
args = [ err ] args = [err];
} }
cb.apply(null, args) cb.apply(null, args);
}) });
} };
module.exports.lock_and_read = function(name, cb) { module.exports.lock_and_read = function(name, cb) {
locker.readFile(name, {lock: true}, function(err, res) { locker.readFile(name, {lock: true}, function(err, res) {
if (err) return cb(err) if (err) return cb(err);
return cb(null, res) return cb(null, res);
}) });
} };
module.exports.lock_and_read_json = function(name, cb) { module.exports.lock_and_read_json = function(name, cb) {
locker.readFile(name, {lock: true, parse: true}, function(err, res) { locker.readFile(name, {lock: true, parse: true}, function(err, res) {
if (err) return cb(err) if (err) return cb(err);
return cb(null, res); return cb(null, res);
}) });
} };
module.exports.unlock_file = function (name, cb) { module.exports.unlock_file = function(name, cb) {
locker.unlockFile(name, cb) locker.unlockFile(name, cb);
} };
module.exports.create = create module.exports.create = create;
module.exports.create_json = function(name, value, cb) { module.exports.create_json = function(name, value, cb) {
create(name, JSON.stringify(value, null, '\t'), cb) create(name, JSON.stringify(value, null, '\t'), cb);
} };
module.exports.update = update module.exports.update = update;
module.exports.update_json = function(name, value, cb) { module.exports.update_json = function(name, value, cb) {
update(name, JSON.stringify(value, null, '\t'), cb) update(name, JSON.stringify(value, null, '\t'), cb);
} };
module.exports.write = write module.exports.write = write;
module.exports.write_json = function(name, value, cb) { module.exports.write_json = function(name, value, cb) {
write(name, JSON.stringify(value, null, '\t'), cb) write(name, JSON.stringify(value, null, '\t'), cb);
} };
module.exports.write_stream = write_stream module.exports.write_stream = write_stream;
module.exports.read_stream = read_stream module.exports.read_stream = read_stream;
module.exports.unlink = fs.unlink module.exports.unlink = fs.unlink;
module.exports.rmdir = fs.rmdir module.exports.rmdir = fs.rmdir;

File diff suppressed because it is too large Load Diff

View File

@ -1,157 +1,178 @@
var Logger = require('bunyan') 'use strict';
var Error = require('http-errors')
var Stream = require('stream')
var Utils = require('./utils')
const Logger = require('bunyan');
const Error = require('http-errors');
const Stream = require('stream');
const chalk = require('chalk');
const Utils = require('./utils');
const pkgJSON = require('../package.json');
/**
*
* @param {*} x
*/
function getlvl(x) { function getlvl(x) {
switch(true) { switch(true) {
case x < 15 : return 'trace' case x < 15 : return 'trace';
case x < 25 : return 'debug' case x < 25 : return 'debug';
case x < 35 : return 'info' case x < 35 : return 'info';
case x == 35 : return 'http' case x == 35 : return 'http';
case x < 45 : return 'warn' case x < 45 : return 'warn';
case x < 55 : return 'error' case x < 55 : return 'error';
default : return 'fatal' default : return 'fatal';
} }
} }
module.exports.setup = function(logs) { module.exports.setup = function(logs) {
var streams = [] let streams = [];
if (logs == null) logs = [{ type: 'stdout', format: 'pretty', level: 'http' }] if (logs == null) logs = [{type: 'stdout', format: 'pretty', level: 'http'}];
logs.forEach(function(target) { logs.forEach(function(target) {
var stream = new Stream() const stream = new Stream();
stream.writable = true stream.writable = true;
if (target.type === 'stdout' || target.type === 'stderr') { if (target.type === 'stdout' || target.type === 'stderr') {
// destination stream // destination stream
var dest = target.type === 'stdout' ? process.stdout : process.stderr const dest = target.type === 'stdout' ? process.stdout : process.stderr;
if (target.format === 'pretty') { if (target.format === 'pretty') {
// making fake stream for prettypritting // making fake stream for prettypritting
stream.write = function(obj) { stream.write = function(obj) {
dest.write(print(obj.level, obj.msg, obj, dest.isTTY) + '\n') dest.write(print(obj.level, obj.msg, obj, dest.isTTY) + '\n');
} };
} else if (target.format === 'pretty-timestamped') { } else if (target.format === 'pretty-timestamped') {
// making fake stream for prettypritting // making fake stream for prettypritting
stream.write = function(obj) { stream.write = function(obj) {
dest.write(obj.time.toISOString() + print(obj.level, obj.msg, obj, dest.isTTY) + '\n') dest.write(obj.time.toISOString() + print(obj.level, obj.msg, obj, dest.isTTY) + '\n');
} };
} else { } else {
stream.write = function(obj) { stream.write = function(obj) {
dest.write(JSON.stringify(obj, Logger.safeCycles()) + '\n') dest.write(JSON.stringify(obj, Logger.safeCycles()) + '\n');
} };
} }
} else if (target.type === 'file') { } else if (target.type === 'file') {
var dest = require('fs').createWriteStream(target.path, {flags: 'a', encoding: 'utf8'}) const dest = require('fs').createWriteStream(target.path, {flags: 'a', encoding: 'utf8'});
dest.on('error', function (err) { dest.on('error', function(err) {
Logger.emit('error', err) Logger.emit('error', err);
}) });
stream.write = function(obj) { stream.write = function(obj) {
if (target.format === 'pretty') { if (target.format === 'pretty') {
dest.write(print(obj.level, obj.msg, obj, false) + '\n') dest.write(print(obj.level, obj.msg, obj, false) + '\n');
} else { } else {
dest.write(JSON.stringify(obj, Logger.safeCycles()) + '\n') dest.write(JSON.stringify(obj, Logger.safeCycles()) + '\n');
} }
} };
} else { } else {
throw Error('wrong target type for a log') throw Error('wrong target type for a log');
} }
if (target.level === 'http') target.level = 35 if (target.level === 'http') target.level = 35;
streams.push({ streams.push({
type: 'raw', type: 'raw',
level: target.level || 35, level: target.level || 35,
stream: stream, stream: stream,
}) });
}) });
var logger = new Logger({ let logger = new Logger({
name: 'verdaccio', name: pkgJSON.name,
streams: streams, streams: streams,
serializers: { serializers: {
err: Logger.stdSerializers.err, err: Logger.stdSerializers.err,
req: Logger.stdSerializers.req, req: Logger.stdSerializers.req,
res: Logger.stdSerializers.res, res: Logger.stdSerializers.res,
}, },
}) });
module.exports.logger = logger module.exports.logger = logger;
} };
// adopted from socket.io // adopted from socket.io
// this part was converted to coffee-script and back again over the years, // this part was converted to coffee-script and back again over the years,
// so it might look weird // so it might look weird
// level to color // level to color
var levels = { let levels = {
fatal : 31, fatal: chalk.red,
error : 31, error: chalk.red,
warn : 33, warn: chalk.yellow,
http : 35, http: chalk.magenta,
info : 36, info: chalk.cyan,
debug : 90, debug: chalk.black,
trace : 90, trace: chalk.white,
} };
var max = 0 let max = 0;
for (var l in levels) { for (let l in levels) {
max = Math.max(max, l.length) max = Math.max(max, l.length);
} }
/**
*
* @param {*} str
*/
function pad(str) { function pad(str) {
if (str.length < max) return str + ' '.repeat(max - str.length) if (str.length < max) return str + ' '.repeat(max - str.length);
return str return str;
} }
var subsystems = [{ /**
in : '\033[32m<--\033[39m', * Build a string
out : '\033[33m-->\033[39m', * @param {*} type
fs : '\033[90m-=-\033[39m', * @param {*} msg
default : '\033[34m---\033[39m', * @param {*} obj
}, { * @param {*} colors
in : '<--', */
out : '-->',
fs : '-=-',
default : '---',
}]
function print(type, msg, obj, colors) { function print(type, msg, obj, colors) {
if (typeof type === 'number') type = getlvl(type) if (typeof type === 'number') type = getlvl(type);
var finalmsg = msg.replace(/@{(!?[$A-Za-z_][$0-9A-Za-z\._]*)}/g, function(_, name) { let finalmsg = msg.replace(/@{(!?[$A-Za-z_][$0-9A-Za-z\._]*)}/g, function(_, name) {
var str = obj, is_error let str = obj;
if (name[0] === '!') { let is_error;
name = name.substr(1) if (name[0] === '!') {
is_error = true name = name.substr(1);
} is_error = true;
}
var _ref = name.split('.') let _ref = name.split('.');
for (var _i = 0; _i < _ref.length; _i++) { for (let _i = 0; _i < _ref.length; _i++) {
var id = _ref[_i] let id = _ref[_i];
if (Utils.is_object(str) || Array.isArray(str)) { if (Utils.is_object(str) || Array.isArray(str)) {
str = str[id] str = str[id];
} else { } else {
str = undefined str = undefined;
} }
} }
if (typeof(str) === 'string') { if (typeof(str) === 'string') {
if (!colors || str.includes('\n')) { if (!colors || str.includes('\n')) {
return str return str;
} else if (is_error) { } else if (is_error) {
return '\033[31m' + str + '\033[39m' return chalk.red(str);
} else { } else {
return '\033[32m' + str + '\033[39m' return chalk.green(str);
} }
} else { } else {
return require('util').inspect(str, null, null, colors) return require('util').inspect(str, null, null, colors);
} }
}) });
var sub = subsystems[colors ? 0 : 1][obj.sub] || subsystems[+!colors].default const subsystems = [{
if (colors) { in: chalk.green('<--'),
return ' \033[' + levels[type] + 'm' + (pad(type)) + '\033[39m ' + sub + ' ' + finalmsg out: chalk.yellow('-->'),
} else { fs: chalk.black('-=-'),
return ' ' + (pad(type)) + ' ' + sub + ' ' + finalmsg default: chalk.blue('---'),
} }, {
in: '<--',
out: '-->',
fs: '-=-',
default: '---',
}];
let sub = subsystems[colors ? 0 : 1][obj.sub] || subsystems[+!colors].default;
if (colors) {
// return ' \033[' + levels[type] + 'm' + (pad(type)) + '\033[39m ' + sub + ' ' + finalmsg
return ` ${levels[type]((pad(type)))}${chalk.white(`${sub} ${finalmsg}`)}`;
} else {
return ` ${(pad(type))}${sub} ${finalmsg}`;
}
} }

View File

@ -1,79 +1,81 @@
var crypto = require('crypto') 'use strict';
var Error = require('http-errors')
var utils = require('./utils') let crypto = require('crypto');
var Logger = require('./logger') let Error = require('http-errors');
let utils = require('./utils');
let Logger = require('./logger');
module.exports.match = function match(regexp) { module.exports.match = function match(regexp) {
return function(req, res, next, value, name) { return function(req, res, next, value, name) {
if (regexp.exec(value)) { if (regexp.exec(value)) {
next() next();
} else { } else {
next('route') next('route');
} }
} };
} };
module.exports.validate_name = function validate_name(req, res, next, value, name) { module.exports.validate_name = function validate_name(req, res, next, value, name) {
if (value.charAt(0) === '-') { if (value.charAt(0) === '-') {
// special case in couchdb usually // special case in couchdb usually
next('route') next('route');
} else if (utils.validate_name(value)) { } else if (utils.validate_name(value)) {
next() next();
} else { } else {
next( Error[403]('invalid ' + name) ) next( Error[403]('invalid ' + name) );
} }
} };
module.exports.validate_package = function validate_package(req, res, next, value, name) { module.exports.validate_package = function validate_package(req, res, next, value, name) {
if (value.charAt(0) === '-') { if (value.charAt(0) === '-') {
// special case in couchdb usually // special case in couchdb usually
next('route') next('route');
} else if (utils.validate_package(value)) { } else if (utils.validate_package(value)) {
next() next();
} else { } else {
next( Error[403]('invalid ' + name) ) next( Error[403]('invalid ' + name) );
} }
} };
module.exports.media = function media(expect) { module.exports.media = function media(expect) {
return function(req, res, next) { return function(req, res, next) {
if (req.headers['content-type'] !== expect) { if (req.headers['content-type'] !== expect) {
next( Error[415]('wrong content-type, expect: ' + expect next( Error[415]('wrong content-type, expect: ' + expect
+ ', got: '+req.headers['content-type']) ) + ', got: '+req.headers['content-type']) );
} else { } else {
next() next();
} }
} };
} };
module.exports.expect_json = function expect_json(req, res, next) { module.exports.expect_json = function expect_json(req, res, next) {
if (!utils.is_object(req.body)) { if (!utils.is_object(req.body)) {
return next( Error[400]("can't parse incoming json") ) return next( Error[400]('can\'t parse incoming json') );
} }
next() next();
} };
module.exports.anti_loop = function(config) { module.exports.anti_loop = function(config) {
return function(req, res, next) { return function(req, res, next) {
if (req.headers.via != null) { if (req.headers.via != null) {
var arr = req.headers.via.split(',') let arr = req.headers.via.split(',');
for (var i=0; i<arr.length; i++) { for (let i=0; i<arr.length; i++) {
var m = arr[i].match(/\s*(\S+)\s+(\S+)/) let m = arr[i].match(/\s*(\S+)\s+(\S+)/);
if (m && m[2] === config.server_id) { if (m && m[2] === config.server_id) {
return next( Error[508]('loop detected') ) return next( Error[508]('loop detected') );
} }
} }
} }
next() next();
} };
} };
// express doesn't do etags with requests <= 1024b // express doesn't do etags with requests <= 1024b
// we use md5 here, it works well on 1k+ bytes, but sucks with fewer data // we use md5 here, it works well on 1k+ bytes, but sucks with fewer data
// could improve performance using crc32 after benchmarks // could improve performance using crc32 after benchmarks
function md5sum(data) { function md5sum(data) {
return crypto.createHash('md5').update(data).digest('hex') return crypto.createHash('md5').update(data).digest('hex');
} }
module.exports.allow = function(auth) { module.exports.allow = function(auth) {
@ -83,41 +85,41 @@ module.exports.allow = function(auth) {
auth['allow_'+action](req.params.package, req.remote_user, function(error, allowed) { auth['allow_'+action](req.params.package, req.remote_user, function(error, allowed) {
req.resume(); req.resume();
if (error) { if (error) {
next(error) next(error);
} else if (allowed) { } else if (allowed) {
next() next();
} else { } else {
// last plugin (that's our built-in one) returns either // last plugin (that's our built-in one) returns either
// cb(err) or cb(null, true), so this should never happen // cb(err) or cb(null, true), so this should never happen
throw Error('bug in the auth plugin system') throw Error('bug in the auth plugin system');
} }
}) });
} };
} };
} };
module.exports.final = function(body, req, res, next) { module.exports.final = function(body, req, res, next) {
if (res.statusCode === 401 && !res.getHeader('WWW-Authenticate')) { if (res.statusCode === 401 && !res.getHeader('WWW-Authenticate')) {
// they say it's required for 401, so... // they say it's required for 401, so...
res.header('WWW-Authenticate', 'Basic, Bearer') res.header('WWW-Authenticate', 'Basic, Bearer');
} }
try { try {
if (typeof(body) === 'string' || typeof(body) === 'object') { if (typeof(body) === 'string' || typeof(body) === 'object') {
if (!res.getHeader('Content-type')) { if (!res.getHeader('Content-type')) {
res.header('Content-type', 'application/json') res.header('Content-type', 'application/json');
} }
if (typeof(body) === 'object' && body != null) { if (typeof(body) === 'object' && body != null) {
if (typeof(body.error) === 'string') { if (typeof(body.error) === 'string') {
res._verdaccio_error = body.error res._verdaccio_error = body.error;
} }
body = JSON.stringify(body, undefined, ' ') + '\n' body = JSON.stringify(body, undefined, ' ') + '\n';
} }
// don't send etags with errors // don't send etags with errors
if (!res.statusCode || (res.statusCode >= 200 && res.statusCode < 300)) { if (!res.statusCode || (res.statusCode >= 200 && res.statusCode < 300)) {
res.header('ETag', '"' + md5sum(body) + '"') res.header('ETag', '"' + md5sum(body) + '"');
} }
} else { } else {
// send(null), send(204), etc. // send(null), send(204), etc.
@ -127,77 +129,77 @@ module.exports.final = function(body, req, res, next) {
// as an error handler, we can't report error properly, // as an error handler, we can't report error properly,
// and should just close socket // and should just close socket
if (err.message.match(/set headers after they are sent/)) { if (err.message.match(/set headers after they are sent/)) {
if (res.socket != null) res.socket.destroy() if (res.socket != null) res.socket.destroy();
return return;
} else { } else {
throw err throw err;
} }
} }
res.send(body) res.send(body);
} };
module.exports.log = function(req, res, next) { module.exports.log = function(req, res, next) {
// logger // logger
req.log = Logger.logger.child({ sub: 'in' }) req.log = Logger.logger.child({sub: 'in'});
var _auth = req.headers.authorization let _auth = req.headers.authorization;
if (_auth != null) req.headers.authorization = '<Classified>' if (_auth != null) req.headers.authorization = '<Classified>';
var _cookie = req.headers.cookie let _cookie = req.headers.cookie;
if (_cookie != null) req.headers.cookie = '<Classified>' if (_cookie != null) req.headers.cookie = '<Classified>';
req.url = req.originalUrl req.url = req.originalUrl;
req.log.info( { req: req, ip: req.ip } req.log.info( {req: req, ip: req.ip}
, '@{ip} requested \'@{req.method} @{req.url}\'' ) , '@{ip} requested \'@{req.method} @{req.url}\'' );
req.originalUrl = req.url req.originalUrl = req.url;
if (_auth != null) req.headers.authorization = _auth if (_auth != null) req.headers.authorization = _auth;
if (_cookie != null) req.headers.cookie = _cookie if (_cookie != null) req.headers.cookie = _cookie;
var bytesin = 0 let bytesin = 0;
req.on('data', function(chunk) { req.on('data', function(chunk) {
bytesin += chunk.length bytesin += chunk.length;
}) });
var bytesout = 0 let bytesout = 0;
var _write = res.write let _write = res.write;
res.write = function(buf) { res.write = function(buf) {
bytesout += buf.length bytesout += buf.length;
_write.apply(res, arguments) _write.apply(res, arguments);
} };
function log() { function log() {
var message = "@{status}, user: @{user}, req: '@{request.method} @{request.url}'" let message = '@{status}, user: @{user}, req: \'@{request.method} @{request.url}\'';
if (res._verdaccio_error) { if (res._verdaccio_error) {
message += ', error: @{!error}' message += ', error: @{!error}';
} else { } else {
message += ', bytes: @{bytes.in}/@{bytes.out}' message += ', bytes: @{bytes.in}/@{bytes.out}';
} }
req.url = req.originalUrl req.url = req.originalUrl;
req.log.warn({ req.log.warn({
request : { method: req.method, url: req.url }, request: {method: req.method, url: req.url},
level : 35, // http level: 35, // http
user : req.remote_user && req.remote_user.name, user: req.remote_user && req.remote_user.name,
status : res.statusCode, status: res.statusCode,
error : res._verdaccio_error, error: res._verdaccio_error,
bytes : { bytes: {
in : bytesin, in: bytesin,
out : bytesout, out: bytesout,
} },
}, message) }, message);
req.originalUrl = req.url req.originalUrl = req.url;
} }
req.on('close', function() { req.on('close', function() {
log(true) log(true);
}) });
var _end = res.end let _end = res.end;
res.end = function(buf) { res.end = function(buf) {
if (buf) bytesout += buf.length if (buf) bytesout += buf.length;
_end.apply(res, arguments) _end.apply(res, arguments);
log() log();
} };
next() next();
} };

View File

@ -1,24 +1,24 @@
var Handlebars = require('handlebars') 'use strict';
var request = require('request')
var Logger = require('./logger') let Handlebars = require('handlebars');
let request = require('request');
let Logger = require('./logger');
module.exports.notify = function(metadata, config) { module.exports.notify = function(metadata, config) {
if (config.notify && config.notify.content) { if (config.notify && config.notify.content) {
let template = Handlebars.compile(config.notify.content);
let content = template( metadata );
var template = Handlebars.compile(config.notify.content) let options = {
var content = template( metadata ) body: content,
};
var options = {
body: content
}
// provides fallback support, it's accept an Object {} and Array of {} // provides fallback support, it's accept an Object {} and Array of {}
if ( config.notify.headers && Array.isArray(config.notify.headers) ) { if ( config.notify.headers && Array.isArray(config.notify.headers) ) {
var header = {}; let header = {};
config.notify.headers.map(function(item) { config.notify.headers.map(function(item) {
if (Object.is(item, item)) { if (Object.is(item, item)) {
for (var key in item) { for (let key in item) {
header[key] = item[key]; header[key] = item[key];
} }
} }
@ -31,19 +31,18 @@ module.exports.notify = function(metadata, config) {
options.method = config.notify.method; options.method = config.notify.method;
if (config.notify.endpoint) { if (config.notify.endpoint) {
options.url = config.notify.endpoint options.url = config.notify.endpoint;
} }
request(options, function(err, response, body) { request(options, function(err, response, body) {
if (err) { if (err) {
Logger.logger.error( { err: err }, ' notify error: @{err.message}' ); Logger.logger.error( {err: err}, ' notify error: @{err.message}' );
} else { } else {
Logger.logger.info({ content: content}, 'A notification has been shipped: @{content}') Logger.logger.info({content: content}, 'A notification has been shipped: @{content}');
if (body) { if (body) {
Logger.logger.debug( { body: body }, ' body: @{body}' ); Logger.logger.debug( {body: body}, ' body: @{body}' );
} }
} }
}); });
} }
} };

View File

@ -1,57 +1,59 @@
var Path = require('path') 'use strict';
let Path = require('path');
function try_load(path) { function try_load(path) {
try { try {
return require(path) return require(path);
} catch(err) { } catch(err) {
if (err.code === 'MODULE_NOT_FOUND') { if (err.code === 'MODULE_NOT_FOUND') {
return null return null;
} }
throw err throw err;
} }
} }
function load_plugins(config, plugin_configs, params, sanity_check) { function load_plugins(config, plugin_configs, params, sanity_check) {
var plugins = Object.keys(plugin_configs || {}).map(function(p) { let plugins = Object.keys(plugin_configs || {}).map(function(p) {
var plugin let plugin;
// try local plugins first // try local plugins first
plugin = try_load(Path.resolve(__dirname + '/plugins', p)) plugin = try_load(Path.resolve(__dirname + '/plugins', p));
// npm package // npm package
if (plugin === null && p.match(/^[^\.\/]/)) { if (plugin === null && p.match(/^[^\.\/]/)) {
plugin = try_load(`verdaccio-${p}`) plugin = try_load(`verdaccio-${p}`);
// compatibility for old sinopia plugins // compatibility for old sinopia plugins
if (!plugin) { if (!plugin) {
plugin = try_load(`sinopia-${p}`) plugin = try_load(`sinopia-${p}`);
} }
} }
if (plugin === null) { if (plugin === null) {
plugin = try_load(p) plugin = try_load(p);
} }
// relative to config path // relative to config path
if (plugin === null && p.match(/^\.\.?($|\/)/)) { if (plugin === null && p.match(/^\.\.?($|\/)/)) {
plugin = try_load(Path.resolve(Path.dirname(config.self_path), p)) plugin = try_load(Path.resolve(Path.dirname(config.self_path), p));
} }
if (plugin === null) { if (plugin === null) {
throw new Error('"' + p + '" plugin not found\ntry "npm install verdaccio-' + p + '"') throw Error('"' + p + '" plugin not found\ntry "npm install verdaccio-' + p + '"');
} }
if (typeof(plugin) !== 'function') if (typeof(plugin) !== 'function')
throw new Error('"' + p + '" doesn\'t look like a valid plugin') throw Error('"' + p + '" doesn\'t look like a valid plugin');
plugin = plugin(plugin_configs[p], params) plugin = plugin(plugin_configs[p], params);
if (plugin === null || !sanity_check(plugin)) if (plugin === null || !sanity_check(plugin))
throw new Error('"' + p + '" doesn\'t look like a valid plugin') throw Error('"' + p + '" doesn\'t look like a valid plugin');
return plugin; return plugin;
}) });
return plugins return plugins;
} }
exports.load_plugins = load_plugins; exports.load_plugins = load_plugins;

View File

@ -1,3 +1,5 @@
'use strict';
/** Node.js Crypt(3) Library /** Node.js Crypt(3) Library
Inspired by (and intended to be compatible with) sendanor/crypt3 Inspired by (and intended to be compatible with) sendanor/crypt3
@ -10,7 +12,7 @@
*/ */
var crypt = require('unix-crypt-td-js'), let crypt = require('unix-crypt-td-js'),
crypto = require('crypto'); crypto = require('crypto');
function createSalt(type) { function createSalt(type) {
@ -33,7 +35,6 @@ function createSalt(type) {
default: default:
throw new TypeError('Unknown salt type at crypt3.createSalt: ' + type); throw new TypeError('Unknown salt type at crypt3.createSalt: ' + type);
} }
} }
function crypt3(key, salt) { function crypt3(key, salt) {

View File

@ -1,49 +1,51 @@
var fs = require('fs') 'use strict';
var Path = require('path')
var utils = require('./utils')
module.exports = HTPasswd let fs = require('fs');
let Path = require('path');
let utils = require('./utils');
module.exports = HTPasswd;
function HTPasswd(config, stuff) { function HTPasswd(config, stuff) {
var self = Object.create(HTPasswd.prototype) let self = Object.create(HTPasswd.prototype);
self._users = {} self._users = {};
// config for this module // config for this module
self._config = config self._config = config;
// verdaccio logger // verdaccio logger
self._logger = stuff.logger self._logger = stuff.logger;
// verdaccio main config object // verdaccio main config object
self._verdaccio_config = stuff.config self._verdaccio_config = stuff.config;
// all this "verdaccio_config" stuff is for b/w compatibility only // all this "verdaccio_config" stuff is for b/w compatibility only
self._maxusers = self._config.max_users self._maxusers = self._config.max_users;
if (!self._maxusers) self._maxusers = self._verdaccio_config.max_users if (!self._maxusers) self._maxusers = self._verdaccio_config.max_users;
// set maxusers to Infinity if not specified // set maxusers to Infinity if not specified
if (!self._maxusers) self._maxusers = Infinity if (!self._maxusers) self._maxusers = Infinity;
self._last_time = null self._last_time = null;
var file = self._config.file let file = self._config.file;
if (!file) file = self._verdaccio_config.users_file if (!file) file = self._verdaccio_config.users_file;
if (!file) throw new Error('should specify "file" in config') if (!file) throw new Error('should specify "file" in config');
self._path = Path.resolve(Path.dirname(self._verdaccio_config.self_path), file) self._path = Path.resolve(Path.dirname(self._verdaccio_config.self_path), file);
return self return self;
} }
HTPasswd.prototype.authenticate = function (user, password, cb) { HTPasswd.prototype.authenticate = function(user, password, cb) {
var self = this let self = this;
self._reload(function (err) { self._reload(function(err) {
if (err) return cb(err.code === 'ENOENT' ? null : err) if (err) return cb(err.code === 'ENOENT' ? null : err);
if (!self._users[user]) return cb(null, false) if (!self._users[user]) return cb(null, false);
if (!utils.verify_password(user, password, self._users[user])) return cb(null, false) if (!utils.verify_password(user, password, self._users[user])) return cb(null, false);
// authentication succeeded! // authentication succeeded!
// return all usergroups this user has access to; // return all usergroups this user has access to;
// (this particular package has no concept of usergroups, so just return user herself) // (this particular package has no concept of usergroups, so just return user herself)
return cb(null, [user]) return cb(null, [user]);
}) });
} };
// hopefully race-condition-free way to add users: // hopefully race-condition-free way to add users:
// 1. lock file for writing (other processes can still read) // 1. lock file for writing (other processes can still read)
@ -52,85 +54,82 @@ HTPasswd.prototype.authenticate = function (user, password, cb) {
// 4. move .htpasswd.tmp to .htpasswd // 4. move .htpasswd.tmp to .htpasswd
// 5. reload .htpasswd // 5. reload .htpasswd
// 6. unlock file // 6. unlock file
HTPasswd.prototype.adduser = function (user, password, real_cb) { HTPasswd.prototype.adduser = function(user, password, real_cb) {
var self = this let self = this;
function sanity_check() { function sanity_check() {
var err = null let err = null;
if (self._users[user]) { if (self._users[user]) {
err = Error('this user already exists') err = Error('this user already exists');
} else if (Object.keys(self._users).length >= self._maxusers) { } else if (Object.keys(self._users).length >= self._maxusers) {
err = Error('maximum amount of users reached') err = Error('maximum amount of users reached');
} }
if (err) err.status = 403 if (err) err.status = 403;
return err return err;
} }
// preliminary checks, just to ensure that file won't be reloaded if it's not needed // preliminary checks, just to ensure that file won't be reloaded if it's not needed
var s_err = sanity_check() let s_err = sanity_check();
if (s_err) return real_cb(s_err, false) if (s_err) return real_cb(s_err, false);
utils.lock_and_read(self._path, function (err, res) { utils.lock_and_read(self._path, function(err, res) {
var locked = false let locked = false;
// callback that cleans up lock first // callback that cleans up lock first
function cb(err) { function cb(err) {
if (locked) { if (locked) {
utils.unlock_file(self._path, function () { utils.unlock_file(self._path, function() {
// ignore any error from the unlock // ignore any error from the unlock
real_cb(err, !err) real_cb(err, !err);
}) });
} else { } else {
real_cb(err, !err) real_cb(err, !err);
} }
} }
if (!err) { if (!err) {
locked = true locked = true;
} }
// ignore ENOENT errors, we'll just create .htpasswd in that case // ignore ENOENT errors, we'll just create .htpasswd in that case
if (err && err.code !== 'ENOENT') return cb(err) if (err && err.code !== 'ENOENT') return cb(err);
var body = (res || '').toString('utf8') let body = (res || '').toString('utf8');
self._users = utils.parse_htpasswd(body) self._users = utils.parse_htpasswd(body);
// real checks, to prevent race conditions // real checks, to prevent race conditions
var s_err = sanity_check() let s_err = sanity_check();
if (s_err) return cb(s_err) if (s_err) return cb(s_err);
try { try {
body = utils.add_user_to_htpasswd(body, user, password) body = utils.add_user_to_htpasswd(body, user, password);
} catch (err) { } catch (err) {
return cb(err) return cb(err);
} }
fs.writeFile(self._path, body, function (err) { fs.writeFile(self._path, body, function(err) {
if (err) return cb(err) if (err) return cb(err);
self._reload(function () { self._reload(function() {
cb(null, true) cb(null, true);
}) });
})
})
}
HTPasswd.prototype._reload = function (_callback) {
var self = this
fs.stat(self._path, function (err, stats) {
if (err) return _callback(err)
if (self._last_time === stats.mtime) return _callback()
self._last_time = stats.mtime
fs.readFile(self._path, 'utf8', function (err, buffer) {
if (err) return _callback(err)
self._users = utils.parse_htpasswd(buffer)
_callback()
}); });
}); });
};
} HTPasswd.prototype._reload = function(_callback) {
let self = this;
fs.stat(self._path, function(err, stats) {
if (err) return _callback(err);
if (self._last_time === stats.mtime) return _callback();
self._last_time = stats.mtime;
fs.readFile(self._path, 'utf8', function(err, buffer) {
if (err) return _callback(err);
self._users = utils.parse_htpasswd(buffer);
_callback();
});
});
};

View File

@ -1,68 +1,70 @@
var crypto = require('crypto') 'use strict';
var crypt3 = require('./crypt3')
var md5 = require('apache-md5') let crypto = require('crypto');
var locker = require('../../file-locking') let crypt3 = require('./crypt3');
let md5 = require('apache-md5');
let locker = require('../../file-locking');
// this function neither unlocks file nor closes it // this function neither unlocks file nor closes it
// it'll have to be done manually later // it'll have to be done manually later
function lock_and_read(name, cb) { function lock_and_read(name, cb) {
locker.readFile(name, {lock: true}, function (err, res) { locker.readFile(name, {lock: true}, function(err, res) {
if (err) { if (err) {
return cb(err) return cb(err);
} }
return cb(null, res) return cb(null, res);
}) });
} }
// close and unlock file // close and unlock file
function unlock_file(name, cb) { function unlock_file(name, cb) {
locker.unlockFile(name, cb) locker.unlockFile(name, cb);
} }
function parse_htpasswd(input) { function parse_htpasswd(input) {
var result = {} let result = {};
input.split('\n').forEach(function(line) { input.split('\n').forEach(function(line) {
var args = line.split(':', 3) let args = line.split(':', 3);
if (args.length > 1) result[args[0]] = args[1] if (args.length > 1) result[args[0]] = args[1];
}) });
return result return result;
} }
function verify_password(user, passwd, hash) { function verify_password(user, passwd, hash) {
if (hash.indexOf('{PLAIN}') === 0) { if (hash.indexOf('{PLAIN}') === 0) {
return passwd === hash.substr(7) return passwd === hash.substr(7);
} else if (hash.indexOf('{SHA}') === 0) { } else if (hash.indexOf('{SHA}') === 0) {
return crypto.createHash('sha1').update(passwd, 'binary').digest('base64') === hash.substr(5) return crypto.createHash('sha1').update(passwd, 'binary').digest('base64') === hash.substr(5);
} else { } else {
return ( return (
// for backwards compatibility, first check md5 then check crypt3 // for backwards compatibility, first check md5 then check crypt3
md5(passwd, hash) === hash || md5(passwd, hash) === hash ||
crypt3(passwd, hash) === hash crypt3(passwd, hash) === hash
) );
} }
} }
function add_user_to_htpasswd(body, user, passwd) { function add_user_to_htpasswd(body, user, passwd) {
if (user !== encodeURIComponent(user)) { if (user !== encodeURIComponent(user)) {
var err = Error('username should not contain non-uri-safe characters') let err = Error('username should not contain non-uri-safe characters');
err.status = 409 err.status = 409;
throw err throw err;
} }
if (crypt3) { if (crypt3) {
passwd = crypt3(passwd) passwd = crypt3(passwd);
} else { } else {
passwd = '{SHA}' + crypto.createHash('sha1').update(passwd, 'binary').digest('base64') passwd = '{SHA}' + crypto.createHash('sha1').update(passwd, 'binary').digest('base64');
} }
var comment = 'autocreated ' + (new Date()).toJSON() let comment = 'autocreated ' + (new Date()).toJSON();
var newline = user + ':' + passwd + ':' + comment + '\n' let newline = user + ':' + passwd + ':' + comment + '\n';
if (body.length && body[body.length-1] !== '\n') newline = '\n' + newline if (body.length && body[body.length-1] !== '\n') newline = '\n' + newline;
return body + newline return body + newline;
} }
module.exports.parse_htpasswd = parse_htpasswd module.exports.parse_htpasswd = parse_htpasswd;
module.exports.verify_password = verify_password module.exports.verify_password = verify_password;
module.exports.add_user_to_htpasswd = add_user_to_htpasswd module.exports.add_user_to_htpasswd = add_user_to_htpasswd;
module.exports.lock_and_read = lock_and_read module.exports.lock_and_read = lock_and_read;
module.exports.unlock_file = unlock_file module.exports.unlock_file = unlock_file;

View File

@ -1,60 +1,51 @@
"use strict"; 'use strict';
const lunr = require('lunr') const lunr = require('lunr');
class Search { class Search {
constructor() { constructor() {
this.index = lunr(function() { this.index = lunr(function() {
this.field('name' , { boost: 10 }) this.field('name', {boost: 10});
this.field('description' , { boost: 4 }) this.field('description', {boost: 4});
this.field('author' , { boost: 6 }) this.field('author', {boost: 6});
this.field('readme') this.field('readme');
}) });
} }
query(q) { query(q) {
return q === '*' return q === '*'
? this.storage.config.localList.get().map( function( pkg ) { ? this.storage.config.localList.get().map( function( pkg ) {
return { ref: pkg, score: 1 }; return {ref: pkg, score: 1};
}) : this.index.search(q); }) : this.index.search(q);
} }
add(pkg) { add(pkg) {
this.index.add({ this.index.add({
id: pkg.name, id: pkg.name,
name: pkg.name, name: pkg.name,
description: pkg.description, description: pkg.description,
author: pkg._npmUser ? pkg._npmUser.name : '???', author: pkg._npmUser ? pkg._npmUser.name : '???',
}) });
}
add(pkg) {
this.index.add({
id: pkg.name,
name: pkg.name,
description: pkg.description,
author: pkg._npmUser ? pkg._npmUser.name : '???',
})
} }
remove(name) { remove(name) {
this.index.remove({ id: name }) this.index.remove({id: name});
} }
reindex() { reindex() {
var self = this let self = this;
this.storage.get_local(function(err, packages) { this.storage.get_local(function(err, packages) {
if (err) throw err // that function shouldn't produce any if (err) throw err; // that function shouldn't produce any
var i = packages.length let i = packages.length;
while (i--) { while (i--) {
self.add(packages[i]) self.add(packages[i]);
} }
}) });
} }
configureStorage(storage) { configureStorage(storage) {
this.storage = storage this.storage = storage;
this.reindex() this.reindex();
} }
} }

View File

@ -1,3 +1,4 @@
'use strict';
// see https://secure.flickr.com/photos/girliemac/sets/72157628409467125 // see https://secure.flickr.com/photos/girliemac/sets/72157628409467125
@ -51,24 +52,24 @@ const images = {
508: 'aVdnYa', // '6509400445', // 508 - Loop Detected 508: 'aVdnYa', // '6509400445', // 508 - Loop Detected
509: 'aXXg1V', // '6540399865', // 509 - Bandwidth Limit Exceeded 509: 'aXXg1V', // '6540399865', // 509 - Bandwidth Limit Exceeded
599: 'aVdo7v', // '6509400929', // 599 - Network connect timeout error 599: 'aVdo7v', // '6509400929', // 599 - Network connect timeout error
} };
module.exports.get_image = function(status) { module.exports.get_image = function(status) {
if (status in images) { if (status in images) {
return 'http://flic.kr/p/' + images[status] return 'http://flic.kr/p/' + images[status];
//return 'https://secure.flickr.com/photos/girliemac/'+images[status]+'/in/set-72157628409467125/lightbox/' // return 'https://secure.flickr.com/photos/girliemac/'+images[status]+'/in/set-72157628409467125/lightbox/'
} }
} };
module.exports.middleware = function(req, res, next) { module.exports.middleware = function(req, res, next) {
var _writeHead = res.writeHead let _writeHead = res.writeHead;
res.writeHead = function(status) { res.writeHead = function(status) {
if (status in images) { if (status in images) {
res.setHeader('X-Status-Cat', module.exports.get_image(status)) res.setHeader('X-Status-Cat', module.exports.get_image(status));
} }
_writeHead.apply(res, arguments) _writeHead.apply(res, arguments);
} };
next() next();
} };

View File

@ -1,14 +1,14 @@
"use strict"; 'use strict';
const assert = require('assert') const assert = require('assert');
const async = require('async') const async = require('async');
const Error = require('http-errors') const Error = require('http-errors');
const Stream = require('stream') const Stream = require('stream');
const Local = require('./local-storage') const Local = require('./local-storage');
const Logger = require('./logger') const Logger = require('./logger');
const MyStreams = require('./streams') const MyStreams = require('./streams');
const Proxy = require('./up-storage') const Proxy = require('./up-storage');
const Utils = require('./utils') const Utils = require('./utils');
// //
// Implements Storage interface // Implements Storage interface
@ -21,17 +21,17 @@ class Storage {
* @param {*} config * @param {*} config
*/ */
constructor(config) { constructor(config) {
this.config = config this.config = config;
// we support a number of uplinks, but only one local storage // we support a number of uplinks, but only one local storage
// Proxy and Local classes should have similar API interfaces // Proxy and Local classes should have similar API interfaces
this.uplinks = {} this.uplinks = {};
for (let p in config.uplinks) { for (let p in config.uplinks) {
// instance for each up-link definition // instance for each up-link definition
this.uplinks[p] = new Proxy(config.uplinks[p], config) this.uplinks[p] = new Proxy(config.uplinks[p], config);
this.uplinks[p].upname = p this.uplinks[p].upname = p;
} }
// an instance for local storage // an instance for local storage
this.local = new Local(config) this.local = new Local(config);
this.logger = Logger.logger.child(); this.logger = Logger.logger.child();
} }
@ -45,7 +45,7 @@ class Storage {
* @param {*} callback * @param {*} callback
*/ */
add_package(name, metadata, callback) { add_package(name, metadata, callback) {
var self = this let self = this;
// NOTE: // NOTE:
// - when we checking package for existance, we ask ALL uplinks // - when we checking package for existance, we ask ALL uplinks
@ -53,52 +53,52 @@ class Storage {
// so all requests are necessary // so all requests are necessary
check_package_local(function(err) { check_package_local(function(err) {
if (err) return callback(err) if (err) return callback(err);
check_package_remote(function(err) { check_package_remote(function(err) {
if (err) return callback(err) if (err) return callback(err);
publish_package(function(err) { publish_package(function(err) {
if (err) return callback(err) if (err) return callback(err);
callback() callback();
}) });
}) });
}) });
function check_package_local(cb) { function check_package_local(cb) {
self.local.get_package(name, {}, function(err, results) { self.local.get_package(name, {}, function(err, results) {
if (err && err.status !== 404) return cb(err) if (err && err.status !== 404) return cb(err);
if (results) return cb( Error[409]('this package is already present') ) if (results) return cb( Error[409]('this package is already present') );
cb() cb();
}) });
} }
function check_package_remote(cb) { function check_package_remote(cb) {
self._sync_package_with_uplinks(name, null, {}, function(err, results, err_results) { self._sync_package_with_uplinks(name, null, {}, function(err, results, err_results) {
// something weird // something weird
if (err && err.status !== 404) return cb(err) if (err && err.status !== 404) return cb(err);
// checking package // checking package
if (results) return cb( Error[409]('this package is already present') ) if (results) return cb( Error[409]('this package is already present') );
for (var i=0; i<err_results.length; i++) { for (let i=0; i<err_results.length; i++) {
// checking error // checking error
// if uplink fails with a status other than 404, we report failure // if uplink fails with a status other than 404, we report failure
if (err_results[i][0] != null) { if (err_results[i][0] != null) {
if (err_results[i][0].status !== 404) { if (err_results[i][0].status !== 404) {
return cb( Error[503]('one of the uplinks is down, refuse to publish') ) return cb( Error[503]('one of the uplinks is down, refuse to publish') );
} }
} }
} }
return cb() return cb();
}) });
} }
function publish_package(cb) { function publish_package(cb) {
self.local.add_package(name, metadata, callback) self.local.add_package(name, metadata, callback);
} }
} }
@ -112,7 +112,7 @@ class Storage {
* @param {*} callback * @param {*} callback
*/ */
add_version(name, version, metadata, tag, callback) { add_version(name, version, metadata, tag, callback) {
return this.local.add_version(name, version, metadata, tag, callback) return this.local.add_version(name, version, metadata, tag, callback);
} }
/** /**
@ -123,7 +123,7 @@ class Storage {
* @param {*} callback * @param {*} callback
*/ */
merge_tags(name, tag_hash, callback) { merge_tags(name, tag_hash, callback) {
return this.local.merge_tags(name, tag_hash, callback) return this.local.merge_tags(name, tag_hash, callback);
} }
/** /**
@ -134,7 +134,7 @@ class Storage {
* @param {*} callback * @param {*} callback
*/ */
replace_tags(name, tag_hash, callback) { replace_tags(name, tag_hash, callback) {
return this.local.replace_tags(name, tag_hash, callback) return this.local.replace_tags(name, tag_hash, callback);
} }
/** /**
@ -147,7 +147,7 @@ class Storage {
* @param {*} callback * @param {*} callback
*/ */
change_package(name, metadata, revision, callback) { change_package(name, metadata, revision, callback) {
return this.local.change_package(name, metadata, revision, callback) return this.local.change_package(name, metadata, revision, callback);
} }
/** /**
@ -158,7 +158,7 @@ class Storage {
* @param {*} callback * @param {*} callback
*/ */
remove_package(name, callback) { remove_package(name, callback) {
return this.local.remove_package(name, callback) return this.local.remove_package(name, callback);
} }
/** /**
@ -173,7 +173,7 @@ class Storage {
* @param {*} callback * @param {*} callback
*/ */
remove_tarball(name, filename, revision, callback) { remove_tarball(name, filename, revision, callback) {
return this.local.remove_tarball(name, filename, revision, callback) return this.local.remove_tarball(name, filename, revision, callback);
} }
/** /**
@ -184,7 +184,7 @@ class Storage {
* @param {*} filename * @param {*} filename
*/ */
add_tarball(name, filename) { add_tarball(name, filename) {
return this.local.add_tarball(name, filename) return this.local.add_tarball(name, filename);
} }
/** /**
@ -197,101 +197,100 @@ class Storage {
* @param {*} filename * @param {*} filename
*/ */
get_tarball(name, filename) { get_tarball(name, filename) {
var stream = MyStreams.ReadTarballStream() let stream = MyStreams.ReadTarballStream();
stream.abort = function() {} stream.abort = function() {};
var self = this let self = this;
// if someone requesting tarball, it means that we should already have some // if someone requesting tarball, it means that we should already have some
// information about it, so fetching package info is unnecessary // information about it, so fetching package info is unnecessary
// trying local first // trying local first
var rstream = self.local.get_tarball(name, filename) let rstream = self.local.get_tarball(name, filename);
var is_open = false let is_open = false;
rstream.on('error', function(err) { rstream.on('error', function(err) {
if (is_open || err.status !== 404) { if (is_open || err.status !== 404) {
return stream.emit('error', err) return stream.emit('error', err);
} }
// local reported 404 // local reported 404
var err404 = err let err404 = err;
rstream.abort() rstream.abort();
rstream = null // gc rstream = null; // gc
self.local.get_package(name, function(err, info) { self.local.get_package(name, function(err, info) {
if (!err && info._distfiles && info._distfiles[filename] != null) { if (!err && info._distfiles && info._distfiles[filename] != null) {
// information about this file exists locally // information about this file exists locally
serve_file(info._distfiles[filename]) serve_file(info._distfiles[filename]);
} else { } else {
// we know nothing about this file, trying to get information elsewhere // we know nothing about this file, trying to get information elsewhere
self._sync_package_with_uplinks(name, info, {}, function(err, info) { self._sync_package_with_uplinks(name, info, {}, function(err, info) {
if (err) return stream.emit('error', err) if (err) return stream.emit('error', err);
if (!info._distfiles || info._distfiles[filename] == null) { if (!info._distfiles || info._distfiles[filename] == null) {
return stream.emit('error', err404) return stream.emit('error', err404);
} }
serve_file(info._distfiles[filename]) serve_file(info._distfiles[filename]);
}) });
} }
}) });
}) });
rstream.on('content-length', function(v) { rstream.on('content-length', function(v) {
stream.emit('content-length', v) stream.emit('content-length', v);
}) });
rstream.on('open', function() { rstream.on('open', function() {
is_open = true is_open = true;
rstream.pipe(stream) rstream.pipe(stream);
}) });
return stream return stream;
function serve_file(file) { function serve_file(file) {
var uplink = null let uplink = null;
for (var p in self.uplinks) { for (let p in self.uplinks) {
if (self.uplinks[p].can_fetch_url(file.url)) { if (self.uplinks[p].can_fetch_url(file.url)) {
uplink = self.uplinks[p] uplink = self.uplinks[p];
} }
} }
if (uplink == null) { if (uplink == null) {
uplink = Proxy({ uplink = Proxy({
url: file.url, url: file.url,
_autogenerated: true, _autogenerated: true,
}, self.config) }, self.config);
} }
var savestream = self.local.add_tarball(name, filename) let savestream = self.local.add_tarball(name, filename);
var on_open = function() { var on_open = function() {
on_open = function(){} // prevent it from being called twice on_open = function() {}; // prevent it from being called twice
var rstream2 = uplink.get_url(file.url) let rstream2 = uplink.get_url(file.url);
rstream2.on('error', function(err) { rstream2.on('error', function(err) {
if (savestream) savestream.abort() if (savestream) savestream.abort();
savestream = null savestream = null;
stream.emit('error', err) stream.emit('error', err);
}) });
rstream2.on('end', function() { rstream2.on('end', function() {
if (savestream) savestream.done() if (savestream) savestream.done();
}) });
rstream2.on('content-length', function(v) { rstream2.on('content-length', function(v) {
stream.emit('content-length', v) stream.emit('content-length', v);
if (savestream) savestream.emit('content-length', v) if (savestream) savestream.emit('content-length', v);
}) });
rstream2.pipe(stream) rstream2.pipe(stream);
if (savestream) rstream2.pipe(savestream) if (savestream) rstream2.pipe(savestream);
} };
savestream.on('open', function() { savestream.on('open', function() {
on_open() on_open();
}) });
savestream.on('error', function(err) { savestream.on('error', function(err) {
self.logger.warn( { err: err } self.logger.warn( {err: err}
, 'error saving file: @{err.message}\n@{err.stack}' ) , 'error saving file: @{err.message}\n@{err.stack}' );
if (savestream) savestream.abort() if (savestream) savestream.abort();
savestream = null savestream = null;
on_open() on_open();
}) });
} }
} }
@ -313,24 +312,24 @@ class Storage {
this.local.get_package(name, options, (err, data) => { this.local.get_package(name, options, (err, data) => {
if (err && (!err.status || err.status >= 500)) { if (err && (!err.status || err.status >= 500)) {
// report internal errors right away // report internal errors right away
return callback(err) return callback(err);
} }
this._sync_package_with_uplinks(name, data, options, function(err, result, uplink_errors) { this._sync_package_with_uplinks(name, data, options, function(err, result, uplink_errors) {
if (err) return callback(err) if (err) return callback(err);
const whitelist = [ '_rev', 'name', 'versions', 'dist-tags', 'readme' ] const whitelist = ['_rev', 'name', 'versions', 'dist-tags', 'readme'];
for (var i in result) { for (let i in result) {
if (whitelist.indexOf(i) === -1) delete result[i] if (whitelist.indexOf(i) === -1) delete result[i];
} }
Utils.normalize_dist_tags(result) Utils.normalize_dist_tags(result);
// npm can throw if this field doesn't exist // npm can throw if this field doesn't exist
result._attachments = {} result._attachments = {};
callback(null, result, uplink_errors) callback(null, result, uplink_errors);
}) });
}) });
} }
/** /**
@ -345,37 +344,39 @@ class Storage {
* @param {*} options * @param {*} options
*/ */
search(startkey, options) { search(startkey, options) {
var self = this let self = this;
var stream = new Stream.PassThrough({ objectMode: true }) let stream = 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 // shortcut: if `local=1` is supplied, don't call uplinks
if (options.req.query.local !== undefined) return cb() if (options.req.query.local !== undefined) return cb();
var lstream = self.uplinks[up_name].search(startkey, options) let lstream = self.uplinks[up_name].search(startkey, options);
lstream.pipe(stream, { end: false }) lstream.pipe(stream, {end: false});
lstream.on('error', function (err) { lstream.on('error', function(err) {
self.logger.error({ err: err }, 'uplink error: @{err.message}') self.logger.error({err: err}, 'uplink error: @{err.message}');
cb(), cb = function () {} cb(), cb = function() {};
}) });
lstream.on('end', function () { lstream.on('end', function() {
cb(), cb = function () {} cb(), cb = function() {};
}) });
stream.abort = function () { stream.abort = function() {
if (lstream.abort) lstream.abort() if (lstream.abort) lstream.abort();
cb(), cb = function () {} cb(), cb = function() {};
} };
}, function () { }, function() {
var lstream = self.local.search(startkey, options) let lstream = self.local.search(startkey, options);
stream.abort = function () { lstream.abort() } stream.abort = function() {
lstream.pipe(stream, { end: true }) lstream.abort();
lstream.on('error', function (err) { };
self.logger.error({ err: err }, 'search error: @{err.message}') lstream.pipe(stream, {end: true});
stream.end() lstream.on('error', function(err) {
}) self.logger.error({err: err}, 'search error: @{err.message}');
}) stream.end();
});
});
return stream; return stream;
} }
@ -385,34 +386,34 @@ class Storage {
* @param {*} callback * @param {*} callback
*/ */
get_local(callback) { get_local(callback) {
var self = this let self = this;
var locals = this.config.localList.get() let locals = this.config.localList.get();
var packages = [] let packages = [];
var getPackage = function(i) { var getPackage = function(i) {
self.local.get_package(locals[i], function(err, info) { self.local.get_package(locals[i], function(err, info) {
if (!err) { if (!err) {
var latest = info['dist-tags'].latest let latest = info['dist-tags'].latest;
if (latest && info.versions[latest]) { if (latest && info.versions[latest]) {
packages.push(info.versions[latest]) packages.push(info.versions[latest]);
} else { } else {
self.logger.warn( { package: locals[i] } self.logger.warn( {package: locals[i]}
, 'package @{package} does not have a "latest" tag?' ) , 'package @{package} does not have a "latest" tag?' );
} }
} }
if (i >= locals.length - 1) { if (i >= locals.length - 1) {
callback(null, packages) callback(null, packages);
} else { } else {
getPackage(i + 1) getPackage(i + 1);
} }
}) });
} };
if (locals.length) { if (locals.length) {
getPackage(0) getPackage(0);
} else { } else {
callback(null, []) callback(null, []);
} }
} }
@ -426,68 +427,68 @@ class Storage {
* @param {*} callback * @param {*} callback
*/ */
_sync_package_with_uplinks(name, pkginfo, options, callback) { _sync_package_with_uplinks(name, pkginfo, options, callback) {
var self = this let self = this;
let exists = false; let exists = false;
if (!pkginfo) { if (!pkginfo) {
exists = false exists = false;
pkginfo = { pkginfo = {
name : name, 'name': name,
versions : {}, 'versions': {},
'dist-tags' : {}, 'dist-tags': {},
_uplinks : {}, '_uplinks': {},
} };
} else { } else {
exists = true exists = true;
} }
var uplinks = [] let uplinks = [];
for (let i in self.uplinks) { for (let i in self.uplinks) {
if (self.config.can_proxy_to(name, i)) { if (self.config.can_proxy_to(name, i)) {
uplinks.push(self.uplinks[i]) uplinks.push(self.uplinks[i]);
} }
} }
async.map(uplinks, function(up, cb) { async.map(uplinks, function(up, cb) {
var _options = Object.assign({}, options) let _options = Object.assign({}, options);
if (Utils.is_object(pkginfo._uplinks[up.upname])) { if (Utils.is_object(pkginfo._uplinks[up.upname])) {
var fetched = pkginfo._uplinks[up.upname].fetched let fetched = pkginfo._uplinks[up.upname].fetched;
if (fetched && fetched > (Date.now() - up.maxage)) { if (fetched && fetched > (Date.now() - up.maxage)) {
return cb() return cb();
} }
_options.etag = pkginfo._uplinks[up.upname].etag _options.etag = pkginfo._uplinks[up.upname].etag;
} }
up.get_package(name, _options, function(err, up_res, etag) { up.get_package(name, _options, function(err, up_res, etag) {
if (err && err.status === 304) if (err && err.status === 304)
pkginfo._uplinks[up.upname].fetched = Date.now() pkginfo._uplinks[up.upname].fetched = Date.now();
if (err || !up_res) return cb(null, [err || Error('no data')]) if (err || !up_res) return cb(null, [err || Error('no data')]);
try { try {
Utils.validate_metadata(up_res, name) Utils.validate_metadata(up_res, name);
} catch(err) { } catch(err) {
self.logger.error({ self.logger.error({
sub: 'out', sub: 'out',
err: err, err: err,
}, 'package.json validating error @{!err.message}\n@{err.stack}') }, 'package.json validating error @{!err.message}\n@{err.stack}');
return cb(null, [ err ]) return cb(null, [err]);
} }
pkginfo._uplinks[up.upname] = { pkginfo._uplinks[up.upname] = {
etag: etag, etag: etag,
fetched: Date.now() fetched: Date.now(),
} };
for (let i in up_res.versions) { for (let i in up_res.versions) {
// this won't be serialized to json, // this won't be serialized to json,
// kinda like an ES6 Symbol // kinda like an ES6 Symbol
//FIXME: perhaps Symbol('_verdaccio_uplink') here? // FIXME: perhaps Symbol('_verdaccio_uplink') here?
Object.defineProperty(up_res.versions[i], '_verdaccio_uplink', { Object.defineProperty(up_res.versions[i], '_verdaccio_uplink', {
value : up.upname, value: up.upname,
enumerable : false, enumerable: false,
configurable : false, configurable: false,
writable : true, writable: true,
}); });
} }
@ -497,29 +498,29 @@ class Storage {
self.logger.error({ self.logger.error({
sub: 'out', sub: 'out',
err: err, err: err,
}, 'package.json parsing error @{!err.message}\n@{err.stack}') }, 'package.json parsing error @{!err.message}\n@{err.stack}');
return cb(null, [ err ]) return cb(null, [err]);
} }
// if we got to this point, assume that the correct package exists // if we got to this point, assume that the correct package exists
// on the uplink // on the uplink
exists = true exists = true;
cb() cb();
}) });
}, function(err, uplink_errors) { }, function(err, uplink_errors) {
assert(!err && Array.isArray(uplink_errors)) assert(!err && Array.isArray(uplink_errors));
if (!exists) { if (!exists) {
return callback( Error[404]('no such package available') return callback( Error[404]('no such package available')
, null , null
, uplink_errors ) , uplink_errors );
} }
self.local.update_versions(name, pkginfo, function(err, pkginfo) { self.local.update_versions(name, pkginfo, function(err, pkginfo) {
if (err) return callback(err) if (err) return callback(err);
return callback(null, pkginfo, uplink_errors) return callback(null, pkginfo, uplink_errors);
}) });
}) });
} }
/** /**
@ -534,17 +535,17 @@ class Storage {
// NOTE: if a certain version was updated, we can't refresh it reliably // NOTE: if a certain version was updated, we can't refresh it reliably
for (var i in up.versions) { for (var i in up.versions) {
if (local.versions[i] == null) { if (local.versions[i] == null) {
local.versions[i] = up.versions[i] local.versions[i] = up.versions[i];
} }
} }
// refresh dist-tags // refresh dist-tags
for (var i in up['dist-tags']) { for (var i in up['dist-tags']) {
if (local['dist-tags'][i] !== up['dist-tags'][i]) { if (local['dist-tags'][i] !== up['dist-tags'][i]) {
local['dist-tags'][i] = up['dist-tags'][i] local['dist-tags'][i] = up['dist-tags'][i];
if (i === 'latest') { if (i === 'latest') {
// if remote has more fresh package, we should borrow its readme // if remote has more fresh package, we should borrow its readme
local.readme = up.readme local.readme = up.readme;
} }
} }
} }

View File

@ -1,60 +1,62 @@
var Stream = require('stream') 'use strict';
var Util = require('util')
module.exports.ReadTarballStream = ReadTarball let Stream = require('stream');
module.exports.UploadTarballStream = UploadTarball let Util = require('util');
module.exports.ReadTarballStream = ReadTarball;
module.exports.UploadTarballStream = UploadTarball;
// //
// This stream is used to read tarballs from repository // This stream is used to read tarballs from repository
// //
function ReadTarball(options) { function ReadTarball(options) {
var self = new Stream.PassThrough(options) let self = new Stream.PassThrough(options);
Object.setPrototypeOf(self, ReadTarball.prototype) Object.setPrototypeOf(self, ReadTarball.prototype);
// called when data is not needed anymore // called when data is not needed anymore
add_abstract_method(self, 'abort') add_abstract_method(self, 'abort');
return self return self;
} }
Util.inherits(ReadTarball, Stream.PassThrough) Util.inherits(ReadTarball, Stream.PassThrough);
// //
// This stream is used to upload tarballs to a repository // This stream is used to upload tarballs to a repository
// //
function UploadTarball(options) { function UploadTarball(options) {
var self = new Stream.PassThrough(options) let self = new Stream.PassThrough(options);
Object.setPrototypeOf(self, UploadTarball.prototype) Object.setPrototypeOf(self, UploadTarball.prototype);
// called when user closes connection before upload finishes // called when user closes connection before upload finishes
add_abstract_method(self, 'abort') add_abstract_method(self, 'abort');
// called when upload finishes successfully // called when upload finishes successfully
add_abstract_method(self, 'done') add_abstract_method(self, 'done');
return self return self;
} }
Util.inherits(UploadTarball, Stream.PassThrough) Util.inherits(UploadTarball, Stream.PassThrough);
// //
// This function intercepts abstract calls and replays them allowing // This function intercepts abstract calls and replays them allowing
// us to attach those functions after we are ready to do so // us to attach those functions after we are ready to do so
// //
function add_abstract_method(self, name) { function add_abstract_method(self, name) {
self._called_methods = self._called_methods || {} self._called_methods = self._called_methods || {};
self.__defineGetter__(name, function() { self.__defineGetter__(name, function() {
return function() { return function() {
self._called_methods[name] = true self._called_methods[name] = true;
} };
}) });
self.__defineSetter__(name, function(fn) { self.__defineSetter__(name, function(fn) {
delete self[name] delete self[name];
self[name] = fn self[name] = fn;
if (self._called_methods && self._called_methods[name]) { if (self._called_methods && self._called_methods[name]) {
delete self._called_methods[name] delete self._called_methods[name];
self[name]() self[name]();
} }
}) });
} }

View File

@ -1,32 +1,32 @@
"use strict"; 'use strict';
const JSONStream = require('JSONStream') const JSONStream = require('JSONStream');
const Error = require('http-errors') const Error = require('http-errors');
const request = require('request') const request = require('request');
const Stream = require('readable-stream') const Stream = require('readable-stream');
const URL = require('url') const URL = require('url');
const parse_interval = require('./config').parse_interval const parse_interval = require('./config').parse_interval;
const Logger = require('./logger') const Logger = require('./logger');
const MyStreams = require('./streams') const MyStreams = require('./streams');
const Utils = require('./utils') const Utils = require('./utils');
const encode = function(thing) { const encode = function(thing) {
return encodeURIComponent(thing).replace(/^%40/, '@'); return encodeURIComponent(thing).replace(/^%40/, '@');
}; };
const _setupProxy = function(hostname, config, mainconfig, isHTTPS) { const _setupProxy = function(hostname, config, mainconfig, isHTTPS) {
var no_proxy let no_proxy;
var proxy_key = isHTTPS ? 'https_proxy' : 'http_proxy' let proxy_key = isHTTPS ? 'https_proxy' : 'http_proxy';
// get http_proxy and no_proxy configs // get http_proxy and no_proxy configs
if (proxy_key in config) { if (proxy_key in config) {
this.proxy = config[proxy_key] this.proxy = config[proxy_key];
} else if (proxy_key in mainconfig) { } else if (proxy_key in mainconfig) {
this.proxy = mainconfig[proxy_key] this.proxy = mainconfig[proxy_key];
} }
if ('no_proxy' in config) { if ('no_proxy' in config) {
no_proxy = config.no_proxy no_proxy = config.no_proxy;
} else if ('no_proxy' in mainconfig) { } else if ('no_proxy' in mainconfig) {
no_proxy = mainconfig.no_proxy no_proxy = mainconfig.no_proxy;
} }
// use wget-like algorithm to determine if proxy shouldn't be used // use wget-like algorithm to determine if proxy shouldn't be used
@ -34,17 +34,17 @@ const _setupProxy = function(hostname, config, mainconfig, isHTTPS) {
hostname = '.' + hostname; hostname = '.' + hostname;
} }
if (typeof(no_proxy) === 'string' && no_proxy.length) { if (typeof(no_proxy) === 'string' && no_proxy.length) {
no_proxy = no_proxy.split(',') no_proxy = no_proxy.split(',');
} }
if (Array.isArray(no_proxy)) { if (Array.isArray(no_proxy)) {
for (let i=0; i<no_proxy.length; i++) { for (let i=0; i<no_proxy.length; i++) {
var no_proxy_item = no_proxy[i] let no_proxy_item = no_proxy[i];
if (no_proxy_item[0] !== '.') no_proxy_item = '.' + no_proxy_item if (no_proxy_item[0] !== '.') no_proxy_item = '.' + no_proxy_item;
if (hostname.lastIndexOf(no_proxy_item) === hostname.length - no_proxy_item.length) { if (hostname.lastIndexOf(no_proxy_item) === hostname.length - no_proxy_item.length) {
if (this.proxy) { if (this.proxy) {
this.logger.debug({url: this.url.href, rule: no_proxy_item}, this.logger.debug({url: this.url.href, rule: no_proxy_item},
'not using proxy for @{url}, excluded by @{rule} rule') 'not using proxy for @{url}, excluded by @{rule} rule');
this.proxy = false this.proxy = false;
} }
break; break;
} }
@ -53,12 +53,12 @@ const _setupProxy = function(hostname, config, mainconfig, isHTTPS) {
// if it's non-string (i.e. "false"), don't use it // if it's non-string (i.e. "false"), don't use it
if (typeof(this.proxy) !== 'string') { if (typeof(this.proxy) !== 'string') {
delete this.proxy delete this.proxy;
} else { } else {
this.logger.debug( { url: this.url.href, proxy: this.proxy } this.logger.debug( {url: this.url.href, proxy: this.proxy}
, 'using proxy @{proxy} for @{url}' ) , 'using proxy @{proxy} for @{url}' );
} }
} };
// //
// Implements Storage interface // Implements Storage interface
@ -72,35 +72,35 @@ class Storage {
* @param {*} mainconfig * @param {*} mainconfig
*/ */
constructor(config, mainconfig) { constructor(config, mainconfig) {
this.config = config this.config = config;
this.failed_requests = 0 this.failed_requests = 0;
this.userAgent = mainconfig.user_agent this.userAgent = mainconfig.user_agent;
this.ca = config.ca this.ca = config.ca;
this.logger = Logger.logger.child({sub: 'out'}) this.logger = Logger.logger.child({sub: 'out'});
this.server_id = mainconfig.server_id this.server_id = mainconfig.server_id;
this.url = URL.parse(this.config.url) this.url = URL.parse(this.config.url);
_setupProxy.call(this, this.url.hostname, config, mainconfig, this.url.protocol === 'https:') _setupProxy.call(this, this.url.hostname, config, mainconfig, this.url.protocol === 'https:');
this.config.url = this.config.url.replace(/\/$/, '') this.config.url = this.config.url.replace(/\/$/, '');
if (Number(this.config.timeout) >= 1000) { if (Number(this.config.timeout) >= 1000) {
this.logger.warn([ 'Too big timeout value: ' + this.config.timeout, this.logger.warn(['Too big timeout value: ' + this.config.timeout,
'We changed time format to nginx-like one', 'We changed time format to nginx-like one',
'(see http://wiki.nginx.org/ConfigNotation)', '(see http://wiki.nginx.org/ConfigNotation)',
'so please update your config accordingly' ].join('\n')) 'so please update your config accordingly'].join('\n'));
} }
// a bunch of different configurable timers // a bunch of different configurable timers
this.maxage = parse_interval(config_get('maxage' , '2m' )) this.maxage = parse_interval(config_get('maxage', '2m' ));
this.timeout = parse_interval(config_get('timeout' , '30s')) this.timeout = parse_interval(config_get('timeout', '30s'));
this.max_fails = Number(config_get('max_fails' , 2 )) this.max_fails = Number(config_get('max_fails', 2 ));
this.fail_timeout = parse_interval(config_get('fail_timeout', '5m' )) this.fail_timeout = parse_interval(config_get('fail_timeout', '5m' ));
return this return this;
// just a helper (`config[key] || default` doesn't work because of zeroes) // just a helper (`config[key] || default` doesn't work because of zeroes)
function config_get(key, def) { function config_get(key, def) {
return config[key] != null ? config[key] : def return config[key] != null ? config[key] : def;
} }
} }
} }
@ -108,15 +108,15 @@ class Storage {
Storage.prototype.request = function(options, cb) { Storage.prototype.request = function(options, cb) {
if (!this.status_check()) { if (!this.status_check()) {
var req = new Stream.Readable() var req = new Stream.Readable();
process.nextTick(function() { process.nextTick(function() {
if (typeof(cb) === 'function') cb(Error('uplink is offline')) if (typeof(cb) === 'function') cb(Error('uplink is offline'));
req.emit('error', Error('uplink is offline')) req.emit('error', Error('uplink is offline'));
}) });
req._read = function(){} req._read = function() {};
// preventing 'Uncaught, unspecified "error" event' // preventing 'Uncaught, unspecified "error" event'
req.on('error', function(){}) req.on('error', function() {});
return req return req;
} }
var self = this var self = this
@ -129,256 +129,257 @@ Storage.prototype.request = function(options, cb) {
// add/override headers specified in the config // add/override headers specified in the config
for (let key in this.config.headers) { for (let key in this.config.headers) {
headers[key] = this.config.headers[key] headers[key] = this.config.headers[key];
} }
var method = options.method || 'GET' let method = options.method || 'GET';
var uri = options.uri_full || (this.config.url + options.uri) let uri = options.uri_full || (this.config.url + options.uri);
self.logger.info({ self.logger.info({
method : method, method: method,
headers : headers, headers: headers,
uri : uri, uri: uri,
}, "making request: '@{method} @{uri}'") }, 'making request: \'@{method} @{uri}\'');
if (Utils.is_object(options.json)) { if (Utils.is_object(options.json)) {
var json = JSON.stringify(options.json) var json = JSON.stringify(options.json);
headers['Content-Type'] = headers['Content-Type'] || 'application/json' headers['Content-Type'] = headers['Content-Type'] || 'application/json';
} }
var request_callback = cb ? (function (err, res, body) { let request_callback = cb ? (function(err, res, body) {
var error let error;
var res_length = err ? 0 : body.length let res_length = err ? 0 : body.length;
do_decode() do_decode();
do_log() do_log();
cb(err, res, body) cb(err, res, body);
function do_decode() { function do_decode() {
if (err) { if (err) {
error = err.message error = err.message;
return return;
} }
if (options.json && res.statusCode < 300) { if (options.json && res.statusCode < 300) {
try { try {
body = JSON.parse(body.toString('utf8')) body = JSON.parse(body.toString('utf8'));
} catch(_err) { } catch(_err) {
body = {} body = {};
err = _err err = _err;
error = err.message error = err.message;
} }
} }
if (!err && Utils.is_object(body)) { if (!err && Utils.is_object(body)) {
if (typeof(body.error) === 'string') { if (typeof(body.error) === 'string') {
error = body.error error = body.error;
} }
} }
} }
function do_log() { function do_log() {
var message = '@{!status}, req: \'@{request.method} @{request.url}\'' let message = '@{!status}, req: \'@{request.method} @{request.url}\'';
message += error message += error
? ', error: @{!error}' ? ', error: @{!error}'
: ', bytes: @{bytes.in}/@{bytes.out}' : ', bytes: @{bytes.in}/@{bytes.out}';
self.logger.warn({ self.logger.warn({
err : err, err: err,
request : { method: method, url: uri }, request: {method: method, url: uri},
level : 35, // http level: 35, // http
status : res != null ? res.statusCode : 'ERR', status: res != null ? res.statusCode : 'ERR',
error : error, error: error,
bytes : { bytes: {
in : json ? json.length : 0, in: json ? json.length : 0,
out : res_length || 0, out: res_length || 0,
} },
}, message) }, message);
} }
}) : undefined }) : undefined;
var req = request({ var req = request({
url : uri, url: uri,
method : method, method: method,
headers : headers, headers: headers,
body : json, body: json,
ca : this.ca, ca: this.ca,
proxy : this.proxy, proxy: this.proxy,
encoding : null, encoding: null,
gzip : true, gzip: true,
timeout : this.timeout, timeout: this.timeout,
}, request_callback) }, request_callback);
var status_called = false let status_called = false;
req.on('response', function(res) { req.on('response', function(res) {
if (!req._verdaccio_aborted && !status_called) { if (!req._verdaccio_aborted && !status_called) {
status_called = true status_called = true;
self.status_check(true) self.status_check(true);
} }
if (!request_callback) { if (!request_callback) {
;(function do_log() { (function do_log() {
var message = '@{!status}, req: \'@{request.method} @{request.url}\' (streaming)' let message = '@{!status}, req: \'@{request.method} @{request.url}\' (streaming)';
self.logger.warn({ self.logger.warn({
request : { method: method, url: uri }, request: {method: method, url: uri},
level : 35, // http level: 35, // http
status : res != null ? res.statusCode : 'ERR', status: res != null ? res.statusCode : 'ERR',
}, message) }, message);
})() })();
} }
}) });
req.on('error', function(_err) { req.on('error', function(_err) {
if (!req._verdaccio_aborted && !status_called) { if (!req._verdaccio_aborted && !status_called) {
status_called = true status_called = true;
self.status_check(false) self.status_check(false);
} }
}) });
return req return req;
} };
Storage.prototype.status_check = function(alive) { Storage.prototype.status_check = function(alive) {
if (arguments.length === 0) { if (arguments.length === 0) {
if (this.failed_requests >= this.max_fails if (this.failed_requests >= this.max_fails
&& Math.abs(Date.now() - this.last_request_time) < this.fail_timeout) { && Math.abs(Date.now() - this.last_request_time) < this.fail_timeout) {
return false return false;
} else { } else {
return true return true;
} }
} else { } else {
if (alive) { if (alive) {
if (this.failed_requests >= this.max_fails) { 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 this.failed_requests = 0;
} else { } else {
this.failed_requests++ this.failed_requests++;
if (this.failed_requests === this.max_fails) { 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() this.last_request_time = Date.now();
} }
} };
Storage.prototype.can_fetch_url = function(url) { Storage.prototype.can_fetch_url = function(url) {
url = URL.parse(url) url = URL.parse(url);
return url.protocol === this.url.protocol return url.protocol === this.url.protocol
&& url.host === this.url.host && url.host === this.url.host
&& url.path.indexOf(this.url.path) === 0 && url.path.indexOf(this.url.path) === 0;
} };
Storage.prototype.get_package = function(name, options, callback) { Storage.prototype.get_package = function(name, options, callback) {
if (typeof(options) === 'function') callback = options, options = {} if (typeof(options) === 'function') callback = options, options = {};
var headers = {} let headers = {};
if (options.etag) { if (options.etag) {
headers['If-None-Match'] = options.etag headers['If-None-Match'] = options.etag;
headers['Accept'] = 'application/octet-stream' headers['Accept'] = 'application/octet-stream';
} }
this.request({ this.request({
uri : '/' + encode(name), uri: '/' + encode(name),
json : true, json: true,
headers : headers, headers: headers,
req : options.req, req: options.req,
}, function(err, res, body) { }, function(err, res, body) {
if (err) return callback(err) if (err) return callback(err);
if (res.statusCode === 404) { if (res.statusCode === 404) {
return callback( Error[404]("package doesn't exist on uplink") ) return callback( Error[404]('package doesn\'t exist on uplink') );
} }
if (!(res.statusCode >= 200 && res.statusCode < 300)) { if (!(res.statusCode >= 200 && res.statusCode < 300)) {
var error = Error('bad status code: ' + res.statusCode) let error = Error('bad status code: ' + res.statusCode);
error.remoteStatus = res.statusCode error.remoteStatus = res.statusCode;
return callback(error) return callback(error);
} }
callback(null, body, res.headers.etag) callback(null, body, res.headers.etag);
}) });
} };
Storage.prototype.get_tarball = function(name, options, filename) { Storage.prototype.get_tarball = function(name, options, filename) {
if (!options) options = {} if (!options) options = {};
return this.get_url(this.config.url + '/' + name + '/-/' + filename) return this.get_url(this.config.url + '/' + name + '/-/' + filename);
} };
Storage.prototype.get_url = function(url) { Storage.prototype.get_url = function(url) {
var stream = MyStreams.ReadTarballStream() let stream = MyStreams.ReadTarballStream();
stream.abort = function() {} stream.abort = function() {};
var current_length = 0, expected_length let current_length = 0, expected_length;
var rstream = this.request({ let rstream = this.request({
uri_full: url, uri_full: url,
encoding: null, encoding: null,
headers: { Accept: 'application/octet-stream' }, headers: {Accept: 'application/octet-stream'},
}) });
rstream.on('response', function(res) { rstream.on('response', function(res) {
if (res.statusCode === 404) { if (res.statusCode === 404) {
return stream.emit('error', Error[404]("file doesn't exist on uplink")) return stream.emit('error', Error[404]('file doesn\'t exist on uplink'));
} }
if (!(res.statusCode >= 200 && res.statusCode < 300)) { if (!(res.statusCode >= 200 && res.statusCode < 300)) {
return stream.emit('error', Error('bad uplink status code: ' + res.statusCode)) return stream.emit('error', Error('bad uplink status code: ' + res.statusCode));
} }
if (res.headers['content-length']) { if (res.headers['content-length']) {
expected_length = res.headers['content-length'] expected_length = res.headers['content-length'];
stream.emit('content-length', res.headers['content-length']) stream.emit('content-length', res.headers['content-length']);
} }
rstream.pipe(stream) rstream.pipe(stream);
}) });
rstream.on('error', function(err) { rstream.on('error', function(err) {
stream.emit('error', err) stream.emit('error', err);
}) });
rstream.on('data', function(d) { rstream.on('data', function(d) {
current_length += d.length current_length += d.length;
}) });
rstream.on('end', function(d) { rstream.on('end', function(d) {
if (d) current_length += d.length if (d) current_length += d.length;
if (expected_length && current_length != expected_length) if (expected_length && current_length != expected_length)
stream.emit('error', Error('content length mismatch')) stream.emit('error', Error('content length mismatch'));
}) });
return stream return stream;
} };
Storage.prototype.search = function(startkey, options) { Storage.prototype.search = function(startkey, options) {
var self = this let self = this;
var stream = new Stream.PassThrough({ objectMode: true }) let stream = new Stream.PassThrough({objectMode: true});
var req = self.request({ var req = self.request({
uri: options.req.url, uri: options.req.url,
req: options.req, req: options.req,
headers: { headers: {
referer: options.req.headers.referer referer: options.req.headers.referer,
} },
}) });
req.on('response', function (res) { req.on('response', (res) => {
if (!String(res.statusCode).match(/^2\d\d$/)) { if (!String(res.statusCode).match(/^2\d\d$/)) {
return stream.emit('error', Error('bad status code ' + res.statusCode + ' from uplink')) return stream.emit('error', Error('bad status code ' + res.statusCode + ' from uplink'));
} }
res.pipe(JSONStream.parse('*')).on('data', function (pkg) { res.pipe(JSONStream.parse('*')).on('data', (pkg) => {
if (Utils.is_object(pkg)) { if (Utils.is_object(pkg)) {
stream.emit('data', pkg) stream.emit('data', pkg);
} }
}) });
res.on('end', function () { res.on('end', () => {
stream.emit('end') stream.emit('end');
}) });
}) });
req.on('error', function (err) { req.on('error', (err) => {
stream.emit('error', err) stream.emit('error', err);
}) });
stream.abort = function () { stream.abort = () => {
req.abort() req.abort();
stream.emit('end') stream.emit('end');
} };
return stream
} return stream;
};
Storage.prototype._add_proxy_headers = function(req, headers) { Storage.prototype._add_proxy_headers = function(req, headers) {
if (req) { if (req) {
@ -393,7 +394,7 @@ Storage.prototype._add_proxy_headers = function(req, headers) {
req && req.headers['x-forwarded-for'] req && req.headers['x-forwarded-for']
? req.headers['x-forwarded-for'] + ', ' ? req.headers['x-forwarded-for'] + ', '
: '' : ''
) + req.connection.remoteAddress ) + req.connection.remoteAddress;
} }
} }
@ -401,10 +402,10 @@ Storage.prototype._add_proxy_headers = function(req, headers) {
headers['Via'] = headers['Via'] =
req && req.headers['via'] req && req.headers['via']
? req.headers['via'] + ', ' ? req.headers['via'] + ', '
: '' : '';
headers['Via'] += '1.1 ' + this.server_id + ' (Verdaccio)' headers['Via'] += '1.1 ' + this.server_id + ' (Verdaccio)';
} };
module.exports = Storage; module.exports = Storage;

View File

@ -1,25 +1,27 @@
var assert = require('assert') 'use strict';
var Semver = require('semver')
var URL = require('url') let assert = require('assert');
var Logger = require('./logger') let Semver = require('semver');
let URL = require('url');
let Logger = require('./logger');
module.exports.validate_package = function(name) { module.exports.validate_package = function(name) {
name = name.split('/', 2) name = name.split('/', 2);
if (name.length === 1) { if (name.length === 1) {
// normal package // normal package
return module.exports.validate_name(name[0]) return module.exports.validate_name(name[0]);
} else { } else {
// scoped package // scoped package
return name[0][0] === '@' return name[0][0] === '@'
&& module.exports.validate_name(name[0].slice(1)) && module.exports.validate_name(name[0].slice(1))
&& module.exports.validate_name(name[1]) && module.exports.validate_name(name[1]);
} }
} };
// from normalize-package-data/lib/fixer.js // from normalize-package-data/lib/fixer.js
module.exports.validate_name = function(name) { module.exports.validate_name = function(name) {
if (typeof(name) !== 'string') return false if (typeof(name) !== 'string') return false;
name = name.toLowerCase() name = name.toLowerCase();
// all URL-safe characters and "@" for issue #75 // all URL-safe characters and "@" for issue #75
if (!name.match(/^[-a-zA-Z0-9_.!~*'()@]+$/) if (!name.match(/^[-a-zA-Z0-9_.!~*'()@]+$/)
@ -30,88 +32,88 @@ module.exports.validate_name = function(name) {
|| name === 'package.json' || name === 'package.json'
|| name === 'favicon.ico' || name === 'favicon.ico'
) { ) {
return false return false;
} else { } else {
return true return true;
} }
} };
module.exports.is_object = function(obj) { module.exports.is_object = function(obj) {
return typeof(obj) === 'object' && obj !== null && !Array.isArray(obj) return typeof(obj) === 'object' && obj !== null && !Array.isArray(obj);
} };
module.exports.validate_metadata = function(object, name) { module.exports.validate_metadata = function(object, name) {
assert(module.exports.is_object(object), 'not a json object') assert(module.exports.is_object(object), 'not a json object');
assert.equal(object.name, name) assert.equal(object.name, name);
if (!module.exports.is_object(object['dist-tags'])) { if (!module.exports.is_object(object['dist-tags'])) {
object['dist-tags'] = {} object['dist-tags'] = {};
} }
if (!module.exports.is_object(object['versions'])) { if (!module.exports.is_object(object['versions'])) {
object['versions'] = {} object['versions'] = {};
} }
return object; return object;
} };
module.exports.filter_tarball_urls = function(pkg, req, config) { module.exports.filter_tarball_urls = function(pkg, req, config) {
function filter(_url) { function filter(_url) {
if (!req.headers.host) return _url if (!req.headers.host) return _url;
var filename = URL.parse(_url).pathname.replace(/^.*\//, '') let filename = URL.parse(_url).pathname.replace(/^.*\//, '');
if (config.url_prefix != null) { if (config.url_prefix != null) {
var result = config.url_prefix.replace(/\/$/, '') var result = config.url_prefix.replace(/\/$/, '');
} else { } else {
var result = req.protocol + '://' + req.headers.host var result = req.protocol + '://' + req.headers.host;
} }
return `${result}/${pkg.name.replace(/\//g, '%2f')}/-/${filename}`; return `${result}/${pkg.name.replace(/\//g, '%2f')}/-/${filename}`;
} }
for (var ver in pkg.versions) { for (let ver in pkg.versions) {
var dist = pkg.versions[ver].dist let dist = pkg.versions[ver].dist;
if (dist != null && dist.tarball != null) { if (dist != null && dist.tarball != null) {
//dist.__verdaccio_orig_tarball = dist.tarball // dist.__verdaccio_orig_tarball = dist.tarball
dist.tarball = filter(dist.tarball) dist.tarball = filter(dist.tarball);
} }
} }
return pkg return pkg;
} };
module.exports.tag_version = function(data, version, tag) { module.exports.tag_version = function(data, version, tag) {
if (tag) { if (tag) {
if (data['dist-tags'][tag] !== version) { if (data['dist-tags'][tag] !== version) {
if (Semver.parse(version, true)) { if (Semver.parse(version, true)) {
// valid version - store // valid version - store
data['dist-tags'][tag] = version data['dist-tags'][tag] = version;
return true return true;
} }
} }
Logger.logger.warn({ver: version, tag: tag}, 'ignoring bad version @{ver} in @{tag}') Logger.logger.warn({ver: version, tag: tag}, 'ignoring bad version @{ver} in @{tag}');
if (tag && data['dist-tags'][tag]) { if (tag && data['dist-tags'][tag]) {
delete data['dist-tags'][tag] delete data['dist-tags'][tag];
} }
} }
return false return false;
} };
// gets version from a package object taking into account semver weirdness // gets version from a package object taking into account semver weirdness
module.exports.get_version = function(object, version) { module.exports.get_version = function(object, version) {
if (object.versions[version] != null) return object.versions[version] if (object.versions[version] != null) return object.versions[version];
try { try {
version = Semver.parse(version, true) version = Semver.parse(version, true);
for (var k in object.versions) { for (let k in object.versions) {
if (version.compare(Semver.parse(k, true)) === 0) { if (version.compare(Semver.parse(k, true)) === 0) {
return object.versions[k] return object.versions[k];
} }
} }
} catch (err) { } catch (err) {
return undefined return undefined;
} }
} };
module.exports.parse_address = function parse_address(addr) { module.exports.parse_address = function parse_address(addr) {
// //
@ -129,68 +131,67 @@ module.exports.parse_address = function parse_address(addr) {
// TODO: refactor it to something more reasonable? // TODO: refactor it to something more reasonable?
// //
// protocol : // ( host )|( ipv6 ): port / // protocol : // ( host )|( ipv6 ): port /
var m = /^((https?):(\/\/)?)?((([^\/:]*)|\[([^\[\]]+)\]):)?(\d+)\/?$/.exec(addr) var m = /^((https?):(\/\/)?)?((([^\/:]*)|\[([^\[\]]+)\]):)?(\d+)\/?$/.exec(addr);
if (m) return { if (m) return {
proto: m[2] || 'http', proto: m[2] || 'http',
host: m[6] || m[7] || 'localhost', host: m[6] || m[7] || 'localhost',
port: m[8] || '4873', port: m[8] || '4873',
} };
var m = /^((https?):(\/\/)?)?unix:(.*)$/.exec(addr) var m = /^((https?):(\/\/)?)?unix:(.*)$/.exec(addr);
if (m) return { if (m) return {
proto: m[2] || 'http', proto: m[2] || 'http',
path: m[4], path: m[4],
} };
return null return null;
} };
// function filters out bad semver versions and sorts the array // function filters out bad semver versions and sorts the array
module.exports.semver_sort = function semver_sort(array) { module.exports.semver_sort = function semver_sort(array) {
return array return array
.filter(function(x) { .filter(function(x) {
if (!Semver.parse(x, true)) { if (!Semver.parse(x, true)) {
Logger.logger.warn( {ver: x}, 'ignoring bad version @{ver}' ) Logger.logger.warn( {ver: x}, 'ignoring bad version @{ver}' );
return false return false;
} }
return true return true;
}) })
.sort(Semver.compareLoose) .sort(Semver.compareLoose)
.map(String) .map(String);
} };
// flatten arrays of tags // flatten arrays of tags
module.exports.normalize_dist_tags = function (data) { module.exports.normalize_dist_tags = function(data) {
var sorted let sorted;
if (!data['dist-tags'].latest) { if (!data['dist-tags'].latest) {
// overwrite latest with highest known version based on semver sort // overwrite latest with highest known version based on semver sort
sorted = module.exports.semver_sort(Object.keys(data.versions)) sorted = module.exports.semver_sort(Object.keys(data.versions));
if (sorted && sorted.length) { if (sorted && sorted.length) {
data['dist-tags'].latest = sorted.pop() data['dist-tags'].latest = sorted.pop();
} }
} }
for (var tag in data['dist-tags']) { for (let tag in data['dist-tags']) {
if (Array.isArray(data['dist-tags'][tag])) { if (Array.isArray(data['dist-tags'][tag])) {
if (data['dist-tags'][tag].length) { if (data['dist-tags'][tag].length) {
// sort array // sort array
sorted = module.exports.semver_sort(data['dist-tags'][tag]) sorted = module.exports.semver_sort(data['dist-tags'][tag]);
if (sorted.length) { if (sorted.length) {
// use highest version based on semver sort // use highest version based on semver sort
data['dist-tags'][tag] = sorted.pop() data['dist-tags'][tag] = sorted.pop();
} }
} else { } else {
delete data['dist-tags'][tag] delete data['dist-tags'][tag];
} }
} else if (typeof data['dist-tags'][tag] === 'string') { } else if (typeof data['dist-tags'][tag] === 'string') {
if (!Semver.parse(data['dist-tags'][tag], true)) { if (!Semver.parse(data['dist-tags'][tag], true)) {
// if the version is invalid, delete the dist-tag entry // if the version is invalid, delete the dist-tag entry
delete data['dist-tags'][tag] delete data['dist-tags'][tag];
} }
} }
} }
} };

View File

@ -20,6 +20,7 @@
"async": "^2.0.1", "async": "^2.0.1",
"body-parser": "^1.15.0", "body-parser": "^1.15.0",
"bunyan": "^1.8.0", "bunyan": "^1.8.0",
"chalk": "^1.1.3",
"commander": "^2.9.0", "commander": "^2.9.0",
"compression": "^1.6.1", "compression": "^1.6.1",
"cookies": "^0.6.1", "cookies": "^0.6.1",
@ -44,6 +45,7 @@
"browserify": "^13.0.0", "browserify": "^13.0.0",
"browserify-handlebars": "^1.0.0", "browserify-handlebars": "^1.0.0",
"eslint": "^3.19.0", "eslint": "^3.19.0",
"eslint-config-google": "^0.7.1",
"grunt": "^1.0.1", "grunt": "^1.0.1",
"grunt-browserify": "^5.0.0", "grunt-browserify": "^5.0.0",
"grunt-cli": "^1.2.0", "grunt-cli": "^1.2.0",
@ -66,9 +68,9 @@
"server" "server"
], ],
"scripts": { "scripts": {
"test": "eslint . && mocha ./test/functional ./test/unit", "test": "npm run lint && mocha ./test/functional ./test/unit",
"test:coverage": "nyc --reporter=html --reporter=text mocha -R spec ./test/functional ./test/unit", "test:coverage": "nyc --reporter=html --reporter=text mocha -R spec ./test/functional ./test/unit",
"test-travis": "eslint . && npm run test:coverage", "test-travis": "npm run lint && npm run test:coverage",
"test-only": "mocha ./test/functional ./test/unit", "test-only": "mocha ./test/functional ./test/unit",
"lint": "eslint .", "lint": "eslint .",
"build-docker": "docker build -t verdaccio .", "build-docker": "docker build -t verdaccio .",

View File

@ -1,5 +1,12 @@
# vim: syntax=yaml
extends: ["eslint:recommended"]
env: env:
node: true node: true
mocha: true mocha: true
es6: true
valid-jsdoc: 0
no-redeclare: 1
no-console: 1

View File

@ -18,12 +18,12 @@ Plugin.prototype.allow_access = function(user, pkg, cb) {
return cb(null, false); return cb(null, false);
} }
if (user.name !== self._config.allow_user) { if (user.name !== self._config.allow_user) {
var err = Error('i don\'t know anything about you'); let err = Error('i don\'t know anything about you');
err.status = 403; err.status = 403;
return cb(err); return cb(err);
} }
if (pkg.name !== self._config.to_access) { if (pkg.name !== self._config.to_access) {
var err = Error('you\'re not allowed here'); let err = Error('you\'re not allowed here');
err.status = 403; err.status = 403;
return cb(err); return cb(err);
} }

View File

@ -59,7 +59,7 @@ module.exports = function() {
it('uploading 10 diff versions', function(callback) { it('uploading 10 diff versions', function(callback) {
let fns = []; let fns = [];
for (let i=0; i<10; i++) { for (let i=0; i<10; i++) {
;(function(i) { (function(i) {
fns.push(function(cb_) { fns.push(function(cb_) {
let _res; let _res;
server.put_version('race', '0.1.'+String(i), require('./lib/package')('race')) server.put_version('race', '0.1.'+String(i), require('./lib/package')('race'))

View File

@ -22,13 +22,13 @@ describe('Use proxy', function() {
}); });
it('no_proxy is invalid', function() { it('no_proxy is invalid', function() {
var x = setup('http://x/x', {http_proxy: '123', no_proxy: false}, {}); let x = setup('http://x/x', {http_proxy: '123', no_proxy: false}, {});
assert.equal(x.proxy, '123'); assert.equal(x.proxy, '123');
var x = setup('http://x/x', {http_proxy: '123', no_proxy: null}, {}); x = setup('http://x/x', {http_proxy: '123', no_proxy: null}, {});
assert.equal(x.proxy, '123'); assert.equal(x.proxy, '123');
var x = setup('http://x/x', {http_proxy: '123', no_proxy: []}, {}); x = setup('http://x/x', {http_proxy: '123', no_proxy: []}, {});
assert.equal(x.proxy, '123'); assert.equal(x.proxy, '123');
var x = setup('http://x/x', {http_proxy: '123', no_proxy: ''}, {}); x = setup('http://x/x', {http_proxy: '123', no_proxy: ''}, {});
assert.equal(x.proxy, '123'); assert.equal(x.proxy, '123');
}); });
@ -43,48 +43,48 @@ describe('Use proxy', function() {
}); });
it('no_proxy - various, single string', function() { it('no_proxy - various, single string', function() {
var x = setup('http://blahblah', {http_proxy: '123'}, {no_proxy: 'blah'}); let x = setup('http://blahblah', {http_proxy: '123'}, {no_proxy: 'blah'});
assert.equal(x.proxy, '123'); assert.equal(x.proxy, '123');
var x = setup('http://blah.blah', {}, {http_proxy: '123', no_proxy: 'blah'}); x = setup('http://blah.blah', {}, {http_proxy: '123', no_proxy: 'blah'});
assert.equal(x.proxy, null); assert.equal(x.proxy, null);
var x = setup('http://blahblah', {}, {http_proxy: '123', no_proxy: '.blah'}); x = setup('http://blahblah', {}, {http_proxy: '123', no_proxy: '.blah'});
assert.equal(x.proxy, '123'); assert.equal(x.proxy, '123');
var x = setup('http://blah.blah', {http_proxy: '123', no_proxy: '.blah'}, {}); x = setup('http://blah.blah', {http_proxy: '123', no_proxy: '.blah'}, {});
assert.equal(x.proxy, null); assert.equal(x.proxy, null);
var x = setup('http://blah', {http_proxy: '123', no_proxy: '.blah'}, {}); x = setup('http://blah', {http_proxy: '123', no_proxy: '.blah'}, {});
assert.equal(x.proxy, null); assert.equal(x.proxy, null);
var x = setup('http://blahh', {http_proxy: '123', no_proxy: 'blah'}, {}); x = setup('http://blahh', {http_proxy: '123', no_proxy: 'blah'}, {});
assert.equal(x.proxy, '123'); assert.equal(x.proxy, '123');
}); });
it('no_proxy - various, array', function() { it('no_proxy - various, array', function() {
var x = setup('http://blahblah', {http_proxy: '123'}, {no_proxy: 'foo,bar,blah'}); let x = setup('http://blahblah', {http_proxy: '123'}, {no_proxy: 'foo,bar,blah'});
assert.equal(x.proxy, '123'); assert.equal(x.proxy, '123');
var x = setup('http://blah.blah', {http_proxy: '123'}, {no_proxy: 'foo,bar,blah'}); x = setup('http://blah.blah', {http_proxy: '123'}, {no_proxy: 'foo,bar,blah'});
assert.equal(x.proxy, null); assert.equal(x.proxy, null);
var x = setup('http://blah.foo', {http_proxy: '123'}, {no_proxy: 'foo,bar,blah'}); x = setup('http://blah.foo', {http_proxy: '123'}, {no_proxy: 'foo,bar,blah'});
assert.equal(x.proxy, null); assert.equal(x.proxy, null);
var x = setup('http://foo.baz', {http_proxy: '123'}, {no_proxy: 'foo,bar,blah'}); x = setup('http://foo.baz', {http_proxy: '123'}, {no_proxy: 'foo,bar,blah'});
assert.equal(x.proxy, '123'); assert.equal(x.proxy, '123');
var x = setup('http://blahblah', {http_proxy: '123'}, {no_proxy: ['foo', 'bar', 'blah']}); x = setup('http://blahblah', {http_proxy: '123'}, {no_proxy: ['foo', 'bar', 'blah']});
assert.equal(x.proxy, '123'); assert.equal(x.proxy, '123');
var x = setup('http://blah.blah', {http_proxy: '123'}, {no_proxy: ['foo', 'bar', 'blah']}); x = setup('http://blah.blah', {http_proxy: '123'}, {no_proxy: ['foo', 'bar', 'blah']});
assert.equal(x.proxy, null); assert.equal(x.proxy, null);
}); });
it('no_proxy - hostport', function() { it('no_proxy - hostport', function() {
var x = setup('http://localhost:80', {http_proxy: '123'}, {no_proxy: 'localhost'}); let x = setup('http://localhost:80', {http_proxy: '123'}, {no_proxy: 'localhost'});
assert.equal(x.proxy, null); assert.equal(x.proxy, null);
var x = setup('http://localhost:8080', {http_proxy: '123'}, {no_proxy: 'localhost'}); x = setup('http://localhost:8080', {http_proxy: '123'}, {no_proxy: 'localhost'});
assert.equal(x.proxy, null); assert.equal(x.proxy, null);
}); });
it('no_proxy - secure', function() { it('no_proxy - secure', function() {
var x = setup('https://something', {http_proxy: '123'}, {}); let x = setup('https://something', {http_proxy: '123'}, {});
assert.equal(x.proxy, null); assert.equal(x.proxy, null);
var x = setup('https://something', {https_proxy: '123'}, {}); x = setup('https://something', {https_proxy: '123'}, {});
assert.equal(x.proxy, '123'); assert.equal(x.proxy, '123');
var x = setup('https://something', {http_proxy: '456', https_proxy: '123'}, {}); x = setup('https://something', {http_proxy: '456', https_proxy: '123'}, {});
assert.equal(x.proxy, '123'); assert.equal(x.proxy, '123');
}); });
}); });

View File

@ -56,10 +56,10 @@ describe('search', function() {
}, },
}; };
Search.add(item); Search.add(item);
var result = Search.query('test6'); let result = Search.query('test6');
assert(result.length === 1); assert(result.length === 1);
Search.remove(item.name); Search.remove(item.name);
var result = Search.query('test6'); result = Search.query('test6');
assert(result.length === 0); assert(result.length === 0);
}); });
}); });

View File

@ -23,9 +23,10 @@ function test(file) {
var t; var t;
inner.split('/').forEach(function(x) { inner.split('/').forEach(function(x) {
t = x.match(/^:([^?:]*)\??$/);
if (m[1] === 'param') { if (m[1] === 'param') {
params[x] = 'ok'; params[x] = 'ok';
} else if (t = x.match(/^:([^?:]*)\??$/)) { } else if (t) {
params[t[1]] = params[t[1]] || m[0].trim(); params[t[1]] = params[t[1]] || m[0].trim();
} }
}); });