diff --git a/lib/auth.js b/lib/auth.js index 92d73712d..5138cc040 100644 --- a/lib/auth.js +++ b/lib/auth.js @@ -1,53 +1,126 @@ var Path = require('path') , crypto = require('crypto') - , UError = require('./error').UError + , UError = require('./error').UserError + , Logger = require('./logger') + , assert = require('assert') module.exports = Auth function Auth(config) { if (!(this instanceof Auth)) return new Auth(config) this.config = config + this.logger = Logger.logger.child({sub: 'auth'}) + var stuff = { + config: config, + logger: this.logger, + } if (config.users_file) { - this.HTPasswd = require('./htpasswd')( - Path.resolve( - Path.dirname(config.self_path), - config.users_file - ) - ) - } -} - -Auth.prototype.authenticate = function(user, password, cb) { - if (this.config.users != null && this.config.users[user] != null) { - // if user exists in this.users, verify password against it no matter what is in htpasswd - return cb(null, crypto.createHash('sha1').update(password).digest('hex') === this.config.users[user].password ? [user] : null) - } - - if (!this.HTPasswd) return cb(null, false) - this.HTPasswd.reload(function() { - cb(null, this.HTPasswd.verify(user, password) ? [user] : null) - }.bind(this)) -} - -Auth.prototype.add_user = function(user, password, cb) { - if (this.config.users && this.config.users[user]) return cb(new UError({ - status: 403, - message: 'this user already exists', - })) - - if (this.HTPasswd) { - if (this.max_users || this.max_users == null) { - var max_users = Number(this.max_users || Infinity) - this.HTPasswd.add_user(user, password, max_users, cb) - return + if (!config.auth || !config.auth.htpasswd) { + // b/w compat + config.auth = config.auth || {} + config.auth.htpasswd = {file: config.users_file} } } - return cb(new UError({ - status: 409, - message: 'registration is disabled', - })) + this.plugins = Object.keys(config.auth || {}).map(function(p) { + var plugin, name + try { + name = 'sinopia-' + p + plugin = require(name) + } catch(x) { + try { + name = p + plugin = require(name) + } catch(x) {} + } + + if (plugin == null) { + throw Error('"' + p + '" auth plugin not found\n' + + 'try "npm install sinopia-' + p + '"') + } + + if (typeof(plugin) !== 'function') + throw Error('"' + name + '" doesn\'t look like a valid auth plugin') + + plugin = plugin(config.auth[p], stuff) + + if (plugin == null || typeof(plugin.authenticate) !== 'function') + throw Error('"' + name + '" doesn\'t look like a valid auth plugin') + + return plugin + }) + + this.plugins.unshift({ + 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(new UError({ + status: 403, + message: 'this user already exists', + })) + + return cb() + }, + }) + + this.plugins.push({ + authenticate: function(user, password, cb) { + return cb(new UError({ + status: 403, + message: 'bad username/password, access denied', + })) + }, + + adduser: function(user, password, cb) { + return cb(new UError({ + status: 409, + message: 'registration is disabled', + })) + }, + }) +} + +Auth.prototype.authenticate = function(user, password, cb) { + var plugins = this.plugins.slice(0) + + !function next() { + var p = plugins.shift() + p.authenticate(user, password, function(err, groups) { + if (err || groups) return cb(err, groups) + next() + }) + }() +} + +Auth.prototype.add_user = function(user, password, cb) { + var plugins = this.plugins.slice(0) + + !function next() { + var p = plugins.shift() + var 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 || ok) return cb(err, ok) + next() + }) + } + }() } Auth.prototype.middleware = function() { @@ -90,17 +163,14 @@ Auth.prototype.middleware = function() { var user = credentials.slice(0, index) , pass = credentials.slice(index + 1) +debugger self.authenticate(user, pass, function(err, groups) { - if (err) return next(err) - if (groups != null && groups != false) { + if (!err && groups != null && groups != false) { req.remote_user = AuthenticatedUser(user, groups) next() } else { req.remote_user = AnonymousUser() - next({ - status: 403, - message: 'bad username/password, access denied', - }) + next(err) } }) } diff --git a/lib/config_def.yaml b/lib/config_def.yaml index 308498398..803826c5b 100644 --- a/lib/config_def.yaml +++ b/lib/config_def.yaml @@ -12,11 +12,12 @@ users: title: Sinopia # logo: logo.png -users_file: ./htpasswd - -# Maximum amount of users allowed to register, defaults to "+inf". -# You can set this to 0 to disable registration. -#max_users: 1000 +auth: + htpasswd: + users_file: ./htpasswd + # Maximum amount of users allowed to register, defaults to "+inf". + # You can set this to 0 to disable registration. + #max_users: 1000 # a list of other known repositories we can talk to uplinks: @@ -51,9 +52,11 @@ packages: # storage: 'local_storage' '*': - # allow all users to read packages ('all' is a keyword) - # this includes non-authenticated users - allow_access: all + # allow all users to read packages (including non-authenticated users) + # + # you can specify usernames/groupnames (depending on your auth plugin) + # and three keywords: "@all", "@anonymous", "@authenticated" + allow_access: @all # allow 'admin' to publish packages allow_publish: admin