2015-07-20 00:09:47 -07:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
|
|
exports = module.exports = {
|
|
|
|
|
UserError: UserError,
|
|
|
|
|
|
|
|
|
|
list: listUsers,
|
|
|
|
|
create: createUser,
|
2016-06-07 09:59:29 -07:00
|
|
|
count: count,
|
2015-07-20 00:09:47 -07:00
|
|
|
verify: verify,
|
2016-04-05 16:27:04 +02:00
|
|
|
verifyWithUsername: verifyWithUsername,
|
2015-07-20 00:09:47 -07:00
|
|
|
verifyWithEmail: verifyWithEmail,
|
|
|
|
|
remove: removeUser,
|
|
|
|
|
get: getUser,
|
|
|
|
|
getByResetToken: getByResetToken,
|
2016-01-15 16:04:33 +01:00
|
|
|
getAllAdmins: getAllAdmins,
|
2015-07-20 00:09:47 -07:00
|
|
|
resetPasswordByIdentifier: resetPasswordByIdentifier,
|
|
|
|
|
setPassword: setPassword,
|
|
|
|
|
update: updateUser,
|
2016-01-13 12:28:38 -08:00
|
|
|
createOwner: createOwner,
|
2016-01-18 15:16:18 +01:00
|
|
|
getOwner: getOwner,
|
2016-02-09 15:47:02 -08:00
|
|
|
sendInvite: sendInvite,
|
2016-05-06 13:56:26 +02:00
|
|
|
setGroups: setGroups,
|
2016-09-21 15:34:58 -07:00
|
|
|
setAliases: setAliases,
|
|
|
|
|
getAliases: getAliases,
|
2016-05-06 13:56:26 +02:00
|
|
|
setShowTutorial: setShowTutorial
|
2015-07-20 00:09:47 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var assert = require('assert'),
|
2016-06-03 14:47:06 +02:00
|
|
|
clients = require('./clients.js'),
|
2015-07-20 00:09:47 -07:00
|
|
|
crypto = require('crypto'),
|
2016-08-31 21:20:48 -07:00
|
|
|
config = require('./config.js'),
|
2016-07-12 10:07:55 -07:00
|
|
|
constants = require('./constants.js'),
|
2016-05-29 23:15:55 -07:00
|
|
|
debug = require('debug')('box:user'),
|
2015-07-20 00:09:47 -07:00
|
|
|
DatabaseError = require('./databaseerror.js'),
|
2016-05-01 20:01:34 -07:00
|
|
|
eventlog = require('./eventlog.js'),
|
2016-02-08 15:16:59 -08:00
|
|
|
groups = require('./groups.js'),
|
2016-02-09 15:47:02 -08:00
|
|
|
GroupError = groups.GroupError,
|
2015-07-20 00:09:47 -07:00
|
|
|
hat = require('hat'),
|
2016-02-08 15:15:42 -08:00
|
|
|
mailer = require('./mailer.js'),
|
2016-09-22 15:55:18 -07:00
|
|
|
mailboxdb = require('./mailboxdb.js'),
|
2016-07-12 10:07:55 -07:00
|
|
|
safe = require('safetydance'),
|
2015-07-20 00:09:47 -07:00
|
|
|
tokendb = require('./tokendb.js'),
|
2016-02-08 15:15:42 -08:00
|
|
|
userdb = require('./userdb.js'),
|
2015-07-20 00:09:47 -07:00
|
|
|
util = require('util'),
|
2016-04-01 16:48:34 +02:00
|
|
|
uuid = require('node-uuid'),
|
2016-02-08 15:15:42 -08:00
|
|
|
validatePassword = require('./password.js').validate,
|
2015-07-20 00:09:47 -07:00
|
|
|
validator = require('validator'),
|
|
|
|
|
_ = require('underscore');
|
|
|
|
|
|
|
|
|
|
var CRYPTO_SALT_SIZE = 64; // 512-bit salt
|
|
|
|
|
var CRYPTO_ITERATIONS = 10000; // iterations
|
|
|
|
|
var CRYPTO_KEY_LENGTH = 512; // bits
|
|
|
|
|
|
2016-09-22 15:55:18 -07:00
|
|
|
function asyncIf(cond, func, next) {
|
|
|
|
|
if (!cond) return next();
|
|
|
|
|
|
|
|
|
|
func(next);
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-20 00:09:47 -07:00
|
|
|
// http://dustinsenos.com/articles/customErrorsInNode
|
|
|
|
|
// http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi
|
|
|
|
|
function UserError(reason, errorOrMessage) {
|
|
|
|
|
assert.strictEqual(typeof reason, 'string');
|
|
|
|
|
assert(errorOrMessage instanceof Error || typeof errorOrMessage === 'string' || typeof errorOrMessage === 'undefined');
|
|
|
|
|
|
|
|
|
|
Error.call(this);
|
|
|
|
|
Error.captureStackTrace(this, this.constructor);
|
|
|
|
|
|
|
|
|
|
this.name = this.constructor.name;
|
|
|
|
|
this.reason = reason;
|
|
|
|
|
if (typeof errorOrMessage === 'undefined') {
|
|
|
|
|
this.message = reason;
|
|
|
|
|
} else if (typeof errorOrMessage === 'string') {
|
|
|
|
|
this.message = errorOrMessage;
|
|
|
|
|
} else {
|
|
|
|
|
this.message = 'Internal error';
|
|
|
|
|
this.nestedError = errorOrMessage;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
util.inherits(UserError, Error);
|
|
|
|
|
UserError.INTERNAL_ERROR = 'Internal Error';
|
|
|
|
|
UserError.ALREADY_EXISTS = 'Already Exists';
|
|
|
|
|
UserError.NOT_FOUND = 'Not Found';
|
|
|
|
|
UserError.WRONG_PASSWORD = 'Wrong User or Password';
|
|
|
|
|
UserError.BAD_FIELD = 'Bad field';
|
|
|
|
|
UserError.BAD_TOKEN = 'Bad token';
|
|
|
|
|
|
|
|
|
|
function validateUsername(username) {
|
|
|
|
|
assert.strictEqual(typeof username, 'string');
|
2016-04-13 16:50:20 -07:00
|
|
|
// https://github.com/gogits/gogs/blob/52c8f691630548fe091d30bcfe8164545a05d3d5/models/repo.go#L393
|
2016-04-14 13:34:41 -07:00
|
|
|
// admin@fqdn is also reservd for sending emails
|
2016-05-17 12:47:10 -07:00
|
|
|
var RESERVED_USERNAMES = [ 'admin', 'no-reply', 'postmaster', 'mailer-daemon' ]; // apps like wordpress, gogs don't like these
|
2015-07-20 00:09:47 -07:00
|
|
|
|
2016-04-01 16:48:34 +02:00
|
|
|
// allow empty usernames
|
|
|
|
|
if (username === '') return null;
|
|
|
|
|
|
2016-06-02 00:06:54 -07:00
|
|
|
if (username.length <= 1) return new UserError(UserError.BAD_FIELD, 'Username must be atleast 2 chars');
|
|
|
|
|
if (username.length > 256) return new UserError(UserError.BAD_FIELD, 'Username too long');
|
2015-07-20 00:09:47 -07:00
|
|
|
|
2016-06-02 00:06:54 -07:00
|
|
|
if (RESERVED_USERNAMES.indexOf(username) !== -1) return new UserError(UserError.BAD_FIELD, 'Username is reserved');
|
2016-04-13 16:50:20 -07:00
|
|
|
|
2016-05-25 21:36:20 -07:00
|
|
|
// +/- can be tricky in emails
|
2016-06-02 00:06:54 -07:00
|
|
|
if (/[^a-zA-Z0-9.]/.test(username)) return new UserError(UserError.BAD_FIELD, 'Username can only contain alphanumerals and dot');
|
2016-05-25 21:36:20 -07:00
|
|
|
|
2016-05-30 01:32:18 -07:00
|
|
|
// app emails are sent using the .app suffix
|
2016-06-02 00:06:54 -07:00
|
|
|
if (username.indexOf('.app') !== -1) return new UserError(UserError.BAD_FIELD, 'Username pattern is reserved for apps');
|
2016-05-18 21:45:02 -07:00
|
|
|
|
2015-07-20 00:09:47 -07:00
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function validateEmail(email) {
|
|
|
|
|
assert.strictEqual(typeof email, 'string');
|
|
|
|
|
|
2016-06-02 00:06:54 -07:00
|
|
|
if (!validator.isEmail(email)) return new UserError(UserError.BAD_FIELD, 'Invalid email');
|
2015-07-20 00:09:47 -07:00
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function validateToken(token) {
|
|
|
|
|
assert.strictEqual(typeof token, 'string');
|
|
|
|
|
|
|
|
|
|
if (token.length !== 64) return new UserError(UserError.BAD_TOKEN, 'Invalid token'); // 256-bit hex coded token
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2016-01-19 23:34:49 -08:00
|
|
|
function validateDisplayName(name) {
|
|
|
|
|
assert.strictEqual(typeof name, 'string');
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2016-05-01 20:01:34 -07:00
|
|
|
function createUser(username, password, email, displayName, auditSource, options, callback) {
|
2015-07-20 00:09:47 -07:00
|
|
|
assert.strictEqual(typeof username, 'string');
|
|
|
|
|
assert.strictEqual(typeof password, 'string');
|
|
|
|
|
assert.strictEqual(typeof email, 'string');
|
2016-01-19 23:34:49 -08:00
|
|
|
assert.strictEqual(typeof displayName, 'string');
|
2016-05-01 20:01:34 -07:00
|
|
|
assert.strictEqual(typeof auditSource, 'object');
|
2016-02-08 21:05:02 -08:00
|
|
|
|
|
|
|
|
if (typeof options === 'function') {
|
|
|
|
|
callback = options;
|
|
|
|
|
options = null;
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-08 21:17:21 -08:00
|
|
|
var invitor = options && options.invitor ? options.invitor : null,
|
|
|
|
|
sendInvite = options && options.sendInvite ? true : false,
|
|
|
|
|
owner = options && options.owner ? true : false;
|
2015-07-20 00:09:47 -07:00
|
|
|
|
2016-04-14 16:25:46 +02:00
|
|
|
// We store usernames and email in lowercase
|
|
|
|
|
username = username.toLowerCase();
|
|
|
|
|
email = email.toLowerCase();
|
|
|
|
|
|
2015-07-20 00:09:47 -07:00
|
|
|
var error = validateUsername(username);
|
|
|
|
|
if (error) return callback(error);
|
|
|
|
|
|
|
|
|
|
error = validatePassword(password);
|
2016-06-02 00:06:54 -07:00
|
|
|
if (error) return callback(new UserError(UserError.BAD_FIELD, error.message));
|
2015-07-20 00:09:47 -07:00
|
|
|
|
|
|
|
|
error = validateEmail(email);
|
|
|
|
|
if (error) return callback(error);
|
|
|
|
|
|
2016-01-19 23:34:49 -08:00
|
|
|
error = validateDisplayName(displayName);
|
|
|
|
|
if (error) return callback(error);
|
|
|
|
|
|
2015-07-20 00:09:47 -07:00
|
|
|
crypto.randomBytes(CRYPTO_SALT_SIZE, function (error, salt) {
|
|
|
|
|
if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error));
|
|
|
|
|
|
|
|
|
|
crypto.pbkdf2(password, salt, CRYPTO_ITERATIONS, CRYPTO_KEY_LENGTH, function (error, derivedKey) {
|
|
|
|
|
if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error));
|
|
|
|
|
|
2015-09-17 13:50:20 -07:00
|
|
|
var now = (new Date()).toISOString();
|
2015-07-20 00:09:47 -07:00
|
|
|
var user = {
|
2016-04-01 16:48:34 +02:00
|
|
|
id: 'uid-' + uuid.v4(),
|
2015-07-20 00:09:47 -07:00
|
|
|
username: username,
|
|
|
|
|
email: email,
|
|
|
|
|
password: new Buffer(derivedKey, 'binary').toString('hex'),
|
|
|
|
|
salt: salt.toString('hex'),
|
|
|
|
|
createdAt: now,
|
|
|
|
|
modifiedAt: now,
|
2016-01-19 12:40:50 +01:00
|
|
|
resetToken: hat(256),
|
2016-05-06 13:56:26 +02:00
|
|
|
displayName: displayName,
|
|
|
|
|
showTutorial: true
|
2015-07-20 00:09:47 -07:00
|
|
|
};
|
|
|
|
|
|
2016-09-22 15:55:18 -07:00
|
|
|
asyncIf(!!username, mailboxdb.add.bind(null, username, user.id /* owner */, mailboxdb.TYPE_USER), function (error) {
|
2016-06-02 15:39:21 +02:00
|
|
|
if (error && error.reason === DatabaseError.ALREADY_EXISTS) return callback(new UserError(UserError.ALREADY_EXISTS, error.message));
|
2015-07-20 00:09:47 -07:00
|
|
|
if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error));
|
|
|
|
|
|
2016-09-22 15:55:18 -07:00
|
|
|
userdb.add(user.id, user, function (error) {
|
|
|
|
|
if (error && error.reason === DatabaseError.ALREADY_EXISTS) return callback(new UserError(UserError.ALREADY_EXISTS, error.message));
|
|
|
|
|
if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error));
|
2016-05-01 20:01:34 -07:00
|
|
|
|
2016-09-22 15:55:18 -07:00
|
|
|
eventlog.add(eventlog.ACTION_USER_ADD, auditSource, { userId: user.id, email: user.email });
|
2015-07-20 00:09:47 -07:00
|
|
|
|
2016-09-22 15:55:18 -07:00
|
|
|
callback(null, user);
|
|
|
|
|
|
|
|
|
|
if (!owner) mailer.userAdded(user, sendInvite);
|
|
|
|
|
if (sendInvite) mailer.sendInvite(user, invitor);
|
|
|
|
|
});
|
2015-07-20 00:09:47 -07:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2016-07-12 10:07:55 -07:00
|
|
|
// returns true if ghost user was matched
|
|
|
|
|
function verifyGhost(username, password) {
|
|
|
|
|
assert.strictEqual(typeof username, 'string');
|
|
|
|
|
assert.strictEqual(typeof password, 'string');
|
|
|
|
|
|
2016-07-12 11:37:44 -07:00
|
|
|
var ghostData = safe.require(constants.GHOST_USER_FILE);
|
2016-07-12 10:07:55 -07:00
|
|
|
if (!ghostData) return false;
|
|
|
|
|
|
|
|
|
|
if (username in ghostData && ghostData[username] === password) {
|
|
|
|
|
debug('verifyGhost: matched ghost user');
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2016-04-05 16:27:04 +02:00
|
|
|
function verify(userId, password, callback) {
|
|
|
|
|
assert.strictEqual(typeof userId, 'string');
|
|
|
|
|
assert.strictEqual(typeof password, 'string');
|
|
|
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
|
|
2016-09-26 16:28:52 +02:00
|
|
|
getUser(userId, function (error, user) {
|
|
|
|
|
if (error) return callback(error);
|
2016-04-05 16:27:04 +02:00
|
|
|
|
2016-07-12 10:07:55 -07:00
|
|
|
if (verifyGhost(user.username, password)) return callback(null, user);
|
|
|
|
|
|
2016-04-05 16:27:04 +02:00
|
|
|
var saltBinary = new Buffer(user.salt, 'hex');
|
|
|
|
|
crypto.pbkdf2(password, saltBinary, CRYPTO_ITERATIONS, CRYPTO_KEY_LENGTH, function (error, derivedKey) {
|
|
|
|
|
if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error));
|
|
|
|
|
|
|
|
|
|
var derivedKeyHex = new Buffer(derivedKey, 'binary').toString('hex');
|
|
|
|
|
if (derivedKeyHex !== user.password) return callback(new UserError(UserError.WRONG_PASSWORD));
|
|
|
|
|
|
|
|
|
|
callback(null, user);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function verifyWithUsername(username, password, callback) {
|
2015-07-20 00:09:47 -07:00
|
|
|
assert.strictEqual(typeof username, 'string');
|
|
|
|
|
assert.strictEqual(typeof password, 'string');
|
|
|
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
|
|
2016-04-14 16:25:46 +02:00
|
|
|
userdb.getByUsername(username.toLowerCase(), function (error, user) {
|
2015-07-20 00:09:47 -07:00
|
|
|
if (error && error.reason == DatabaseError.NOT_FOUND) return callback(new UserError(UserError.NOT_FOUND));
|
|
|
|
|
if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error));
|
|
|
|
|
|
2016-06-23 11:58:05 +02:00
|
|
|
verify(user.id, password, callback);
|
2015-07-20 00:09:47 -07:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function verifyWithEmail(email, password, callback) {
|
|
|
|
|
assert.strictEqual(typeof email, 'string');
|
|
|
|
|
assert.strictEqual(typeof password, 'string');
|
|
|
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
|
|
2016-04-14 16:25:46 +02:00
|
|
|
userdb.getByEmail(email.toLowerCase(), function (error, user) {
|
2015-07-20 00:09:47 -07:00
|
|
|
if (error && error.reason == DatabaseError.NOT_FOUND) return callback(new UserError(UserError.NOT_FOUND));
|
|
|
|
|
if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error));
|
|
|
|
|
|
2016-06-23 11:58:05 +02:00
|
|
|
verify(user.id, password, callback);
|
2015-07-20 00:09:47 -07:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2016-06-02 22:25:48 -07:00
|
|
|
function removeUser(userId, auditSource, callback) {
|
|
|
|
|
assert.strictEqual(typeof userId, 'string');
|
2016-05-01 20:09:31 -07:00
|
|
|
assert.strictEqual(typeof auditSource, 'object');
|
2015-07-20 00:09:47 -07:00
|
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
|
|
2016-06-02 22:25:48 -07:00
|
|
|
getUser(userId, function (error, user) {
|
|
|
|
|
if (error) return callback(error);
|
2015-07-20 00:09:47 -07:00
|
|
|
|
2016-08-31 23:41:28 -07:00
|
|
|
if (config.isDemo() && user.username === constants.DEMO_USERNAME) return callback(new UserError(UserError.BAD_FIELD, 'Not allowed in demo mode'));
|
|
|
|
|
|
2016-06-02 22:25:48 -07:00
|
|
|
userdb.del(userId, function (error) {
|
|
|
|
|
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new UserError(UserError.NOT_FOUND));
|
|
|
|
|
if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error));
|
2016-05-01 20:09:31 -07:00
|
|
|
|
2016-06-02 22:25:48 -07:00
|
|
|
eventlog.add(eventlog.ACTION_USER_REMOVE, auditSource, { userId: userId });
|
2015-07-20 00:09:47 -07:00
|
|
|
|
2016-09-26 00:14:03 -07:00
|
|
|
asyncIf(!!user.username, mailboxdb.delByOwnerId.bind(null, user.id), callback);
|
2016-06-02 22:25:48 -07:00
|
|
|
|
|
|
|
|
mailer.userRemoved(user);
|
|
|
|
|
});
|
2015-07-20 00:09:47 -07:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-09 09:25:17 -08:00
|
|
|
function listUsers(callback) {
|
|
|
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
|
|
2016-06-03 00:04:17 -07:00
|
|
|
userdb.getAllWithGroupIds(function (error, results) {
|
2016-02-09 09:25:17 -08:00
|
|
|
if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error));
|
|
|
|
|
|
2016-06-03 00:04:17 -07:00
|
|
|
results.forEach(function (result) {
|
2016-09-20 15:07:11 -07:00
|
|
|
result.admin = result.groupIds.indexOf(constants.ADMIN_GROUP_ID) !== -1;
|
2016-02-09 09:25:17 -08:00
|
|
|
});
|
2016-09-26 16:28:52 +02:00
|
|
|
|
2016-06-03 00:04:17 -07:00
|
|
|
return callback(null, results);
|
2016-02-09 09:25:17 -08:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2016-06-07 09:59:29 -07:00
|
|
|
function count(callback) {
|
|
|
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
|
|
|
|
|
|
userdb.count(function (error, count) {
|
|
|
|
|
if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error));
|
|
|
|
|
|
|
|
|
|
callback(null, count);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-20 00:09:47 -07:00
|
|
|
function getUser(userId, callback) {
|
|
|
|
|
assert.strictEqual(typeof userId, 'string');
|
|
|
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
|
|
|
|
|
|
userdb.get(userId, function (error, result) {
|
|
|
|
|
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new UserError(UserError.NOT_FOUND));
|
|
|
|
|
if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error));
|
|
|
|
|
|
2016-02-08 20:38:50 -08:00
|
|
|
groups.getGroups(userId, function (error, groupIds) {
|
|
|
|
|
if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error));
|
|
|
|
|
|
|
|
|
|
result.groupIds = groupIds;
|
2016-09-20 15:07:11 -07:00
|
|
|
result.admin = groupIds.indexOf(constants.ADMIN_GROUP_ID) !== -1;
|
2016-02-08 20:38:50 -08:00
|
|
|
|
|
|
|
|
return callback(null, result);
|
|
|
|
|
});
|
2015-07-20 00:09:47 -07:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getByResetToken(resetToken, callback) {
|
|
|
|
|
assert.strictEqual(typeof resetToken, 'string');
|
|
|
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
|
|
|
|
|
|
var error = validateToken(resetToken);
|
|
|
|
|
if (error) return callback(error);
|
|
|
|
|
|
|
|
|
|
userdb.getByResetToken(resetToken, function (error, result) {
|
|
|
|
|
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new UserError(UserError.NOT_FOUND));
|
|
|
|
|
if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error));
|
|
|
|
|
|
|
|
|
|
callback(null, result);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2016-06-02 23:53:06 -07:00
|
|
|
function updateUser(userId, data, auditSource, callback) {
|
2015-07-20 00:09:47 -07:00
|
|
|
assert.strictEqual(typeof userId, 'string');
|
2016-06-02 23:53:06 -07:00
|
|
|
assert.strictEqual(typeof data, 'object');
|
2016-05-01 20:09:31 -07:00
|
|
|
assert.strictEqual(typeof auditSource, 'object');
|
2015-07-20 00:09:47 -07:00
|
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
|
|
2016-06-02 23:53:06 -07:00
|
|
|
var error;
|
|
|
|
|
data = _.pick(data, 'email', 'displayName', 'username');
|
2016-04-14 16:25:46 +02:00
|
|
|
|
2016-06-02 23:53:06 -07:00
|
|
|
if (_.isEmpty(data)) return callback();
|
2015-07-20 00:09:47 -07:00
|
|
|
|
2016-06-02 23:53:06 -07:00
|
|
|
if (data.username) {
|
|
|
|
|
data.username = data.username.toLowerCase();
|
|
|
|
|
error = validateUsername(data.username);
|
|
|
|
|
if (error) return callback(error);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (data.email) {
|
|
|
|
|
data.email = data.email.toLowerCase();
|
|
|
|
|
error = validateEmail(data.email);
|
|
|
|
|
if (error) return callback(error);
|
|
|
|
|
}
|
2015-07-20 00:09:47 -07:00
|
|
|
|
2016-09-25 23:51:39 -07:00
|
|
|
userdb.get(userId, function (error, user) {
|
|
|
|
|
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new UserError(UserError.NOT_FOUND));
|
2015-07-20 00:09:47 -07:00
|
|
|
if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error));
|
2016-05-01 20:09:31 -07:00
|
|
|
|
2016-09-25 23:51:39 -07:00
|
|
|
asyncIf(data.username && user.username !== data.username, mailboxdb.add.bind(null, data.username, userId /* owner */, mailboxdb.TYPE_USER), function (error) {
|
2016-09-22 15:55:18 -07:00
|
|
|
if (error && error.reason === DatabaseError.ALREADY_EXISTS) return callback(new UserError(UserError.ALREADY_EXISTS, error.message));
|
|
|
|
|
if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error));
|
2016-05-01 20:09:31 -07:00
|
|
|
|
2016-09-25 23:51:39 -07:00
|
|
|
userdb.update(userId, data, function (error) {
|
|
|
|
|
if (error && error.reason === DatabaseError.ALREADY_EXISTS) return callback(new UserError(UserError.ALREADY_EXISTS, error.message));
|
|
|
|
|
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new UserError(UserError.NOT_FOUND, error));
|
|
|
|
|
if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error));
|
|
|
|
|
|
|
|
|
|
eventlog.add(eventlog.ACTION_USER_UPDATE, auditSource, { userId: userId });
|
2016-09-22 15:55:18 -07:00
|
|
|
|
2016-09-25 23:51:39 -07:00
|
|
|
// delete old mailbox
|
2016-09-26 11:05:13 -07:00
|
|
|
asyncIf(data.username && user.username && user.username !== data.username, mailboxdb.del.bind(null, user.username), callback);
|
2016-09-25 23:51:39 -07:00
|
|
|
});
|
2016-09-22 15:55:18 -07:00
|
|
|
});
|
2016-09-21 15:34:58 -07:00
|
|
|
});
|
2015-07-20 00:09:47 -07:00
|
|
|
}
|
|
|
|
|
|
2016-02-09 15:47:02 -08:00
|
|
|
function setGroups(userId, groupIds, callback) {
|
|
|
|
|
assert.strictEqual(typeof userId, 'string');
|
|
|
|
|
assert(Array.isArray(groupIds));
|
|
|
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
|
|
2016-05-04 13:54:32 +02:00
|
|
|
groups.getGroups(userId, function (error, oldGroupIds) {
|
|
|
|
|
if (error && error.reason !== GroupError.NOT_FOUND) return callback(new UserError(UserError.INTERNAL_ERROR, error));
|
2016-02-09 15:47:02 -08:00
|
|
|
|
2016-05-04 13:54:32 +02:00
|
|
|
oldGroupIds = oldGroupIds || [];
|
2016-03-09 06:15:02 +01:00
|
|
|
|
2016-05-04 13:54:32 +02:00
|
|
|
groups.setGroups(userId, groupIds, function (error) {
|
|
|
|
|
if (error && error.reason === GroupError.NOT_FOUND) return callback(new UserError(UserError.NOT_FOUND, 'One or more groups not found'));
|
|
|
|
|
if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error));
|
|
|
|
|
|
2016-09-20 15:07:11 -07:00
|
|
|
var isAdmin = groupIds.some(function (g) { return g === constants.ADMIN_GROUP_ID; });
|
|
|
|
|
var wasAdmin = oldGroupIds.some(function (g) { return g === constants.ADMIN_GROUP_ID; });
|
2016-03-09 06:15:02 +01:00
|
|
|
|
2016-05-04 13:54:32 +02:00
|
|
|
if ((isAdmin && !wasAdmin) || (!isAdmin && wasAdmin)) {
|
|
|
|
|
getUser(userId, function (error, result) {
|
|
|
|
|
if (error) return console.error('Failed to send admin change mail.', error);
|
|
|
|
|
|
|
|
|
|
mailer.adminChanged(result, isAdmin);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
callback(null);
|
|
|
|
|
});
|
2016-02-09 15:47:02 -08:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2016-01-15 16:04:33 +01:00
|
|
|
function getAllAdmins(callback) {
|
|
|
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
|
|
|
|
|
|
userdb.getAllAdmins(function (error, admins) {
|
|
|
|
|
if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error));
|
|
|
|
|
callback(null, admins);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-20 00:09:47 -07:00
|
|
|
function resetPasswordByIdentifier(identifier, callback) {
|
|
|
|
|
assert.strictEqual(typeof identifier, 'string');
|
|
|
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
|
|
|
|
|
|
var getter;
|
|
|
|
|
if (identifier.indexOf('@') === -1) getter = userdb.getByUsername;
|
|
|
|
|
else getter = userdb.getByEmail;
|
|
|
|
|
|
2016-04-14 16:25:46 +02:00
|
|
|
getter(identifier.toLowerCase(), function (error, result) {
|
2015-07-20 00:09:47 -07:00
|
|
|
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new UserError(UserError.NOT_FOUND));
|
|
|
|
|
if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error));
|
|
|
|
|
|
|
|
|
|
result.resetToken = hat(256);
|
|
|
|
|
|
|
|
|
|
userdb.update(result.id, result, function (error) {
|
|
|
|
|
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new UserError(UserError.NOT_FOUND));
|
|
|
|
|
if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error));
|
|
|
|
|
|
|
|
|
|
mailer.passwordReset(result);
|
|
|
|
|
|
|
|
|
|
callback(null);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function setPassword(userId, newPassword, callback) {
|
|
|
|
|
assert.strictEqual(typeof userId, 'string');
|
|
|
|
|
assert.strictEqual(typeof newPassword, 'string');
|
|
|
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
|
|
|
|
|
|
var error = validatePassword(newPassword);
|
2016-06-02 00:06:54 -07:00
|
|
|
if (error) return callback(new UserError(UserError.BAD_FIELD, error.message));
|
2015-07-20 00:09:47 -07:00
|
|
|
|
|
|
|
|
userdb.get(userId, function (error, user) {
|
|
|
|
|
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new UserError(UserError.NOT_FOUND));
|
|
|
|
|
if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error));
|
|
|
|
|
|
2016-08-31 23:41:28 -07:00
|
|
|
if (config.isDemo() && user.username === constants.DEMO_USERNAME) return callback(new UserError(UserError.BAD_FIELD, 'Not allowed in demo mode'));
|
|
|
|
|
|
2015-07-20 00:09:47 -07:00
|
|
|
var saltBuffer = new Buffer(user.salt, 'hex');
|
|
|
|
|
crypto.pbkdf2(newPassword, saltBuffer, CRYPTO_ITERATIONS, CRYPTO_KEY_LENGTH, function (error, derivedKey) {
|
|
|
|
|
if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error));
|
|
|
|
|
|
2015-09-17 13:50:20 -07:00
|
|
|
user.modifiedAt = (new Date()).toISOString();
|
2015-07-20 00:09:47 -07:00
|
|
|
user.password = new Buffer(derivedKey, 'binary').toString('hex');
|
|
|
|
|
user.resetToken = '';
|
|
|
|
|
|
|
|
|
|
userdb.update(userId, user, function (error) {
|
|
|
|
|
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new UserError(UserError.NOT_FOUND));
|
|
|
|
|
if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error));
|
|
|
|
|
|
|
|
|
|
// Also generate a token so the new user can get logged in immediately
|
2016-06-08 14:09:06 +02:00
|
|
|
clients.get('cid-webadmin', function (error, result) {
|
2015-07-20 00:09:47 -07:00
|
|
|
if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error));
|
|
|
|
|
|
|
|
|
|
var token = tokendb.generateToken();
|
2016-08-01 10:14:45 +02:00
|
|
|
var expiresAt = Date.now() + constants.DEFAULT_TOKEN_EXPIRATION;
|
2015-07-20 00:09:47 -07:00
|
|
|
|
2016-06-03 13:00:07 +02:00
|
|
|
tokendb.add(token, user.id, result.id, expiresAt, '*', function (error) {
|
2015-07-20 00:09:47 -07:00
|
|
|
if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error));
|
|
|
|
|
|
|
|
|
|
callback(null, { token: token, expiresAt: expiresAt });
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2016-05-01 20:01:34 -07:00
|
|
|
function createOwner(username, password, email, displayName, auditSource, callback) {
|
2016-04-04 15:14:00 +02:00
|
|
|
assert.strictEqual(typeof username, 'string');
|
|
|
|
|
assert.strictEqual(typeof password, 'string');
|
|
|
|
|
assert.strictEqual(typeof email, 'string');
|
|
|
|
|
assert.strictEqual(typeof displayName, 'string');
|
2016-05-01 20:01:34 -07:00
|
|
|
assert.strictEqual(typeof auditSource, 'object');
|
2016-04-04 15:14:00 +02:00
|
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
|
|
2016-04-14 16:25:46 +02:00
|
|
|
// This is only not allowed for the owner
|
2016-06-02 00:06:54 -07:00
|
|
|
if (username === '') return callback(new UserError(UserError.BAD_FIELD, 'Username cannot be empty'));
|
2016-04-04 15:16:16 +02:00
|
|
|
|
2015-07-20 00:09:47 -07:00
|
|
|
userdb.count(function (error, count) {
|
|
|
|
|
if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error));
|
2016-06-02 15:39:21 +02:00
|
|
|
if (count !== 0) return callback(new UserError(UserError.ALREADY_EXISTS, 'Owner already exists'));
|
2015-07-20 00:09:47 -07:00
|
|
|
|
2016-05-01 20:01:34 -07:00
|
|
|
createUser(username, password, email, displayName, auditSource, { owner: true }, function (error, user) {
|
2016-02-08 15:16:59 -08:00
|
|
|
if (error) return callback(error);
|
|
|
|
|
|
2016-09-20 15:07:11 -07:00
|
|
|
groups.addMember(constants.ADMIN_GROUP_ID, user.id, function (error) {
|
2016-02-08 15:16:59 -08:00
|
|
|
if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error));
|
|
|
|
|
|
2016-02-08 16:53:20 -08:00
|
|
|
callback(null, user);
|
2016-02-08 15:16:59 -08:00
|
|
|
});
|
|
|
|
|
});
|
2015-07-20 00:09:47 -07:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2016-01-13 12:28:38 -08:00
|
|
|
function getOwner(callback) {
|
|
|
|
|
userdb.getOwner(function (error, owner) {
|
|
|
|
|
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new UserError(UserError.NOT_FOUND));
|
|
|
|
|
if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error));
|
|
|
|
|
|
|
|
|
|
return callback(null, owner);
|
|
|
|
|
});
|
|
|
|
|
}
|
2016-01-18 15:16:18 +01:00
|
|
|
|
|
|
|
|
function sendInvite(userId, callback) {
|
|
|
|
|
assert.strictEqual(typeof userId, 'string');
|
|
|
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
|
|
|
|
|
|
userdb.get(userId, function (error, userObject) {
|
|
|
|
|
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new UserError(UserError.NOT_FOUND));
|
|
|
|
|
if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error));
|
|
|
|
|
|
|
|
|
|
userObject.resetToken = hat(256);
|
|
|
|
|
|
|
|
|
|
userdb.update(userId, userObject, function (error) {
|
|
|
|
|
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new UserError(UserError.NOT_FOUND));
|
|
|
|
|
if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error));
|
|
|
|
|
|
2016-01-18 16:11:00 +01:00
|
|
|
mailer.sendInvite(userObject, null);
|
2016-01-18 15:16:18 +01:00
|
|
|
|
2016-04-04 18:14:07 +02:00
|
|
|
callback(null, userObject.resetToken);
|
2016-01-18 15:16:18 +01:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
2016-05-06 13:56:26 +02:00
|
|
|
|
|
|
|
|
function setShowTutorial(userId, showTutorial, callback) {
|
|
|
|
|
assert.strictEqual(typeof userId, 'string');
|
|
|
|
|
assert.strictEqual(typeof showTutorial, 'boolean');
|
|
|
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
|
|
|
|
|
|
userdb.update(userId, { showTutorial: showTutorial }, function (error) {
|
|
|
|
|
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new UserError(UserError.NOT_FOUND, error));
|
|
|
|
|
if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error));
|
|
|
|
|
|
|
|
|
|
callback(null);
|
|
|
|
|
});
|
|
|
|
|
}
|
2016-09-21 15:34:58 -07:00
|
|
|
|
|
|
|
|
function setAliases(userId, aliases, callback) {
|
|
|
|
|
assert.strictEqual(typeof userId, 'string');
|
|
|
|
|
assert(util.isArray(aliases));
|
|
|
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
|
|
|
|
|
|
for (var i = 0; i < aliases.length; i++) {
|
|
|
|
|
aliases[i] = aliases[i].toLowerCase();
|
|
|
|
|
|
|
|
|
|
var error = validateUsername(aliases[i]);
|
|
|
|
|
if (error) return callback(error);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
userdb.get(userId, function (error, user) {
|
|
|
|
|
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new UserError(UserError.NOT_FOUND));
|
|
|
|
|
if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error));
|
|
|
|
|
|
2016-09-26 00:14:03 -07:00
|
|
|
if (!user.username) return new UserError(UserError.BAD_FIELD, 'Username must be set before settings aliases');
|
|
|
|
|
|
2016-09-26 14:02:23 -07:00
|
|
|
mailboxdb.setAliasesForName(user.username, aliases, function (error) {
|
2016-09-21 15:34:58 -07:00
|
|
|
if (error && error.reason === DatabaseError.ALREADY_EXISTS) return callback(new UserError(UserError.ALREADY_EXISTS, error.message));
|
|
|
|
|
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new UserError(UserError.NOT_FOUND));
|
|
|
|
|
if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error));
|
|
|
|
|
|
|
|
|
|
callback(null);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getAliases(userId, callback) {
|
|
|
|
|
assert.strictEqual(typeof userId, 'string');
|
|
|
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
|
|
|
|
|
|
userdb.get(userId, function (error, user) {
|
|
|
|
|
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new UserError(UserError.NOT_FOUND));
|
|
|
|
|
if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error));
|
|
|
|
|
|
2016-09-26 14:02:23 -07:00
|
|
|
mailboxdb.getAliasesForName(user.username, function (error, aliases) {
|
2016-09-21 15:34:58 -07:00
|
|
|
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new UserError(UserError.NOT_FOUND));
|
|
|
|
|
if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error));
|
|
|
|
|
|
|
|
|
|
callback(null, aliases);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|