emailengine-app 2.68.1 → 2.70.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.
Files changed (95) hide show
  1. package/.github/workflows/deploy.yml +8 -3
  2. package/.github/workflows/release.yaml +6 -0
  3. package/CHANGELOG.md +59 -0
  4. package/Gruntfile.js +3 -1
  5. package/config/default.toml +2 -0
  6. package/data/google-crawlers.json +7 -1
  7. package/getswagger.sh +40 -4
  8. package/gettext-extract.js +163 -0
  9. package/lib/account.js +135 -72
  10. package/lib/api-routes/account-routes.js +684 -106
  11. package/lib/api-routes/blocklist-routes.js +344 -0
  12. package/lib/api-routes/chat-routes.js +32 -14
  13. package/lib/api-routes/delivery-test-routes.js +346 -0
  14. package/lib/api-routes/export-routes.js +28 -14
  15. package/lib/api-routes/gateway-routes.js +427 -0
  16. package/lib/api-routes/license-routes.js +156 -0
  17. package/lib/api-routes/mailbox-routes.js +344 -0
  18. package/lib/api-routes/message-routes.js +221 -187
  19. package/lib/api-routes/oauth2-app-routes.js +697 -0
  20. package/lib/api-routes/outbox-routes.js +185 -0
  21. package/lib/api-routes/pubsub-routes.js +102 -0
  22. package/lib/api-routes/route-helpers.js +58 -0
  23. package/lib/api-routes/settings-routes.js +357 -0
  24. package/lib/api-routes/stats-routes.js +111 -0
  25. package/lib/api-routes/submit-routes.js +461 -0
  26. package/lib/api-routes/template-routes.js +60 -75
  27. package/lib/api-routes/token-routes.js +297 -0
  28. package/lib/api-routes/webhook-route-routes.js +181 -0
  29. package/lib/autodetect-imap-settings.js +0 -2
  30. package/lib/consts.js +5 -0
  31. package/lib/email-client/base-client.js +28 -6
  32. package/lib/email-client/gmail-client.js +133 -112
  33. package/lib/email-client/imap/mailbox.js +34 -11
  34. package/lib/email-client/imap/subconnection.js +20 -13
  35. package/lib/email-client/imap/sync-operations.js +131 -3
  36. package/lib/email-client/imap-client.js +152 -75
  37. package/lib/email-client/notification-handler.js +1 -4
  38. package/lib/email-client/outlook-client.js +134 -75
  39. package/lib/export.js +97 -20
  40. package/lib/feature-flags.js +2 -2
  41. package/lib/gateway.js +4 -9
  42. package/lib/get-raw-email.js +5 -5
  43. package/lib/imapproxy/imap-core/lib/commands/starttls.js +18 -0
  44. package/lib/imapproxy/imap-core/lib/imap-command.js +6 -1
  45. package/lib/imapproxy/imap-core/lib/imap-connection.js +106 -24
  46. package/lib/imapproxy/imap-core/lib/imap-server.js +24 -0
  47. package/lib/imapproxy/imap-core/lib/imap-stream.js +26 -0
  48. package/lib/logger.js +24 -21
  49. package/lib/message-port-stream.js +113 -16
  50. package/lib/metrics-collector.js +0 -2
  51. package/lib/oauth2-apps.js +13 -4
  52. package/lib/outbox.js +24 -40
  53. package/lib/redis-operations.js +1 -1
  54. package/lib/reject-worker-calls.js +42 -0
  55. package/lib/routes-ui.js +37 -8778
  56. package/lib/schemas.js +429 -84
  57. package/lib/sentry.js +139 -0
  58. package/lib/settings.js +9 -3
  59. package/lib/stream-encrypt.js +1 -1
  60. package/lib/templates.js +1 -1
  61. package/lib/tokens.js +5 -3
  62. package/lib/tools.js +70 -4
  63. package/lib/ui-routes/account-routes.js +45 -212
  64. package/lib/ui-routes/admin-config-routes.js +928 -489
  65. package/lib/ui-routes/admin-entities-routes.js +1 -0
  66. package/lib/ui-routes/auth-routes.js +1339 -0
  67. package/lib/ui-routes/dashboard-routes.js +188 -0
  68. package/lib/ui-routes/document-store-routes.js +800 -0
  69. package/lib/ui-routes/export-routes.js +217 -0
  70. package/lib/ui-routes/internals-routes.js +354 -0
  71. package/lib/ui-routes/network-config-routes.js +759 -0
  72. package/lib/ui-routes/{oauth-routes.js → oauth-config-routes.js} +369 -91
  73. package/lib/ui-routes/route-helpers.js +314 -0
  74. package/lib/ui-routes/smtp-test-routes.js +236 -0
  75. package/lib/ui-routes/unsubscribe-routes.js +232 -0
  76. package/lib/webhook-request.js +36 -0
  77. package/lib/webhooks.js +8 -4
  78. package/package.json +13 -12
  79. package/sbom.json +1 -1
  80. package/server.js +222 -39
  81. package/static/licenses.html +160 -300
  82. package/translations/messages.pot +112 -132
  83. package/update-info.sh +19 -1
  84. package/views/config/logging.hbs +48 -0
  85. package/views/dashboard.hbs +7 -26
  86. package/views/internals/index.hbs +15 -0
  87. package/views/tokens/index.hbs +9 -0
  88. package/workers/api.js +200 -4424
  89. package/workers/documents.js +2 -22
  90. package/workers/export.js +103 -104
  91. package/workers/imap-proxy.js +3 -23
  92. package/workers/imap.js +32 -36
  93. package/workers/smtp.js +2 -22
  94. package/workers/submit.js +26 -35
  95. package/workers/webhooks.js +9 -43
package/workers/submit.js CHANGED
@@ -7,32 +7,12 @@ const config = require('@zone-eu/wild-config');
7
7
  const logger = require('../lib/logger');
8
8
 
9
9
  const { REDIS_PREFIX } = require('../lib/consts');
10
- const { getDuration, readEnvValue, threadStats, reloadHttpProxyAgent } = require('../lib/tools');
10
+ const { getDuration, readEnvValue, threadStats, maybeReloadHttpProxyAgent } = require('../lib/tools');
11
11
  const { webhooks: Webhooks } = require('../lib/webhooks');
12
12
  const settings = require('../lib/settings');
13
13
 
14
- const Bugsnag = require('@bugsnag/js');
15
- if (readEnvValue('BUGSNAG_API_KEY')) {
16
- Bugsnag.start({
17
- apiKey: readEnvValue('BUGSNAG_API_KEY'),
18
- appVersion: packageData.version,
19
- logger: {
20
- debug(...args) {
21
- logger.debug({ msg: args.shift(), worker: 'submit', source: 'bugsnag', args: args.length ? args : undefined });
22
- },
23
- info(...args) {
24
- logger.debug({ msg: args.shift(), worker: 'submit', source: 'bugsnag', args: args.length ? args : undefined });
25
- },
26
- warn(...args) {
27
- logger.warn({ msg: args.shift(), worker: 'submit', source: 'bugsnag', args: args.length ? args : undefined });
28
- },
29
- error(...args) {
30
- logger.error({ msg: args.shift(), worker: 'submit', source: 'bugsnag', args: args.length ? args : undefined });
31
- }
32
- }
33
- });
34
- logger.notifyError = Bugsnag.notify.bind(Bugsnag);
35
- }
14
+ const { initSentry } = require('../lib/sentry');
15
+ initSentry('submit');
36
16
 
37
17
  const util = require('util');
38
18
  const { redis, queueConf, submitQueue } = require('../lib/db');
@@ -222,13 +202,15 @@ const submitWorker = new Worker(
222
202
  }
223
203
 
224
204
  let backoffDelay = Number(job.opts.backoff && job.opts.backoff.delay) || 0;
225
- let nextAttempt = job.attemptsMade < job.opts.attempts ? Math.round(job.processedOn + Math.pow(2, job.attemptsMade) * backoffDelay) : false;
205
+ // job.attemptsMade is not yet incremented for the ongoing attempt, so the
206
+ // next retry (if this attempt fails) is delayed by 2^attemptsMade * base
207
+ let nextAttempt = job.attemptsMade + 1 < job.opts.attempts ? Math.round(job.processedOn + Math.pow(2, job.attemptsMade) * backoffDelay) : false;
226
208
 
227
209
  queueEntry.job = {
228
210
  id: job.id,
229
211
  attemptsMade: job.attemptsMade,
230
212
  attempts: job.opts.attempts,
231
- nextAttempt: new Date(nextAttempt).toISOString()
213
+ nextAttempt: nextAttempt ? new Date(nextAttempt).toISOString() : false
232
214
  };
233
215
 
234
216
  let res = await accountObject.submitMessage(queueEntry);
@@ -409,12 +391,24 @@ submitWorker.on('failed', async job => {
409
391
  logger.error({ msg: 'Failed to remove queue entry', account: job.data.account, queueId: job.data.queueId, messageId: job.data.messageId, err });
410
392
  }
411
393
  // report as failed
412
- await notify(job.data.account, EMAIL_FAILED_NOTIFY, {
413
- messageId: job.data.messageId,
414
- queueId: job.data.queueId,
415
- error: job.stacktrace && job.stacktrace[0] && job.stacktrace[0].split('\n').shift(),
416
- networkRouting: job.progress?.networkRouting
417
- });
394
+ try {
395
+ await notify(job.data.account, EMAIL_FAILED_NOTIFY, {
396
+ messageId: job.data.messageId,
397
+ queueId: job.data.queueId,
398
+ error: job.stacktrace && job.stacktrace[0] && job.stacktrace[0].split('\n').shift(),
399
+ networkRouting: job.progress?.networkRouting
400
+ });
401
+ } catch (notifyErr) {
402
+ // A failed webhook notification must not bubble out of this BullMQ
403
+ // event listener as an unhandled rejection and take down the worker.
404
+ logger.error({
405
+ msg: 'Failed to deliver submission failure notification',
406
+ account: job.data.account,
407
+ queueId: job.data.queueId,
408
+ messageId: job.data.messageId,
409
+ err: notifyErr
410
+ });
411
+ }
418
412
  }
419
413
  }
420
414
 
@@ -497,10 +491,7 @@ parentPort.on('message', message => {
497
491
  }
498
492
 
499
493
  if (message && message.cmd === 'settings') {
500
- let d = message.data || {};
501
- if ('httpProxyEnabled' in d || 'httpProxyUrl' in d) {
502
- reloadHttpProxyAgent().catch(err => logger.error({ msg: 'Failed to reload HTTP proxy agent', err }));
503
- }
494
+ maybeReloadHttpProxyAgent(message.data);
504
495
  }
505
496
  });
506
497
 
@@ -10,30 +10,11 @@ const { webhooks: Webhooks } = require('../lib/webhooks');
10
10
 
11
11
  const { GooglePubSub } = require('../lib/oauth/pubsub/google');
12
12
 
13
- const { readEnvValue, threadStats, getDuration, httpAgent, getServiceSecret, reloadHttpProxyAgent } = require('../lib/tools');
14
-
15
- const Bugsnag = require('@bugsnag/js');
16
- if (readEnvValue('BUGSNAG_API_KEY')) {
17
- Bugsnag.start({
18
- apiKey: readEnvValue('BUGSNAG_API_KEY'),
19
- appVersion: packageData.version,
20
- logger: {
21
- debug(...args) {
22
- logger.debug({ msg: args.shift(), worker: 'webhooks', source: 'bugsnag', args: args.length ? args : undefined });
23
- },
24
- info(...args) {
25
- logger.debug({ msg: args.shift(), worker: 'webhooks', source: 'bugsnag', args: args.length ? args : undefined });
26
- },
27
- warn(...args) {
28
- logger.warn({ msg: args.shift(), worker: 'webhooks', source: 'bugsnag', args: args.length ? args : undefined });
29
- },
30
- error(...args) {
31
- logger.error({ msg: args.shift(), worker: 'webhooks', source: 'bugsnag', args: args.length ? args : undefined });
32
- }
33
- }
34
- });
35
- logger.notifyError = Bugsnag.notify.bind(Bugsnag);
36
- }
13
+ const { readEnvValue, threadStats, getDuration, httpAgent, getServiceSecret, maybeReloadHttpProxyAgent } = require('../lib/tools');
14
+ const { sendWebhookRequest } = require('../lib/webhook-request');
15
+
16
+ const { initSentry } = require('../lib/sentry');
17
+ initSentry('webhooks');
37
18
 
38
19
  const { redis, queueConf } = require('../lib/db');
39
20
  const { Worker } = require('bullmq');
@@ -199,10 +180,7 @@ parentPort.on('message', message => {
199
180
  }
200
181
 
201
182
  if (message && message.cmd === 'settings') {
202
- let d = message.data || {};
203
- if ('httpProxyEnabled' in d || 'httpProxyUrl' in d) {
204
- reloadHttpProxyAgent().catch(err => logger.error({ msg: 'Failed to reload HTTP proxy agent', err }));
205
- }
183
+ maybeReloadHttpProxyAgent(message.data);
206
184
  }
207
185
  });
208
186
 
@@ -424,9 +402,9 @@ const notifyWorker = new Worker(
424
402
  headers['X-EE-Wh-Signature'] = hmac.digest('base64url');
425
403
 
426
404
  try {
427
- let res;
405
+ let status;
428
406
  try {
429
- res = await fetchCmd(parsed.toString(), {
407
+ status = await sendWebhookRequest(fetchCmd, parsed.toString(), {
430
408
  method: 'post',
431
409
  body,
432
410
  headers,
@@ -438,18 +416,6 @@ const notifyWorker = new Worker(
438
416
  throw err.cause || err;
439
417
  }
440
418
 
441
- if (!res.ok) {
442
- // Drain response body to release connection back to pool
443
- try {
444
- await res.text();
445
- } catch {
446
- // ignore drain errors
447
- }
448
- let err = new Error(res.statusText || `Invalid response: ${res.status} ${res.statusText}`);
449
- err.statusCode = res.status;
450
- throw err;
451
- }
452
-
453
419
  logger.trace({
454
420
  msg: 'Webhook posted',
455
421
  action: 'webhook',
@@ -460,7 +426,7 @@ const notifyWorker = new Worker(
460
426
  requestBodySize: body.length,
461
427
  accountWebhooks: !!accountWebhooks,
462
428
  event: job.name,
463
- status: res.status,
429
+ status,
464
430
  account: job.data.account,
465
431
  route: customRoute && customRoute.id
466
432
  });