From ff8a5e99ece088560179147838bbd8684334e96c Mon Sep 17 00:00:00 2001 From: Alex Kocharin Date: Mon, 21 Jul 2014 17:02:02 +0400 Subject: [PATCH] add user registration --- lib/config.js | 16 ++++++++++ lib/htpasswd.js | 78 +++++++++++++++++++++++++++++++++++++++++++++++-- lib/index.js | 25 +++++++++++++--- 3 files changed, 112 insertions(+), 7 deletions(-) diff --git a/lib/config.js b/lib/config.js index 34ada9d21..9602b5fae 100644 --- a/lib/config.js +++ b/lib/config.js @@ -2,6 +2,7 @@ var assert = require('assert') , crypto = require('crypto') , Path = require('path') , minimatch = require('minimatch') + , UError = require('./error').UserError , utils = require('./utils') // [[a, [b, c]], d] -> [a, b, c, d] @@ -168,6 +169,21 @@ Config.prototype.authenticate = function(user, password, cb) { }.bind(this)) } +Config.prototype.add_user = function(user, password, cb) { + 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 = { diff --git a/lib/htpasswd.js b/lib/htpasswd.js index 337a9df12..cc052e5ff 100644 --- a/lib/htpasswd.js +++ b/lib/htpasswd.js @@ -31,12 +31,85 @@ function verify_password(user, passwd, hash) { } } +function add_user_to_htpasswd(body, user, passwd) { + if (user != encodeURIComponent(user)) { + throw new UError({ + status: 409, + message: "username shouldn't contain non-uri-safe characters", + }) + } + + passwd = crypt3(passwd) + if (!passwd) { + passwd = '{SHA}' + crypto.createHash('sha1').update(passwd, 'binary').digest('base64') + } + var comment = 'autocreated ' + (new Date()).toJSON() + + var newline = user + ':' + passwd + ':' + comment + '\n' + if (body.length && body[body.length-1] != '\n') newline = '\n' + newline + return body + newline +} + module.exports = function(path) { var result = {} var users = {} var last_time - result.add_user = function(user, passwd, cb) { - // TODO + + // 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 + result.add_user = function(user, passwd, maxusers, real_cb) { + function sanity_check() { + if (users[user]) { + return new UError({ + status: 409, + message: 'this user already exists', + }) + } else if (Object.keys(users).length >= maxusers) { + return new UError({ + status: 409, + message: 'maximum amount of users reached', + }) + } + } + + // 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) + + fs_storage.lock_and_read(path, function(err, fd, res) { + // callback that cleanups fd first + function cb(err) { + if (!fd) return real_cb(err) + fs.close(fd, function() { + real_cb(err) + }) + } + + // ignore ENOENT errors, we'll just create .htpasswd in that case + if (err && err.code != 'ENOENT') return cb(err) + + var body = (res || '').toString('utf8') + users = parse_htpasswd(body) + + // real checks, to prevent race conditions + var s_err = sanity_check() + if (s_err) return cb(s_err) + + try { + body = add_user_to_htpasswd(body, user, passwd) + } catch(err) { + return cb(err) + } + fs_storage.write(path, body, function(err) { + if (err) return cb(err) + result.reload(cb) + }) + }) } result.verify = function(user, passwd) { if (!users[user]) return false @@ -45,7 +118,6 @@ module.exports = function(path) { result.reload = function(callback) { fs.open(path, 'r', function(err, fd) { if (err) return callback(err) - fs.fstat(fd, function(err, st) { if (err) return callback(err) if (last_time === st.mtime) return callback() diff --git a/lib/index.js b/lib/index.js index 964d48021..47696ada1 100644 --- a/lib/index.js +++ b/lib/index.js @@ -206,10 +206,27 @@ module.exports = function(config_hash) { }) app.put('/-/user/:org_couchdb_user', function(req, res, next) { - res.status(409) - return res.send({ - error: 'registration is not implemented', - }) + if (req.remoteUser != null) { + res.status(200) + return res.send({ + ok: 'you are authenticated as "' + req.remoteUser + '"', + }) + } else { + if (typeof(req.body.name) !== 'string' || typeof(req.body.password) !== 'string') { + res.status(409) + return res.send({ + ok: 'user/password is not found in request (npm issue?)', + }) + } + config.add_user(req.body.name, req.body.password, function(err) { + if (err) return next(err) + + res.status(201) + return res.send({ + ok: 'user "' + req.remoteUser + '" created', + }) + }) + } }) app.put('/-/user/:org_couchdb_user/-rev/*', function(req, res, next) {