diff --git a/src/appstore.js b/src/appstore.js index 396f54a73..74f766924 100644 --- a/src/appstore.js +++ b/src/appstore.js @@ -135,7 +135,7 @@ async function getSubscription() { .ok(() => true)); if (error) throw new BoxError(BoxError.NETWORK_ERROR, error); - if (response.status === 401) throw new BoxError(BoxError.INVALID_CREDENTIALS); + if (response.status === 401) throw new BoxError(BoxError.INVALID_CREDENTIALS, response.body.message); if (response.status === 502) throw new BoxError(BoxError.EXTERNAL_ERROR, `Stripe error: ${response.status} ${JSON.stringify(response.body)}`); if (response.status !== 200) throw new BoxError(BoxError.EXTERNAL_ERROR, `Unknown error: ${response.status} ${JSON.stringify(response.body)}`); @@ -166,8 +166,8 @@ async function purchaseApp(data) { .ok(() => true)); if (error) throw new BoxError(BoxError.NETWORK_ERROR, error); - if (response.status === 404) throw new BoxError(BoxError.NOT_FOUND); // appstoreId does not exist - if (response.status === 401) throw new BoxError(BoxError.INVALID_CREDENTIALS); + if (response.status === 404) throw new BoxError(BoxError.NOT_FOUND, 'appstoreId does not exist'); + if (response.status === 401) throw new BoxError(BoxError.INVALID_CREDENTIALS, response.body.message); if (response.status === 402) throw new BoxError(BoxError.LICENSE_ERROR, response.body.message); // 200 if already purchased, 201 is newly purchased if (response.status !== 201 && response.status !== 200) throw new BoxError(BoxError.EXTERNAL_ERROR, `App purchase failed. ${response.status} ${JSON.stringify(response.body)}`); @@ -190,7 +190,7 @@ async function unpurchaseApp(appId, data) { if (error) throw new BoxError(BoxError.NETWORK_ERROR, error); if (response.status === 404) return; // was never purchased - if (response.status === 401) throw new BoxError(BoxError.INVALID_CREDENTIALS); + if (response.status === 401) throw new BoxError(BoxError.INVALID_CREDENTIALS, response.body.message); if (response.status !== 200) throw new BoxError(BoxError.EXTERNAL_ERROR, `App unpurchase failed to get app. status:${response.status}`); [error, response] = await safe(superagent.del(url) @@ -200,7 +200,7 @@ async function unpurchaseApp(appId, data) { .ok(() => true)); if (error) throw new BoxError(BoxError.NETWORK_ERROR, error); - if (response.status === 401) throw new BoxError(BoxError.INVALID_CREDENTIALS); + if (response.status === 401) throw new BoxError(BoxError.INVALID_CREDENTIALS, response.body.message); if (response.status !== 204) throw new BoxError(BoxError.EXTERNAL_ERROR, `App unpurchase failed. status:${response.status}`); } @@ -222,7 +222,7 @@ async function getBoxUpdate(options) { .ok(() => true)); if (error) throw new BoxError(BoxError.NETWORK_ERROR, error); - if (response.status === 401) throw new BoxError(BoxError.INVALID_CREDENTIALS); + if (response.status === 401) throw new BoxError(BoxError.INVALID_CREDENTIALS, response.body.message); if (response.status === 204) return; // no update if (response.status !== 200 || !response.body) throw new BoxError(BoxError.EXTERNAL_ERROR, `Bad response: ${response.status} ${response.text}`); @@ -265,7 +265,7 @@ async function getAppUpdate(app, options) { .ok(() => true)); if (error) throw new BoxError(BoxError.NETWORK_ERROR, error); - if (response.status === 401) throw new BoxError(BoxError.INVALID_CREDENTIALS); + if (response.status === 401) throw new BoxError(BoxError.INVALID_CREDENTIALS, response.body.message); if (response.status === 204) return; // no update if (response.status !== 200 || !response.body) throw new BoxError(BoxError.EXTERNAL_ERROR, `Bad response: ${response.status} ${response.text}`); @@ -333,7 +333,7 @@ async function updateCloudron(data) { .ok(() => true)); if (error) throw new BoxError(BoxError.NETWORK_ERROR, error); - if (response.status === 401) throw new BoxError(BoxError.INVALID_CREDENTIALS); + if (response.status === 401) throw new BoxError(BoxError.INVALID_CREDENTIALS, response.body.message); if (response.status !== 200) throw new BoxError(BoxError.EXTERNAL_ERROR, `Bad response: ${response.status} ${response.text}`); debug(`updateCloudron: Cloudron updated with data ${JSON.stringify(data)}`); @@ -403,7 +403,7 @@ async function createTicket(info, auditSource) { const [error, response] = await safe(request); if (error) throw new BoxError(BoxError.NETWORK_ERROR, error); - if (response.status === 401) throw new BoxError(BoxError.INVALID_CREDENTIALS); + if (response.status === 401) throw new BoxError(BoxError.INVALID_CREDENTIALS, response.body.message); if (response.status !== 201) throw new BoxError(BoxError.EXTERNAL_ERROR, `Bad response: ${response.status} ${response.text}`); await eventlog.add(eventlog.ACTION_SUPPORT_TICKET, auditSource, info); @@ -445,7 +445,7 @@ async function getApps() { .ok(() => true)); if (error) throw new BoxError(BoxError.NETWORK_ERROR, error); - if (response.status === 403 || response.status === 401) throw new BoxError(BoxError.INVALID_CREDENTIALS); + if (response.status === 403 || response.status === 401) throw new BoxError(BoxError.INVALID_CREDENTIALS, response.body.message); if (response.status !== 200) throw new BoxError(BoxError.EXTERNAL_ERROR, `App listing failed. ${response.status} ${JSON.stringify(response.body)}`); if (!response.body.apps) throw new BoxError(BoxError.EXTERNAL_ERROR, `Bad response: ${response.status} ${response.text}`); @@ -468,8 +468,8 @@ async function getAppVersion(appId, version) { .ok(() => true)); if (error) throw new BoxError(BoxError.NETWORK_ERROR, error); - if (response.status === 403 || response.statusCode === 401) throw new BoxError(BoxError.INVALID_CREDENTIALS); - if (response.status === 404) throw new BoxError(BoxError.NOT_FOUND); + if (response.status === 403 || response.statusCode === 401) throw new BoxError(BoxError.INVALID_CREDENTIALS, response.body.message); + if (response.status === 404) throw new BoxError(BoxError.NOT_FOUND, `Could not find app ${appId}`); if (response.status !== 200) throw new BoxError(BoxError.EXTERNAL_ERROR, `App fetch failed. ${response.status} ${JSON.stringify(response.body)}`); return response.body; diff --git a/src/apptask.js b/src/apptask.js index c81016609..621fe07a0 100644 --- a/src/apptask.js +++ b/src/apptask.js @@ -238,7 +238,7 @@ async function downloadImage(manifest) { const [dfError, diskUsage] = await safe(df.file(info.DockerRootDir)); if (dfError) throw new BoxError(BoxError.FS_ERROR, `Error getting file system info: ${dfError.message}`); - if (diskUsage.available < (1024*1024*1024)) throw new BoxError(BoxError.DOCKER_ERROR, 'Not enough disk space to pull docker image', { diskUsage: diskUsage, dockerRootDir: info.DockerRootDir }); + if (diskUsage.available < (1024*1024*1024)) throw new BoxError(BoxError.DOCKER_ERROR, `Not enough disk space to pull docker image. available: ${diskUsage.available}`); await docker.downloadImage(manifest); } diff --git a/src/boxerror.js b/src/boxerror.js index f7c59fd34..ac5d29f03 100644 --- a/src/boxerror.js +++ b/src/boxerror.js @@ -8,9 +8,10 @@ const assert = require('assert'), exports = module.exports = BoxError; -function BoxError(reason, errorOrMessage) { +function BoxError(reason, errorOrMessage, extra) { assert.strictEqual(typeof reason, 'string'); - assert(errorOrMessage instanceof Error || typeof errorOrMessage === 'string' || typeof errorOrMessage === 'undefined'); + assert(errorOrMessage instanceof Error || typeof errorOrMessage === 'string'); + assert(typeof override === 'object' || typeof override === 'undefined'); Error.call(this); Error.captureStackTrace(this, this.constructor); @@ -27,9 +28,11 @@ function BoxError(reason, errorOrMessage) { const messages = errorOrMessage.errors.map(e => e.message); this.message = `${errorOrMessage.message} messages: ${messages.join(',')}`; this.nestedError = errorOrMessage; + Object.assign(this, extra); } else { // error object this.message = errorOrMessage.message; this.nestedError = errorOrMessage; + Object.assign(this, extra); } } util.inherits(BoxError, Error); diff --git a/src/dns.js b/src/dns.js index 39bee25d7..fddb607e6 100644 --- a/src/dns.js +++ b/src/dns.js @@ -202,12 +202,12 @@ async function waitForLocations(locations, progressCallback) { if (ipv4) { const [error] = await safe(waitForDnsRecord(subdomain, domain, 'A', ipv4, { times: 240 })); - if (error) throw new BoxError(BoxError.DNS_ERROR, `DNS A Record is not synced yet: ${error.message}`, { ipv4, subdomain, domain }); + if (error) throw new BoxError(BoxError.DNS_ERROR, `DNS A Record of ${fqdn(subdomain, domain)} is not synced yet: ${error.message}`); } if (ipv6) { const [error] = await safe(waitForDnsRecord(subdomain, domain, 'AAAA', ipv6, { times: 240 })); - if (error) throw new BoxError(BoxError.DNS_ERROR, `DNS AAAA Record is not synced yet: ${error.message}`, { ipv6, subdomain, domain }); + if (error) throw new BoxError(BoxError.DNS_ERROR, `DNS AAAA Record of ${fqdn(subdomain, domain)} is not synced yet: ${error.message}`); } } } diff --git a/src/docker.js b/src/docker.js index 4bfdd1a76..4d7e03a9a 100644 --- a/src/docker.js +++ b/src/docker.js @@ -431,7 +431,7 @@ async function startContainer(containerId) { const container = gConnection.getContainer(containerId); const [error] = await safe(container.start()); - if (error && error.statusCode === 404) throw new BoxError(BoxError.NOT_FOUND); + if (error && error.statusCode === 404) throw new BoxError(BoxError.NOT_FOUND, `Container ${containerId} not found`); if (error && error.statusCode === 400) throw new BoxError(BoxError.BAD_FIELD, error); // e.g start.sh is not executable if (error && error.statusCode !== 304) throw new BoxError(BoxError.DOCKER_ERROR, error); // 304 means already started } @@ -442,7 +442,7 @@ async function restartContainer(containerId) { const container = gConnection.getContainer(containerId); const [error] = await safe(container.restart()); - if (error && error.statusCode === 404) throw new BoxError(BoxError.NOT_FOUND); + if (error && error.statusCode === 404) throw new BoxError(BoxError.NOT_FOUND, `Contanier ${containerId} not found`); if (error && error.statusCode === 400) throw new BoxError(BoxError.BAD_FIELD, error); // e.g start.sh is not executable if (error && error.statusCode !== 204) throw new BoxError(BoxError.DOCKER_ERROR, error); } @@ -572,7 +572,7 @@ async function createExec(containerId, options) { const container = gConnection.getContainer(containerId); const [error, exec] = await safe(container.exec(options)); - if (error && error.statusCode === 404) throw new BoxError(BoxError.NOT_FOUND); + if (error && error.statusCode === 404) throw new BoxError(BoxError.NOT_FOUND, `Container ${containerId} not found`); if (error && error.statusCode === 409) throw new BoxError(BoxError.BAD_STATE, error.message); // container restarting/not running if (error) throw new BoxError(BoxError.DOCKER_ERROR, error); @@ -585,7 +585,7 @@ async function startExec(execId, options) { const exec = gConnection.getExec(execId); const [error, stream] = await safe(exec.start(options)); /* in hijacked mode, stream is a net.socket */ - if (error && error.statusCode === 404) throw new BoxError(BoxError.NOT_FOUND); + if (error && error.statusCode === 404) throw new BoxError(BoxError.NOT_FOUND, `Exec container ${execId} not found`); if (error) throw new BoxError(BoxError.DOCKER_ERROR, error); return stream; } @@ -607,7 +607,7 @@ async function resizeExec(execId, options) { const exec = gConnection.getExec(execId); const [error] = await safe(exec.resize(options)); // { h, w } - if (error && error.statusCode === 404) throw new BoxError(BoxError.NOT_FOUND); + if (error && error.statusCode === 404) throw new BoxError(BoxError.NOT_FOUND, `Exec container ${execId} not found`); if (error) throw new BoxError(BoxError.DOCKER_ERROR, error); } @@ -625,7 +625,7 @@ async function memoryUsage(containerId) { const container = gConnection.getContainer(containerId); const [error, result] = await safe(container.stats({ stream: false })); - if (error && error.statusCode === 404) throw new BoxError(BoxError.NOT_FOUND); + if (error && error.statusCode === 404) throw new BoxError(BoxError.NOT_FOUND, `Container ${containerId} not found`); if (error) throw new BoxError(BoxError.DOCKER_ERROR, error); return result; diff --git a/src/externalldap.js b/src/externalldap.js index 3109e9459..177150fa3 100644 --- a/src/externalldap.js +++ b/src/externalldap.js @@ -104,8 +104,8 @@ async function getClient(config, options) { assert.strictEqual(typeof options, 'object'); // basic validation to not crash - try { ldap.parseDN(config.baseDn); } catch (e) { throw new BoxError(BoxError.BAD_FIELD, 'invalid baseDn'); } - try { ldap.parseFilter(config.filter); } catch (e) { throw new BoxError(BoxError.BAD_FIELD, 'invalid filter'); } + try { ldap.parseDN(config.baseDn); } catch (e) { throw new BoxError(BoxError.BAD_FIELD, `invalid baseDn ${config.baseDn}: ${e.message}`); } + try { ldap.parseFilter(config.filter); } catch (e) { throw new BoxError(BoxError.BAD_FIELD, `invalid filter ${config.filter}: ${e.mssage}`); } let client; try { @@ -135,7 +135,7 @@ async function getClient(config, options) { if (!config.bindDn || !options.bind) return resolve(client); client.bind(config.bindDn, config.bindPassword, function (error) { - if (error instanceof ldap.InvalidCredentialsError) return reject(new BoxError(BoxError.INVALID_CREDENTIALS)); + if (error instanceof ldap.InvalidCredentialsError) return reject(new BoxError(BoxError.INVALID_CREDENTIALS, 'Incorrect bind password')); if (error) return reject(new BoxError(BoxError.EXTERNAL_ERROR, error)); resolve(client); @@ -149,11 +149,11 @@ async function clientSearch(client, dn, searchOptions) { assert.strictEqual(typeof searchOptions, 'object'); // basic validation to not crash - try { ldap.parseDN(dn); } catch (e) { throw new BoxError(BoxError.BAD_FIELD, 'invalid DN'); } + try { ldap.parseDN(dn); } catch (e) { throw new BoxError(BoxError.BAD_FIELD, `invalid dn ${dn}: ${e.message}`); } return await new Promise((resolve, reject) => { client.search(dn, searchOptions, function (error, result) { - if (error instanceof ldap.NoSuchObjectError) return reject(new BoxError(BoxError.NOT_FOUND)); + if (error instanceof ldap.NoSuchObjectError) return reject(new BoxError(BoxError.NOT_FOUND, `dn not found ${dn}`)); if (error) return reject(new BoxError(BoxError.EXTERNAL_ERROR, error)); const ldapObjects = []; @@ -184,7 +184,7 @@ async function ldapGetByDN(config, dn) { const client = await getClient(config, { bind: true }); const result = await clientSearch(client, dn, searchOptions); client.unbind(); - if (result.length === 0) throw new BoxError(BoxError.NOT_FOUND); + if (result.length === 0) throw new BoxError(BoxError.NOT_FOUND, `dn ${dn} not found`); return result[0]; } @@ -243,20 +243,20 @@ async function testConfig(config) { // bindDn may not be a dn! if (!config.baseDn) return new BoxError(BoxError.BAD_FIELD, 'basedn must not be empty'); - try { ldap.parseDN(config.baseDn); } catch (e) { return new BoxError(BoxError.BAD_FIELD, 'invalid baseDn'); } + try { ldap.parseDN(config.baseDn); } catch (e) { throw new BoxError(BoxError.BAD_FIELD, `invalid base ${config.baseDn}: ${e.message}`); } if (!config.filter) return new BoxError(BoxError.BAD_FIELD, 'filter must not be empty'); - try { ldap.parseFilter(config.filter); } catch (e) { return new BoxError(BoxError.BAD_FIELD, 'invalid filter'); } + try { ldap.parseFilter(config.filter); } catch (e) { return new BoxError(BoxError.BAD_FIELD, `invalid filter ${config.filter}: ${e.message}`); } if ('syncGroups' in config && typeof config.syncGroups !== 'boolean') return new BoxError(BoxError.BAD_FIELD, 'syncGroups must be a boolean'); if ('acceptSelfSignedCerts' in config && typeof config.acceptSelfSignedCerts !== 'boolean') return new BoxError(BoxError.BAD_FIELD, 'acceptSelfSignedCerts must be a boolean'); if (config.syncGroups) { if (!config.groupBaseDn) return new BoxError(BoxError.BAD_FIELD, 'groupBaseDn must not be empty'); - try { ldap.parseDN(config.groupBaseDn); } catch (e) { return new BoxError(BoxError.BAD_FIELD, 'invalid groupBaseDn'); } + try { ldap.parseDN(config.groupBaseDn); } catch (e) { return new BoxError(BoxError.BAD_FIELD, `invalid groupBaseDn ${config.groupBaseDn}: ${e.message}`); } if (!config.groupFilter) return new BoxError(BoxError.BAD_FIELD, 'groupFilter must not be empty'); - try { ldap.parseFilter(config.groupFilter); } catch (e) { return new BoxError(BoxError.BAD_FIELD, 'invalid groupFilter'); } + try { ldap.parseFilter(config.groupFilter); } catch (e) { return new BoxError(BoxError.BAD_FIELD, `invalid groupFilter ${config.groupFilter}: ${e.message}`); } if (!config.groupnameField || typeof config.groupnameField !== 'string') return new BoxError(BoxError.BAD_FIELD, 'groupFilter must not be empty'); } @@ -284,11 +284,11 @@ async function maybeCreateUser(identifier) { if (!config.autoCreate) throw new BoxError(BoxError.BAD_STATE, 'auto create not enabled'); const ldapUsers = await ldapUserSearch(config, { filter: `${config.usernameField}=${identifier}` }); - if (ldapUsers.length === 0) throw new BoxError(BoxError.NOT_FOUND); - if (ldapUsers.length > 1) throw new BoxError(BoxError.CONFLICT); + if (ldapUsers.length === 0) throw new BoxError(BoxError.NOT_FOUND, `no users found for filter ${config.usernameField}=${identifier}`); + if (ldapUsers.length > 1) throw new BoxError(BoxError.CONFLICT, `more than 1 user matches filter ${config.usernameField}=${identifier}`); const user = translateUser(config, ldapUsers[0]); - if (!user) throw new BoxError(BoxError.BAD_FIELD); + if (!user) throw new BoxError(BoxError.BAD_FIELD, 'Failed to translate user'); return await users.add(user.email, { username: user.username, password: null, displayName: user.displayName, source: 'ldap' }, AuditSource.EXTERNAL_LDAP); } @@ -302,8 +302,8 @@ async function verifyPassword(username, password, options) { if (config.provider === 'noop') throw new BoxError(BoxError.BAD_STATE, 'not enabled'); const ldapUsers = await ldapUserSearch(config, { filter: `${config.usernameField}=${username}` }); - if (ldapUsers.length === 0) throw new BoxError(BoxError.NOT_FOUND); - if (ldapUsers.length > 1) throw new BoxError(BoxError.CONFLICT); + if (ldapUsers.length === 0) throw new BoxError(BoxError.NOT_FOUND, 'no such user'); + if (ldapUsers.length > 1) throw new BoxError(BoxError.CONFLICT, 'multiple users found'); const client = await getClient(config, { bind: false }); @@ -322,7 +322,7 @@ async function verifyPassword(username, password, options) { if (error) throw new BoxError(BoxError.EXTERNAL_ERROR, error); const user = translateUser(config, ldapUsers[0]); - if (!user) throw new BoxError(BoxError.BAD_FIELD); + if (!user) throw new BoxError(BoxError.BAD_FIELD, 'could not translate user'); return user; } diff --git a/src/ldapserver.js b/src/ldapserver.js index b929caf13..ffe52e797 100644 --- a/src/ldapserver.js +++ b/src/ldapserver.js @@ -84,8 +84,7 @@ async function getUsersWithAccessToApp(req) { // helper function to deal with pagination function finalSend(results, req, res, next) { - let min = 0; - let max = results.length; + const min = 0, max = results.length; let cookie = null; let pageSize = 0; @@ -151,7 +150,7 @@ async function userSearch(req, res, next) { const [groupsError, allGroups] = await safe(groups.listWithMembers()); if (groupsError) return next(new ldap.OperationsError(groupsError.message)); - let results = []; + const results = []; // send user objects result.forEach(function (user) { @@ -200,14 +199,12 @@ async function groupSearch(req, res, next) { const results = []; - let [groupsListError, resultGroups] = await safe(groups.listWithMembers()); + const [groupsListError, groupsResult] = await safe(groups.listWithMembers()); if (groupsListError) return next(new ldap.OperationsError(groupsListError.message)); - if (req.app.accessRestriction && req.app.accessRestriction.groups) { - resultGroups = resultGroups.filter(function (g) { return req.app.accessRestriction.groups.indexOf(g.id) !== -1; }); - } + for (const group of groupsResult) { + if (req.app.accessRestriction?.groups?.indexOf(group.id) === -1) continue; - resultGroups.forEach(function (group) { const dn = ldap.parseDN(`cn=${group.name},ou=groups,dc=cloudron`); const obj = { @@ -227,7 +224,7 @@ async function groupSearch(req, res, next) { if ((req.dn.equals(dn) || req.dn.parentOf(dn)) && lowerCaseFilter.matches(obj.attributes)) { results.push(obj); } - }); + } finalSend(results, req, res, next); } @@ -308,14 +305,13 @@ async function mailboxSearch(req, res, next) { } } else { // new sogo and dovecot listing (doveadm -A) // TODO figure out how proper pagination here could work - let [error, mailboxes] = await safe(mail.listAllMailboxes(1, 100000)); + const [error, mailboxes] = await safe(mail.listAllMailboxes(1, 100000)); if (error) return next(new ldap.OperationsError(error.message)); - mailboxes = mailboxes.filter(m => m.active); - - let results = []; + const results = []; for (const mailbox of mailboxes) { + if (!mailbox.active) continue; const dn = ldap.parseDN(`cn=${mailbox.name}@${mailbox.domain},ou=mailboxes,dc=cloudron`); if (mailbox.ownerType === mail.OWNERTYPE_APP) continue; // cannot login with app mailbox anyway @@ -397,8 +393,8 @@ async function mailingListSearch(req, res, next) { if (!req.dn.rdns[0].attrs.cn) return next(new ldap.NoSuchObjectError('Missing CN')); - let email = req.dn.rdns[0].attrs.cn.value.toLowerCase(); - let parts = email.split('@'); + const email = req.dn.rdns[0].attrs.cn.value.toLowerCase(); + const parts = email.split('@'); if (parts.length !== 2) return next(new ldap.NoSuchObjectError('Invalid CN')); const name = parts[0], domain = parts[1]; @@ -474,10 +470,10 @@ async function verifyMailboxPassword(mailbox, password) { break; // found a matching validated user } - if (!verifiedUser) throw new BoxError(BoxError.INVALID_CREDENTIALS); + if (!verifiedUser) throw new BoxError(BoxError.INVALID_CREDENTIALS, 'Incorrect password'); return verifiedUser; } else { - throw new BoxError(BoxError.INVALID_CREDENTIALS); + throw new BoxError(BoxError.INVALID_CREDENTIALS, 'Incorrect password'); } } @@ -490,11 +486,11 @@ async function authenticateSftp(req, res, next) { const parts = email.split('@'); if (parts.length !== 2) return next(new ldap.NoSuchObjectError('Invalid CN')); - let [error, app] = await safe(apps.getByFqdn(parts[1])); - if (error || !app) return next(new ldap.InvalidCredentialsError()); + const [getAppError, app] = await safe(apps.getByFqdn(parts[1])); + if (getAppError || !app) return next(new ldap.InvalidCredentialsError()); - [error] = await safe(users.verifyWithUsername(parts[0], req.credentials, app.id, { skipTotpCheck: true })); - if (error) return next(new ldap.InvalidCredentialsError(error.message)); + const [verifyError] = await safe(users.verifyWithUsername(parts[0], req.credentials, app.id, { skipTotpCheck: true })); + if (verifyError) return next(new ldap.InvalidCredentialsError(verifyError.message)); debug('sftp auth: success'); @@ -551,11 +547,11 @@ async function verifyAppMailboxPassword(serviceId, username, password) { const pattern = serviceId === 'msa' ? 'MAIL_SMTP' : 'MAIL_IMAP'; const addonId = serviceId === 'msa' ? 'sendmail' : 'recvmail'; const appId = await addonConfigs.getAppIdByValue(addonId, `%${pattern}_PASSWORD`, password); // search by password because this is unique for each app - if (!appId) throw new BoxError(BoxError.NOT_FOUND); + if (!appId) throw new BoxError(BoxError.NOT_FOUND, 'Could not find app'); const result = await addonConfigs.get(appId, addonId); - if (!result.some(r => r.name.endsWith(`${pattern}_USERNAME`) && r.value === username)) throw new BoxError(BoxError.INVALID_CREDENTIALS); + if (!result.some(r => r.name.endsWith(`${pattern}_USERNAME`) && r.value === username)) throw new BoxError(BoxError.INVALID_CREDENTIALS, 'Could not locate user'); } async function authenticateService(serviceId, dn, req, res, next) { diff --git a/src/users.js b/src/users.js index bccfef76a..1eae65d0d 100644 --- a/src/users.js +++ b/src/users.js @@ -344,7 +344,7 @@ async function verifyAppPassword(userId, password, identifier) { if (hashedPasswords.includes(hash)) return; - throw new BoxError(BoxError.INVALID_CREDENTIALS); + throw new BoxError(BoxError.INVALID_CREDENTIALS, 'Password is not valid'); } // identifier is only used to check if password is valid for a specific app @@ -881,7 +881,7 @@ async function setTwoFactorAuthenticationSecret(user, auditSource) { if (constants.DEMO && user.username === constants.DEMO_USERNAME) throw new BoxError(BoxError.BAD_STATE, 'Not allowed in demo mode'); - if (user.twoFactorAuthenticationEnabled) throw new BoxError(BoxError.ALREADY_EXISTS); + if (user.twoFactorAuthenticationEnabled) throw new BoxError(BoxError.ALREADY_EXISTS, '2FA is already enabled'); const { fqdn:dashboardFqdn } = await dashboard.getLocation(); const secret = speakeasy.generateSecret({ name: `Cloudron ${dashboardFqdn} (${user.username})` }); @@ -903,9 +903,9 @@ async function enableTwoFactorAuthentication(user, totpToken, auditSource) { if (user.source === 'ldap' && externalLdap.supports2FA(externalLdapConfig)) throw new BoxError(BoxError.BAD_STATE, 'Cannot enable 2FA of external auth user'); const verified = speakeasy.totp.verify({ secret: user.twoFactorAuthenticationSecret, encoding: 'base32', token: totpToken, window: 2 }); - if (!verified) throw new BoxError(BoxError.INVALID_CREDENTIALS); + if (!verified) throw new BoxError(BoxError.INVALID_CREDENTIALS, 'Invalid 2FA code'); - if (user.twoFactorAuthenticationEnabled) throw new BoxError(BoxError.ALREADY_EXISTS); + if (user.twoFactorAuthenticationEnabled) throw new BoxError(BoxError.ALREADY_EXISTS, '2FA already enabled'); await update(user, { twoFactorAuthenticationEnabled: true }, auditSource); }