mirror of
https://github.com/verdaccio/verdaccio.git
synced 2025-01-06 22:40:26 -05:00
Refactor Auth class, reduce eslint warnings, moved to es6
This commit is contained in:
parent
2961d21124
commit
ba6543a322
2 changed files with 427 additions and 339 deletions
760
lib/auth.js
760
lib/auth.js
|
@ -1,4 +1,5 @@
|
|||
/* eslint prefer-spread: "off" */
|
||||
/* eslint prefer-rest-params: "off" */
|
||||
|
||||
'use strict';
|
||||
|
||||
|
@ -7,363 +8,442 @@ const jju = require('jju');
|
|||
const Error = require('http-errors');
|
||||
const Logger = require('./logger');
|
||||
const load_plugins = require('./plugin-loader').load_plugins;
|
||||
const pkgJson = require('../package.json');
|
||||
/**
|
||||
* Handles the authentification, load auth plugins.
|
||||
*/
|
||||
class Auth {
|
||||
|
||||
module.exports = Auth;
|
||||
/**
|
||||
* @param {*} config config reference
|
||||
*/
|
||||
constructor(config) {
|
||||
this.config = config;
|
||||
this.logger = Logger.logger.child({sub: 'auth'});
|
||||
this.secret = config.secret;
|
||||
|
||||
function Auth(config) {
|
||||
let self = Object.create(Auth.prototype);
|
||||
self.config = config;
|
||||
self.logger = Logger.logger.child({sub: 'auth'});
|
||||
self.secret = config.secret;
|
||||
const plugin_params = {
|
||||
config: config,
|
||||
logger: this.logger,
|
||||
};
|
||||
|
||||
let plugin_params = {
|
||||
config: config,
|
||||
logger: self.logger,
|
||||
};
|
||||
|
||||
if (config.users_file) {
|
||||
if (!config.auth || !config.auth.htpasswd) {
|
||||
// b/w compat
|
||||
config.auth = config.auth || {};
|
||||
config.auth.htpasswd = {file: config.users_file};
|
||||
if (config.users_file) {
|
||||
if (!config.auth || !config.auth.htpasswd) {
|
||||
// b/w compat
|
||||
config.auth = config.auth || {};
|
||||
config.auth.htpasswd = {file: config.users_file};
|
||||
}
|
||||
}
|
||||
|
||||
this.plugins = load_plugins(config, config.auth, plugin_params, function(p) {
|
||||
return p.authenticate || p.allow_access || p.allow_publish;
|
||||
});
|
||||
|
||||
this.plugins.unshift({
|
||||
verdaccio_version: pkgJson.version,
|
||||
|
||||
authenticate: function(user, password, cb) {
|
||||
if (config.users != null
|
||||
&& config.users[user] != null
|
||||
&& (Crypto.createHash('sha1').update(password).digest('hex')
|
||||
=== config.users[user].password)
|
||||
) {
|
||||
return cb(null, [user]);
|
||||
}
|
||||
|
||||
return cb();
|
||||
},
|
||||
|
||||
adduser: function(user, password, cb) {
|
||||
if (config.users && config.users[user])
|
||||
return cb( Error[403]('this user already exists') );
|
||||
|
||||
return cb();
|
||||
},
|
||||
});
|
||||
|
||||
const allow_action = function(action) {
|
||||
return function(user, pkg, cb) {
|
||||
let ok = pkg[action].reduce(function(prev, curr) {
|
||||
if (user.groups.indexOf(curr) !== -1) return true;
|
||||
return prev;
|
||||
}, false);
|
||||
|
||||
if (ok) return cb(null, true);
|
||||
|
||||
if (user.name) {
|
||||
cb( Error[403]('user ' + user.name + ' is not allowed to ' + action + ' package ' + pkg.name) );
|
||||
} else {
|
||||
cb( Error[403]('unregistered users are not allowed to ' + action + ' package ' + pkg.name) );
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
this.plugins.push({
|
||||
authenticate: function(user, password, cb) {
|
||||
return cb( Error[403]('bad username/password, access denied') );
|
||||
},
|
||||
|
||||
add_user: function(user, password, cb) {
|
||||
return cb( Error[409]('registration is disabled') );
|
||||
},
|
||||
|
||||
allow_access: allow_action('access'),
|
||||
allow_publish: allow_action('publish'),
|
||||
});
|
||||
}
|
||||
|
||||
self.plugins = load_plugins(config, config.auth, plugin_params, function(p) {
|
||||
return p.authenticate || p.allow_access || p.allow_publish;
|
||||
});
|
||||
/**
|
||||
* Authenticate an user.
|
||||
* @param {*} user
|
||||
* @param {*} password
|
||||
* @param {*} cb
|
||||
*/
|
||||
authenticate(user, password, cb) {
|
||||
const plugins = this.plugins.slice(0)
|
||||
;(function next() {
|
||||
let p = plugins.shift();
|
||||
|
||||
self.plugins.unshift({
|
||||
verdaccio_version: '1.1.0',
|
||||
|
||||
authenticate: function(user, password, cb) {
|
||||
if (config.users != null
|
||||
&& config.users[user] != null
|
||||
&& (Crypto.createHash('sha1').update(password).digest('hex')
|
||||
=== config.users[user].password)
|
||||
) {
|
||||
return cb(null, [user]);
|
||||
if (typeof(p.authenticate) !== 'function') {
|
||||
return next();
|
||||
}
|
||||
|
||||
return cb();
|
||||
},
|
||||
p.authenticate(user, password, function(err, groups) {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
if (groups != null && groups != false) {
|
||||
return cb(err, authenticatedUser(user, groups));
|
||||
}
|
||||
next();
|
||||
});
|
||||
})();
|
||||
}
|
||||
|
||||
adduser: function(user, password, cb) {
|
||||
if (config.users && config.users[user])
|
||||
return cb( Error[403]('this user already exists') );
|
||||
/**
|
||||
* Add a new user.
|
||||
* @param {*} user
|
||||
* @param {*} password
|
||||
* @param {*} cb
|
||||
*/
|
||||
add_user(user, password, cb) {
|
||||
let self = this;
|
||||
let plugins = this.plugins.slice(0)
|
||||
|
||||
return cb();
|
||||
},
|
||||
});
|
||||
|
||||
function allow_action(action) {
|
||||
return function(user, pkg, cb) {
|
||||
let ok = pkg[action].reduce(function(prev, curr) {
|
||||
if (user.groups.indexOf(curr) !== -1) return true;
|
||||
return prev;
|
||||
}, false);
|
||||
|
||||
if (ok) return cb(null, true);
|
||||
|
||||
if (user.name) {
|
||||
cb( Error[403]('user ' + user.name + ' is not allowed to ' + action + ' package ' + pkg.name) );
|
||||
;(function next() {
|
||||
let p = plugins.shift();
|
||||
let n = 'adduser';
|
||||
if (typeof(p[n]) !== 'function') {
|
||||
n = 'add_user';
|
||||
}
|
||||
if (typeof(p[n]) !== 'function') {
|
||||
next();
|
||||
} else {
|
||||
cb( Error[403]('unregistered users are not allowed to ' + action + ' package ' + pkg.name) );
|
||||
p[n](user, password, function(err, ok) {
|
||||
if (err) return cb(err);
|
||||
if (ok) return self.authenticate(user, password, cb);
|
||||
next();
|
||||
});
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow user to access a package.
|
||||
* @param {*} package_name
|
||||
* @param {*} user
|
||||
* @param {*} callback
|
||||
*/
|
||||
allow_access(package_name, user, callback) {
|
||||
let plugins = this.plugins.slice(0);
|
||||
let pkg = Object.assign({name: package_name},
|
||||
this.config.get_package_spec(package_name))
|
||||
|
||||
;(function next() {
|
||||
let p = plugins.shift();
|
||||
|
||||
if (typeof(p.allow_access) !== 'function') {
|
||||
return next();
|
||||
}
|
||||
|
||||
p.allow_access(user, pkg, function(err, ok) {
|
||||
if (err) return callback(err);
|
||||
if (ok) return callback(null, ok);
|
||||
next(); // cb(null, false) causes next plugin to roll
|
||||
});
|
||||
})();
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow user to publish a package.
|
||||
* @param {*} package_name
|
||||
* @param {*} user
|
||||
* @param {*} callback
|
||||
*/
|
||||
allow_publish(package_name, user, callback) {
|
||||
let plugins = this.plugins.slice(0);
|
||||
let pkg = Object.assign({name: package_name},
|
||||
this.config.get_package_spec(package_name))
|
||||
|
||||
;(function next() {
|
||||
let p = plugins.shift();
|
||||
|
||||
if (typeof(p.allow_publish) !== 'function') {
|
||||
return next();
|
||||
}
|
||||
|
||||
p.allow_publish(user, pkg, function(err, ok) {
|
||||
if (err) return callback(err);
|
||||
if (ok) return callback(null, ok);
|
||||
next(); // cb(null, false) causes next plugin to roll
|
||||
});
|
||||
})();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up a basic middleware.
|
||||
* @return {Function}
|
||||
*/
|
||||
basic_middleware() {
|
||||
let self = this;
|
||||
let credentials;
|
||||
return function(req, res, _next) {
|
||||
req.pause();
|
||||
|
||||
const next = function(err) {
|
||||
req.resume();
|
||||
// uncomment this to reject users with bad auth headers
|
||||
// return _next.apply(null, arguments)
|
||||
|
||||
// swallow error, user remains unauthorized
|
||||
// set remoteUserError to indicate that user was attempting authentication
|
||||
if (err) {
|
||||
req.remote_user.error = err.message;
|
||||
}
|
||||
return _next();
|
||||
};
|
||||
|
||||
if (req.remote_user != null && req.remote_user.name !== undefined) {
|
||||
return next();
|
||||
}
|
||||
req.remote_user = buildAnonymousUser();
|
||||
|
||||
let authorization = req.headers.authorization;
|
||||
if (authorization == null) return next();
|
||||
|
||||
let parts = authorization.split(' ');
|
||||
|
||||
if (parts.length !== 2) {
|
||||
return next( Error[400]('bad authorization header') );
|
||||
}
|
||||
|
||||
const scheme = parts[0];
|
||||
if (scheme === 'Basic') {
|
||||
credentials = new Buffer(parts[1], 'base64').toString();
|
||||
} else if (scheme === 'Bearer') {
|
||||
credentials = self.aes_decrypt(new Buffer(parts[1], 'base64')).toString('utf8');
|
||||
if (!credentials) {
|
||||
return next();
|
||||
}
|
||||
} else {
|
||||
return next();
|
||||
}
|
||||
|
||||
const index = credentials.indexOf(':');
|
||||
if (index < 0) {
|
||||
return next();
|
||||
}
|
||||
|
||||
const user = credentials.slice(0, index);
|
||||
const pass = credentials.slice(index + 1);
|
||||
|
||||
self.authenticate(user, pass, function(err, user) {
|
||||
if (!err) {
|
||||
req.remote_user = user;
|
||||
next();
|
||||
} else {
|
||||
req.remote_user = buildAnonymousUser();
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
self.plugins.push({
|
||||
authenticate: function(user, password, cb) {
|
||||
return cb( Error[403]('bad username/password, access denied') );
|
||||
},
|
||||
/**
|
||||
* Set up the bearer middleware.
|
||||
* @return {Function}
|
||||
*/
|
||||
bearer_middleware() {
|
||||
let self = this;
|
||||
return function(req, res, _next) {
|
||||
req.pause();
|
||||
const next = function(_err) {
|
||||
req.resume();
|
||||
return _next.apply(null, arguments);
|
||||
};
|
||||
|
||||
add_user: function(user, password, cb) {
|
||||
return cb( Error[409]('registration is disabled') );
|
||||
},
|
||||
if (req.remote_user != null && req.remote_user.name !== undefined) {
|
||||
return next();
|
||||
}
|
||||
req.remote_user = buildAnonymousUser();
|
||||
|
||||
allow_access: allow_action('access'),
|
||||
allow_publish: allow_action('publish'),
|
||||
});
|
||||
let authorization = req.headers.authorization;
|
||||
if (authorization == null) {
|
||||
return next();
|
||||
}
|
||||
|
||||
return self;
|
||||
let parts = authorization.split(' ');
|
||||
|
||||
if (parts.length !== 2) {
|
||||
return next( Error[400]('bad authorization header') );
|
||||
}
|
||||
|
||||
let scheme = parts[0];
|
||||
let token = parts[1];
|
||||
|
||||
if (scheme !== 'Bearer')
|
||||
return next();
|
||||
let user;
|
||||
try {
|
||||
user = self.decode_token(token);
|
||||
} catch(err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
req.remote_user = authenticatedUser(user.u, user.g);
|
||||
req.remote_user.token = token;
|
||||
next();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up cookie middleware.
|
||||
* @return {Function}
|
||||
*/
|
||||
cookie_middleware() {
|
||||
let self = this;
|
||||
return function(req, res, _next) {
|
||||
req.pause();
|
||||
const next = function(_err) {
|
||||
req.resume();
|
||||
return _next();
|
||||
};
|
||||
|
||||
if (req.remote_user != null && req.remote_user.name !== undefined)
|
||||
return next();
|
||||
|
||||
req.remote_user = buildAnonymousUser();
|
||||
|
||||
let token = req.cookies.get('token');
|
||||
if (token == null) {
|
||||
return next();
|
||||
}
|
||||
let credentials = self.aes_decrypt(new Buffer(token, 'base64')).toString('utf8');
|
||||
if (!credentials) {
|
||||
return next();
|
||||
}
|
||||
|
||||
let index = credentials.indexOf(':');
|
||||
if (index < 0) {
|
||||
return next();
|
||||
}
|
||||
const user = credentials.slice(0, index);
|
||||
const pass = credentials.slice(index + 1);
|
||||
|
||||
self.authenticate(user, pass, function(err, user) {
|
||||
if (!err) {
|
||||
req.remote_user = user;
|
||||
next();
|
||||
} else {
|
||||
req.remote_user = buildAnonymousUser();
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the token.
|
||||
* @param {*} user
|
||||
* @return {String}
|
||||
*/
|
||||
issue_token(user) {
|
||||
let data = jju.stringify({
|
||||
u: user.name,
|
||||
g: user.real_groups && user.real_groups.length ? user.real_groups : undefined,
|
||||
t: ~~(Date.now()/1000),
|
||||
}, {indent: false});
|
||||
|
||||
data = new Buffer(data, 'utf8');
|
||||
const mac = Crypto.createHmac('sha256', this.secret).update(data).digest();
|
||||
return Buffer.concat([data, mac]).toString('base64');
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes the token.
|
||||
* @param {*} str
|
||||
* @param {*} expire_time
|
||||
* @return {Object}
|
||||
*/
|
||||
decode_token(str, expire_time) {
|
||||
const buf = new Buffer(str, 'base64');
|
||||
if (buf.length <= 32) {
|
||||
throw Error[401]('invalid token');
|
||||
}
|
||||
|
||||
let data = buf.slice(0, buf.length - 32);
|
||||
let their_mac = buf.slice(buf.length - 32);
|
||||
let good_mac = Crypto.createHmac('sha256', this.secret).update(data).digest();
|
||||
|
||||
their_mac = Crypto.createHash('sha512').update(their_mac).digest('hex');
|
||||
good_mac = Crypto.createHash('sha512').update(good_mac).digest('hex');
|
||||
if (their_mac !== good_mac) throw Error[401]('bad signature');
|
||||
|
||||
// make token expire in 24 hours
|
||||
// TODO: make configurable?
|
||||
expire_time = expire_time || 24*60*60;
|
||||
|
||||
data = jju.parse(data.toString('utf8'));
|
||||
if (Math.abs(data.t - ~~(Date.now()/1000)) > expire_time) {
|
||||
throw Error[401]('token expired');
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt a string.
|
||||
* @param {String} buf
|
||||
* @return {Buffer}
|
||||
*/
|
||||
aes_encrypt(buf) {
|
||||
const c = Crypto.createCipher('aes192', this.secret);
|
||||
const b1 = c.update(buf);
|
||||
const b2 = c.final();
|
||||
return Buffer.concat([b1, b2]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dencrypt a string.
|
||||
* @param {String} buf
|
||||
* @return {Buffer}
|
||||
*/
|
||||
aes_decrypt(buf) {
|
||||
try {
|
||||
const c = Crypto.createDecipher('aes192', this.secret);
|
||||
const b1 = c.update(buf);
|
||||
const b2 = c.final();
|
||||
return Buffer.concat([b1, b2]);
|
||||
} catch(_) {
|
||||
return new Buffer(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Auth.prototype.authenticate = function(user, password, cb) {
|
||||
let plugins = this.plugins.slice(0)
|
||||
|
||||
;(function next() {
|
||||
let p = plugins.shift();
|
||||
|
||||
if (typeof(p.authenticate) !== 'function') {
|
||||
return next();
|
||||
}
|
||||
|
||||
p.authenticate(user, password, function(err, groups) {
|
||||
if (err) return cb(err);
|
||||
if (groups != null && groups != false)
|
||||
return cb(err, authenticatedUser(user, groups));
|
||||
next();
|
||||
});
|
||||
})();
|
||||
};
|
||||
|
||||
Auth.prototype.add_user = function(user, password, cb) {
|
||||
let self = this;
|
||||
let plugins = this.plugins.slice(0)
|
||||
|
||||
;(function next() {
|
||||
let p = plugins.shift();
|
||||
let n = 'adduser';
|
||||
if (typeof(p[n]) !== 'function') {
|
||||
n = 'add_user';
|
||||
}
|
||||
if (typeof(p[n]) !== 'function') {
|
||||
next();
|
||||
} else {
|
||||
p[n](user, password, function(err, ok) {
|
||||
if (err) return cb(err);
|
||||
if (ok) return self.authenticate(user, password, cb);
|
||||
next();
|
||||
});
|
||||
}
|
||||
})();
|
||||
};
|
||||
|
||||
Auth.prototype.allow_access = function(package_name, user, callback) {
|
||||
let plugins = this.plugins.slice(0);
|
||||
let pkg = Object.assign({name: package_name},
|
||||
this.config.get_package_spec(package_name))
|
||||
|
||||
;(function next() {
|
||||
let p = plugins.shift();
|
||||
|
||||
if (typeof(p.allow_access) !== 'function') {
|
||||
return next();
|
||||
}
|
||||
|
||||
p.allow_access(user, pkg, function(err, ok) {
|
||||
if (err) return callback(err);
|
||||
if (ok) return callback(null, ok);
|
||||
next(); // cb(null, false) causes next plugin to roll
|
||||
});
|
||||
})();
|
||||
};
|
||||
|
||||
Auth.prototype.allow_publish = function(package_name, user, callback) {
|
||||
let plugins = this.plugins.slice(0);
|
||||
let pkg = Object.assign({name: package_name},
|
||||
this.config.get_package_spec(package_name))
|
||||
|
||||
;(function next() {
|
||||
let p = plugins.shift();
|
||||
|
||||
if (typeof(p.allow_publish) !== 'function') {
|
||||
return next();
|
||||
}
|
||||
|
||||
p.allow_publish(user, pkg, function(err, ok) {
|
||||
if (err) return callback(err);
|
||||
if (ok) return callback(null, ok);
|
||||
next(); // cb(null, false) causes next plugin to roll
|
||||
});
|
||||
})();
|
||||
};
|
||||
|
||||
Auth.prototype.basic_middleware = function() {
|
||||
let self = this;
|
||||
return function(req, res, _next) {
|
||||
req.pause();
|
||||
function next(err) {
|
||||
req.resume();
|
||||
// uncomment this to reject users with bad auth headers
|
||||
// return _next.apply(null, arguments)
|
||||
|
||||
// swallow error, user remains unauthorized
|
||||
// set remoteUserError to indicate that user was attempting authentication
|
||||
if (err) req.remote_user.error = err.message;
|
||||
return _next();
|
||||
}
|
||||
|
||||
if (req.remote_user != null && req.remote_user.name !== undefined)
|
||||
return next();
|
||||
req.remote_user = buildAnonymousUser();
|
||||
|
||||
let authorization = req.headers.authorization;
|
||||
if (authorization == null) return next();
|
||||
|
||||
let parts = authorization.split(' ');
|
||||
|
||||
if (parts.length !== 2)
|
||||
return next( Error[400]('bad authorization header') );
|
||||
|
||||
let scheme = parts[0];
|
||||
if (scheme === 'Basic') {
|
||||
var credentials = new Buffer(parts[1], 'base64').toString();
|
||||
} else if (scheme === 'Bearer') {
|
||||
var credentials = self.aes_decrypt(new Buffer(parts[1], 'base64')).toString('utf8');
|
||||
if (!credentials) return next();
|
||||
} else {
|
||||
return next();
|
||||
}
|
||||
|
||||
let index = credentials.indexOf(':');
|
||||
if (index < 0) return next();
|
||||
|
||||
let user = credentials.slice(0, index);
|
||||
let pass = credentials.slice(index + 1);
|
||||
|
||||
self.authenticate(user, pass, function(err, user) {
|
||||
if (!err) {
|
||||
req.remote_user = user;
|
||||
next();
|
||||
} else {
|
||||
req.remote_user = buildAnonymousUser();
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
Auth.prototype.bearer_middleware = function() {
|
||||
let self = this;
|
||||
return function(req, res, _next) {
|
||||
req.pause();
|
||||
function next(_err) {
|
||||
req.resume();
|
||||
return _next.apply(null, arguments);
|
||||
}
|
||||
|
||||
if (req.remote_user != null && req.remote_user.name !== undefined)
|
||||
return next();
|
||||
req.remote_user = buildAnonymousUser();
|
||||
|
||||
let authorization = req.headers.authorization;
|
||||
if (authorization == null) return next();
|
||||
|
||||
let parts = authorization.split(' ');
|
||||
|
||||
if (parts.length !== 2)
|
||||
return next( Error[400]('bad authorization header') );
|
||||
|
||||
let scheme = parts[0];
|
||||
let token = parts[1];
|
||||
|
||||
if (scheme !== 'Bearer')
|
||||
return next();
|
||||
|
||||
try {
|
||||
var user = self.decode_token(token);
|
||||
} catch(err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
req.remote_user = authenticatedUser(user.u, user.g);
|
||||
req.remote_user.token = token;
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
Auth.prototype.cookie_middleware = function() {
|
||||
let self = this;
|
||||
return function(req, res, _next) {
|
||||
req.pause();
|
||||
function next(_err) {
|
||||
req.resume();
|
||||
return _next();
|
||||
}
|
||||
|
||||
if (req.remote_user != null && req.remote_user.name !== undefined)
|
||||
return next();
|
||||
|
||||
req.remote_user = buildAnonymousUser();
|
||||
|
||||
let token = req.cookies.get('token');
|
||||
if (token == null) return next();
|
||||
|
||||
/* try {
|
||||
var user = self.decode_token(token, 60*60)
|
||||
} catch(err) {
|
||||
return next()
|
||||
}
|
||||
|
||||
req.remote_user = authenticatedUser(user.u, user.g)
|
||||
req.remote_user.token = token
|
||||
next()*/
|
||||
let credentials = self.aes_decrypt(new Buffer(token, 'base64')).toString('utf8');
|
||||
if (!credentials) return next();
|
||||
|
||||
let index = credentials.indexOf(':');
|
||||
if (index < 0) return next();
|
||||
|
||||
let user = credentials.slice(0, index);
|
||||
let pass = credentials.slice(index + 1);
|
||||
|
||||
self.authenticate(user, pass, function(err, user) {
|
||||
if (!err) {
|
||||
req.remote_user = user;
|
||||
next();
|
||||
} else {
|
||||
req.remote_user = buildAnonymousUser();
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
Auth.prototype.issue_token = function(user) {
|
||||
let data = jju.stringify({
|
||||
u: user.name,
|
||||
g: user.real_groups && user.real_groups.length ? user.real_groups : undefined,
|
||||
t: ~~(Date.now()/1000),
|
||||
}, {indent: false});
|
||||
|
||||
data = new Buffer(data, 'utf8');
|
||||
let mac = Crypto.createHmac('sha256', this.secret).update(data).digest();
|
||||
return Buffer.concat([data, mac]).toString('base64');
|
||||
};
|
||||
|
||||
Auth.prototype.decode_token = function(str, expire_time) {
|
||||
let buf = new Buffer(str, 'base64');
|
||||
if (buf.length <= 32) throw Error[401]('invalid token');
|
||||
|
||||
let data = buf.slice(0, buf.length - 32);
|
||||
let their_mac = buf.slice(buf.length - 32);
|
||||
let good_mac = Crypto.createHmac('sha256', this.secret).update(data).digest();
|
||||
|
||||
their_mac = Crypto.createHash('sha512').update(their_mac).digest('hex');
|
||||
good_mac = Crypto.createHash('sha512').update(good_mac).digest('hex');
|
||||
if (their_mac !== good_mac) throw Error[401]('bad signature');
|
||||
|
||||
// make token expire in 24 hours
|
||||
// TODO: make configurable?
|
||||
expire_time = expire_time || 24*60*60;
|
||||
|
||||
data = jju.parse(data.toString('utf8'));
|
||||
if (Math.abs(data.t - ~~(Date.now()/1000)) > expire_time)
|
||||
throw Error[401]('token expired');
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
Auth.prototype.aes_encrypt = function(buf) {
|
||||
let c = Crypto.createCipher('aes192', this.secret);
|
||||
let b1 = c.update(buf);
|
||||
let b2 = c.final();
|
||||
return Buffer.concat([b1, b2]);
|
||||
};
|
||||
|
||||
Auth.prototype.aes_decrypt = function(buf) {
|
||||
try {
|
||||
let c = Crypto.createDecipher('aes192', this.secret);
|
||||
let b1 = c.update(buf);
|
||||
let b2 = c.final();
|
||||
return Buffer.concat([b1, b2]);
|
||||
} catch(_) {
|
||||
return new Buffer(0);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds an anonymous user in case none is logged in.
|
||||
* @return {Object} { name: xx, groups: [], real_groups: [] }
|
||||
*/
|
||||
function buildAnonymousUser() {
|
||||
return {
|
||||
name: undefined,
|
||||
|
@ -373,6 +453,12 @@ function buildAnonymousUser() {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate an user.
|
||||
* @param {*} name
|
||||
* @param {*} groups
|
||||
* @return {Object} { name: xx, groups: [], real_groups: [] }
|
||||
*/
|
||||
function authenticatedUser(name, groups) {
|
||||
let _groups = (groups || []).concat(['$all', '$authenticated', '@all', '@authenticated', 'all']);
|
||||
return {
|
||||
|
@ -381,3 +467,5 @@ function authenticatedUser(name, groups) {
|
|||
real_groups: groups,
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = Auth;
|
||||
|
|
|
@ -15,14 +15,14 @@ module.exports = function(config_hash) {
|
|||
|
||||
let config = new Config(config_hash);
|
||||
let storage = new Storage(config);
|
||||
let auth = Auth(config);
|
||||
let auth = new Auth(config);
|
||||
let app = express();
|
||||
|
||||
// run in production mode by default, just in case
|
||||
// it shouldn't make any difference anyway
|
||||
app.set('env', process.env.NODE_ENV || 'production');
|
||||
|
||||
function error_reporting_middleware(req, res, next) {
|
||||
const error_reporting_middleware = function(req, res, next) {
|
||||
res.report_error = res.report_error || function(err) {
|
||||
if (err.status && err.status >= 400 && err.status < 600) {
|
||||
if (!res.headersSent) {
|
||||
|
@ -44,7 +44,7 @@ module.exports = function(config_hash) {
|
|||
}
|
||||
};
|
||||
next();
|
||||
}
|
||||
};
|
||||
|
||||
app.use(Middleware.log);
|
||||
app.use(error_reporting_middleware);
|
||||
|
|
Loading…
Reference in a new issue