0
Fork 0
mirror of https://github.com/verdaccio/verdaccio.git synced 2025-01-06 22:40:26 -05:00

auth refactoring, part 1

moving stuff to auth.js basically + allowing usergroups
This commit is contained in:
Alex Kocharin 2014-09-02 03:09:08 +04:00
parent 7742e11cde
commit 8086c6f0bf
4 changed files with 145 additions and 114 deletions

124
lib/auth.js Normal file
View file

@ -0,0 +1,124 @@
var Path = require('path')
, crypto = require('crypto')
, UError = require('./error').UError
module.exports = Auth
function Auth(config) {
if (!(this instanceof Auth)) return new Auth(config)
this.config = config
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
}
}
return cb(new UError({
status: 409,
message: 'registration is disabled',
}))
}
Auth.prototype.middleware = function() {
var 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) return next()
req.remote_user = AnonymousUser()
var authorization = req.headers.authorization
if (authorization == null) return next()
var parts = authorization.split(' ')
if (parts.length !== 2) return next({
status: 400,
message: 'bad authorization header',
})
var scheme = parts[0]
, credentials = new Buffer(parts[1], 'base64').toString()
, index = credentials.indexOf(':')
if (scheme !== 'Basic' || index < 0) return next({
status: 400,
message: 'bad authorization header',
})
var user = credentials.slice(0, index)
, pass = credentials.slice(index + 1)
self.authenticate(user, pass, function(err, groups) {
if (err) return next(err)
if (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',
})
}
})
}
}
function AnonymousUser() {
return {
name: undefined,
// groups without '@' are going to be deprecated eventually
groups: ['@all', '@anonymous', 'all', 'undefined', 'anonymous'],
}
}
function AuthenticatedUser(name, groups) {
groups = groups.concat(['@all', '@authenticated', 'all'])
return {
name: name,
groups: groups,
}
}

View file

@ -113,31 +113,23 @@ function Config(config) {
if (this.ignore_latest_tag == null) this.ignore_latest_tag = false
if (this.users_file) {
this.HTPasswd = require('./htpasswd')(
Path.resolve(
Path.dirname(this.self_path),
this.users_file
)
)
}
return this
}
function allow_action(package, who, action) {
return (this.get_package_setting(package, action) || []).reduce(function(prev, curr) {
if (curr === String(who) || curr === 'all') return true
if (typeof(who) === 'string' && curr === who) return true
if (Array.isArray(who) && who.indexOf(curr) !== -1) return true
return prev
}, false)
}
Config.prototype.allow_access = function(package, user) {
return allow_action.call(this, package, user, 'allow_access') || allow_action.call(this, package, user, 'access')
return allow_action.call(this, package, user.groups, 'allow_access') || allow_action.call(this, package, user, 'access')
}
Config.prototype.allow_publish = function(package, user) {
return allow_action.call(this, package, user, 'allow_publish') || allow_action.call(this, package, user, 'publish')
return allow_action.call(this, package, user.groups, 'allow_publish') || allow_action.call(this, package, user, 'publish')
}
Config.prototype.proxy_access = function(package, uplink) {
@ -145,7 +137,8 @@ Config.prototype.proxy_access = function(package, uplink) {
}
Config.prototype.proxy_publish = function(package, uplink) {
return allow_action.call(this, package, uplink, 'proxy_publish')
throw new Error('deprecated')
//return allow_action.call(this, package, uplink, 'proxy_publish')
}
Config.prototype.get_package_setting = function(package, setting) {
@ -157,38 +150,6 @@ Config.prototype.get_package_setting = function(package, setting) {
return undefined
}
Config.prototype.authenticate = function(user, password, cb) {
if (this.users != null && this.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.users[user].password)
}
if (!this.HTPasswd) return cb(null, false)
this.HTPasswd.reload(function() {
cb(null, this.HTPasswd.verify(user, password))
}.bind(this))
}
Config.prototype.add_user = function(user, password, cb) {
if (this.users && this.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
}
}
return cb(new UError({
status: 409,
message: 'registration is disabled',
}))
}
module.exports = Config
var parse_interval_table = {

View file

@ -7,7 +7,6 @@ var express = require('express')
, Middleware = require('./middleware')
, Logger = require('./logger')
, Cats = require('./status-cats')
, basic_auth = Middleware.basic_auth
, validate_name = Middleware.validate_name
, media = Middleware.media
, expect_json = Middleware.expect_json
@ -16,6 +15,7 @@ var express = require('express')
, localList = require('./local-list')
, search = require('./search')
, marked = require('marked')
, Auth = require('./auth')
function match(regexp) {
return function(req, res, next, value, name) {
@ -29,18 +29,19 @@ function match(regexp) {
module.exports = function(config_hash) {
var config = new Config(config_hash)
, storage = new Storage(config);
, storage = new Storage(config)
, auth = new Auth(config)
search.configureStorage(storage);
var can = function(action) {
return function(req, res, next) {
if (config['allow_'+action](req.params.package, req.remoteUser)) {
if (config['allow_'+action](req.params.package, req.remote_user)) {
next()
} else {
if (!req.remoteUser) {
if (req.remoteUserError) {
var message = "can't "+action+' restricted package, ' + req.remoteUserError
if (!req.remote_user.name) {
if (req.remote_user.error) {
var message = "can't "+action+' restricted package, ' + req.remote_user.error
} else {
var message = "can't "+action+" restricted package without auth, did you forget 'npm set always-auth true'?"
}
@ -51,7 +52,7 @@ module.exports = function(config_hash) {
} else {
next(new UError({
status: 403,
message: 'user '+req.remoteUser+' not allowed to '+action+' it'
message: 'user '+req.remote_user.name+' not allowed to '+action+' it'
}))
}
}
@ -94,9 +95,7 @@ module.exports = function(config_hash) {
next()
})
app.use(Cats.middleware)
app.use(basic_auth(function(user, pass, cb) {
config.authenticate(user, pass, cb)
}))
app.use(auth.middleware())
app.use(express.json({strict: false, limit: config.max_body_size || '10mb'}))
app.use(express.compress())
app.use(Middleware.anti_loop(config))
@ -190,7 +189,7 @@ module.exports = function(config_hash) {
storage.search(req.param.startkey || 0, {req: req}, function(err, result) {
if (err) return next(err)
for (var pkg in result) {
if (!config.allow_access(pkg, req.remoteUser)) {
if (!config.allow_access(pkg, req.remote_user)) {
delete result[pkg]
}
}
@ -215,15 +214,15 @@ module.exports = function(config_hash) {
app.get('/-/user/:org_couchdb_user', function(req, res, next) {
res.status(200)
return res.send({
ok: 'you are authenticated as "' + req.remoteUser + '"',
ok: 'you are authenticated as "' + req.remote_user.name + '"',
})
})
app.put('/-/user/:org_couchdb_user/:_rev?/:revision?', function(req, res, next) {
if (req.remoteUser != null) {
if (req.remote_user.name != null) {
res.status(201)
return res.send({
ok: 'you are authenticated as "' + req.remoteUser + '"',
ok: 'you are authenticated as "' + req.remote_user.name + '"',
})
} else {
if (typeof(req.body.name) !== 'string' || typeof(req.body.password) !== 'string') {
@ -232,7 +231,7 @@ module.exports = function(config_hash) {
message: 'user/password is not found in request (npm issue?)',
}))
}
config.add_user(req.body.name, req.body.password, function(err) {
auth.add_user(req.body.name, req.body.password, function(err) {
if (err) {
if (err.status < 500 && err.message === 'this user already exists') {
// with npm registering is the same as logging in

View file

@ -40,59 +40,6 @@ module.exports.expect_json = function expect_json(req, res, next) {
next()
}
module.exports.basic_auth = function basic_auth(callback) {
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.remoteUserError = err.message
return _next()
}
var authorization = req.headers.authorization
if (req.remoteUser != null) return next()
if (authorization == null) return next()
var parts = authorization.split(' ')
if (parts.length !== 2) return next({
status: 400,
message: 'bad authorization header',
})
var scheme = parts[0]
, credentials = new Buffer(parts[1], 'base64').toString()
, index = credentials.indexOf(':')
if (scheme !== 'Basic' || index < 0) return next({
status: 400,
message: 'bad authorization header',
})
var user = credentials.slice(0, index)
, pass = credentials.slice(index + 1)
callback(user, pass, function(err, is_ok) {
if (err) return next(err)
if (is_ok) {
req.remoteUser = user
next()
} else {
next({
status: 403,
message: 'bad username/password, access denied',
})
}
})
}
}
module.exports.anti_loop = function(config) {
return function(req, res, next) {
if (req.headers.via != null) {
@ -187,7 +134,7 @@ module.exports.log_and_etagify = function(req, res, next) {
req.log.warn({
request: {method: req.method, url: req.url},
level: 35, // http
user: req.remoteUser,
user: req.remote_user.name,
status: res.statusCode,
error: res._sinopia_error,
bytes: {