mirror of
https://github.com/nodemailer/wildduck.git
synced 2024-09-20 15:26:03 +08:00
feat(mailbox-count-limit): Set a limit for maximum number of mailbox folders ZMS-93 (#542)
* add max mailboxes to settings and consts * rewrite mailbox handler create function, convert it to async as well as add check for max mailboxes * mailboxes.js add support for new createAsync function, refactor. tools.js add support for new error code * make userDate check the first check * fix error message, make it clearer. Remove OVERQUOTA error code and replace with CANNOT. Remove OVERQUOTA error in the tools.js as well * fix createAsync wrapper, strict ordering. Settings handler remove unnecessary second param
This commit is contained in:
parent
917e029a90
commit
779bb11e83
|
@ -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',
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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',
|
||||
|
|
Loading…
Reference in a new issue