unify totp check

the totp check is done in several places causing errors like 3552232e99

* ldap (addon)
* accesscontrol (dashboard)
* proxyauth
* directoryserver (exposed ldap)
* externalldap (the connector)

The code also makes externalldap auto-create work now across all the cases where there is a username
This commit is contained in:
Girish Ramakrishnan
2023-03-12 15:09:20 +01:00
parent 8e0d1b61af
commit 53e9eccf72
11 changed files with 103 additions and 147 deletions

View File

@@ -331,10 +331,11 @@ async function verifyAppPassword(userId, password, identifier) {
}
// identifier is only used to check if password is valid for a specific app
async function verify(userId, password, identifier) {
async function verify(userId, password, identifier, options) {
assert.strictEqual(typeof userId, 'string');
assert.strictEqual(typeof password, 'string');
assert.strictEqual(typeof identifier, 'string');
assert.strictEqual(typeof options, 'object');
const user = await get(userId);
if (!user) throw new BoxError(BoxError.NOT_FOUND, 'User not found');
@@ -356,42 +357,61 @@ async function verify(userId, password, identifier) {
return user;
}
const relaxedTotpCheck = !!options.relaxedTotpCheck; // will enforce totp only if totpToken is valid
const totpToken = options.totpToken || null;
if (user.source === 'ldap') {
const ldapUser = await externalLdap.verifyPassword(user, password);
// currently we store twoFactorAuthenticationEnabled in the db as local so amend it to user object
user.twoFactorAuthenticationEnabled = !!ldapUser.twoFactorAuthenticationEnabled;
await externalLdap.verifyPassword(user, password, totpToken);
} else {
const saltBinary = Buffer.from(user.salt, 'hex');
const [error, derivedKey] = await safe(pbkdf2Async(password, saltBinary, CRYPTO_ITERATIONS, CRYPTO_KEY_LENGTH, CRYPTO_DIGEST));
if (error) throw new BoxError(BoxError.CRYPTO_ERROR, error);
const derivedKeyHex = Buffer.from(derivedKey, 'binary').toString('hex');
if (derivedKeyHex !== user.password) throw new BoxError(BoxError.INVALID_CREDENTIALS);
if (derivedKeyHex !== user.password) throw new BoxError(BoxError.INVALID_CREDENTIALS, 'Username and password does not match');
if (user.twoFactorAuthenticationEnabled) {
if (totpToken) {
const verified = speakeasy.totp.verify({ secret: user.twoFactorAuthenticationSecret, encoding: 'base32', token: totpToken, window: 2 });
if (!verified) throw new BoxError(BoxError.INVALID_CREDENTIALS, 'Invalid totpToken');
} else if (!relaxedTotpCheck) {
throw new BoxError(BoxError.INVALID_CREDENTIALS, 'A totpToken must be provided');
}
}
}
return user;
}
async function verifyWithUsername(username, password, identifier) {
async function verifyWithUsername(username, password, identifier, options) {
assert.strictEqual(typeof username, 'string');
assert.strictEqual(typeof password, 'string');
assert.strictEqual(typeof identifier, 'string');
assert.strictEqual(typeof options, 'object');
const user = await getByUsername(username.toLowerCase());
if (!user) throw new BoxError(BoxError.NOT_FOUND, 'User not found');
if (user) return await verify(user.id, password, identifier, options);
return await verify(user.id, password, identifier);
const [error, newUserId] = await safe(externalLdap.maybeCreateUser(username.toLowerCase()));
if (error && error.reason === BoxError.BAD_STATE) throw new BoxError(BoxError.NOT_FOUND, 'User not found'); // no external ldap or no auto create
if (error) {
debug(`verifyWithUsername: failed to auto create user ${username}`, error);
throw new BoxError(BoxError.NOT_FOUND, 'User not found');
}
return await verify(newUserId, password, identifier, options);
}
async function verifyWithEmail(email, password, identifier) {
async function verifyWithEmail(email, password, identifier, options) {
assert.strictEqual(typeof email, 'string');
assert.strictEqual(typeof password, 'string');
assert.strictEqual(typeof identifier, 'string');
assert.strictEqual(typeof options, 'object');
const user = await getByEmail(email.toLowerCase());
if (!user) throw new BoxError(BoxError.NOT_FOUND, 'User not found');
return await verify(user.id, password, identifier);
return await verify(user.id, password, identifier, options);
}
async function del(user, auditSource) {