Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ZMS-93 #542

Merged
merged 6 commits into from
Oct 24, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 1 addition & 9 deletions lib/api/mailboxes.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,7 @@ module.exports = (db, server, mailboxHandler) => {
const getMailboxCounter = util.promisify(tools.getMailboxCounter);
const updateMailbox = util.promisify(mailboxHandler.update.bind(mailboxHandler));
const deleteMailbox = util.promisify(mailboxHandler.del.bind(mailboxHandler));
const createMailbox = util.promisify((...args) => {
let callback = args.pop();
mailboxHandler.create(...args, (err, status, id) => {
if (err) {
return callback(err);
}
return callback(null, { status, id });
});
});
const createMailbox = mailboxHandler.createAsync.bind(mailboxHandler);

server.get(
'/users/:user/mailboxes',
Expand Down
5 changes: 4 additions & 1 deletion lib/consts.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,5 +134,8 @@ module.exports = {
MAX_IMAP_UPLOAD: 10 * 1024 * 1024 * 1024,

// maximum number of filters per account
MAX_FILTERS: 400
MAX_FILTERS: 400,

// maximum amount of mailboxes per user
MAX_MAILBOXES: 1500
};
156 changes: 75 additions & 81 deletions lib/mailbox-handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
const ObjectId = require('mongodb').ObjectId;
const ImapNotifier = require('./imap-notifier');
const { publish, MAILBOX_CREATED, MAILBOX_RENAMED, MAILBOX_DELETED } = require('./events');
const { SettingsHandler } = require('./settings-handler');

class MailboxHandler {
constructor(options) {
Expand All @@ -19,99 +20,92 @@ class MailboxHandler {
redis: this.redis,
pushOnly: true
});

this.settingsHandler = new SettingsHandler({ db: this.database });
}

create(user, path, opts, callback) {
this.database.collection('mailboxes').findOne(
{
user,
path
},
(err, mailboxData) => {
if (err) {
return callback(err);
}
if (mailboxData) {
const err = new Error('Mailbox creation failed with code MailboxAlreadyExists');
err.code = 'ALREADYEXISTS';
err.responseCode = 400;
return callback(err, 'ALREADYEXISTS');
}
this.createAsync(user, path, opts)
.then(mailboxData => callback(null, ...Object.values(mailboxData)))
NickOvt marked this conversation as resolved.
Show resolved Hide resolved
.catch(err => callback(err));
}

this.users.collection('users').findOne(
{
_id: user
},
{
projection: {
retention: true
}
},
(err, userData) => {
if (err) {
return callback(err);
}
async createAsync(user, path, opts) {
let mailboxData = await this.database.collection('mailboxes').findOne({ user, path });

if (!userData) {
const err = new Error('This user does not exist');
err.code = 'UserNotFound';
err.responseCode = 404;
return callback(err, 'UserNotFound');
}
if (mailboxData) {
const err = new Error('Mailbox creation failed with code MailboxAlreadyExists');
err.code = 'ALREADYEXISTS';
err.responseCode = 400;
throw err;
}

mailboxData = {
_id: new ObjectId(),
user,
path,
uidValidity: Math.floor(Date.now() / 1000),
uidNext: 1,
modifyIndex: 0,
subscribed: true,
flags: [],
retention: userData.retention
};
const mailboxCountForUser = await this.database.collection('mailboxes').countDocuments({ user });

Object.keys(opts || {}).forEach(key => {
if (!['_id', 'user', 'path'].includes(key)) {
mailboxData[key] = opts[key];
}
});
if (mailboxCountForUser > (await this.settingsHandler.get('const:max:mailboxes', {}))) {
NickOvt marked this conversation as resolved.
Show resolved Hide resolved
const err = new Error('Mailbox creation failed with code ReachedMailboxCountLimit');
err.code = 'OVERQUOTA';
NickOvt marked this conversation as resolved.
Show resolved Hide resolved
err.responseCode = 400;
throw err;
}

this.database.collection('mailboxes').insertOne(mailboxData, { writeConcern: 'majority' }, (err, r) => {
if (err) {
if (err.code === 11000) {
const err = new Error('Mailbox creation failed with code MailboxAlreadyExists');
err.code = 'ALREADYEXISTS';
err.responseCode = 400;
return callback(err, 'ALREADYEXISTS');
}
return callback(err);
}
const userData = await this.database.collection('users').findOne({ _id: user }, { projection: { retention: true } });
NickOvt marked this conversation as resolved.
Show resolved Hide resolved

publish(this.redis, {
ev: MAILBOX_CREATED,
user,
mailbox: r.insertedId,
path: mailboxData.path
}).catch(() => false);
if (!userData) {
const err = new Error('This user does not exist');
err.code = 'UserNotFound';
err.responseCode = 404;
throw err;
}

return this.notifier.addEntries(
mailboxData,
{
command: 'CREATE',
mailbox: r.insertedId,
path
},
() => {
this.notifier.fire(user);
return callback(null, true, mailboxData._id);
}
);
});
}
);
mailboxData = {
_id: new ObjectId(),
user,
path,
uidValidity: Math.floor(Date.now() / 1000),
uidNext: 1,
modifyIndex: 0,
subscribed: true,
flags: [],
retention: userData.retention
};

Object.keys(opts || {}).forEach(key => {
if (!['_id', 'user', 'path'].includes(key)) {
mailboxData[key] = opts[key];
}
});

const r = this.database.collection('mailboxes').insertOne(mailboxData, { writeConcern: 'majority' });

try {
await publish(this.redis, {
ev: MAILBOX_CREATED,
user,
mailbox: r.insertedId,
path: mailboxData.path
});
} catch {
// ignore
}

await this.notifier.addEntries(
mailboxData,
{
command: 'CREATE',
mailbox: r.insertedId,
path
},
() => {
this.notifier.fire(user);
return;
}
);

return {
status: true,
id: mailboxData._id
};
}

rename(user, mailbox, newname, opts, callback) {
Expand Down
9 changes: 9 additions & 0 deletions lib/settings-handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,15 @@ const SETTING_KEYS = [
schema: Joi.number()
},

{
key: 'const:max:mailboxes',
name: 'Max mailboxes',
description: 'Maximum amount of mailboxes for a user',
type: 'number',
constKey: 'MAX_MAILBOXES',
schema: Joi.number()
},

{
key: 'const:max:rcpt_to',
name: 'Max message recipients',
Expand Down
4 changes: 4 additions & 0 deletions lib/tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,10 @@ module.exports = {
err.responseCode = err.responseCode || 404;
err.code = 'NoSuchMailbox';
break;
case 'OVERQUOTA':
NickOvt marked this conversation as resolved.
Show resolved Hide resolved
err.responseCode = err.responseCode || 400;
err.code = 'ReachedMailboxCountLimit';
break;
case 'CANNOT':
err.responseCode = err.responseCode || 400;
err.code = 'DisallowedMailboxMethod';
Expand Down