Move UsersError to BoxError

This commit is contained in:
Girish Ramakrishnan
2019-10-24 14:40:26 -07:00
parent 9b4d43075e
commit bc3169deb3
9 changed files with 169 additions and 219 deletions

View File

@@ -1,8 +1,6 @@
'use strict';
exports = module.exports = {
UsersError: UsersError,
removePrivateFields: removePrivateFields,
removeRestrictedFields: removeRestrictedFields,
@@ -38,7 +36,6 @@ let assert = require('assert'),
crypto = require('crypto'),
constants = require('./constants.js'),
debug = require('debug')('box:user'),
DatabaseError = require('./databaseerror.js'),
eventlog = require('./eventlog.js'),
externalldap = require('./externalldap.js'),
groups = require('./groups.js'),
@@ -49,7 +46,6 @@ let assert = require('assert'),
settings = require('./settings.js'),
speakeasy = require('speakeasy'),
userdb = require('./userdb.js'),
util = require('util'),
uuid = require('uuid'),
validator = require('validator'),
_ = require('underscore');
@@ -59,48 +55,20 @@ var CRYPTO_ITERATIONS = 10000; // iterations
var CRYPTO_KEY_LENGTH = 512; // bits
var CRYPTO_DIGEST = 'sha1'; // used to be the default in node 4.1.1 cannot change since it will affect existing db records
// http://dustinsenos.com/articles/customErrorsInNode
// http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi
function UsersError(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(UsersError, Error);
UsersError.INTERNAL_ERROR = 'Internal Error';
UsersError.ALREADY_EXISTS = 'Already Exists';
UsersError.NOT_FOUND = 'Not Found';
UsersError.WRONG_PASSWORD = 'Wrong User or Password';
UsersError.BAD_FIELD = 'Bad field';
UsersError.BAD_TOKEN = 'Bad token';
// keep this in sync with validateGroupname and validateAlias
function validateUsername(username) {
assert.strictEqual(typeof username, 'string');
if (username.length < 1) return new UsersError(UsersError.BAD_FIELD, 'Username must be atleast 1 char');
if (username.length >= 200) return new UsersError(UsersError.BAD_FIELD, 'Username too long');
if (username.length < 1) return new BoxError(BoxError.BAD_FIELD, 'Username must be atleast 1 char');
if (username.length >= 200) return new BoxError(BoxError.BAD_FIELD, 'Username too long');
if (constants.RESERVED_NAMES.indexOf(username) !== -1) return new UsersError(UsersError.BAD_FIELD, 'Username is reserved');
if (constants.RESERVED_NAMES.indexOf(username) !== -1) return new BoxError(BoxError.BAD_FIELD, 'Username is reserved');
// also need to consider valid LDAP characters here (e.g '+' is reserved)
if (/[^a-zA-Z0-9.-]/.test(username)) return new UsersError(UsersError.BAD_FIELD, 'Username can only contain alphanumerals, dot and -');
if (/[^a-zA-Z0-9.-]/.test(username)) return new BoxError(BoxError.BAD_FIELD, 'Username can only contain alphanumerals, dot and -');
// app emails are sent using the .app suffix
if (username.indexOf('.app') !== -1) return new UsersError(UsersError.BAD_FIELD, 'Username pattern is reserved for apps');
if (username.indexOf('.app') !== -1) return new BoxError(BoxError.BAD_FIELD, 'Username pattern is reserved for apps');
return null;
}
@@ -108,7 +76,7 @@ function validateUsername(username) {
function validateEmail(email) {
assert.strictEqual(typeof email, 'string');
if (!validator.isEmail(email)) return new UsersError(UsersError.BAD_FIELD, 'Invalid email');
if (!validator.isEmail(email)) return new BoxError(BoxError.BAD_FIELD, 'Invalid email');
return null;
}
@@ -116,7 +84,7 @@ function validateEmail(email) {
function validateToken(token) {
assert.strictEqual(typeof token, 'string');
if (token.length !== 64) return new UsersError(UsersError.BAD_TOKEN, 'Invalid token'); // 256-bit hex coded token
if (token.length !== 64) return new BoxError(BoxError.BAD_FIELD, 'Invalid token'); // 256-bit hex coded token
return null;
}
@@ -130,8 +98,8 @@ function validateDisplayName(name) {
function validatePassword(password) {
assert.strictEqual(typeof password, 'string');
if (password.length < 8) return new UsersError(UsersError.BAD_FIELD, 'Password must be atleast 8 characters');
if (password.length > 256) return new UsersError(UsersError.BAD_FIELD, 'Password cannot be more than 256 characters');
if (password.length < 8) return new BoxError(BoxError.BAD_FIELD, 'Password must be atleast 8 characters');
if (password.length > 256) return new BoxError(BoxError.BAD_FIELD, 'Password cannot be more than 256 characters');
return null;
}
@@ -169,7 +137,7 @@ function create(username, password, email, displayName, options, auditSource, ca
if (password !== null) {
error = validatePassword(password);
if (error) return callback(new UsersError(UsersError.BAD_FIELD, error.message));
if (error) return callback(error);
} else {
password = hat(8 * 8);
}
@@ -182,10 +150,10 @@ function create(username, password, email, displayName, options, auditSource, ca
if (error) return callback(error);
crypto.randomBytes(CRYPTO_SALT_SIZE, function (error, salt) {
if (error) return callback(new UsersError(UsersError.INTERNAL_ERROR, error));
if (error) return callback(new BoxError(BoxError.CRYPTO_ERROR, error));
crypto.pbkdf2(password, salt, CRYPTO_ITERATIONS, CRYPTO_KEY_LENGTH, CRYPTO_DIGEST, function (error, derivedKey) {
if (error) return callback(new UsersError(UsersError.INTERNAL_ERROR, error));
if (error) return callback(new BoxError(BoxError.CRYPTO_ERROR, error));
var now = (new Date()).toISOString();
var user = {
@@ -204,8 +172,7 @@ function create(username, password, email, displayName, options, auditSource, ca
};
userdb.add(user.id, user, function (error) {
if (error && error.reason === DatabaseError.ALREADY_EXISTS) return callback(new UsersError(UsersError.ALREADY_EXISTS, error.message));
if (error) return callback(new UsersError(UsersError.INTERNAL_ERROR, error));
if (error) return callback(error);
// when this is used to create the owner, then we have to patch the auditSource to contain himself
if (isOwner) {
@@ -245,7 +212,7 @@ function verify(userId, password, callback) {
get(userId, function (error, user) {
if (error) return callback(error);
if (!user.active) return callback(new UsersError(UsersError.NOT_FOUND));
if (!user.active) return callback(new BoxError(BoxError.NOT_FOUND));
// for just invited users the username may be still null
if (user.username && verifyGhost(user.username, password)) {
@@ -255,18 +222,17 @@ function verify(userId, password, callback) {
if (user.source === 'ldap') {
externalldap.verifyPassword(user, password, function (error) {
if (error && error.reason === BoxError.INVALID_CREDENTIALS) return callback(new UsersError(UsersError.WRONG_PASSWORD));
if (error) return callback(new UsersError(UsersError.INTERNAL_ERROR, error));
if (error) return callback(error);
callback(null, user);
});
} else {
var saltBinary = Buffer.from(user.salt, 'hex');
crypto.pbkdf2(password, saltBinary, CRYPTO_ITERATIONS, CRYPTO_KEY_LENGTH, CRYPTO_DIGEST, function (error, derivedKey) {
if (error) return callback(new UsersError(UsersError.INTERNAL_ERROR, error));
if (error) return callback(new BoxError(BoxError.CRYPTO_ERROR, error));
var derivedKeyHex = Buffer.from(derivedKey, 'binary').toString('hex');
if (derivedKeyHex !== user.password) return callback(new UsersError(UsersError.WRONG_PASSWORD));
if (derivedKeyHex !== user.password) return callback(new BoxError(BoxError.INVALID_CREDENTIALS));
callback(null, user);
});
@@ -280,8 +246,7 @@ function verifyWithUsername(username, password, callback) {
assert.strictEqual(typeof callback, 'function');
userdb.getByUsername(username.toLowerCase(), function (error, user) {
if (error && error.reason == DatabaseError.NOT_FOUND) return callback(new UsersError(UsersError.NOT_FOUND));
if (error) return callback(new UsersError(UsersError.INTERNAL_ERROR, error));
if (error) return callback(error);
verify(user.id, password, callback);
});
@@ -293,8 +258,7 @@ function verifyWithEmail(email, password, callback) {
assert.strictEqual(typeof callback, 'function');
userdb.getByEmail(email.toLowerCase(), function (error, user) {
if (error && error.reason == DatabaseError.NOT_FOUND) return callback(new UsersError(UsersError.NOT_FOUND));
if (error) return callback(new UsersError(UsersError.INTERNAL_ERROR, error));
if (error) return callback(error);
verify(user.id, password, callback);
});
@@ -308,11 +272,10 @@ function removeUser(userId, auditSource, callback) {
get(userId, function (error, user) {
if (error) return callback(error);
if (settings.isDemo() && user.username === constants.DEMO_USERNAME) return callback(new UsersError(UsersError.BAD_FIELD, 'Not allowed in demo mode'));
if (settings.isDemo() && user.username === constants.DEMO_USERNAME) return callback(new BoxError(BoxError.BAD_FIELD, 'Not allowed in demo mode'));
userdb.del(userId, function (error) {
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new UsersError(UsersError.NOT_FOUND));
if (error) return callback(new UsersError(UsersError.INTERNAL_ERROR, error));
if (error) return callback(error);
eventlog.add(eventlog.ACTION_USER_REMOVE, auditSource, { userId: userId, user: removePrivateFields(user) }, callback);
});
@@ -323,7 +286,7 @@ function getAll(callback) {
assert.strictEqual(typeof callback, 'function');
userdb.getAllWithGroupIds(function (error, results) {
if (error) return callback(new UsersError(UsersError.INTERNAL_ERROR, error));
if (error) return callback(error);
return callback(null, results);
});
@@ -336,7 +299,7 @@ function getAllPaged(search, page, perPage, callback) {
assert.strictEqual(typeof callback, 'function');
userdb.getAllWithGroupIdsPaged(search, page, perPage, function (error, results) {
if (error) return callback(new UsersError(UsersError.INTERNAL_ERROR, error));
if (error) return callback(error);
return callback(null, results);
});
@@ -346,7 +309,7 @@ function count(callback) {
assert.strictEqual(typeof callback, 'function');
userdb.count(function (error, count) {
if (error) return callback(new UsersError(UsersError.INTERNAL_ERROR, error));
if (error) return callback(error);
callback(null, count);
});
@@ -367,11 +330,10 @@ function get(userId, callback) {
assert.strictEqual(typeof callback, 'function');
userdb.get(userId, function (error, result) {
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new UsersError(UsersError.NOT_FOUND));
if (error) return callback(new UsersError(UsersError.INTERNAL_ERROR, error));
if (error) return callback(error);
groups.getMembership(userId, function (error, groupIds) {
if (error) return callback(new UsersError(UsersError.INTERNAL_ERROR, error));
if (error) return callback(error);
result.groupIds = groupIds;
@@ -392,8 +354,7 @@ function getByResetToken(email, resetToken, callback) {
if (error) return callback(error);
userdb.getByResetToken(email, resetToken, function (error, result) {
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new UsersError(UsersError.NOT_FOUND));
if (error) return callback(new UsersError(UsersError.INTERNAL_ERROR, error));
if (error) return callback(error);
callback(null, result);
});
@@ -404,8 +365,7 @@ function getByUsername(username, callback) {
assert.strictEqual(typeof callback, 'function');
userdb.getByUsername(username.toLowerCase(), function (error, result) {
if (error && error.reason == DatabaseError.NOT_FOUND) return callback(new UsersError(UsersError.NOT_FOUND));
if (error) return callback(new UsersError(UsersError.INTERNAL_ERROR, error));
if (error) return callback(error);
get(result.id, callback);
});
@@ -441,18 +401,15 @@ function updateUser(userId, data, auditSource, callback) {
}
userdb.get(userId, function (error, oldUser) {
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new UsersError(UsersError.NOT_FOUND));
if (error) return callback(new UsersError(UsersError.INTERNAL_ERROR, error));
if (error) return callback(error);
userdb.update(userId, data, function (error) {
if (error && error.reason === DatabaseError.ALREADY_EXISTS) return callback(new UsersError(UsersError.ALREADY_EXISTS, error.message));
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new UsersError(UsersError.NOT_FOUND, error.message));
if (error) return callback(new UsersError(UsersError.INTERNAL_ERROR, error));
if (error) return callback(error);
callback(null);
get(userId, function (error, result) {
if (error) return callback(new UsersError(UsersError.INTERNAL_ERROR, error));
if (error) return callback(error);
eventlog.add(eventlog.ACTION_USER_UPDATE, auditSource, {
userId: userId,
@@ -471,8 +428,7 @@ function setMembership(userId, groupIds, callback) {
assert.strictEqual(typeof callback, 'function');
groups.setMembership(userId, groupIds, function (error) {
if (error && error.reason === BoxError.NOT_FOUND) return callback(new UsersError(UsersError.NOT_FOUND, 'One or more groups not found'));
if (error) return callback(new UsersError(UsersError.INTERNAL_ERROR, error));
if (error) return callback(error);
callback(null);
});
@@ -482,7 +438,7 @@ function getAllAdmins(callback) {
assert.strictEqual(typeof callback, 'function');
userdb.getAllAdmins(function (error, admins) {
if (error) return callback(new UsersError(UsersError.INTERNAL_ERROR, error));
if (error) return callback(error);
callback(null, admins);
});
@@ -497,14 +453,12 @@ function resetPasswordByIdentifier(identifier, callback) {
else getter = userdb.getByEmail;
getter(identifier.toLowerCase(), function (error, result) {
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new UsersError(UsersError.NOT_FOUND));
if (error) return callback(new UsersError(UsersError.INTERNAL_ERROR, error));
if (error) return callback(error);
result.resetToken = hat(256);
userdb.update(result.id, result, function (error) {
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new UsersError(UsersError.NOT_FOUND));
if (error) return callback(new UsersError(UsersError.INTERNAL_ERROR, error));
if (error) return callback(error);
mailer.passwordReset(result);
@@ -519,25 +473,23 @@ function setPassword(userId, newPassword, callback) {
assert.strictEqual(typeof callback, 'function');
var error = validatePassword(newPassword);
if (error) return callback(new UsersError(UsersError.BAD_FIELD, error.message));
if (error) return callback(error);
userdb.get(userId, function (error, user) {
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new UsersError(UsersError.NOT_FOUND));
if (error) return callback(new UsersError(UsersError.INTERNAL_ERROR, error));
if (error) return callback(error);
if (settings.isDemo() && user.username === constants.DEMO_USERNAME) return callback(new UsersError(UsersError.BAD_FIELD, 'Not allowed in demo mode'));
if (settings.isDemo() && user.username === constants.DEMO_USERNAME) return callback(new BoxError(BoxError.BAD_FIELD, 'Not allowed in demo mode'));
var saltBuffer = Buffer.from(user.salt, 'hex');
crypto.pbkdf2(newPassword, saltBuffer, CRYPTO_ITERATIONS, CRYPTO_KEY_LENGTH, CRYPTO_DIGEST, function (error, derivedKey) {
if (error) return callback(new UsersError(UsersError.INTERNAL_ERROR, error));
if (error) return callback(new BoxError(BoxError.CRYPTO_ERROR, error));
user.modifiedAt = (new Date()).toISOString();
user.password = Buffer.from(derivedKey, 'binary').toString('hex');
user.resetToken = '';
userdb.update(userId, user, function (error) {
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new UsersError(UsersError.NOT_FOUND));
if (error) return callback(new UsersError(UsersError.INTERNAL_ERROR, error));
if (error) return callback(error);
callback();
});
@@ -554,11 +506,11 @@ function createOwner(username, password, email, displayName, auditSource, callba
assert.strictEqual(typeof callback, 'function');
// This is only not allowed for the owner
if (username === '') return callback(new UsersError(UsersError.BAD_FIELD, 'Username cannot be empty'));
if (username === '') return callback(new BoxError(BoxError.BAD_FIELD, 'Username cannot be empty'));
count(function (error, count) {
if (error) return callback(new UsersError(UsersError.INTERNAL_ERROR, error));
if (count !== 0) return callback(new UsersError(UsersError.ALREADY_EXISTS, 'Owner already exists'));
if (error) return callback(error);
if (count !== 0) return callback(new BoxError(BoxError.ALREADY_EXISTS, 'Owner already exists'));
create(username, password, email, displayName, { owner: true }, auditSource, function (error, user) {
if (error) return callback(error);
@@ -570,8 +522,7 @@ function createOwner(username, password, email, displayName, auditSource, callba
function getOwner(callback) {
userdb.getOwner(function (error, owner) {
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new UsersError(UsersError.NOT_FOUND));
if (error) return callback(new UsersError(UsersError.INTERNAL_ERROR, error));
if (error) return callback(error);
return callback(null, owner);
});
@@ -582,14 +533,12 @@ function createInvite(userId, callback) {
assert.strictEqual(typeof callback, 'function');
userdb.get(userId, function (error, userObject) {
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new UsersError(UsersError.NOT_FOUND));
if (error) return callback(new UsersError(UsersError.INTERNAL_ERROR, error));
if (error) return callback(error);
userObject.resetToken = hat(256);
userdb.update(userId, userObject, function (error) {
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new UsersError(UsersError.NOT_FOUND));
if (error) return callback(new UsersError(UsersError.INTERNAL_ERROR, error));
if (error) return callback(error);
callback(null, userObject.resetToken);
});
@@ -602,10 +551,9 @@ function sendInvite(userId, options, callback) {
assert.strictEqual(typeof callback, 'function');
userdb.get(userId, function (error, userObject) {
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new UsersError(UsersError.NOT_FOUND));
if (error) return callback(new UsersError(UsersError.INTERNAL_ERROR, error));
if (error) return callback(error);
if (!userObject.resetToken) return callback(new UsersError(UsersError.BAD_FIELD, 'Must generate resetToken to send inivitation'));
if (!userObject.resetToken) return callback(new BoxError(BoxError.BAD_FIELD, 'Must generate resetToken to send inivitation'));
mailer.sendInvite(userObject, options.invitor || null);
@@ -618,19 +566,17 @@ function setTwoFactorAuthenticationSecret(userId, callback) {
assert.strictEqual(typeof callback, 'function');
userdb.get(userId, function (error, result) {
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new UsersError(UsersError.NOT_FOUND));
if (error) return callback(new UsersError(UsersError.INTERNAL_ERROR, error));
if (error) return callback(error);
if (result.twoFactorAuthenticationEnabled) return callback(new UsersError(UsersError.ALREADY_EXISTS));
if (result.twoFactorAuthenticationEnabled) return callback(new BoxError(BoxError.ALREADY_EXISTS));
var secret = speakeasy.generateSecret({ name: `Cloudron ${settings.adminFqdn()} (${result.username})` });
userdb.update(userId, { twoFactorAuthenticationSecret: secret.base32, twoFactorAuthenticationEnabled: false }, function (error) {
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new UsersError(UsersError.NOT_FOUND, error.message));
if (error) return callback(new UsersError(UsersError.INTERNAL_ERROR, error));
if (error) return callback(error);
qrcode.toDataURL(secret.otpauth_url, function (error, dataUrl) {
if (error) return callback(new UsersError(UsersError.INTERNAL_ERROR, error));
if (error) return callback(new BoxError(BoxError.INTERNAL_ERROR, error));
callback(null, { secret: secret.base32, qrcode: dataUrl });
});
@@ -644,17 +590,15 @@ function enableTwoFactorAuthentication(userId, totpToken, callback) {
assert.strictEqual(typeof callback, 'function');
userdb.get(userId, function (error, result) {
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new UsersError(UsersError.NOT_FOUND));
if (error) return callback(new UsersError(UsersError.INTERNAL_ERROR, error));
if (error) return callback(error);
var verified = speakeasy.totp.verify({ secret: result.twoFactorAuthenticationSecret, encoding: 'base32', token: totpToken, window: 2 });
if (!verified) return callback(new UsersError(UsersError.BAD_TOKEN));
if (!verified) return callback(new BoxError(BoxError.INVALID_CREDENTIALS));
if (result.twoFactorAuthenticationEnabled) return callback(new UsersError(UsersError.ALREADY_EXISTS));
if (result.twoFactorAuthenticationEnabled) return callback(new BoxError(BoxError.ALREADY_EXISTS));
userdb.update(userId, { twoFactorAuthenticationEnabled: true }, function (error) {
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new UsersError(UsersError.NOT_FOUND, error.message));
if (error) return callback(new UsersError(UsersError.INTERNAL_ERROR, error));
if (error) return callback(error);
callback(null);
});
@@ -666,8 +610,7 @@ function disableTwoFactorAuthentication(userId, callback) {
assert.strictEqual(typeof callback, 'function');
userdb.update(userId, { twoFactorAuthenticationEnabled: false, twoFactorAuthenticationSecret: '' }, function (error) {
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new UsersError(UsersError.NOT_FOUND, error.message));
if (error) return callback(new UsersError(UsersError.INTERNAL_ERROR, error));
if (error) return callback(error);
callback(null);
});