mirror of
https://github.com/verdaccio/verdaccio.git
synced 2025-02-17 23:45:29 -05:00
Add package settings and authorization plugin system
This commit is contained in:
parent
3fe188df21
commit
76a1e8df80
7 changed files with 221 additions and 71 deletions
47
lib/auth.js
47
lib/auth.js
|
@ -1,9 +1,10 @@
|
|||
var assert = require('assert')
|
||||
var Crypto = require('crypto')
|
||||
var jju = require('jju')
|
||||
var Error = require('http-errors')
|
||||
var Path = require('path')
|
||||
var Logger = require('./logger')
|
||||
var assert = require('assert')
|
||||
var Crypto = require('crypto')
|
||||
var jju = require('jju')
|
||||
var Error = require('http-errors')
|
||||
var Path = require('path')
|
||||
var Logger = require('./logger')
|
||||
var load_plugins = require('./plugin-loader').load_plugins
|
||||
|
||||
module.exports = Auth
|
||||
|
||||
|
@ -13,9 +14,9 @@ function Auth(config) {
|
|||
self.logger = Logger.logger.child({ sub: 'auth' })
|
||||
self.secret = config.secret
|
||||
|
||||
var stuff = {
|
||||
var plugin_params = {
|
||||
config: config,
|
||||
logger: self.logger,
|
||||
logger: self.logger
|
||||
}
|
||||
|
||||
if (config.users_file) {
|
||||
|
@ -26,33 +27,7 @@ function Auth(config) {
|
|||
}
|
||||
}
|
||||
|
||||
self.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
|
||||
})
|
||||
self.plugins = load_plugins(config.auth, plugin_params, 'auth', ['authenticate'])
|
||||
|
||||
self.plugins.unshift({
|
||||
authenticate: function(user, password, cb) {
|
||||
|
@ -321,7 +296,7 @@ Auth.prototype.aes_decrypt = function(buf) {
|
|||
function AnonymousUser() {
|
||||
return {
|
||||
name: undefined,
|
||||
// groups without '$' are going to be deprecated eventually
|
||||
// groups without '$' are going to be deprecated eventually
|
||||
groups: [ '$all', '$anonymous', '@all', '@anonymous', 'all', 'undefined', 'anonymous' ],
|
||||
real_groups: [],
|
||||
}
|
||||
|
|
|
@ -11,9 +11,9 @@ var media = Middleware.media
|
|||
var validate_name = Middleware.validate_name
|
||||
var validate_pkg = Middleware.validate_package
|
||||
|
||||
module.exports = function(config, auth, storage) {
|
||||
module.exports = function(config, auth, storage, packages) {
|
||||
var app = express.Router()
|
||||
var can = Middleware.allow(config)
|
||||
var can = Middleware.allow(config, packages)
|
||||
|
||||
// validate all of these params as a package name
|
||||
// this might be too harsh, so ask if it causes trouble
|
||||
|
|
|
@ -11,9 +11,9 @@ var match = Middleware.match
|
|||
var validate_name = Middleware.validate_name
|
||||
var validate_pkg = Middleware.validate_package
|
||||
|
||||
module.exports = function(config, auth, storage) {
|
||||
module.exports = function(config, auth, storage, packages) {
|
||||
var app = express.Router()
|
||||
var can = Middleware.allow(config)
|
||||
var can = Middleware.allow(config, packages)
|
||||
|
||||
// validate all of these params as a package name
|
||||
// this might be too harsh, so ask if it causes trouble
|
||||
|
|
36
lib/index.js
36
lib/index.js
|
@ -1,22 +1,24 @@
|
|||
var express = require('express')
|
||||
var fs = require('fs')
|
||||
var Error = require('http-errors')
|
||||
var compression = require('compression')
|
||||
var Auth = require('./auth')
|
||||
var Logger = require('./logger')
|
||||
var Config = require('./config')
|
||||
var Middleware = require('./middleware')
|
||||
var Cats = require('./status-cats')
|
||||
var Storage = require('./storage')
|
||||
var express = require('express')
|
||||
var fs = require('fs')
|
||||
var Error = require('http-errors')
|
||||
var compression = require('compression')
|
||||
var Auth = require('./auth')
|
||||
var Logger = require('./logger')
|
||||
var Config = require('./config')
|
||||
var Middleware = require('./middleware')
|
||||
var Cats = require('./status-cats')
|
||||
var Storage = require('./storage')
|
||||
var PackageProvider = require('./packages')
|
||||
|
||||
module.exports = function(config_hash) {
|
||||
Logger.setup(config_hash.logs)
|
||||
|
||||
var config = Config(config_hash)
|
||||
var storage = Storage(config)
|
||||
var auth = Auth(config)
|
||||
var app = express()
|
||||
var can = Middleware.allow(config)
|
||||
var config = Config(config_hash)
|
||||
var storage = Storage(config)
|
||||
var auth = Auth(config)
|
||||
var packages = PackageProvider(config)
|
||||
var app = express()
|
||||
var can = Middleware.allow(config, packages)
|
||||
|
||||
// run in production mode by default, just in case
|
||||
// it shouldn't make any difference anyway
|
||||
|
@ -87,14 +89,14 @@ module.exports = function(config_hash) {
|
|||
})
|
||||
}
|
||||
|
||||
app.use(require('./index-api')(config, auth, storage))
|
||||
app.use(require('./index-api')(config, auth, storage, packages))
|
||||
|
||||
if (config.web && config.web.enable === false) {
|
||||
app.get('/', function(req, res, next) {
|
||||
next( Error[404]('web interface is disabled in the config file') )
|
||||
})
|
||||
} else {
|
||||
app.use(require('./index-web')(config, auth, storage))
|
||||
app.use(require('./index-web')(config, auth, storage, packages))
|
||||
}
|
||||
|
||||
app.get('/*', function(req, res, next) {
|
||||
|
|
|
@ -76,24 +76,30 @@ function md5sum(data) {
|
|||
return crypto.createHash('md5').update(data).digest('hex')
|
||||
}
|
||||
|
||||
module.exports.allow = function(config) {
|
||||
module.exports.allow = function(config, packages) {
|
||||
return function(action) {
|
||||
return function(req, res, next) {
|
||||
if (config['allow_'+action](req.params.package, req.remote_user)) {
|
||||
next()
|
||||
} else {
|
||||
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'?"
|
||||
}
|
||||
next( Error[403](message) )
|
||||
req.pause();
|
||||
packages['allow_'+action](req.params.package, req.remote_user, function(error, is_allowed) {
|
||||
req.resume();
|
||||
if(error) {
|
||||
next(error)
|
||||
} else if(is_allowed) {
|
||||
next()
|
||||
} else {
|
||||
next( Error[403]('user ' + req.remote_user.name
|
||||
+ ' not allowed to ' + action + ' it') )
|
||||
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'?"
|
||||
}
|
||||
next( Error[403](message) )
|
||||
} else {
|
||||
next( Error[403]('user ' + req.remote_user.name
|
||||
+ ' not allowed to ' + action + ' it') )
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
126
lib/packages.js
Normal file
126
lib/packages.js
Normal file
|
@ -0,0 +1,126 @@
|
|||
var Logger = require('./logger')
|
||||
var load_plugins = require('./plugin-loader').load_plugins
|
||||
var async = require('async')
|
||||
|
||||
module.exports = PackageProvider
|
||||
|
||||
// provides configuration and access control information for packages
|
||||
function PackageProvider(config) {
|
||||
var self = Object.create(PackageProvider.prototype)
|
||||
self.config = config
|
||||
self.logger = Logger.logger.child({ sub: 'packages' })
|
||||
|
||||
var plugin_params = {
|
||||
config: config,
|
||||
logger: self.logger
|
||||
}
|
||||
|
||||
self.plugins = load_plugins(config.package_provider, plugin_params, 'package_provider', ['allow_access'])
|
||||
|
||||
self.plugins.push(new ConfigPackageProvider({}, plugin_params))
|
||||
self.plugins.push(new DenyPackageProvider({}, plugin_params))
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
function check_plugin_result(function_name, package, arg, cb) {
|
||||
var current_result
|
||||
async.eachSeries(this.plugins, function(plugin, next) {
|
||||
if(current_result === undefined && typeof plugin[function_name] === 'function') {
|
||||
plugin[function_name](package, arg, function(error, result) {
|
||||
if(error) {
|
||||
next(error)
|
||||
} else {
|
||||
current_result = result
|
||||
next()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
}, function(error) {
|
||||
if(error) {
|
||||
cb(error)
|
||||
} else {
|
||||
cb(null, current_result)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
PackageProvider.prototype.allow_access = function(package, user, cb) {
|
||||
check_plugin_result.call(this, 'allow_access', package, user, cb)
|
||||
}
|
||||
|
||||
PackageProvider.prototype.allow_publish = function(package, user, cb) {
|
||||
check_plugin_result.call(this, 'allow_publish', package, user, cb)
|
||||
}
|
||||
|
||||
PackageProvider.prototype.proxy_access = function(package, user, cb) {
|
||||
check_plugin_result.call(this, 'proxy_access', package, user, cb)
|
||||
}
|
||||
|
||||
PackageProvider.prototype.get_package_setting = function(package, user, cb) {
|
||||
check_plugin_result.call(this, 'get_package_setting', package, user, cb)
|
||||
}
|
||||
|
||||
|
||||
// default fallthrough package provider engine to read packages from config file
|
||||
function ConfigPackageProvider(settings, params) {
|
||||
var self = Object.create(ConfigPackageProvider.prototype)
|
||||
self.config = params.config
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
ConfigPackageProvider.prototype.allow_access = function(package, user, cb) {
|
||||
var self = this
|
||||
setImmediate(function() {
|
||||
cb(null, self.config.allow_access(package, user))
|
||||
})
|
||||
}
|
||||
|
||||
ConfigPackageProvider.prototype.allow_publish = function(package, user, cb) {
|
||||
var self = this
|
||||
setImmediate(function() {
|
||||
cb(null, self.config.allow_publish(package, user))
|
||||
})
|
||||
}
|
||||
|
||||
ConfigPackageProvider.prototype.proxy_access = function(package, uplink, cb) {
|
||||
var self = this
|
||||
setImmediate(function() {
|
||||
cb(null, self.config.proxy_access(package, uplink))
|
||||
})
|
||||
}
|
||||
|
||||
ConfigPackageProvider.prototype.get_package_setting = function(package, setting, cb) {
|
||||
var self = this
|
||||
setImmediate(function() {
|
||||
cb(null, self.config.get_package_setting(package, setting))
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// package provider to deny all access, used as default after everything else falls through
|
||||
function DenyPackageProvider() {
|
||||
var self = Object.create(DenyPackageProvider.prototype)
|
||||
return self
|
||||
}
|
||||
|
||||
DenyPackageProvider.prototype.allow_access = function(package, user, cb) {
|
||||
setImmediate(function() {
|
||||
cb(null, false)
|
||||
})
|
||||
}
|
||||
|
||||
DenyPackageProvider.prototype.allow_publish = function(package, user, cb) {
|
||||
setImmediate(function() {
|
||||
cb(null, false)
|
||||
})
|
||||
}
|
||||
|
||||
DenyPackageProvider.prototype.proxy_access = function(package, uplink, cb) {
|
||||
setImmediate(function() {
|
||||
cb(null, false)
|
||||
})
|
||||
}
|
41
lib/plugin-loader.js
Normal file
41
lib/plugin-loader.js
Normal file
|
@ -0,0 +1,41 @@
|
|||
|
||||
function load_plugins(plugin_configs, params, type, required_functions) {
|
||||
var plugins = Object.keys(plugin_configs || {}).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 + '" plugin not found\n'
|
||||
+ 'try "npm install sinopia-' + p + '"')
|
||||
}
|
||||
|
||||
if (typeof(plugin) !== 'function')
|
||||
throw Error('"' + name + '" doesn\'t look like a valid plugin')
|
||||
|
||||
plugin = plugin(plugin_configs[p], params)
|
||||
|
||||
if (plugin == null)
|
||||
throw Error('"' + name + '" doesn\'t look like a valid plugin')
|
||||
|
||||
if(required_functions) {
|
||||
for(var i = 0; i < required_functions.length; ++i) {
|
||||
if(typeof plugin[required_functions[i]] !== 'function')
|
||||
throw Error('"' + name + '" doesn\'t look like a valid ' + type + ' plugin')
|
||||
}
|
||||
}
|
||||
|
||||
return plugin
|
||||
})
|
||||
|
||||
return plugins
|
||||
}
|
||||
|
||||
exports.load_plugins = load_plugins;
|
Loading…
Add table
Reference in a new issue