2021-09-04 05:19:24 +08:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
const { encrypt, decrypt } = require('./encrypt');
|
2021-09-04 15:30:50 +08:00
|
|
|
const consts = require('./consts');
|
|
|
|
const Joi = require('joi');
|
2021-10-04 16:57:43 +08:00
|
|
|
const tools = require('./tools');
|
2022-07-05 16:57:57 +08:00
|
|
|
const config = require('wild-config');
|
2021-09-04 15:30:50 +08:00
|
|
|
|
|
|
|
const SETTING_KEYS = [
|
|
|
|
{
|
|
|
|
key: 'const:archive:time',
|
|
|
|
name: 'Archive time',
|
|
|
|
description: 'Time in ms after deleted messages will be purged',
|
|
|
|
type: 'duration',
|
|
|
|
constKey: 'ARCHIVE_TIME',
|
|
|
|
schema: Joi.number()
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
key: 'const:max:storage',
|
|
|
|
name: 'Disk quota',
|
|
|
|
description: 'Maximum allowed storage size in bytes',
|
|
|
|
type: 'size',
|
2022-07-05 16:57:57 +08:00
|
|
|
constKey: false,
|
|
|
|
confValue: (Number(config.maxStorage) || 0) * 1024 * 1024 || consts.MAX_STORAGE,
|
2021-09-04 15:30:50 +08:00
|
|
|
schema: Joi.number()
|
2021-09-04 20:32:27 +08:00
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
key: 'const:max:recipients',
|
2022-12-04 17:18:32 +08:00
|
|
|
name: 'Max daily recipients',
|
2021-09-04 20:32:27 +08:00
|
|
|
description: 'Daily maximum recipients count',
|
|
|
|
type: 'number',
|
|
|
|
constKey: 'MAX_RECIPIENTS',
|
|
|
|
schema: Joi.number()
|
|
|
|
},
|
|
|
|
|
2023-10-24 16:07:12 +08:00
|
|
|
{
|
|
|
|
key: 'const:max:mailboxes',
|
|
|
|
name: 'Max mailboxes',
|
|
|
|
description: 'Maximum amount of mailboxes for a user',
|
|
|
|
type: 'number',
|
|
|
|
constKey: 'MAX_MAILBOXES',
|
|
|
|
schema: Joi.number()
|
|
|
|
},
|
|
|
|
|
2022-12-04 17:18:32 +08:00
|
|
|
{
|
|
|
|
key: 'const:max:rcpt_to',
|
|
|
|
name: 'Max message recipients',
|
|
|
|
description: 'Maximum recipients count for a single email',
|
|
|
|
type: 'number',
|
|
|
|
constKey: 'MAX_RCPT_TO',
|
|
|
|
schema: Joi.number()
|
|
|
|
},
|
|
|
|
|
2021-09-04 20:32:27 +08:00
|
|
|
{
|
|
|
|
key: 'const:max:forwards',
|
|
|
|
name: 'Max forwards',
|
|
|
|
description: 'Daily maximum forward count',
|
|
|
|
type: 'number',
|
|
|
|
constKey: 'MAX_FORWARDS',
|
|
|
|
schema: Joi.number()
|
2021-09-05 19:11:24 +08:00
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
key: 'const:authlog:time',
|
|
|
|
name: 'Auth log time',
|
|
|
|
description: 'Time in ms after authentication log entries will be purged',
|
|
|
|
type: 'duration',
|
|
|
|
constKey: 'AUTHLOG_TIME',
|
|
|
|
schema: Joi.number()
|
2021-09-06 19:19:47 +08:00
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
key: 'const:autoreply:interval',
|
|
|
|
name: 'Autoreply interval',
|
|
|
|
description: 'Delay between autoreplies for the same sender',
|
|
|
|
type: 'duration',
|
|
|
|
constKey: 'MAX_AUTOREPLY_INTERVAL',
|
|
|
|
schema: Joi.number()
|
2022-07-04 22:18:07 +08:00
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
key: 'const:asp:limit',
|
|
|
|
name: 'ASP limit',
|
|
|
|
description: 'How many application passwords users can register',
|
|
|
|
type: 'number',
|
|
|
|
constKey: 'MAX_ASP_COUNT',
|
|
|
|
schema: Joi.number()
|
2022-07-05 16:57:57 +08:00
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
key: 'const:max:imap:download',
|
|
|
|
name: 'Max IMAP download',
|
|
|
|
description: 'Maximum default daily IMAP download size',
|
|
|
|
type: 'size',
|
|
|
|
constKey: false,
|
|
|
|
confValue: ((config.imap && config.imap.maxDownloadMB) || consts.MAX_IMAP_DOWNLOAD) * 1024 * 1024,
|
|
|
|
schema: Joi.number()
|
|
|
|
},
|
|
|
|
{
|
|
|
|
key: 'const:max:pop3:download',
|
|
|
|
name: 'Max POP3 download',
|
|
|
|
description: 'Maximum default daily POP3 download size',
|
|
|
|
type: 'size',
|
|
|
|
constKey: false,
|
|
|
|
confValue: ((config.pop3 && config.pop3.maxDownloadMB) || consts.MAX_POP3_DOWNLOAD) * 1024 * 1024,
|
|
|
|
schema: Joi.number()
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
key: 'const:max:imap:upload',
|
|
|
|
name: 'Max IMAP upload',
|
|
|
|
description: 'Maximum default daily IMAP upload size',
|
|
|
|
type: 'size',
|
|
|
|
constKey: false,
|
|
|
|
confValue: ((config.imap && config.imap.maxUploadMB) || consts.MAX_IMAP_UPLOAD) * 1024 * 1024,
|
|
|
|
schema: Joi.number()
|
2022-07-28 16:54:26 +08:00
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
key: 'const:max:filters',
|
|
|
|
name: 'Max filters',
|
|
|
|
description: 'Maximum number of filters for an account',
|
|
|
|
type: 'number',
|
|
|
|
constKey: 'MAX_FILTERS',
|
|
|
|
schema: Joi.number()
|
2023-12-14 20:00:31 +08:00
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
key: 'const:sender:defer_times',
|
|
|
|
name: 'Deferred email delay',
|
|
|
|
description: 'Comma separated list of times between deferred delivery attempts. Eg. "5m, 15m, 20m, 1h, 1h, 1h"',
|
|
|
|
type: 'string',
|
|
|
|
confValue: '5m, 7m, 8m, 25m, 75m, 2h, 4h, 4h, 4h, 4h, 4h, 4h, 4h, 4h, 4h, 4h, 4h',
|
|
|
|
schema: Joi.string()
|
|
|
|
.allow('')
|
|
|
|
.trim()
|
|
|
|
.pattern(/^\d+\s*[a-z]*(\s*,\s*\d+\s*[a-z]*)*$/)
|
2021-09-04 15:30:50 +08:00
|
|
|
}
|
|
|
|
];
|
2021-09-04 05:19:24 +08:00
|
|
|
|
|
|
|
class SettingsHandler {
|
|
|
|
constructor(opts) {
|
|
|
|
opts = opts || {};
|
|
|
|
this.db = opts.db;
|
2021-09-04 15:30:50 +08:00
|
|
|
|
|
|
|
this.keys = SETTING_KEYS;
|
2021-09-04 05:19:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
async set(key, value, options) {
|
|
|
|
options = options || {};
|
|
|
|
|
|
|
|
let encrypted = false;
|
|
|
|
if (options.secret && options.encrypt) {
|
|
|
|
value = await encrypt(JSON.stringify(value), options.secret);
|
|
|
|
} else {
|
|
|
|
value = JSON.stringify(value);
|
|
|
|
}
|
|
|
|
|
|
|
|
let $set = {
|
|
|
|
key,
|
|
|
|
value
|
|
|
|
};
|
|
|
|
|
|
|
|
if (encrypted) {
|
|
|
|
$set.encrypted = true;
|
|
|
|
} else {
|
|
|
|
$set.encrypted = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
let $setOnInsert = {
|
|
|
|
created: new Date()
|
|
|
|
};
|
|
|
|
|
|
|
|
if (options && 'enumerable' in options) {
|
|
|
|
$set.enumerable = !!options.enumerable;
|
|
|
|
} else {
|
|
|
|
// default for new keys
|
|
|
|
$setOnInsert.enumerable = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
let r = await this.db.collection('settings').findOneAndUpdate(
|
|
|
|
{
|
|
|
|
key
|
|
|
|
},
|
|
|
|
{
|
|
|
|
$set,
|
|
|
|
$setOnInsert
|
|
|
|
},
|
|
|
|
{ upsert: true, returnDocument: 'after' }
|
|
|
|
);
|
|
|
|
|
|
|
|
return r.value && r.value.value;
|
|
|
|
}
|
|
|
|
|
2021-09-04 15:30:50 +08:00
|
|
|
async getMulti(keys, options) {
|
|
|
|
options = options || {};
|
|
|
|
|
|
|
|
let rows = await this.db
|
|
|
|
.collection('settings')
|
|
|
|
.find({
|
|
|
|
key: { $in: keys }
|
|
|
|
})
|
|
|
|
.toArray();
|
|
|
|
|
|
|
|
let result = {};
|
|
|
|
for (let key of keys) {
|
|
|
|
let row = rows.find(row => row.key === key);
|
|
|
|
if (row && row.encrypted && typeof row.value === 'string') {
|
|
|
|
if (!options.secret) {
|
|
|
|
throw new Error('Secret not provided for encrypted value');
|
|
|
|
}
|
|
|
|
let value = await decrypt(row.value, options.secret);
|
|
|
|
result[key] = JSON.parse(value);
|
|
|
|
} else if (row && typeof row.value === 'string') {
|
|
|
|
result[key] = JSON.parse(row.value);
|
|
|
|
} else {
|
|
|
|
let keyInfo = this.keys.find(k => k.key === key) || {};
|
2022-07-05 16:57:57 +08:00
|
|
|
|
|
|
|
let confDefaultValue = keyInfo.constKey ? consts[keyInfo.constKey] : keyInfo.confValue;
|
|
|
|
let defaultValue = 'default' in options ? options.default : confDefaultValue;
|
2021-09-04 15:30:50 +08:00
|
|
|
|
|
|
|
result[key] = row ? row.value : defaultValue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2021-09-04 05:19:24 +08:00
|
|
|
async get(key, options) {
|
|
|
|
options = options || {};
|
2021-09-04 15:30:50 +08:00
|
|
|
|
2021-09-04 05:19:24 +08:00
|
|
|
let row = await this.db.collection('settings').findOne({
|
|
|
|
key
|
|
|
|
});
|
|
|
|
|
|
|
|
if (row && row.encrypted && typeof row.value === 'string') {
|
|
|
|
if (!options.secret) {
|
|
|
|
throw new Error('Secret not provided for encrypted value');
|
|
|
|
}
|
|
|
|
let value = await decrypt(row.value, options.secret);
|
|
|
|
return JSON.parse(value);
|
|
|
|
} else if (row && typeof row.value === 'string') {
|
|
|
|
return JSON.parse(row.value);
|
|
|
|
}
|
|
|
|
|
2021-09-05 19:59:15 +08:00
|
|
|
let keyInfo = this.keys.find(k => k.key === key) || {};
|
2022-07-05 16:57:57 +08:00
|
|
|
|
|
|
|
let confDefaultValue = keyInfo.constKey ? consts[keyInfo.constKey] : keyInfo.confValue;
|
|
|
|
let defaultValue = 'default' in options ? options.default : confDefaultValue;
|
2021-09-04 15:30:50 +08:00
|
|
|
|
|
|
|
return row ? row.value : defaultValue;
|
2021-09-04 05:19:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
async del(key) {
|
|
|
|
return await this.db.collection('settings').deleteOne({
|
|
|
|
key
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-09-04 20:32:27 +08:00
|
|
|
async list(filter, options) {
|
2021-09-04 05:19:24 +08:00
|
|
|
options = options || {};
|
|
|
|
let query = { enumerable: true };
|
2021-09-04 20:32:27 +08:00
|
|
|
if (filter) {
|
2021-09-04 05:19:24 +08:00
|
|
|
query.key = {
|
2021-10-04 16:57:43 +08:00
|
|
|
$regex: tools.escapeRegexStr(filter),
|
2021-09-04 05:19:24 +08:00
|
|
|
$options: 'i'
|
|
|
|
};
|
|
|
|
}
|
2021-09-04 15:30:50 +08:00
|
|
|
|
2021-09-04 05:19:24 +08:00
|
|
|
let list = await this.db.collection('settings').find(query).project({ key: true, value: true }).toArray();
|
2021-09-04 15:30:50 +08:00
|
|
|
let results = [];
|
2021-09-04 05:19:24 +08:00
|
|
|
for (let row of list) {
|
|
|
|
try {
|
|
|
|
if (row && row.encrypted && typeof row.value === 'string') {
|
|
|
|
if (!options.secret) {
|
|
|
|
throw new Error('Secret not provided for encrypted value');
|
|
|
|
}
|
|
|
|
let value = await decrypt(row.value, options.secret);
|
|
|
|
row.value = JSON.parse(value);
|
|
|
|
} else if (row && typeof row.value === 'string') {
|
|
|
|
row.value = JSON.parse(row.value);
|
|
|
|
}
|
|
|
|
|
2021-09-04 15:30:50 +08:00
|
|
|
let keyInfo = this.keys.find(k => k.key === row.key) || {};
|
|
|
|
|
|
|
|
results.push({
|
|
|
|
key: row.key,
|
|
|
|
value: row.value,
|
|
|
|
name: keyInfo.name,
|
|
|
|
description: keyInfo.description,
|
2022-07-05 16:57:57 +08:00
|
|
|
default: keyInfo.constKey ? consts[keyInfo.constKey] : keyInfo.confValue,
|
2021-09-05 19:11:24 +08:00
|
|
|
type: keyInfo.type,
|
2021-09-04 20:32:27 +08:00
|
|
|
custom: true
|
2021-09-04 15:30:50 +08:00
|
|
|
});
|
2021-09-04 05:19:24 +08:00
|
|
|
} catch (err) {
|
|
|
|
// ignore?
|
|
|
|
}
|
|
|
|
}
|
2021-09-04 15:30:50 +08:00
|
|
|
|
|
|
|
for (let row of this.keys) {
|
|
|
|
if (results.some(k => k.key === row.key)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
results.push({
|
|
|
|
key: row.key,
|
2022-07-05 16:57:57 +08:00
|
|
|
value: row.constKey ? consts[row.constKey] : row.confValue,
|
2021-09-04 15:30:50 +08:00
|
|
|
name: row.name,
|
|
|
|
description: row.description,
|
2022-07-05 16:57:57 +08:00
|
|
|
default: row.constKey ? consts[row.constKey] : row.confValue,
|
2021-09-05 19:11:24 +08:00
|
|
|
type: row.type,
|
2021-09-04 20:32:27 +08:00
|
|
|
custom: false
|
2021-09-04 15:30:50 +08:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return results.sort((a, b) => a.key.localeCompare(b.key));
|
2021-09-04 05:19:24 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports.SettingsHandler = SettingsHandler;
|