2017-04-23 20:02:26 +02:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
let crypto = require('crypto');
|
|
|
|
let Error = require('http-errors');
|
|
|
|
let utils = require('./utils');
|
|
|
|
let Logger = require('./logger');
|
2013-06-08 03:16:28 +02:00
|
|
|
|
2014-11-13 18:13:37 +01:00
|
|
|
module.exports.match = function match(regexp) {
|
|
|
|
return function(req, res, next, value, name) {
|
|
|
|
if (regexp.exec(value)) {
|
2017-04-23 20:02:26 +02:00
|
|
|
next();
|
2014-11-13 18:13:37 +01:00
|
|
|
} else {
|
2017-04-23 20:02:26 +02:00
|
|
|
next('route');
|
2014-11-13 18:13:37 +01:00
|
|
|
}
|
2017-04-23 20:02:26 +02:00
|
|
|
};
|
|
|
|
};
|
2014-11-13 18:13:37 +01:00
|
|
|
|
2013-06-08 03:16:28 +02:00
|
|
|
module.exports.validate_name = function validate_name(req, res, next, value, name) {
|
2014-11-12 12:14:37 +01:00
|
|
|
if (value.charAt(0) === '-') {
|
|
|
|
// special case in couchdb usually
|
2017-04-23 20:02:26 +02:00
|
|
|
next('route');
|
2014-11-12 12:14:37 +01:00
|
|
|
} else if (utils.validate_name(value)) {
|
2017-04-23 20:02:26 +02:00
|
|
|
next();
|
2014-11-12 12:14:37 +01:00
|
|
|
} else {
|
2017-04-23 20:02:26 +02:00
|
|
|
next( Error[403]('invalid ' + name) );
|
2014-11-12 12:14:37 +01:00
|
|
|
}
|
2017-04-23 20:02:26 +02:00
|
|
|
};
|
2013-06-08 03:16:28 +02:00
|
|
|
|
2014-11-16 18:44:46 +01:00
|
|
|
module.exports.validate_package = function validate_package(req, res, next, value, name) {
|
|
|
|
if (value.charAt(0) === '-') {
|
|
|
|
// special case in couchdb usually
|
2017-04-23 20:02:26 +02:00
|
|
|
next('route');
|
2014-11-16 18:44:46 +01:00
|
|
|
} else if (utils.validate_package(value)) {
|
2017-04-23 20:02:26 +02:00
|
|
|
next();
|
2014-11-16 18:44:46 +01:00
|
|
|
} else {
|
2017-04-23 20:02:26 +02:00
|
|
|
next( Error[403]('invalid ' + name) );
|
2014-11-16 18:44:46 +01:00
|
|
|
}
|
2017-04-23 20:02:26 +02:00
|
|
|
};
|
2014-11-16 18:44:46 +01:00
|
|
|
|
2013-06-08 03:16:28 +02:00
|
|
|
module.exports.media = function media(expect) {
|
2014-11-12 12:14:37 +01:00
|
|
|
return function(req, res, next) {
|
|
|
|
if (req.headers['content-type'] !== expect) {
|
|
|
|
next( Error[415]('wrong content-type, expect: ' + expect
|
2017-04-23 20:02:26 +02:00
|
|
|
+ ', got: '+req.headers['content-type']) );
|
2014-11-12 12:14:37 +01:00
|
|
|
} else {
|
2017-04-23 20:02:26 +02:00
|
|
|
next();
|
2014-11-12 12:14:37 +01:00
|
|
|
}
|
2017-04-23 20:02:26 +02:00
|
|
|
};
|
|
|
|
};
|
2013-06-08 03:16:28 +02:00
|
|
|
|
|
|
|
module.exports.expect_json = function expect_json(req, res, next) {
|
2014-11-12 12:14:37 +01:00
|
|
|
if (!utils.is_object(req.body)) {
|
2017-04-23 20:02:26 +02:00
|
|
|
return next( Error[400]('can\'t parse incoming json') );
|
2014-11-12 12:14:37 +01:00
|
|
|
}
|
2017-04-23 20:02:26 +02:00
|
|
|
next();
|
|
|
|
};
|
2013-06-08 03:16:28 +02:00
|
|
|
|
2013-12-09 04:59:31 +01:00
|
|
|
module.exports.anti_loop = function(config) {
|
2014-11-12 12:14:37 +01:00
|
|
|
return function(req, res, next) {
|
|
|
|
if (req.headers.via != null) {
|
2017-04-23 20:02:26 +02:00
|
|
|
let arr = req.headers.via.split(',');
|
2014-11-12 12:14:37 +01:00
|
|
|
|
2017-04-23 20:02:26 +02:00
|
|
|
for (let i=0; i<arr.length; i++) {
|
|
|
|
let m = arr[i].match(/\s*(\S+)\s+(\S+)/);
|
2014-11-12 12:14:37 +01:00
|
|
|
if (m && m[2] === config.server_id) {
|
2017-04-23 20:02:26 +02:00
|
|
|
return next( Error[508]('loop detected') );
|
2014-11-12 12:14:37 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-04-23 20:02:26 +02:00
|
|
|
next();
|
|
|
|
};
|
|
|
|
};
|
2013-06-08 03:16:28 +02:00
|
|
|
|
2013-07-03 03:49:24 +02:00
|
|
|
// express doesn't do etags with requests <= 1024b
|
|
|
|
// we use md5 here, it works well on 1k+ bytes, but sucks with fewer data
|
|
|
|
// could improve performance using crc32 after benchmarks
|
|
|
|
function md5sum(data) {
|
2017-04-23 20:02:26 +02:00
|
|
|
return crypto.createHash('md5').update(data).digest('hex');
|
2013-07-03 03:49:24 +02:00
|
|
|
}
|
|
|
|
|
2015-04-08 22:54:59 +02:00
|
|
|
module.exports.allow = function(auth) {
|
2014-11-13 19:32:31 +01:00
|
|
|
return function(action) {
|
|
|
|
return function(req, res, next) {
|
2015-02-24 20:28:16 +01:00
|
|
|
req.pause();
|
2017-04-22 10:33:56 +02:00
|
|
|
auth['allow_'+action](req.params.package, req.remote_user, function(error, allowed) {
|
2015-02-24 20:28:16 +01:00
|
|
|
req.resume();
|
2015-04-08 22:54:59 +02:00
|
|
|
if (error) {
|
2017-04-23 20:02:26 +02:00
|
|
|
next(error);
|
2017-04-22 10:33:56 +02:00
|
|
|
} else if (allowed) {
|
2017-04-23 20:02:26 +02:00
|
|
|
next();
|
2015-02-24 20:28:16 +01:00
|
|
|
} else {
|
2015-04-08 22:54:59 +02:00
|
|
|
// last plugin (that's our built-in one) returns either
|
|
|
|
// cb(err) or cb(null, true), so this should never happen
|
2017-04-23 20:02:26 +02:00
|
|
|
throw Error('bug in the auth plugin system');
|
2014-11-13 19:32:31 +01:00
|
|
|
}
|
2017-04-23 20:02:26 +02:00
|
|
|
});
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
2014-11-13 19:32:31 +01:00
|
|
|
|
|
|
|
module.exports.final = function(body, req, res, next) {
|
2014-11-16 13:37:50 +01:00
|
|
|
if (res.statusCode === 401 && !res.getHeader('WWW-Authenticate')) {
|
|
|
|
// they say it's required for 401, so...
|
2017-04-23 20:02:26 +02:00
|
|
|
res.header('WWW-Authenticate', 'Basic, Bearer');
|
2014-11-16 13:37:50 +01:00
|
|
|
}
|
|
|
|
|
2014-11-13 19:32:31 +01:00
|
|
|
try {
|
|
|
|
if (typeof(body) === 'string' || typeof(body) === 'object') {
|
|
|
|
if (!res.getHeader('Content-type')) {
|
2017-04-23 20:02:26 +02:00
|
|
|
res.header('Content-type', 'application/json');
|
2014-11-13 19:32:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof(body) === 'object' && body != null) {
|
|
|
|
if (typeof(body.error) === 'string') {
|
2017-04-23 20:02:26 +02:00
|
|
|
res._verdaccio_error = body.error;
|
2014-11-13 19:32:31 +01:00
|
|
|
}
|
2017-04-23 20:02:26 +02:00
|
|
|
body = JSON.stringify(body, undefined, ' ') + '\n';
|
2014-11-13 19:32:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// don't send etags with errors
|
|
|
|
if (!res.statusCode || (res.statusCode >= 200 && res.statusCode < 300)) {
|
2017-04-23 20:02:26 +02:00
|
|
|
res.header('ETag', '"' + md5sum(body) + '"');
|
2014-11-13 19:32:31 +01:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// send(null), send(204), etc.
|
|
|
|
}
|
|
|
|
} catch(err) {
|
2016-11-07 18:15:38 +01:00
|
|
|
// if verdaccio sends headers first, and then calls res.send()
|
2014-11-13 19:32:31 +01:00
|
|
|
// as an error handler, we can't report error properly,
|
|
|
|
// and should just close socket
|
|
|
|
if (err.message.match(/set headers after they are sent/)) {
|
2017-04-23 20:02:26 +02:00
|
|
|
if (res.socket != null) res.socket.destroy();
|
|
|
|
return;
|
2014-11-13 19:32:31 +01:00
|
|
|
} else {
|
2017-04-23 20:02:26 +02:00
|
|
|
throw err;
|
2014-11-13 19:32:31 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-23 20:02:26 +02:00
|
|
|
res.send(body);
|
|
|
|
};
|
2014-11-13 19:32:31 +01:00
|
|
|
|
|
|
|
module.exports.log = function(req, res, next) {
|
2014-11-12 12:14:37 +01:00
|
|
|
// logger
|
2017-04-23 20:02:26 +02:00
|
|
|
req.log = Logger.logger.child({sub: 'in'});
|
2014-11-12 12:14:37 +01:00
|
|
|
|
2017-04-23 20:02:26 +02:00
|
|
|
let _auth = req.headers.authorization;
|
|
|
|
if (_auth != null) req.headers.authorization = '<Classified>';
|
|
|
|
let _cookie = req.headers.cookie;
|
|
|
|
if (_cookie != null) req.headers.cookie = '<Classified>';
|
2014-11-12 17:18:30 +01:00
|
|
|
|
2017-04-23 20:02:26 +02:00
|
|
|
req.url = req.originalUrl;
|
|
|
|
req.log.info( {req: req, ip: req.ip}
|
|
|
|
, '@{ip} requested \'@{req.method} @{req.url}\'' );
|
|
|
|
req.originalUrl = req.url;
|
2014-11-12 17:18:30 +01:00
|
|
|
|
2017-04-23 20:02:26 +02:00
|
|
|
if (_auth != null) req.headers.authorization = _auth;
|
|
|
|
if (_cookie != null) req.headers.cookie = _cookie;
|
2014-11-12 12:14:37 +01:00
|
|
|
|
2017-04-23 20:02:26 +02:00
|
|
|
let bytesin = 0;
|
2014-11-12 12:14:37 +01:00
|
|
|
req.on('data', function(chunk) {
|
2017-04-23 20:02:26 +02:00
|
|
|
bytesin += chunk.length;
|
|
|
|
});
|
2014-11-12 12:14:37 +01:00
|
|
|
|
2017-04-23 20:02:26 +02:00
|
|
|
let bytesout = 0;
|
|
|
|
let _write = res.write;
|
2014-11-12 12:14:37 +01:00
|
|
|
res.write = function(buf) {
|
2017-04-23 20:02:26 +02:00
|
|
|
bytesout += buf.length;
|
|
|
|
_write.apply(res, arguments);
|
|
|
|
};
|
2014-11-12 12:14:37 +01:00
|
|
|
|
|
|
|
function log() {
|
2017-04-23 20:02:26 +02:00
|
|
|
let message = '@{status}, user: @{user}, req: \'@{request.method} @{request.url}\'';
|
2016-11-07 18:15:38 +01:00
|
|
|
if (res._verdaccio_error) {
|
2017-04-23 20:02:26 +02:00
|
|
|
message += ', error: @{!error}';
|
2014-11-12 12:14:37 +01:00
|
|
|
} else {
|
2017-04-23 20:02:26 +02:00
|
|
|
message += ', bytes: @{bytes.in}/@{bytes.out}';
|
2014-11-12 12:14:37 +01:00
|
|
|
}
|
2014-11-12 17:18:30 +01:00
|
|
|
|
2017-04-23 20:02:26 +02:00
|
|
|
req.url = req.originalUrl;
|
2014-11-12 12:14:37 +01:00
|
|
|
req.log.warn({
|
2017-04-23 20:02:26 +02:00
|
|
|
request: {method: req.method, url: req.url},
|
|
|
|
level: 35, // http
|
|
|
|
user: req.remote_user && req.remote_user.name,
|
|
|
|
status: res.statusCode,
|
|
|
|
error: res._verdaccio_error,
|
|
|
|
bytes: {
|
|
|
|
in: bytesin,
|
|
|
|
out: bytesout,
|
|
|
|
},
|
|
|
|
}, message);
|
|
|
|
req.originalUrl = req.url;
|
2014-11-12 12:14:37 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
req.on('close', function() {
|
2017-04-23 20:02:26 +02:00
|
|
|
log(true);
|
|
|
|
});
|
2014-11-12 12:14:37 +01:00
|
|
|
|
2017-04-23 20:02:26 +02:00
|
|
|
let _end = res.end;
|
2014-11-12 12:14:37 +01:00
|
|
|
res.end = function(buf) {
|
2017-04-23 20:02:26 +02:00
|
|
|
if (buf) bytesout += buf.length;
|
|
|
|
_end.apply(res, arguments);
|
|
|
|
log();
|
|
|
|
};
|
|
|
|
next();
|
|
|
|
};
|