security: do not password reset mail to cloudron owned mail domain
https://forum.cloudron.io/topic/7951/privilege-escalation-through-mail-manager-role
This commit is contained in:
40
src/users.js
40
src/users.js
@@ -80,6 +80,7 @@ const appPasswords = require('./apppasswords.js'),
|
||||
eventlog = require('./eventlog.js'),
|
||||
externalLdap = require('./externalldap.js'),
|
||||
hat = require('./hat.js'),
|
||||
mail = require('./mail.js'),
|
||||
mailer = require('./mailer.js'),
|
||||
mysql = require('mysql'),
|
||||
qrcode = require('qrcode'),
|
||||
@@ -630,24 +631,6 @@ async function getSuperadmins() {
|
||||
return await getByRole(exports.ROLE_OWNER);
|
||||
}
|
||||
|
||||
async function sendPasswordResetByIdentifier(identifier, auditSource) {
|
||||
assert.strictEqual(typeof identifier, 'string');
|
||||
assert.strictEqual(typeof auditSource, 'object');
|
||||
|
||||
const user = identifier.indexOf('@') === -1 ? await getByUsername(identifier.toLowerCase()) : await getByEmail(identifier.toLowerCase());
|
||||
if (!user) throw new BoxError(BoxError.NOT_FOUND, 'User not found');
|
||||
|
||||
const resetToken = hat(256);
|
||||
const resetTokenCreationTime = new Date();
|
||||
|
||||
user.resetToken = resetToken;
|
||||
user.resetTokenCreationTime = resetTokenCreationTime;
|
||||
await update(user, { resetToken,resetTokenCreationTime }, auditSource);
|
||||
|
||||
const resetLink = `${settings.dashboardOrigin()}/login.html?resetToken=${user.resetToken}`;
|
||||
await mailer.passwordReset(user, user.fallbackEmail || user.email, resetLink);
|
||||
}
|
||||
|
||||
async function getPasswordResetLink(user, auditSource) {
|
||||
assert.strictEqual(typeof user, 'object');
|
||||
assert.strictEqual(typeof auditSource, 'object');
|
||||
@@ -667,6 +650,23 @@ async function getPasswordResetLink(user, auditSource) {
|
||||
return resetLink;
|
||||
}
|
||||
|
||||
async function sendPasswordResetByIdentifier(identifier, auditSource) {
|
||||
assert.strictEqual(typeof identifier, 'string');
|
||||
assert.strictEqual(typeof auditSource, 'object');
|
||||
|
||||
const user = identifier.indexOf('@') === -1 ? await getByUsername(identifier.toLowerCase()) : await getByEmail(identifier.toLowerCase());
|
||||
if (!user) throw new BoxError(BoxError.NOT_FOUND, 'User not found');
|
||||
|
||||
const email = user.fallbackEmail || user.email;
|
||||
|
||||
// security measure to prevent a mail manager or admin resetting the superadmin's password
|
||||
const mailDomains = await mail.listDomains();
|
||||
if (mailDomains.some(d => d.enabled && email.endsWith(`@${d.domain}`))) throw new BoxError(BoxError.CONFLICT, 'Password reset email cannot be sent to email addresses hosted on Cloudron');
|
||||
|
||||
const resetLink = await getPasswordResetLink(user, auditSource);
|
||||
await mailer.passwordReset(user, email, resetLink);
|
||||
}
|
||||
|
||||
async function sendPasswordResetEmail(user, email, auditSource) {
|
||||
assert.strictEqual(typeof user, 'object');
|
||||
assert.strictEqual(typeof email, 'string');
|
||||
@@ -675,6 +675,10 @@ async function sendPasswordResetEmail(user, email, auditSource) {
|
||||
const error = validateEmail(email);
|
||||
if (error) throw error;
|
||||
|
||||
// security measure to prevent a mail manager or admin resetting the superadmin's password
|
||||
const mailDomains = await mail.listDomains();
|
||||
if (mailDomains.some(d => d.enabled && email.endsWith(`@${d.domain}`))) throw new BoxError(BoxError.CONFLICT, 'Password reset email cannot be sent to email addresses hosted on Cloudron');
|
||||
|
||||
const resetLink = await getPasswordResetLink(user, auditSource);
|
||||
await mailer.passwordReset(user, email, resetLink);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user