1
0
mirror of https://github.com/verdaccio/verdaccio.git synced 2024-11-13 03:35:52 +01:00
verdaccio/lib/plugins/htpasswd/index.js

137 lines
3.6 KiB
JavaScript
Raw Normal View History

var fs = require('fs')
var Path = require('path')
var utils = require('./utils')
module.exports = HTPasswd
function HTPasswd(config, stuff) {
var self = Object.create(HTPasswd.prototype)
self._users = {}
// config for this module
self._config = config
// sinopia logger
self._logger = stuff.logger
// sinopia main config object
2017-01-22 22:23:30 +01:00
self._verdaccio_config = stuff.config
// all this "sinopia_config" stuff is for b/w compatibility only
self._maxusers = self._config.max_users
2017-01-22 22:23:30 +01:00
if (!self._maxusers) self._maxusers = self._verdaccio_config.max_users
// set maxusers to Infinity if not specified
if (!self._maxusers) self._maxusers = Infinity
self._last_time = null
var file = self._config.file
2017-01-22 22:23:30 +01:00
if (!file) file = self._verdaccio_config.users_file
if (!file) throw new Error('should specify "file" in config')
2017-01-22 22:23:30 +01:00
self._path = Path.resolve(Path.dirname(self._verdaccio_config.self_path), file)
return self
}
HTPasswd.prototype.authenticate = function (user, password, cb) {
var self = this
self._reload(function (err) {
if (err) return cb(err.code === 'ENOENT' ? null : err)
if (!self._users[user]) return cb(null, false)
if (!utils.verify_password(user, password, self._users[user])) return cb(null, false)
// authentication succeeded!
// return all usergroups this user has access to;
// (this particular package has no concept of usergroups, so just return user herself)
return cb(null, [user])
})
}
// hopefully race-condition-free way to add users:
// 1. lock file for writing (other processes can still read)
// 2. reload .htpasswd
// 3. write new data into .htpasswd.tmp
// 4. move .htpasswd.tmp to .htpasswd
// 5. reload .htpasswd
// 6. unlock file
HTPasswd.prototype.adduser = function (user, password, real_cb) {
var self = this
function sanity_check() {
var err = null
if (self._users[user]) {
err = Error('this user already exists')
} else if (Object.keys(self._users).length >= self._maxusers) {
err = Error('maximum amount of users reached')
}
if (err) err.status = 403
return err
}
// preliminary checks, just to ensure that file won't be reloaded if it's not needed
var s_err = sanity_check()
if (s_err) return real_cb(s_err, false)
utils.lock_and_read(self._path, function (err, res) {
var locked = false
// callback that cleans up lock first
function cb(err) {
if (locked) {
utils.unlock_file(self._path, function () {
// ignore any error from the unlock
real_cb(err, !err)
})
} else {
real_cb(err, !err)
}
}
if (!err) {
locked = true
}
// ignore ENOENT errors, we'll just create .htpasswd in that case
if (err && err.code !== 'ENOENT') return cb(err)
var body = (res || '').toString('utf8')
self._users = utils.parse_htpasswd(body)
// real checks, to prevent race conditions
var s_err = sanity_check()
if (s_err) return cb(s_err)
try {
body = utils.add_user_to_htpasswd(body, user, password)
} catch (err) {
return cb(err)
}
fs.writeFile(self._path, body, function (err) {
if (err) return cb(err)
self._reload(function () {
cb(null, true)
})
})
})
}
HTPasswd.prototype._reload = function (_callback) {
var self = this
fs.stat(self._path, function (err, stats) {
if (err) return _callback(err)
if (self._last_time === stats.mtime) return _callback()
self._last_time = stats.mtime
fs.readFile(self._path, 'utf8', function (err, buffer) {
if (err) return _callback(err)
self._users = utils.parse_htpasswd(buffer)
_callback()
});
});
}