Merge pull request #412 from nodemailer/feature-limit-filters

Allow to limit the count of filters per account
This commit is contained in:
Andris Reinman 2022-07-28 12:10:46 +03:00 committed by GitHub
commit c6007001e0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 122 additions and 34 deletions

2
api.js
View file

@ -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);

View file

@ -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

View file

@ -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);

View file

@ -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),

View file

@ -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
};

View file

@ -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()
}
];

View file

@ -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,