From 957359c5288d70777d4cd4fb00cc149ec4c3fe65 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Thu, 28 Jul 2022 11:54:26 +0300 Subject: [PATCH] Allow to limit the count of filters per account --- api.js | 2 +- docs/api/openapi.yml | 32 +++++++++++++++ lib/api/filters.js | 89 ++++++++++++++++++++++++++--------------- lib/api/users.js | 17 ++++++++ lib/consts.js | 5 ++- lib/settings-handler.js | 9 +++++ lib/user-handler.js | 2 + 7 files changed, 122 insertions(+), 34 deletions(-) diff --git a/api.js b/api.js index c8bb15ba..08641c64 100644 --- a/api.js +++ b/api.js @@ -543,7 +543,7 @@ module.exports = done => { mailboxesRoutes(db, server, mailboxHandler); messagesRoutes(db, server, messageHandler, userHandler, storageHandler, settingsHandler); storageRoutes(db, server, storageHandler); - filtersRoutes(db, server, userHandler); + filtersRoutes(db, server, userHandler, settingsHandler); domainaccessRoutes(db, server); aspsRoutes(db, server, userHandler); totpRoutes(db, server, userHandler); diff --git a/docs/api/openapi.yml b/docs/api/openapi.yml index c83137db..b0496298 100644 --- a/docs/api/openapi.yml +++ b/docs/api/openapi.yml @@ -3426,6 +3426,8 @@ components: $ref: '#/components/schemas/Quota' recipients: $ref: '#/components/schemas/Recipients' + filters: + $ref: '#/components/schemas/Filters' forwards: $ref: '#/components/schemas/Forwards' received: @@ -3469,6 +3471,19 @@ components: type: number description: Time until the end of current 24 hour period description: Sending quota + Filters: + required: + - allowed + - used + type: object + properties: + allowed: + type: number + description: How many filters are allowed + used: + type: number + description: How many filters have been created + description: Sending quota Forwards: required: - allowed @@ -4660,12 +4675,23 @@ components: GetFiltersResponse: required: - success + - limits - results type: object properties: success: type: boolean description: Indicates successful response + limits: + type: object + description: Filter usage limits for the user account + properties: + allowed: + type: number + description: How many filters are allowed + used: + type: number + description: How many filters have been created results: type: array items: @@ -5933,6 +5959,9 @@ components: forwards: type: number description: How many messages per 24 hour can be forwarded + filters: + type: number + description: How many filters are allowed for this account imapMaxUpload: type: number description: How many bytes can be uploaded via IMAP during 24 hour @@ -6059,6 +6088,9 @@ components: forwards: type: number description: How many messages per 24 hour can be forwarded + filters: + type: number + description: How many filters are allowed for this account imapMaxUpload: type: number description: How many bytes can be uploaded via IMAP during 24 hour diff --git a/lib/api/filters.js b/lib/api/filters.js index 077607d5..c94cd0ef 100644 --- a/lib/api/filters.js +++ b/lib/api/filters.js @@ -10,7 +10,7 @@ const roles = require('../roles'); const { nextPageCursorSchema, previousPageCursorSchema, pageNrSchema, sessSchema, sessIPSchema, booleanSchema } = require('../schemas'); const { publish, FILTER_DELETED, FILTER_CREATED, FORWARD_ADDED } = require('../events'); -module.exports = (db, server, userHandler) => { +module.exports = (db, server, userHandler, settingsHandler) => { server.get( { name: 'filters', path: '/filters' }, tools.asyncifyJson(async (req, res, next) => { @@ -210,7 +210,7 @@ module.exports = (db, server, userHandler) => { }, { projection: { - address: true + filters: true } } ); @@ -232,6 +232,9 @@ module.exports = (db, server, userHandler) => { return next(); } + let settings = await settingsHandler.getMulti(['const:max:filters']); + let maxFilters = Number(userData.filters) || settings['const:max:filters']; + let mailboxes; try { mailboxes = await db.database @@ -282,6 +285,11 @@ module.exports = (db, server, userHandler) => { res.json({ success: true, + limits: { + allowed: maxFilters, + used: filters.length + }, + results: filters.map(filterData => { let descriptions = getFilterStrings(filterData, mailboxes); @@ -565,6 +573,53 @@ module.exports = (db, server, userHandler) => { let values = result.value; let user = new ObjectId(values.user); + + let userData; + try { + userData = await db.users.collection('users').findOne( + { + _id: user + }, + { + projection: { + filters: true + } + } + ); + } catch (err) { + res.status(500); + res.json({ + error: 'MongoDB Error: ' + err.message, + code: 'InternalDatabaseError' + }); + return next(); + } + + if (!userData) { + res.status(404); + res.json({ + error: 'This user does not exist', + code: 'UserNotFound' + }); + return next(); + } + + let settings = await settingsHandler.getMulti(['const:max:filters']); + let maxFilters = Number(userData.filters) || settings['const:max:filters']; + const filtersCount = await db.database.collection('filters').countDocuments({ + user + }); + + if (filtersCount >= maxFilters) { + res.status(403); + res.json({ + error: 'Maximum filters limit reached', + code: 'TooMany', + allowed: maxFilters + }); + return next(); + } + let filterData = { _id: new ObjectId(), user, @@ -668,36 +723,6 @@ module.exports = (db, server, userHandler) => { filterData.action.mailbox = mailboxData._id; } - let userData; - try { - userData = await db.users.collection('users').findOne( - { - _id: user - }, - { - projection: { - _id: true - } - } - ); - } catch (err) { - res.status(500); - res.json({ - error: 'MongoDB Error: ' + err.message, - code: 'InternalDatabaseError' - }); - return next(); - } - - if (!userData) { - res.status(404); - res.json({ - error: 'This user does not exist', - code: 'UserNotFound' - }); - return next(); - } - let r; try { r = await db.database.collection('filters').insertOne(filterData); diff --git a/lib/api/users.js b/lib/api/users.js index defa11c1..7a9e8a9f 100644 --- a/lib/api/users.js +++ b/lib/api/users.js @@ -291,6 +291,8 @@ module.exports = (db, server, userHandler, settingsHandler) => { recipients: Joi.number().min(0).default(0), forwards: Joi.number().min(0).default(0), + filters: Joi.number().min(0).default(0), + requirePasswordChange: booleanSchema.default(false), imapMaxUpload: Joi.number().min(0).default(0), @@ -726,10 +728,15 @@ module.exports = (db, server, userHandler, settingsHandler) => { errors.notify(err, { userId: user }); } + const filtersCount = await db.database.collection('filters').countDocuments({ + user + }); + let settings = await settingsHandler.getMulti([ 'const:max:storage', 'const:max:recipients', 'const:max:forwards', + 'const:max:filters', 'const:max:imap:upload', 'const:max:imap:download', 'const:max:pop3:download' @@ -738,6 +745,8 @@ module.exports = (db, server, userHandler, settingsHandler) => { let recipients = Number(userData.recipients) || config.maxRecipients || settings['const:max:recipients']; let forwards = Number(userData.forwards) || config.maxForwards || settings['const:max:forwards']; + let filters = Number(userData.filters) || settings['const:max:filters']; + let recipientsSent = Number(response && response[0] && response[0][1]) || 0; let recipientsTtl = Number(response && response[1] && response[1][1]) || 0; @@ -819,6 +828,11 @@ module.exports = (db, server, userHandler, settingsHandler) => { ttl: receivedTtl >= 0 ? receivedTtl : false }, + filters: { + allowed: filters, + used: filtersCount + }, + imapUpload: { allowed: Number(userData.imapMaxUpload) || settings['const:max:imap:upload'], used: imapUpload, @@ -906,10 +920,13 @@ module.exports = (db, server, userHandler, settingsHandler) => { encryptMessages: booleanSchema, encryptForwarded: booleanSchema, retention: Joi.number().min(0), + quota: Joi.number().min(0), recipients: Joi.number().min(0), forwards: Joi.number().min(0), + filters: Joi.number().min(0), + imapMaxUpload: Joi.number().min(0), imapMaxDownload: Joi.number().min(0), pop3MaxDownload: Joi.number().min(0), diff --git a/lib/consts.js b/lib/consts.js index 0aa99db2..159edcd1 100644 --- a/lib/consts.js +++ b/lib/consts.js @@ -129,5 +129,8 @@ module.exports = { MAX_POP3_DOWNLOAD: 10 * 1024 * 1024 * 1024, // default max IMAP upload size - MAX_IMAP_UPLOAD: 10 * 1024 * 1024 * 1024 + MAX_IMAP_UPLOAD: 10 * 1024 * 1024 * 1024, + + // maximum number of filters per account + MAX_FILTERS: 400 }; diff --git a/lib/settings-handler.js b/lib/settings-handler.js index 8ef0366a..d458f88e 100644 --- a/lib/settings-handler.js +++ b/lib/settings-handler.js @@ -98,6 +98,15 @@ const SETTING_KEYS = [ constKey: false, confValue: ((config.imap && config.imap.maxUploadMB) || consts.MAX_IMAP_UPLOAD) * 1024 * 1024, schema: Joi.number() + }, + + { + key: 'const:max:filters', + name: 'Max filters', + description: 'Maximum number of filters for an account', + type: 'number', + constKey: 'MAX_FILTERS', + schema: Joi.number() } ]; diff --git a/lib/user-handler.js b/lib/user-handler.js index 941d03e5..86af0dd5 100644 --- a/lib/user-handler.js +++ b/lib/user-handler.js @@ -1409,6 +1409,8 @@ class UserHandler { recipients: data.recipients || 0, forwards: data.forwards || 0, + filters: data.filters || 0, + imapMaxUpload: data.imapMaxUpload || 0, imapMaxDownload: data.imapMaxDownload || 0, pop3MaxDownload: data.pop3MaxDownload || 0,