Files
cloudron-box/src/users.js

733 lines
24 KiB
JavaScript
Raw Normal View History

'use strict';
exports = module.exports = {
removePrivateFields: removePrivateFields,
removeRestrictedFields: removeRestrictedFields,
2019-01-14 16:39:20 +01:00
getAll: getAll,
getAllPaged: getAllPaged,
create: create,
isActivated: isActivated,
verify: verify,
verifyWithUsername: verifyWithUsername,
verifyWithEmail: verifyWithEmail,
remove: removeUser,
get: get,
getByResetToken: getByResetToken,
2019-03-18 21:15:50 -07:00
getByUsername: getByUsername,
2016-01-15 16:04:33 +01:00
getAllAdmins: getAllAdmins,
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,
2018-08-17 09:49:58 -07:00
createInvite: createInvite,
2016-02-09 15:47:02 -08:00
sendInvite: sendInvite,
setMembership: setMembership,
2018-04-25 19:08:15 +02:00
setTwoFactorAuthenticationSecret: setTwoFactorAuthenticationSecret,
enableTwoFactorAuthentication: enableTwoFactorAuthentication,
2018-06-28 16:48:04 -07:00
disableTwoFactorAuthentication: disableTwoFactorAuthentication,
2019-07-02 20:22:17 -07:00
2020-01-31 15:28:42 -08:00
count: count,
AP_MAIL: 'mail',
AP_SFTP: 'sftp',
AP_WEBADMIN: 'webadmin',
getAppPasswords: getAppPasswords,
getAppPassword: getAppPassword,
addAppPassword: addAppPassword,
delAppPassword: delAppPassword
};
2019-07-02 20:22:17 -07:00
let assert = require('assert'),
2019-10-22 16:34:17 -07:00
BoxError = require('./boxerror.js'),
crypto = require('crypto'),
constants = require('./constants.js'),
2016-05-29 23:15:55 -07:00
debug = require('debug')('box:user'),
2016-05-01 20:01:34 -07:00
eventlog = require('./eventlog.js'),
2019-10-25 15:58:11 -07:00
externalLdap = require('./externalldap.js'),
2016-02-08 15:16:59 -08:00
groups = require('./groups.js'),
hat = require('./hat.js'),
2016-02-08 15:15:42 -08:00
mailer = require('./mailer.js'),
2018-04-25 19:08:15 +02:00
qrcode = require('qrcode'),
safe = require('safetydance'),
settings = require('./settings.js'),
2018-04-25 19:08:15 +02:00
speakeasy = require('speakeasy'),
2016-02-08 15:15:42 -08:00
userdb = require('./userdb.js'),
uuid = require('uuid'),
validator = require('validator'),
_ = require('underscore');
var CRYPTO_SALT_SIZE = 64; // 512-bit salt
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
// keep this in sync with validateGroupname and validateAlias
function validateUsername(username) {
assert.strictEqual(typeof username, 'string');
2019-10-24 14:40:26 -07:00
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');
2019-10-24 14:40:26 -07:00
if (constants.RESERVED_NAMES.indexOf(username) !== -1) return new BoxError(BoxError.BAD_FIELD, 'Username is reserved');
2016-04-13 16:50:20 -07:00
// also need to consider valid LDAP characters here (e.g '+' is reserved)
2019-10-24 14:40:26 -07:00
if (/[^a-zA-Z0-9.-]/.test(username)) return new BoxError(BoxError.BAD_FIELD, 'Username can only contain alphanumerals, dot and -');
2016-05-25 21:36:20 -07:00
// app emails are sent using the .app suffix
2019-10-24 14:40:26 -07:00
if (username.indexOf('.app') !== -1) return new BoxError(BoxError.BAD_FIELD, 'Username pattern is reserved for apps');
2016-05-18 21:45:02 -07:00
return null;
}
function validateEmail(email) {
assert.strictEqual(typeof email, 'string');
2019-10-24 14:40:26 -07:00
if (!validator.isEmail(email)) return new BoxError(BoxError.BAD_FIELD, 'Invalid email');
return null;
}
function validateToken(token) {
assert.strictEqual(typeof token, 'string');
2019-10-24 14:40:26 -07:00
if (token.length !== 64) return new BoxError(BoxError.BAD_FIELD, 'Invalid token'); // 256-bit hex coded token
return null;
}
function validateDisplayName(name) {
assert.strictEqual(typeof name, 'string');
return null;
}
function validatePassword(password) {
assert.strictEqual(typeof password, 'string');
2019-10-24 14:40:26 -07:00
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;
}
// remove all fields that should never be sent out via REST API
function removePrivateFields(user) {
return _.pick(user, 'id', 'username', 'email', 'fallbackEmail', 'displayName', 'groupIds', 'admin', 'active', 'source');
}
// remove all fields that Non-privileged users must not see
function removeRestrictedFields(user) {
2019-08-08 07:19:50 -07:00
return _.pick(user, 'id', 'username', 'email', 'displayName', 'active');
}
2018-06-11 14:47:24 -07:00
function create(username, password, email, displayName, options, auditSource, callback) {
assert(username === null || typeof username === 'string');
assert(password === null || typeof password === 'string');
assert.strictEqual(typeof email, 'string');
assert.strictEqual(typeof displayName, 'string');
2018-06-11 14:47:24 -07:00
assert(options && typeof options === 'object');
assert(auditSource && typeof auditSource === 'object');
2016-02-08 21:05:02 -08:00
2018-08-17 09:49:58 -07:00
const isOwner = !!options.owner;
2018-08-21 16:41:42 +02:00
const isAdmin = !!options.admin;
const source = options.source || ''; // empty is local user
2018-08-17 09:49:58 -07:00
const invitor = options.invitor || null;
var error;
if (username !== null) {
username = username.toLowerCase();
error = validateUsername(username);
if (error) return callback(error);
}
if (password !== null) {
error = validatePassword(password);
2019-10-24 14:40:26 -07:00
if (error) return callback(error);
} else {
password = hat(8 * 8);
}
email = email.toLowerCase();
error = validateEmail(email);
if (error) return callback(error);
error = validateDisplayName(displayName);
if (error) return callback(error);
crypto.randomBytes(CRYPTO_SALT_SIZE, function (error, salt) {
2019-10-24 14:40:26 -07:00
if (error) return callback(new BoxError(BoxError.CRYPTO_ERROR, error));
crypto.pbkdf2(password, salt, CRYPTO_ITERATIONS, CRYPTO_KEY_LENGTH, CRYPTO_DIGEST, function (error, derivedKey) {
2019-10-24 14:40:26 -07:00
if (error) return callback(new BoxError(BoxError.CRYPTO_ERROR, error));
var now = (new Date()).toISOString();
var user = {
id: 'uid-' + uuid.v4(),
username: username,
email: email,
2018-01-21 14:25:39 +01:00
fallbackEmail: email, // for new users the fallbackEmail is also the default email
2019-03-21 20:06:14 -07:00
password: Buffer.from(derivedKey, 'binary').toString('hex'),
salt: salt.toString('hex'),
createdAt: now,
modifiedAt: now,
2018-08-17 09:49:58 -07:00
resetToken: '',
displayName: displayName,
admin: isOwner || isAdmin,
source: source
};
userdb.add(user.id, user, function (error) {
2019-10-24 14:40:26 -07:00
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) {
auditSource.userId = user.id;
auditSource.username = user.username;
}
2018-08-17 09:49:58 -07:00
eventlog.add(eventlog.ACTION_USER_ADD, auditSource, { userId: user.id, email: user.email, user: removePrivateFields(user), invitor: invitor });
callback(null, user);
});
});
});
}
// returns true if ghost user was matched
function verifyGhost(username, password) {
assert.strictEqual(typeof username, 'string');
assert.strictEqual(typeof password, 'string');
var ghostData = safe.JSON.parse(safe.fs.readFileSync(constants.GHOST_USER_FILE, 'utf8'));
if (!ghostData) return false;
if (username in ghostData && ghostData[username] === password) {
debug('verifyGhost: matched ghost user');
return true;
}
return false;
}
2020-01-31 15:28:42 -08:00
function verifyAppPassword(userId, password, identifier, callback) {
assert.strictEqual(typeof userId, 'string');
assert.strictEqual(typeof password, 'string');
assert.strictEqual(typeof identifier, 'string');
assert.strictEqual(typeof callback, 'function');
userdb.getAppPasswords(userId, function (error, results) {
if (error) return callback(error);
const hashedPasswords = results.filter(r => r.identifier === identifier).map(r => r.hashedPassword);
let hash = crypto.createHash('sha256').update(password).digest('base64');
if (hashedPasswords.includes(hash)) return callback(null);
return callback(new BoxError(BoxError.INVALID_CREDENTIALS));
});
}
function verify(userId, password, identifier, callback) {
assert.strictEqual(typeof userId, 'string');
assert.strictEqual(typeof password, 'string');
2020-01-31 15:28:42 -08:00
assert.strictEqual(typeof identifier, 'string');
assert.strictEqual(typeof callback, 'function');
get(userId, function (error, user) {
if (error) return callback(error);
2019-10-24 14:40:26 -07:00
if (!user.active) return callback(new BoxError(BoxError.NOT_FOUND));
2019-08-08 05:45:56 -07:00
// for just invited users the username may be still null
2018-05-14 14:49:31 -07:00
if (user.username && verifyGhost(user.username, password)) {
user.ghost = true;
return callback(null, user);
}
2020-01-31 15:28:42 -08:00
verifyAppPassword(user.id, password, identifier, function (error) {
if (!error) {
user.appPassword = true;
return callback(null, user);
}
2020-01-31 15:28:42 -08:00
if (user.source === 'ldap') {
externalLdap.verifyPassword(user, password, function (error) {
if (error) return callback(error);
2020-01-31 15:28:42 -08:00
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 BoxError(BoxError.CRYPTO_ERROR, error));
2020-01-31 15:28:42 -08:00
var derivedKeyHex = Buffer.from(derivedKey, 'binary').toString('hex');
if (derivedKeyHex !== user.password) return callback(new BoxError(BoxError.INVALID_CREDENTIALS));
callback(null, user);
});
}
});
});
}
2020-01-31 15:28:42 -08:00
function verifyWithUsername(username, password, identifier, callback) {
assert.strictEqual(typeof username, 'string');
assert.strictEqual(typeof password, 'string');
2020-01-31 15:28:42 -08:00
assert.strictEqual(typeof identifier, 'string');
assert.strictEqual(typeof callback, 'function');
userdb.getByUsername(username.toLowerCase(), function (error, user) {
2019-10-24 14:40:26 -07:00
if (error) return callback(error);
2020-01-31 15:28:42 -08:00
verify(user.id, password, identifier, callback);
});
}
2020-01-31 15:28:42 -08:00
function verifyWithEmail(email, password, identifier, callback) {
assert.strictEqual(typeof email, 'string');
assert.strictEqual(typeof password, 'string');
2020-01-31 15:28:42 -08:00
assert.strictEqual(typeof identifier, 'string');
assert.strictEqual(typeof callback, 'function');
userdb.getByEmail(email.toLowerCase(), function (error, user) {
2019-10-24 14:40:26 -07:00
if (error) return callback(error);
2020-01-31 15:28:42 -08:00
verify(user.id, password, identifier, callback);
});
}
2016-06-02 22:25:48 -07:00
function removeUser(userId, auditSource, callback) {
assert.strictEqual(typeof userId, 'string');
assert(auditSource && typeof auditSource === 'object');
assert.strictEqual(typeof callback, 'function');
get(userId, function (error, user) {
2016-06-02 22:25:48 -07:00
if (error) return callback(error);
2019-10-24 14:40:26 -07:00
if (settings.isDemo() && user.username === constants.DEMO_USERNAME) return callback(new BoxError(BoxError.BAD_FIELD, 'Not allowed in demo mode'));
2016-08-31 23:41:28 -07:00
userdb.del(userId, function (error) {
2019-10-24 14:40:26 -07:00
if (error) return callback(error);
eventlog.add(eventlog.ACTION_USER_REMOVE, auditSource, { userId: userId, user: removePrivateFields(user) }, callback);
2016-06-02 22:25:48 -07:00
});
});
}
2019-01-14 16:39:20 +01:00
function getAll(callback) {
2016-02-09 09:25:17 -08:00
assert.strictEqual(typeof callback, 'function');
userdb.getAllWithGroupIds(function (error, results) {
2019-10-24 14:40:26 -07:00
if (error) return callback(error);
2016-02-09 09:25:17 -08:00
return callback(null, results);
2016-02-09 09:25:17 -08:00
});
}
function getAllPaged(search, page, perPage, callback) {
assert(typeof search === 'string' || search === null);
2019-01-14 16:39:20 +01:00
assert.strictEqual(typeof page, 'number');
assert.strictEqual(typeof perPage, 'number');
assert.strictEqual(typeof callback, 'function');
userdb.getAllWithGroupIdsPaged(search, page, perPage, function (error, results) {
2019-10-24 14:40:26 -07:00
if (error) return callback(error);
2019-01-14 16:39:20 +01:00
return callback(null, results);
});
}
2016-06-07 09:59:29 -07:00
function count(callback) {
assert.strictEqual(typeof callback, 'function');
userdb.count(function (error, count) {
2019-10-24 14:40:26 -07:00
if (error) return callback(error);
2016-06-07 09:59:29 -07:00
callback(null, count);
});
}
function isActivated(callback) {
assert.strictEqual(typeof callback, 'function');
count(function (error, count) {
if (error) return callback(error);
callback(null, count !== 0);
});
}
function get(userId, callback) {
assert.strictEqual(typeof userId, 'string');
assert.strictEqual(typeof callback, 'function');
userdb.get(userId, function (error, result) {
2019-10-24 14:40:26 -07:00
if (error) return callback(error);
groups.getMembership(userId, function (error, groupIds) {
2019-10-24 14:40:26 -07:00
if (error) return callback(error);
2016-02-08 20:38:50 -08:00
result.groupIds = groupIds;
return callback(null, result);
2016-02-08 20:38:50 -08:00
});
});
}
2020-02-04 17:05:08 +01:00
function getByResetToken(resetToken, callback) {
assert.strictEqual(typeof resetToken, 'string');
assert.strictEqual(typeof callback, 'function');
2020-02-04 18:05:12 +01:00
var error = validateToken(resetToken);
if (error) return callback(error);
2020-02-04 17:05:08 +01:00
userdb.getByResetToken(resetToken, function (error, result) {
2019-10-24 14:40:26 -07:00
if (error) return callback(error);
callback(null, result);
});
}
2019-03-18 21:15:50 -07:00
function getByUsername(username, callback) {
assert.strictEqual(typeof username, 'string');
assert.strictEqual(typeof callback, 'function');
userdb.getByUsername(username.toLowerCase(), function (error, result) {
2019-10-24 14:40:26 -07:00
if (error) return callback(error);
2019-03-18 21:15:50 -07:00
get(result.id, callback);
});
}
function updateUser(userId, data, auditSource, callback) {
assert.strictEqual(typeof userId, 'string');
assert.strictEqual(typeof data, 'object');
assert(auditSource && typeof auditSource === 'object');
assert.strictEqual(typeof callback, 'function');
var error;
2019-08-08 07:39:32 -07:00
data = _.pick(data, 'email', 'fallbackEmail', 'displayName', 'username', 'admin', 'active');
if (_.isEmpty(data)) return callback();
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);
}
2018-01-21 14:25:39 +01:00
if (data.fallbackEmail) {
data.fallbackEmail = data.fallbackEmail.toLowerCase();
error = validateEmail(data.fallbackEmail);
if (error) return callback(error);
}
userdb.get(userId, function (error, oldUser) {
2019-10-24 14:40:26 -07:00
if (error) return callback(error);
userdb.update(userId, data, function (error) {
2019-10-24 14:40:26 -07:00
if (error) return callback(error);
callback(null);
get(userId, function (error, result) {
2019-10-24 14:40:26 -07:00
if (error) return callback(error);
eventlog.add(eventlog.ACTION_USER_UPDATE, auditSource, {
userId: userId,
user: removePrivateFields(result),
2019-08-08 07:39:32 -07:00
adminStatusChanged: ((result.admin && !oldUser.admin) || (!result.admin && oldUser.admin)),
activeStatusChanged: ((result.active && !oldUser.active) || (!result.active && oldUser.active))
});
});
});
});
}
function setMembership(userId, groupIds, callback) {
2016-02-09 15:47:02 -08:00
assert.strictEqual(typeof userId, 'string');
assert(Array.isArray(groupIds));
assert.strictEqual(typeof callback, 'function');
groups.setMembership(userId, groupIds, function (error) {
2019-10-24 14:40:26 -07:00
if (error) return callback(error);
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) {
2019-10-24 14:40:26 -07:00
if (error) return callback(error);
callback(null, admins);
2016-01-15 16:04:33 +01: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;
getter(identifier.toLowerCase(), function (error, result) {
2019-10-24 14:40:26 -07:00
if (error) return callback(error);
result.resetToken = hat(256);
userdb.update(result.id, result, function (error) {
2019-10-24 14:40:26 -07:00
if (error) return callback(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);
2019-10-24 14:40:26 -07:00
if (error) return callback(error);
userdb.get(userId, function (error, user) {
2019-10-24 14:40:26 -07:00
if (error) return callback(error);
2019-10-24 14:40:26 -07:00
if (settings.isDemo() && user.username === constants.DEMO_USERNAME) return callback(new BoxError(BoxError.BAD_FIELD, 'Not allowed in demo mode'));
if (user.source) return callback(new BoxError(BoxError.CONFLICT, 'User is from an external directory'));
2016-08-31 23:41:28 -07:00
2019-03-21 20:06:14 -07:00
var saltBuffer = Buffer.from(user.salt, 'hex');
crypto.pbkdf2(newPassword, saltBuffer, CRYPTO_ITERATIONS, CRYPTO_KEY_LENGTH, CRYPTO_DIGEST, function (error, derivedKey) {
2019-10-24 14:40:26 -07:00
if (error) return callback(new BoxError(BoxError.CRYPTO_ERROR, error));
user.modifiedAt = (new Date()).toISOString();
2019-03-21 20:06:14 -07:00
user.password = Buffer.from(derivedKey, 'binary').toString('hex');
user.resetToken = '';
userdb.update(userId, user, function (error) {
2019-10-24 14:40:26 -07:00
if (error) return callback(error);
callback();
});
});
});
}
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');
assert(auditSource && typeof auditSource === 'object');
2016-04-04 15:14:00 +02:00
assert.strictEqual(typeof callback, 'function');
// This is only not allowed for the owner
2019-10-24 14:40:26 -07:00
if (username === '') return callback(new BoxError(BoxError.BAD_FIELD, 'Username cannot be empty'));
2019-07-02 20:22:17 -07:00
count(function (error, count) {
2019-10-24 14:40:26 -07:00
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);
2016-02-08 15:16:59 -08:00
callback(null, user);
2016-02-08 15:16:59 -08:00
});
});
}
2016-01-13 12:28:38 -08:00
function getOwner(callback) {
userdb.getOwner(function (error, owner) {
2019-10-24 14:40:26 -07:00
if (error) return callback(error);
2016-01-13 12:28:38 -08:00
return callback(null, owner);
2016-01-13 12:28:38 -08:00
});
}
2016-01-18 15:16:18 +01:00
function inviteLink(user) {
return `${settings.adminOrigin()}/setupaccount.html?resetToken=${user.resetToken}&email=${encodeURIComponent(user.email)}` + (user.username ? `&username=${encodeURIComponent(user.username)}` : '');
}
2018-08-17 09:49:58 -07:00
function createInvite(userId, callback) {
2016-01-18 15:16:18 +01:00
assert.strictEqual(typeof userId, 'string');
assert.strictEqual(typeof callback, 'function');
userdb.get(userId, function (error, userObject) {
2019-10-24 14:40:26 -07:00
if (error) return callback(error);
2016-01-18 15:16:18 +01:00
if (userObject.source) return callback(new BoxError(BoxError.CONFLICT, 'User is from an external directory'));
2016-01-18 15:16:18 +01:00
userObject.resetToken = hat(256);
userdb.update(userId, userObject, function (error) {
2019-10-24 14:40:26 -07:00
if (error) return callback(error);
2016-01-18 15:16:18 +01:00
callback(null, { resetToken: userObject.resetToken, inviteLink: inviteLink(userObject) });
2016-01-18 15:16:18 +01:00
});
});
}
2016-05-06 13:56:26 +02:00
2018-08-17 09:49:58 -07:00
function sendInvite(userId, options, callback) {
assert.strictEqual(typeof userId, 'string');
assert.strictEqual(typeof options, 'object');
assert.strictEqual(typeof callback, 'function');
userdb.get(userId, function (error, userObject) {
2019-10-24 14:40:26 -07:00
if (error) return callback(error);
2018-08-17 09:49:58 -07:00
if (userObject.source) return callback(new BoxError(BoxError.CONFLICT, 'User is from an external directory'));
2019-10-24 15:12:58 -07:00
if (!userObject.resetToken) return callback(new BoxError(BoxError.CONFLICT, 'Must generate resetToken to send invitation'));
2018-08-17 09:49:58 -07:00
mailer.sendInvite(userObject, options.invitor || null, inviteLink(userObject));
2018-08-17 09:49:58 -07:00
callback(null);
});
}
2018-04-25 19:08:15 +02:00
function setTwoFactorAuthenticationSecret(userId, callback) {
assert.strictEqual(typeof userId, 'string');
assert.strictEqual(typeof callback, 'function');
userdb.get(userId, function (error, result) {
2019-10-24 14:40:26 -07:00
if (error) return callback(error);
2018-04-25 19:08:15 +02:00
2019-10-24 14:40:26 -07:00
if (result.twoFactorAuthenticationEnabled) return callback(new BoxError(BoxError.ALREADY_EXISTS));
2018-04-25 19:08:15 +02:00
var secret = speakeasy.generateSecret({ name: `Cloudron ${settings.adminFqdn()} (${result.username})` });
2018-04-25 19:08:15 +02:00
userdb.update(userId, { twoFactorAuthenticationSecret: secret.base32, twoFactorAuthenticationEnabled: false }, function (error) {
2019-10-24 14:40:26 -07:00
if (error) return callback(error);
2018-04-25 19:08:15 +02:00
qrcode.toDataURL(secret.otpauth_url, function (error, dataUrl) {
2019-10-24 14:40:26 -07:00
if (error) return callback(new BoxError(BoxError.INTERNAL_ERROR, error));
2018-04-25 19:08:15 +02:00
callback(null, { secret: secret.base32, qrcode: dataUrl });
});
});
});
}
function enableTwoFactorAuthentication(userId, totpToken, callback) {
assert.strictEqual(typeof userId, 'string');
assert.strictEqual(typeof totpToken, 'string');
assert.strictEqual(typeof callback, 'function');
userdb.get(userId, function (error, result) {
2019-10-24 14:40:26 -07:00
if (error) return callback(error);
2018-04-25 19:08:15 +02:00
var verified = speakeasy.totp.verify({ secret: result.twoFactorAuthenticationSecret, encoding: 'base32', token: totpToken, window: 2 });
2019-10-24 14:40:26 -07:00
if (!verified) return callback(new BoxError(BoxError.INVALID_CREDENTIALS));
2018-04-25 19:08:15 +02:00
2019-10-24 14:40:26 -07:00
if (result.twoFactorAuthenticationEnabled) return callback(new BoxError(BoxError.ALREADY_EXISTS));
2018-04-25 19:08:15 +02:00
userdb.update(userId, { twoFactorAuthenticationEnabled: true }, function (error) {
2019-10-24 14:40:26 -07:00
if (error) return callback(error);
2018-04-25 19:08:15 +02:00
callback(null);
});
});
}
function disableTwoFactorAuthentication(userId, callback) {
assert.strictEqual(typeof userId, 'string');
assert.strictEqual(typeof callback, 'function');
userdb.update(userId, { twoFactorAuthenticationEnabled: false, twoFactorAuthenticationSecret: '' }, function (error) {
2019-10-24 14:40:26 -07:00
if (error) return callback(error);
2019-08-08 05:45:56 -07:00
callback(null);
});
}
2020-01-31 15:28:42 -08:00
function validateAppPasswordName(name) {
assert.strictEqual(typeof name, 'string');
if (name.length < 1) return new BoxError(BoxError.BAD_FIELD, 'name must be atleast 1 char');
if (name.length >= 200) return new BoxError(BoxError.BAD_FIELD, 'name too long');
return null;
}
function getAppPassword(id, callback) {
assert.strictEqual(typeof id, 'string');
assert.strictEqual(typeof callback, 'function');
userdb.getAppPassword(id, function (error, result) {
if (error) return callback(error);
callback(null, _.omit(result, 'hashedPassword'));
});
}
function addAppPassword(userId, identifier, name, callback) {
assert.strictEqual(typeof userId, 'string');
assert.strictEqual(typeof identifier, 'string');
assert.strictEqual(typeof name, 'string');
assert.strictEqual(typeof callback, 'function');
let error = validateAppPasswordName(name);
if (error) return callback(error);
2020-02-01 18:12:33 -08:00
if (identifier.length < 1) return callback(new BoxError(BoxError.BAD_FIELD, 'identifier must be atleast 1 char'));
2020-01-31 15:28:42 -08:00
const password = hat(8 * 4);
const hashedPassword = crypto.createHash('sha256').update(password).digest('base64');
var appPassword = {
id: 'uid-' + uuid.v4(),
name,
userId,
identifier,
password,
hashedPassword
};
userdb.addAppPassword(appPassword.id, appPassword, function (error) {
if (error) return callback(error);
callback(null, _.omit(appPassword, 'hashedPassword'));
});
}
function getAppPasswords(userId, callback) {
assert.strictEqual(typeof userId, 'string');
assert.strictEqual(typeof callback, 'function');
userdb.getAppPasswords(userId, function (error, results) {
if (error) return callback(error);
results.map(r => delete r.hashedPassword);
callback(null, results);
});
}
function delAppPassword(id, callback) {
assert.strictEqual(typeof id, 'string');
assert.strictEqual(typeof callback, 'function');
userdb.delAppPassword(id, function (error) {
if (error) return callback(error);
callback(null);
});
}