emailengine-app 2.61.5 → 2.62.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +78 -0
- package/data/google-crawlers.json +1 -1
- package/lib/account.js +20 -7
- package/lib/api-routes/account-routes.js +28 -5
- package/lib/api-routes/chat-routes.js +1 -1
- package/lib/api-routes/export-routes.js +316 -0
- package/lib/api-routes/message-routes.js +28 -23
- package/lib/api-routes/template-routes.js +28 -7
- package/lib/arf-detect.js +1 -1
- package/lib/consts.js +16 -0
- package/lib/db.js +3 -0
- package/lib/email-client/base-client.js +6 -4
- package/lib/email-client/gmail-client.js +204 -33
- package/lib/email-client/imap/mailbox.js +99 -8
- package/lib/email-client/imap/subconnection.js +5 -5
- package/lib/email-client/imap-client.js +76 -16
- package/lib/email-client/message-builder.js +3 -1
- package/lib/email-client/notification-handler.js +12 -9
- package/lib/email-client/outlook-client.js +362 -69
- package/lib/email-client/smtp-pool-manager.js +1 -1
- package/lib/export.js +528 -0
- package/lib/oauth/gmail.js +21 -13
- package/lib/oauth/mail-ru.js +23 -10
- package/lib/oauth/outlook.js +26 -16
- package/lib/oauth/pubsub/google.js +5 -0
- package/lib/routes-ui.js +235 -1
- package/lib/schemas.js +260 -80
- package/lib/stream-encrypt.js +263 -0
- package/lib/tools.js +30 -4
- package/lib/ui-routes/account-routes.js +23 -0
- package/lib/ui-routes/admin-config-routes.js +11 -4
- package/lib/ui-routes/admin-entities-routes.js +18 -0
- package/lib/webhooks.js +16 -20
- package/package.json +16 -16
- package/sbom.json +1 -1
- package/server.js +41 -5
- package/static/js/ace/ace.js +1 -1
- package/static/js/ace/ext-language_tools.js +1 -1
- package/static/licenses.html +52 -62
- package/translations/de.mo +0 -0
- package/translations/de.po +63 -36
- package/translations/en.mo +0 -0
- package/translations/en.po +64 -37
- package/translations/et.mo +0 -0
- package/translations/et.po +63 -36
- package/translations/fr.mo +0 -0
- package/translations/fr.po +63 -36
- package/translations/ja.mo +0 -0
- package/translations/ja.po +63 -36
- package/translations/messages.pot +80 -47
- package/translations/nl.mo +0 -0
- package/translations/nl.po +63 -36
- package/translations/pl.mo +0 -0
- package/translations/pl.po +63 -36
- package/views/accounts/account.hbs +375 -2
- package/views/config/service.hbs +35 -0
- package/workers/api.js +123 -44
- package/workers/documents.js +1 -0
- package/workers/export.js +926 -0
- package/workers/imap.js +29 -0
- package/workers/submit.js +25 -5
- package/workers/webhooks.js +11 -2
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
const { Transform } = require('stream');
|
|
5
|
+
|
|
6
|
+
const MAGIC = Buffer.from('EE01');
|
|
7
|
+
const VERSION = 1;
|
|
8
|
+
const CHUNK_SIZE = 64 * 1024;
|
|
9
|
+
const IV_LENGTH = 12;
|
|
10
|
+
const AUTH_TAG_LENGTH = 16;
|
|
11
|
+
const SALT_LENGTH = 16;
|
|
12
|
+
const HEADER_SIZE = MAGIC.length + 4 + 4 + SALT_LENGTH;
|
|
13
|
+
const MAX_CHUNK_SIZE = CHUNK_SIZE * 4;
|
|
14
|
+
|
|
15
|
+
function deriveKeyAsync(password, salt) {
|
|
16
|
+
return new Promise((resolve, reject) => {
|
|
17
|
+
crypto.scrypt(password, salt, 32, (err, key) => {
|
|
18
|
+
if (err) reject(err);
|
|
19
|
+
else resolve(key);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
class EncryptStream extends Transform {
|
|
25
|
+
constructor({ key, salt }) {
|
|
26
|
+
super();
|
|
27
|
+
this.key = key;
|
|
28
|
+
this.salt = salt;
|
|
29
|
+
this.buffer = Buffer.alloc(0);
|
|
30
|
+
this.headerWritten = false;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
_writeHeader() {
|
|
34
|
+
if (this.headerWritten) return;
|
|
35
|
+
|
|
36
|
+
const versionBuf = Buffer.alloc(4);
|
|
37
|
+
versionBuf.writeUInt32LE(VERSION);
|
|
38
|
+
|
|
39
|
+
const chunkSizeBuf = Buffer.alloc(4);
|
|
40
|
+
chunkSizeBuf.writeUInt32LE(CHUNK_SIZE);
|
|
41
|
+
|
|
42
|
+
this.push(Buffer.concat([MAGIC, versionBuf, chunkSizeBuf, this.salt]));
|
|
43
|
+
this.headerWritten = true;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
_encryptChunk(chunk) {
|
|
47
|
+
const iv = crypto.randomBytes(IV_LENGTH);
|
|
48
|
+
const cipher = crypto.createCipheriv('aes-256-gcm', this.key, iv, { authTagLength: AUTH_TAG_LENGTH });
|
|
49
|
+
|
|
50
|
+
const encrypted = Buffer.concat([cipher.update(chunk), cipher.final()]);
|
|
51
|
+
const authTag = cipher.getAuthTag();
|
|
52
|
+
|
|
53
|
+
const chunkHeader = Buffer.alloc(IV_LENGTH + 4);
|
|
54
|
+
iv.copy(chunkHeader, 0);
|
|
55
|
+
chunkHeader.writeUInt32LE(encrypted.length, IV_LENGTH);
|
|
56
|
+
|
|
57
|
+
return Buffer.concat([chunkHeader, encrypted, authTag]);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
_transform(data, encoding, callback) {
|
|
61
|
+
try {
|
|
62
|
+
this._writeHeader();
|
|
63
|
+
|
|
64
|
+
this.buffer = Buffer.concat([this.buffer, data]);
|
|
65
|
+
|
|
66
|
+
while (this.buffer.length >= CHUNK_SIZE) {
|
|
67
|
+
const chunk = this.buffer.subarray(0, CHUNK_SIZE);
|
|
68
|
+
this.buffer = this.buffer.subarray(CHUNK_SIZE);
|
|
69
|
+
this.push(this._encryptChunk(chunk));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
callback();
|
|
73
|
+
} catch (err) {
|
|
74
|
+
callback(err);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
_flush(callback) {
|
|
79
|
+
try {
|
|
80
|
+
this._writeHeader();
|
|
81
|
+
|
|
82
|
+
if (this.buffer.length > 0) {
|
|
83
|
+
this.push(this._encryptChunk(this.buffer));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
this.key = null;
|
|
87
|
+
callback();
|
|
88
|
+
} catch (err) {
|
|
89
|
+
callback(err);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
class DecryptStream extends Transform {
|
|
95
|
+
constructor(secret) {
|
|
96
|
+
super();
|
|
97
|
+
this.secret = secret;
|
|
98
|
+
this.buffer = Buffer.alloc(0);
|
|
99
|
+
this.headerParsed = false;
|
|
100
|
+
this.key = null;
|
|
101
|
+
this.chunkSize = CHUNK_SIZE;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
_parseHeader() {
|
|
105
|
+
if (this.buffer.length < HEADER_SIZE) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
let offset = 0;
|
|
110
|
+
|
|
111
|
+
const magic = this.buffer.subarray(offset, offset + MAGIC.length);
|
|
112
|
+
if (!magic.equals(MAGIC)) {
|
|
113
|
+
throw new Error('Invalid encrypted file format: bad magic bytes');
|
|
114
|
+
}
|
|
115
|
+
offset += MAGIC.length;
|
|
116
|
+
|
|
117
|
+
const version = this.buffer.readUInt32LE(offset);
|
|
118
|
+
if (version !== VERSION) {
|
|
119
|
+
throw new Error(`Unsupported encryption version: ${version}`);
|
|
120
|
+
}
|
|
121
|
+
offset += 4;
|
|
122
|
+
|
|
123
|
+
this.chunkSize = this.buffer.readUInt32LE(offset);
|
|
124
|
+
if (this.chunkSize > MAX_CHUNK_SIZE) {
|
|
125
|
+
throw new Error('Invalid chunk size in header');
|
|
126
|
+
}
|
|
127
|
+
offset += 4;
|
|
128
|
+
|
|
129
|
+
this.salt = this.buffer.subarray(offset, offset + SALT_LENGTH);
|
|
130
|
+
|
|
131
|
+
this.buffer = this.buffer.subarray(HEADER_SIZE);
|
|
132
|
+
this.headerParsed = true;
|
|
133
|
+
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
_decryptChunk() {
|
|
138
|
+
const minChunkHeader = IV_LENGTH + 4;
|
|
139
|
+
if (this.buffer.length < minChunkHeader) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const iv = this.buffer.subarray(0, IV_LENGTH);
|
|
144
|
+
const encryptedLength = this.buffer.readUInt32LE(IV_LENGTH);
|
|
145
|
+
if (encryptedLength > this.chunkSize + 256) {
|
|
146
|
+
throw new Error('Invalid encrypted chunk length');
|
|
147
|
+
}
|
|
148
|
+
const totalChunkSize = minChunkHeader + encryptedLength + AUTH_TAG_LENGTH;
|
|
149
|
+
|
|
150
|
+
if (this.buffer.length < totalChunkSize) {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const encryptedData = this.buffer.subarray(minChunkHeader, minChunkHeader + encryptedLength);
|
|
155
|
+
const authTag = this.buffer.subarray(minChunkHeader + encryptedLength, totalChunkSize);
|
|
156
|
+
|
|
157
|
+
const decipher = crypto.createDecipheriv('aes-256-gcm', this.key, iv, { authTagLength: AUTH_TAG_LENGTH });
|
|
158
|
+
decipher.setAuthTag(authTag);
|
|
159
|
+
|
|
160
|
+
let decrypted;
|
|
161
|
+
try {
|
|
162
|
+
decrypted = Buffer.concat([decipher.update(encryptedData), decipher.final()]);
|
|
163
|
+
} catch (err) {
|
|
164
|
+
if (err.message.includes('auth')) {
|
|
165
|
+
throw new Error('Decryption failed: invalid secret or corrupted data');
|
|
166
|
+
}
|
|
167
|
+
throw err;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
this.buffer = this.buffer.subarray(totalChunkSize);
|
|
171
|
+
|
|
172
|
+
return decrypted;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
_processBuffer(callback) {
|
|
176
|
+
try {
|
|
177
|
+
let decrypted;
|
|
178
|
+
while ((decrypted = this._decryptChunk()) !== null) {
|
|
179
|
+
this.push(decrypted);
|
|
180
|
+
}
|
|
181
|
+
callback();
|
|
182
|
+
} catch (err) {
|
|
183
|
+
callback(err);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
_transform(data, encoding, callback) {
|
|
188
|
+
this.buffer = Buffer.concat([this.buffer, data]);
|
|
189
|
+
|
|
190
|
+
if (!this.headerParsed) {
|
|
191
|
+
try {
|
|
192
|
+
if (!this._parseHeader()) {
|
|
193
|
+
callback();
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
} catch (err) {
|
|
197
|
+
callback(err);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
crypto.scrypt(this.secret, this.salt, 32, (err, key) => {
|
|
201
|
+
if (err) {
|
|
202
|
+
callback(err);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
this.key = key;
|
|
206
|
+
this.secret = null;
|
|
207
|
+
this._processBuffer(callback);
|
|
208
|
+
});
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
this._processBuffer(callback);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
_flush(callback) {
|
|
216
|
+
try {
|
|
217
|
+
if (this.buffer.length > 0) {
|
|
218
|
+
if (!this.headerParsed) {
|
|
219
|
+
throw new Error('Invalid encrypted file: incomplete header');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const decrypted = this._decryptChunk();
|
|
223
|
+
if (decrypted !== null) {
|
|
224
|
+
this.push(decrypted);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (this.buffer.length > 0) {
|
|
228
|
+
throw new Error('Invalid encrypted file: incomplete final chunk');
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
this.key = null;
|
|
233
|
+
callback();
|
|
234
|
+
} catch (err) {
|
|
235
|
+
callback(err);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async function createEncryptStream(secret) {
|
|
241
|
+
if (!secret) {
|
|
242
|
+
throw new Error('Encryption secret is required');
|
|
243
|
+
}
|
|
244
|
+
const salt = crypto.randomBytes(SALT_LENGTH);
|
|
245
|
+
const key = await deriveKeyAsync(secret, salt);
|
|
246
|
+
return new EncryptStream({ key, salt });
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async function createDecryptStream(secret) {
|
|
250
|
+
if (!secret) {
|
|
251
|
+
throw new Error('Decryption secret is required');
|
|
252
|
+
}
|
|
253
|
+
return new DecryptStream(secret);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
module.exports = {
|
|
257
|
+
createEncryptStream,
|
|
258
|
+
createDecryptStream,
|
|
259
|
+
MAGIC,
|
|
260
|
+
VERSION,
|
|
261
|
+
CHUNK_SIZE,
|
|
262
|
+
HEADER_SIZE
|
|
263
|
+
};
|
package/lib/tools.js
CHANGED
|
@@ -77,6 +77,22 @@ class LRUCache extends Map {
|
|
|
77
77
|
|
|
78
78
|
const regexCache = new LRUCache(1000);
|
|
79
79
|
|
|
80
|
+
function formatTokenError(provider, tokenRequest) {
|
|
81
|
+
let parts = [`Token request failed for ${provider}`];
|
|
82
|
+
if (tokenRequest) {
|
|
83
|
+
let detail = `${tokenRequest.grant || 'unknown'}, HTTP ${tokenRequest.status || '?'}`;
|
|
84
|
+
parts[0] += ` (${detail})`;
|
|
85
|
+
if (tokenRequest.response) {
|
|
86
|
+
let resp = tokenRequest.response;
|
|
87
|
+
let errorParts = [resp.error, resp.error_description].filter(Boolean);
|
|
88
|
+
if (errorParts.length) {
|
|
89
|
+
parts.push(errorParts.join(' - '));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return parts.join(': ');
|
|
94
|
+
}
|
|
95
|
+
|
|
80
96
|
module.exports = {
|
|
81
97
|
/**
|
|
82
98
|
* Helper function to set specific bit in a buffer
|
|
@@ -1382,8 +1398,12 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEV3QUiYsp13nD9suD1/ZkEXnuMoSg
|
|
|
1382
1398
|
},
|
|
1383
1399
|
|
|
1384
1400
|
validUidValidity(value) {
|
|
1385
|
-
if (typeof value === 'bigint'
|
|
1386
|
-
return
|
|
1401
|
+
if (typeof value === 'bigint') {
|
|
1402
|
+
return value > 0n;
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
if (typeof value === 'number') {
|
|
1406
|
+
return Number.isFinite(value) && value > 0;
|
|
1387
1407
|
}
|
|
1388
1408
|
|
|
1389
1409
|
if (isNaN(value)) {
|
|
@@ -1762,7 +1782,10 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEV3QUiYsp13nD9suD1/ZkEXnuMoSg
|
|
|
1762
1782
|
|
|
1763
1783
|
prepareUrl(url, baseUrl, queryParams) {
|
|
1764
1784
|
let { pathname: baseUrlPathname } = new URL(baseUrl);
|
|
1765
|
-
|
|
1785
|
+
// Ensure pathname ends with trailing slash for proper path concatenation
|
|
1786
|
+
if (!baseUrlPathname.endsWith('/')) {
|
|
1787
|
+
baseUrlPathname += '/';
|
|
1788
|
+
}
|
|
1766
1789
|
|
|
1767
1790
|
const urlObj = new URL(baseUrlPathname + url.replace(/^\//, ''), baseUrl);
|
|
1768
1791
|
|
|
@@ -1777,11 +1800,14 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEV3QUiYsp13nD9suD1/ZkEXnuMoSg
|
|
|
1777
1800
|
return urlObj.href;
|
|
1778
1801
|
},
|
|
1779
1802
|
|
|
1803
|
+
fetchAgent,
|
|
1780
1804
|
retryAgent,
|
|
1781
1805
|
|
|
1782
1806
|
LRUCache,
|
|
1783
1807
|
|
|
1784
|
-
normalizeHashKeys
|
|
1808
|
+
normalizeHashKeys,
|
|
1809
|
+
|
|
1810
|
+
formatTokenError
|
|
1785
1811
|
};
|
|
1786
1812
|
|
|
1787
1813
|
function msgpackDecode(buf) {
|
|
@@ -520,6 +520,11 @@ function init(args) {
|
|
|
520
520
|
|
|
521
521
|
const nonce = data.n || crypto.randomBytes(NONCE_BYTES).toString('base64url');
|
|
522
522
|
|
|
523
|
+
// Validate nonce format (base64url, 21-22 chars; also accept base64 for backward compatibility)
|
|
524
|
+
if (!/^[A-Za-z0-9_\-+/]{21,22}={0,2}$/.test(nonce)) {
|
|
525
|
+
throw Boom.badRequest('Invalid nonce format. Please generate a new authentication URL.');
|
|
526
|
+
}
|
|
527
|
+
|
|
523
528
|
// store account data with atomic SET + EX
|
|
524
529
|
await redis.set(`${REDIS_PREFIX}account:add:${nonce}`, JSON.stringify(accountData), 'EX', Math.floor(MAX_FORM_TTL / 1000));
|
|
525
530
|
|
|
@@ -814,11 +819,29 @@ function init(args) {
|
|
|
814
819
|
case 'EAUTH':
|
|
815
820
|
verifyResult.smtp.error = request.app.gt.gettext('Invalid username or password');
|
|
816
821
|
break;
|
|
822
|
+
case 'ENOAUTH':
|
|
823
|
+
verifyResult.smtp.error = request.app.gt.gettext('Authentication credentials were not provided');
|
|
824
|
+
break;
|
|
825
|
+
case 'EOAUTH2':
|
|
826
|
+
verifyResult.smtp.error = request.app.gt.gettext('OAuth2 authentication failed');
|
|
827
|
+
break;
|
|
828
|
+
case 'ETLS':
|
|
829
|
+
verifyResult.smtp.error = request.app.gt.gettext('TLS protocol error');
|
|
830
|
+
break;
|
|
817
831
|
case 'ESOCKET':
|
|
818
832
|
if (/openssl/.test(verifyResult.smtp.error)) {
|
|
819
833
|
verifyResult.smtp.error = request.app.gt.gettext('TLS protocol error');
|
|
820
834
|
}
|
|
821
835
|
break;
|
|
836
|
+
case 'ETIMEDOUT':
|
|
837
|
+
verifyResult.smtp.error = request.app.gt.gettext('Connection timed out');
|
|
838
|
+
break;
|
|
839
|
+
case 'ECONNECTION':
|
|
840
|
+
verifyResult.smtp.error = request.app.gt.gettext('Could not connect to server');
|
|
841
|
+
break;
|
|
842
|
+
case 'EPROTOCOL':
|
|
843
|
+
verifyResult.smtp.error = request.app.gt.gettext('Unexpected server response');
|
|
844
|
+
break;
|
|
822
845
|
}
|
|
823
846
|
}
|
|
824
847
|
}
|
|
@@ -19,7 +19,8 @@ const { failAction, getByteSize, formatByteSize, getDuration, readEnvValue, hasE
|
|
|
19
19
|
|
|
20
20
|
const { settingsSchema } = require('../schemas');
|
|
21
21
|
|
|
22
|
-
const { DEFAULT_MAX_LOG_LINES, DEFAULT_DELIVERY_ATTEMPTS, REDIS_PREFIX, NONCE_BYTES } =
|
|
22
|
+
const { DEFAULT_MAX_LOG_LINES, DEFAULT_DELIVERY_ATTEMPTS, REDIS_PREFIX, NONCE_BYTES, DEFAULT_GMAIL_EXPORT_BATCH_SIZE, DEFAULT_OUTLOOK_EXPORT_BATCH_SIZE } =
|
|
23
|
+
consts;
|
|
23
24
|
|
|
24
25
|
const { fetch: fetchCmd } = require('undici');
|
|
25
26
|
|
|
@@ -359,7 +360,9 @@ function init(args) {
|
|
|
359
360
|
enableOAuthTokensApi: (await settings.get('enableOAuthTokensApi')) || false,
|
|
360
361
|
ignoreMailCertErrors: (await settings.get('ignoreMailCertErrors')) || false,
|
|
361
362
|
locale: (await settings.get('locale')) || false,
|
|
362
|
-
timezone: (await settings.get('timezone')) || false
|
|
363
|
+
timezone: (await settings.get('timezone')) || false,
|
|
364
|
+
gmailExportBatchSize: (await settings.get('gmailExportBatchSize')) || DEFAULT_GMAIL_EXPORT_BATCH_SIZE,
|
|
365
|
+
outlookExportBatchSize: (await settings.get('outlookExportBatchSize')) || DEFAULT_OUTLOOK_EXPORT_BATCH_SIZE
|
|
363
366
|
};
|
|
364
367
|
|
|
365
368
|
if (typeof values.trackClicks !== 'boolean') {
|
|
@@ -423,7 +426,9 @@ function init(args) {
|
|
|
423
426
|
locale: request.payload.locale,
|
|
424
427
|
timezone: request.payload.timezone,
|
|
425
428
|
deliveryAttempts: request.payload.deliveryAttempts,
|
|
426
|
-
imapIndexer: request.payload.imapIndexer
|
|
429
|
+
imapIndexer: request.payload.imapIndexer,
|
|
430
|
+
gmailExportBatchSize: request.payload.gmailExportBatchSize,
|
|
431
|
+
outlookExportBatchSize: request.payload.outlookExportBatchSize
|
|
427
432
|
};
|
|
428
433
|
|
|
429
434
|
if (request.payload.serviceUrl) {
|
|
@@ -530,7 +535,9 @@ function init(args) {
|
|
|
530
535
|
.empty('')
|
|
531
536
|
.valid(...locales.map(locale => locale.locale))
|
|
532
537
|
.default('en'),
|
|
533
|
-
timezone: settingsSchema.timezone.empty('')
|
|
538
|
+
timezone: settingsSchema.timezone.empty(''),
|
|
539
|
+
gmailExportBatchSize: settingsSchema.gmailExportBatchSize.default(DEFAULT_GMAIL_EXPORT_BATCH_SIZE),
|
|
540
|
+
outlookExportBatchSize: settingsSchema.outlookExportBatchSize.default(DEFAULT_OUTLOOK_EXPORT_BATCH_SIZE)
|
|
534
541
|
})
|
|
535
542
|
}
|
|
536
543
|
}
|
|
@@ -2022,11 +2022,29 @@ return payload;`)
|
|
|
2022
2022
|
case 'EAUTH':
|
|
2023
2023
|
verifyResult.smtp.error = request.app.gt.gettext('Invalid username or password');
|
|
2024
2024
|
break;
|
|
2025
|
+
case 'ENOAUTH':
|
|
2026
|
+
verifyResult.smtp.error = request.app.gt.gettext('Authentication credentials were not provided');
|
|
2027
|
+
break;
|
|
2028
|
+
case 'EOAUTH2':
|
|
2029
|
+
verifyResult.smtp.error = request.app.gt.gettext('OAuth2 authentication failed');
|
|
2030
|
+
break;
|
|
2031
|
+
case 'ETLS':
|
|
2032
|
+
verifyResult.smtp.error = request.app.gt.gettext('TLS protocol error');
|
|
2033
|
+
break;
|
|
2025
2034
|
case 'ESOCKET':
|
|
2026
2035
|
if (/openssl/.test(verifyResult.smtp.error)) {
|
|
2027
2036
|
verifyResult.smtp.error = request.app.gt.gettext('TLS protocol error');
|
|
2028
2037
|
}
|
|
2029
2038
|
break;
|
|
2039
|
+
case 'ETIMEDOUT':
|
|
2040
|
+
verifyResult.smtp.error = request.app.gt.gettext('Connection timed out');
|
|
2041
|
+
break;
|
|
2042
|
+
case 'ECONNECTION':
|
|
2043
|
+
verifyResult.smtp.error = request.app.gt.gettext('Could not connect to server');
|
|
2044
|
+
break;
|
|
2045
|
+
case 'EPROTOCOL':
|
|
2046
|
+
verifyResult.smtp.error = request.app.gt.gettext('Unexpected server response');
|
|
2047
|
+
break;
|
|
2030
2048
|
}
|
|
2031
2049
|
}
|
|
2032
2050
|
}
|
package/lib/webhooks.js
CHANGED
|
@@ -512,9 +512,21 @@ class WebhooksHandler {
|
|
|
512
512
|
}
|
|
513
513
|
|
|
514
514
|
async pushToQueue(event, originalPayload, opts = {}) {
|
|
515
|
-
// custom
|
|
515
|
+
// custom webhook routes
|
|
516
516
|
let webhookRoutes = await this.getWebhookHandlers();
|
|
517
|
-
let queueKeep = (await settings.get('queueKeep'))
|
|
517
|
+
let queueKeep = (await settings.get('queueKeep')) ?? true;
|
|
518
|
+
|
|
519
|
+
let removePolicy = typeof queueKeep === 'number' ? { age: 24 * 3600, count: queueKeep } : queueKeep;
|
|
520
|
+
let jobOpts = {
|
|
521
|
+
removeOnComplete: removePolicy,
|
|
522
|
+
removeOnFail: removePolicy,
|
|
523
|
+
attempts: 10,
|
|
524
|
+
backoff: {
|
|
525
|
+
type: 'exponential',
|
|
526
|
+
delay: 5000,
|
|
527
|
+
jitter: 0.2 // 20% randomization to prevent thundering herd
|
|
528
|
+
}
|
|
529
|
+
};
|
|
518
530
|
|
|
519
531
|
for (let route of webhookRoutes) {
|
|
520
532
|
if (route.enabled && route.targetUrl && typeof route.filterFn === 'function') {
|
|
@@ -560,15 +572,7 @@ class WebhooksHandler {
|
|
|
560
572
|
webhook: route.id
|
|
561
573
|
});
|
|
562
574
|
} else {
|
|
563
|
-
let job = await notifyQueue.add(event, payload,
|
|
564
|
-
removeOnComplete: queueKeep,
|
|
565
|
-
removeOnFail: queueKeep,
|
|
566
|
-
attempts: 10,
|
|
567
|
-
backoff: {
|
|
568
|
-
type: 'exponential',
|
|
569
|
-
delay: 5000
|
|
570
|
-
}
|
|
571
|
-
});
|
|
575
|
+
let job = await notifyQueue.add(event, payload, jobOpts);
|
|
572
576
|
|
|
573
577
|
logger.trace({
|
|
574
578
|
msg: 'Triggered custom webhook route',
|
|
@@ -589,15 +593,7 @@ class WebhooksHandler {
|
|
|
589
593
|
|
|
590
594
|
if (!opts.routesOnly) {
|
|
591
595
|
// MAIN webhook
|
|
592
|
-
await notifyQueue.add(event, originalPayload,
|
|
593
|
-
removeOnComplete: queueKeep,
|
|
594
|
-
removeOnFail: queueKeep,
|
|
595
|
-
attempts: 10,
|
|
596
|
-
backoff: {
|
|
597
|
-
type: 'exponential',
|
|
598
|
-
delay: 5000
|
|
599
|
-
}
|
|
600
|
-
});
|
|
596
|
+
await notifyQueue.add(event, originalPayload, jobOpts);
|
|
601
597
|
}
|
|
602
598
|
}
|
|
603
599
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "emailengine-app",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.62.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"productTitle": "EmailEngine",
|
|
6
6
|
"description": "Email Sync Engine",
|
|
@@ -43,9 +43,9 @@
|
|
|
43
43
|
},
|
|
44
44
|
"homepage": "https://emailengine.app/",
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@bugsnag/js": "8.
|
|
47
|
-
"@bull-board/api": "6.16.
|
|
48
|
-
"@bull-board/hapi": "6.16.
|
|
46
|
+
"@bugsnag/js": "8.8.1",
|
|
47
|
+
"@bull-board/api": "6.16.4",
|
|
48
|
+
"@bull-board/hapi": "6.16.4",
|
|
49
49
|
"@elastic/elasticsearch": "8.15.3",
|
|
50
50
|
"@hapi/accept": "6.0.3",
|
|
51
51
|
"@hapi/bell": "13.1.0",
|
|
@@ -59,16 +59,16 @@
|
|
|
59
59
|
"@postalsys/bounce-classifier": "^2.0.0",
|
|
60
60
|
"@postalsys/certs": "1.0.12",
|
|
61
61
|
"@postalsys/ee-client": "1.3.0",
|
|
62
|
-
"@postalsys/email-ai-tools": "1.11.
|
|
62
|
+
"@postalsys/email-ai-tools": "1.11.3",
|
|
63
63
|
"@postalsys/email-text-tools": "2.4.1",
|
|
64
64
|
"@postalsys/gettext": "4.1.1",
|
|
65
65
|
"@postalsys/joi-messages": "1.0.5",
|
|
66
66
|
"@postalsys/templates": "2.0.0",
|
|
67
67
|
"@zone-eu/mailsplit": "5.4.8",
|
|
68
68
|
"@zone-eu/wild-config": "1.7.3",
|
|
69
|
-
"ace-builds": "1.43.
|
|
69
|
+
"ace-builds": "1.43.6",
|
|
70
70
|
"base32.js": "0.1.0",
|
|
71
|
-
"bullmq": "5.
|
|
71
|
+
"bullmq": "5.67.2",
|
|
72
72
|
"compare-versions": "6.1.1",
|
|
73
73
|
"dotenv": "17.2.3",
|
|
74
74
|
"encoding-japanese": "2.2.0",
|
|
@@ -82,9 +82,9 @@
|
|
|
82
82
|
"html-to-text": "9.0.5",
|
|
83
83
|
"ical.js": "1.5.0",
|
|
84
84
|
"iconv-lite": "0.7.2",
|
|
85
|
-
"imapflow": "1.2.
|
|
85
|
+
"imapflow": "1.2.8",
|
|
86
86
|
"ioredfour": "1.3.0-ioredis-07",
|
|
87
|
-
"ioredis": "5.9.
|
|
87
|
+
"ioredis": "5.9.2",
|
|
88
88
|
"ipaddr.js": "2.3.0",
|
|
89
89
|
"joi": "17.13.3",
|
|
90
90
|
"jquery": "3.7.1",
|
|
@@ -92,26 +92,26 @@
|
|
|
92
92
|
"libmime": "5.3.7",
|
|
93
93
|
"libqp": "2.1.1",
|
|
94
94
|
"license-checker": "25.0.1",
|
|
95
|
-
"mailparser": "3.9.
|
|
95
|
+
"mailparser": "3.9.3",
|
|
96
96
|
"marked": "9.1.6",
|
|
97
97
|
"minimist": "1.2.8",
|
|
98
98
|
"msgpack5": "6.0.2",
|
|
99
99
|
"murmurhash": "2.0.1",
|
|
100
100
|
"nanoid": "3.3.8",
|
|
101
|
-
"nodemailer": "
|
|
102
|
-
"pino": "10.
|
|
101
|
+
"nodemailer": "8.0.0",
|
|
102
|
+
"pino": "10.3.0",
|
|
103
103
|
"popper.js": "1.16.1",
|
|
104
104
|
"prom-client": "15.1.3",
|
|
105
105
|
"psl": "1.15.0",
|
|
106
|
-
"pubface": "1.0.
|
|
106
|
+
"pubface": "1.0.18",
|
|
107
107
|
"punycode.js": "2.3.1",
|
|
108
108
|
"qrcode": "1.5.4",
|
|
109
|
-
"smtp-server": "3.18.
|
|
109
|
+
"smtp-server": "3.18.1",
|
|
110
110
|
"socks": "2.8.7",
|
|
111
111
|
"speakeasy": "2.0.0",
|
|
112
112
|
"startbootstrap-sb-admin-2": "3.3.7",
|
|
113
113
|
"timezones-list": "3.1.0",
|
|
114
|
-
"undici": "7.
|
|
114
|
+
"undici": "7.20.0",
|
|
115
115
|
"xml2js": "0.6.2"
|
|
116
116
|
},
|
|
117
117
|
"devDependencies": {
|
|
@@ -125,7 +125,7 @@
|
|
|
125
125
|
"grunt-wait": "0.3.0",
|
|
126
126
|
"jsxgettext": "0.11.0",
|
|
127
127
|
"pino-pretty": "13.0.0",
|
|
128
|
-
"prettier": "3.8.
|
|
128
|
+
"prettier": "3.8.1",
|
|
129
129
|
"resedit": "3.0.1",
|
|
130
130
|
"spdx-satisfies": "6.0.0",
|
|
131
131
|
"supertest": "7.2.2",
|