diff --git a/lib/api/mailboxes.js b/lib/api/mailboxes.js index f72310f2..b93e8a15 100644 --- a/lib/api/mailboxes.js +++ b/lib/api/mailboxes.js @@ -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', diff --git a/lib/consts.js b/lib/consts.js index d6c971b2..7eda33a5 100644 --- a/lib/consts.js +++ b/lib/consts.js @@ -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 }; diff --git a/lib/mailbox-handler.js b/lib/mailbox-handler.js index da3b5b68..641b2c41 100644 --- a/lib/mailbox-handler.js +++ b/lib/mailbox-handler.js @@ -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) { @@ -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( - { + this.createAsync(user, path, opts) + .then(mailboxData => callback(null, ...[mailboxData.status, mailboxData.id])) + .catch(err => callback(err)); + } + + async createAsync(user, path, opts) { + const userData = await this.database.collection('users').findOne({ _id: user }, { projection: { retention: true } }); + + if (!userData) { + const err = new Error('This user does not exist'); + err.code = 'UserNotFound'; + err.responseCode = 404; + throw err; + } + + let mailboxData = await this.database.collection('mailboxes').findOne({ user, path }); + + if (mailboxData) { + const err = new Error('Mailbox creation failed with code MailboxAlreadyExists'); + err.code = 'ALREADYEXISTS'; + err.responseCode = 400; + throw err; + } + + const mailboxCountForUser = await this.database.collection('mailboxes').countDocuments({ user }); + + if (mailboxCountForUser > (await this.settingsHandler.get('const:max:mailboxes'))) { + const err = new Error('Mailbox creation failed with code ReachedMailboxCountLimit. Max mailboxes count reached.'); + err.code = 'CANNOT'; + 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 + }; + + 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 }, - (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.users.collection('users').findOne( - { - _id: user - }, - { - projection: { - retention: true - } - }, - (err, userData) => { - if (err) { - return callback(err); - } - - if (!userData) { - const err = new Error('This user does not exist'); - err.code = 'UserNotFound'; - err.responseCode = 404; - return callback(err, 'UserNotFound'); - } - - 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]; - } - }); - - 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); - } - - publish(this.redis, { - ev: MAILBOX_CREATED, - user, - mailbox: r.insertedId, - path: mailboxData.path - }).catch(() => false); - - return this.notifier.addEntries( - mailboxData, - { - command: 'CREATE', - mailbox: r.insertedId, - path - }, - () => { - this.notifier.fire(user); - return callback(null, true, mailboxData._id); - } - ); - }); - } - ); + () => { + this.notifier.fire(user); + return; } ); + + return { + status: true, + id: mailboxData._id + }; } rename(user, mailbox, newname, opts, callback) { diff --git a/lib/settings-handler.js b/lib/settings-handler.js index e68cc043..84018e61 100644 --- a/lib/settings-handler.js +++ b/lib/settings-handler.js @@ -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',