Add app passwords feature
This commit is contained in:
151
src/users.js
151
src/users.js
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user