2013-10-26 14:18:36 +02:00
|
|
|
var crypto = require('crypto')
|
|
|
|
, utils = require('./utils')
|
|
|
|
, UError = require('./error').UserError
|
|
|
|
, Logger = require('./logger')
|
2013-06-08 03:16:28 +02:00
|
|
|
|
|
|
|
module.exports.validate_name = function validate_name(req, res, next, value, name) {
|
2014-02-01 09:08:48 +01:00
|
|
|
if (value.charAt(0) === '-') {
|
|
|
|
// special case in couchdb usually
|
|
|
|
next('route')
|
|
|
|
} else if (utils.validate_name(value)) {
|
2013-10-26 14:18:36 +02:00
|
|
|
next()
|
2013-06-08 03:16:28 +02:00
|
|
|
} else {
|
2013-06-14 10:34:29 +02:00
|
|
|
next(new UError({
|
2013-06-08 03:16:28 +02:00
|
|
|
status: 403,
|
2013-12-15 00:09:55 +01:00
|
|
|
msg: 'invalid ' + name,
|
2013-10-26 14:18:36 +02:00
|
|
|
}))
|
2013-06-08 03:16:28 +02:00
|
|
|
}
|
2013-10-26 14:18:36 +02:00
|
|
|
}
|
2013-06-08 03:16:28 +02:00
|
|
|
|
|
|
|
module.exports.media = function media(expect) {
|
|
|
|
return function(req, res, next) {
|
|
|
|
if (req.headers['content-type'] !== expect) {
|
2013-06-14 10:34:29 +02:00
|
|
|
next(new UError({
|
2013-06-08 03:16:28 +02:00
|
|
|
status: 415,
|
2013-09-27 10:55:42 +02:00
|
|
|
msg: 'wrong content-type, expect: '+expect+', got: '+req.headers['content-type'],
|
2013-10-26 14:18:36 +02:00
|
|
|
}))
|
2013-06-08 03:16:28 +02:00
|
|
|
} else {
|
2013-10-26 14:18:36 +02:00
|
|
|
next()
|
2013-06-08 03:16:28 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports.expect_json = function expect_json(req, res, next) {
|
2013-10-22 09:29:57 +02:00
|
|
|
if (!utils.is_object(req.body)) {
|
2013-06-08 03:16:28 +02:00
|
|
|
return next({
|
|
|
|
status: 400,
|
|
|
|
msg: 'can\'t parse incoming json',
|
2013-10-26 14:18:36 +02:00
|
|
|
})
|
2013-06-08 03:16:28 +02:00
|
|
|
}
|
2013-10-26 14:18:36 +02:00
|
|
|
next()
|
2013-06-08 03:16:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
module.exports.basic_auth = function basic_auth(callback) {
|
2013-12-06 18:46:51 +01:00
|
|
|
return function(req, res, _next) {
|
|
|
|
function next(err) {
|
|
|
|
// uncomment this to reject users with bad auth headers
|
|
|
|
//return _next.apply(null, arguments)
|
2013-12-29 07:41:31 +01:00
|
|
|
|
2013-12-06 18:46:51 +01:00
|
|
|
// swallow error, user remains unauthorized
|
2013-12-15 21:38:16 +01:00
|
|
|
// set remoteUserError to indicate that user was attempting authentication
|
|
|
|
if (err) req.remoteUserError = err.msg
|
2013-12-06 18:46:51 +01:00
|
|
|
return _next()
|
|
|
|
}
|
|
|
|
|
2013-10-26 14:18:36 +02:00
|
|
|
var authorization = req.headers.authorization
|
2013-06-08 03:16:28 +02:00
|
|
|
|
2013-12-06 18:46:51 +01:00
|
|
|
if (req.remoteUser != null) return next()
|
|
|
|
if (authorization == null) return next()
|
2013-06-08 03:16:28 +02:00
|
|
|
|
2013-10-26 14:18:36 +02:00
|
|
|
var parts = authorization.split(' ')
|
2013-06-08 03:16:28 +02:00
|
|
|
|
|
|
|
if (parts.length !== 2) return next({
|
|
|
|
status: 400,
|
|
|
|
msg: 'bad authorization header',
|
2013-10-26 14:18:36 +02:00
|
|
|
})
|
2013-06-08 03:16:28 +02:00
|
|
|
|
|
|
|
var scheme = parts[0]
|
2013-12-19 04:18:45 +01:00
|
|
|
, credentials = new Buffer(parts[1], 'base64').toString()
|
|
|
|
, index = credentials.indexOf(':')
|
2013-06-08 03:16:28 +02:00
|
|
|
|
2014-02-06 21:56:46 +01:00
|
|
|
if (scheme !== 'Basic' || index < 0) return next({
|
2013-06-08 03:16:28 +02:00
|
|
|
status: 400,
|
|
|
|
msg: 'bad authorization header',
|
2013-10-26 14:18:36 +02:00
|
|
|
})
|
|
|
|
|
2013-06-08 03:16:28 +02:00
|
|
|
var user = credentials.slice(0, index)
|
2013-12-19 04:18:45 +01:00
|
|
|
, pass = credentials.slice(index + 1)
|
2013-06-08 03:16:28 +02:00
|
|
|
|
2014-06-26 17:23:21 +02:00
|
|
|
callback(user, pass, function(err, is_ok) {
|
|
|
|
if (err) return next(err)
|
|
|
|
if (is_ok) {
|
|
|
|
req.remoteUser = user
|
|
|
|
next()
|
|
|
|
} else {
|
|
|
|
next({
|
|
|
|
status: 403,
|
|
|
|
msg: 'bad username/password, access denied',
|
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
2013-06-08 03:16:28 +02:00
|
|
|
}
|
2013-10-26 14:18:36 +02:00
|
|
|
}
|
2013-12-09 04:59:31 +01:00
|
|
|
|
|
|
|
module.exports.anti_loop = function(config) {
|
|
|
|
return function(req, res, next) {
|
|
|
|
if (req.headers.via != null) {
|
|
|
|
var arr = req.headers.via.split(',')
|
|
|
|
for (var i=0; i<arr.length; i++) {
|
|
|
|
var m = arr[i].match(/\s*(\S+)\s+(\S+)/)
|
|
|
|
if (m && m[2] === config.server_id) {
|
|
|
|
return next(new UError({
|
|
|
|
status: 508,
|
|
|
|
msg: 'loop detected',
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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) {
|
2013-10-26 14:18:36 +02:00
|
|
|
return crypto.createHash('md5').update(data).digest('hex')
|
2013-07-03 03:49:24 +02:00
|
|
|
}
|
|
|
|
|
2013-10-11 07:32:59 +02:00
|
|
|
module.exports.log_and_etagify = function(req, res, next) {
|
|
|
|
// logger
|
2013-10-26 14:18:36 +02:00
|
|
|
req.log = Logger.logger.child({sub: 'in'})
|
2013-10-12 16:37:47 +02:00
|
|
|
|
|
|
|
var _auth = req.headers.authorization
|
|
|
|
if (_auth) req.headers.authorization = '<Classified>'
|
|
|
|
req.log.info({req: req, ip: req.ip}, '@{ip} requested \'@{req.method} @{req.url}\'')
|
|
|
|
if (_auth) req.headers.authorization = _auth
|
2013-10-11 07:32:59 +02:00
|
|
|
|
2013-10-26 14:18:36 +02:00
|
|
|
var bytesin = 0
|
2014-06-18 03:59:22 +02:00
|
|
|
req.on('data', function(chunk) {
|
|
|
|
bytesin += chunk.length
|
|
|
|
})
|
2013-10-11 07:32:59 +02:00
|
|
|
|
2013-10-26 14:18:36 +02:00
|
|
|
var _send = res.send
|
2013-07-03 03:49:24 +02:00
|
|
|
res.send = function(body) {
|
2014-03-07 19:20:41 +01:00
|
|
|
try {
|
|
|
|
if (typeof(body) === 'string' || typeof(body) === 'object') {
|
|
|
|
res.header('Content-type', 'application/json')
|
|
|
|
|
|
|
|
if (typeof(body) === 'object' && body != null) {
|
2014-06-22 16:06:16 +02:00
|
|
|
if (typeof(body.error) === 'string') {
|
2014-03-29 02:08:00 +01:00
|
|
|
res._sinopia_error = body.error
|
2014-03-07 19:20:41 +01:00
|
|
|
}
|
|
|
|
body = JSON.stringify(body, undefined, '\t') + '\n'
|
|
|
|
}
|
2013-07-03 03:49:24 +02:00
|
|
|
|
2014-03-07 19:20:41 +01:00
|
|
|
// don't send etags with errors
|
|
|
|
if (!res.statusCode || (res.statusCode >= 200 && res.statusCode < 300)) {
|
|
|
|
res.header('ETag', '"' + md5sum(body) + '"')
|
2013-10-11 07:32:59 +02:00
|
|
|
}
|
2014-03-07 19:20:41 +01:00
|
|
|
} else {
|
|
|
|
// send(null), send(204), etc.
|
2013-07-03 03:49:24 +02:00
|
|
|
}
|
2014-03-07 19:20:41 +01:00
|
|
|
} catch(err) {
|
|
|
|
// if sinopia sends headers first, and then calls res.send()
|
|
|
|
// 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/)) {
|
|
|
|
return res.socket.destroy()
|
|
|
|
} else {
|
|
|
|
throw err
|
2013-12-06 18:46:11 +01:00
|
|
|
}
|
2013-07-03 03:49:24 +02:00
|
|
|
}
|
2013-10-11 07:32:59 +02:00
|
|
|
|
2013-10-26 14:18:36 +02:00
|
|
|
res.send = _send
|
|
|
|
res.send(body)
|
2013-10-18 23:53:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
var bytesout = 0
|
2013-10-26 14:18:36 +02:00
|
|
|
, _write = res.write
|
2013-10-18 23:53:27 +02:00
|
|
|
res.write = function(buf) {
|
|
|
|
bytesout += buf.length
|
|
|
|
_write.apply(res, arguments)
|
|
|
|
}
|
|
|
|
|
2013-10-22 11:37:28 +02:00
|
|
|
function log() {
|
2013-10-26 14:18:36 +02:00
|
|
|
var msg = '@{status}, user: @{user}, req: \'@{request.method} @{request.url}\''
|
2013-10-18 23:53:27 +02:00
|
|
|
if (res._sinopia_error) {
|
2013-10-26 14:18:36 +02:00
|
|
|
msg += ', error: @{!error}'
|
2013-10-11 07:32:59 +02:00
|
|
|
} else {
|
2013-10-26 14:18:36 +02:00
|
|
|
msg += ', bytes: @{bytes.in}/@{bytes.out}'
|
2013-10-11 07:32:59 +02:00
|
|
|
}
|
|
|
|
req.log.warn({
|
|
|
|
request: {method: req.method, url: req.url},
|
|
|
|
level: 35, // http
|
2013-12-27 12:29:23 +01:00
|
|
|
user: req.remoteUser,
|
2013-10-11 07:32:59 +02:00
|
|
|
status: res.statusCode,
|
2013-10-18 23:53:27 +02:00
|
|
|
error: res._sinopia_error,
|
2013-10-11 07:32:59 +02:00
|
|
|
bytes: {
|
|
|
|
in: bytesin,
|
2013-10-18 23:53:27 +02:00
|
|
|
out: bytesout,
|
2013-10-11 07:32:59 +02:00
|
|
|
}
|
2013-10-26 14:18:36 +02:00
|
|
|
}, msg)
|
2013-10-22 11:37:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
req.on('close', function() {
|
|
|
|
log(true)
|
|
|
|
})
|
|
|
|
|
2013-10-26 14:18:36 +02:00
|
|
|
var _end = res.end
|
2013-10-22 11:37:28 +02:00
|
|
|
res.end = function(buf) {
|
|
|
|
if (buf) bytesout += buf.length
|
|
|
|
_end.apply(res, arguments)
|
|
|
|
log()
|
|
|
|
}
|
|
|
|
next()
|
2013-07-03 03:49:24 +02:00
|
|
|
}
|
|
|
|
|