Add app passwords feature

This commit is contained in:
Girish Ramakrishnan
2020-01-31 15:28:42 -08:00
parent e3878fa381
commit 3427db3983
17 changed files with 459 additions and 58 deletions

View File

@@ -28,7 +28,16 @@ exports = module.exports = {
enableTwoFactorAuthentication: enableTwoFactorAuthentication,
disableTwoFactorAuthentication: disableTwoFactorAuthentication,
count: count
count: count,
AP_MAIL: 'mail',
AP_SFTP: 'sftp',
AP_WEBADMIN: 'webadmin',
getAppPasswords: getAppPasswords,
getAppPassword: getAppPassword,
addAppPassword: addAppPassword,
delAppPassword: delAppPassword
};
let assert = require('assert'),
@@ -204,9 +213,28 @@ function verifyGhost(username, password) {
return false;
}
function verify(userId, password, callback) {
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');
assert.strictEqual(typeof identifier, 'string');
assert.strictEqual(typeof callback, 'function');
get(userId, function (error, user) {
@@ -220,47 +248,56 @@ function verify(userId, password, callback) {
return callback(null, user);
}
if (user.source === 'ldap') {
externalLdap.verifyPassword(user, password, function (error) {
if (error) return callback(error);
verifyAppPassword(user.id, password, identifier, function (error) {
if (!error) {
user.appPassword = true;
return callback(null, user);
}
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));
if (user.source === 'ldap') {
externalLdap.verifyPassword(user, password, function (error) {
if (error) return callback(error);
var derivedKeyHex = Buffer.from(derivedKey, 'binary').toString('hex');
if (derivedKeyHex !== user.password) return callback(new BoxError(BoxError.INVALID_CREDENTIALS));
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));
callback(null, user);
});
}
var derivedKeyHex = Buffer.from(derivedKey, 'binary').toString('hex');
if (derivedKeyHex !== user.password) return callback(new BoxError(BoxError.INVALID_CREDENTIALS));
callback(null, user);
});
}
});
});
}
function verifyWithUsername(username, password, callback) {
function verifyWithUsername(username, password, identifier, callback) {
assert.strictEqual(typeof username, 'string');
assert.strictEqual(typeof password, 'string');
assert.strictEqual(typeof identifier, 'string');
assert.strictEqual(typeof callback, 'function');
userdb.getByUsername(username.toLowerCase(), function (error, user) {
if (error) return callback(error);
verify(user.id, password, callback);
verify(user.id, password, identifier, callback);
});
}
function verifyWithEmail(email, password, callback) {
function verifyWithEmail(email, password, identifier, callback) {
assert.strictEqual(typeof email, 'string');
assert.strictEqual(typeof password, 'string');
assert.strictEqual(typeof identifier, 'string');
assert.strictEqual(typeof callback, 'function');
userdb.getByEmail(email.toLowerCase(), function (error, user) {
if (error) return callback(error);
verify(user.id, password, callback);
verify(user.id, password, identifier, callback);
});
}
@@ -619,3 +656,77 @@ function disableTwoFactorAuthentication(userId, callback) {
callback(null);
});
}
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);
if (identifier.length < 1) return new BoxError(BoxError.BAD_FIELD, 'identifier must be atleast 1 char');
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);
});
}