diff --git a/src/ldapserver.js b/src/ldapserver.js index 1304a2f31..ed6d27c0e 100644 --- a/src/ldapserver.js +++ b/src/ldapserver.js @@ -589,6 +589,7 @@ async function authenticateService(serviceId, dn, req, res, next) { if (appPasswordError.reason !== BoxError.NOT_FOUND) return next(new ldap.OperationsError(appPasswordError.message)); if (!mailbox || !mailbox.active) return next(new ldap.NoSuchObjectError(dn.toString())); // user auth requires active mailbox + const [verifyError, result] = await safe(verifyMailboxPassword(mailbox, req.credentials || '')); if (verifyError && verifyError.reason === BoxError.NOT_FOUND) return next(new ldap.NoSuchObjectError(verifyError.message)); if (verifyError && verifyError.reason === BoxError.INVALID_CREDENTIALS) return next(new ldap.InvalidCredentialsError(verifyError.message)); diff --git a/src/mailpasswords.js b/src/mailpasswords.js index bd6aa5194..1fc16f085 100644 --- a/src/mailpasswords.js +++ b/src/mailpasswords.js @@ -14,6 +14,12 @@ async function get(appId, userId) { return result[0]; } +async function getByUserId(userId) { + assert.strictEqual(typeof userId, 'string'); + + return await database.query('SELECT ' + MAIL_PASSWORD_FIELDS + ' FROM mailPasswords WHERE userId = ?', [ userId ]); +} + async function add(appId, userId, password) { assert.strictEqual(typeof appId, 'string'); assert.strictEqual(typeof userId, 'string'); @@ -50,6 +56,7 @@ async function del(appId, userId) { export default { get, + getByUserId, add, list, del diff --git a/src/users.js b/src/users.js index 9ef3a92e3..5e0eefd2b 100644 --- a/src/users.js +++ b/src/users.js @@ -11,6 +11,7 @@ import externalLdap from './externalldap.js'; import hat from './hat.js'; import mail from './mail.js'; import mailer from './mailer.js'; +import mailPasswords from './mailpasswords.js'; import mysql from 'mysql2'; import notifications from './notifications.js'; import oidcClients from './oidcclients.js'; @@ -572,6 +573,17 @@ async function verifyAppPassword(userId, password, identifier) { throw new BoxError(BoxError.INVALID_CREDENTIALS, 'Password is not valid'); } +async function verifyMailPassword(userId, password) { + assert.strictEqual(typeof userId, 'string'); + assert.strictEqual(typeof password, 'string'); + + const result = await mailPasswords.getByUserId(userId); + + if (result.find(r => r.password === password)) return; + + throw new BoxError(BoxError.INVALID_CREDENTIALS, 'Password is not valid'); +} + // identifier is only used to check if password is valid for a specific app async function verify(user, password, identifier, options) { assert.strictEqual(typeof user, 'object'); @@ -602,6 +614,13 @@ async function verify(user, password, identifier, options) { return user; } + const [mailPasswordError] = await safe(verifyMailPassword(user.id, password)); + if (!mailPasswordError) { // matched app password + debug(`verify: ${user.username || user.id} matched mail password`); + user.mailPassword = true; + return user; + } + let localTotpCheck; // does 2fa need to be verified with local database 2fa creds if (user.source === 'ldap') { await externalLdap.verifyPassword(user.username, password, options);