emailengine-app 2.61.1 → 2.61.3

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 (136) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/data/google-crawlers.json +1 -1
  3. package/lib/account/account-state.js +248 -0
  4. package/lib/account.js +45 -193
  5. package/lib/api-routes/account-routes.js +1023 -0
  6. package/lib/api-routes/message-routes.js +1377 -0
  7. package/lib/consts.js +12 -2
  8. package/lib/email-client/base-client.js +282 -771
  9. package/lib/email-client/gmail/gmail-api.js +243 -0
  10. package/lib/email-client/gmail-client.js +145 -53
  11. package/lib/email-client/imap/mailbox.js +24 -698
  12. package/lib/email-client/imap/sync-operations.js +812 -0
  13. package/lib/email-client/imap-client.js +1 -1
  14. package/lib/email-client/message-builder.js +566 -0
  15. package/lib/email-client/notification-handler.js +314 -0
  16. package/lib/email-client/outlook/graph-api.js +326 -0
  17. package/lib/email-client/outlook-client.js +159 -113
  18. package/lib/email-client/smtp-pool-manager.js +196 -0
  19. package/lib/imapproxy/imap-server.js +3 -12
  20. package/lib/oauth/gmail.js +4 -4
  21. package/lib/oauth/mail-ru.js +30 -5
  22. package/lib/oauth/outlook.js +57 -3
  23. package/lib/oauth/pubsub/google.js +30 -11
  24. package/lib/oauth/scope-checker.js +202 -0
  25. package/lib/oauth2-apps.js +8 -4
  26. package/lib/redis-operations.js +484 -0
  27. package/lib/routes-ui.js +283 -2582
  28. package/lib/tools.js +4 -196
  29. package/lib/ui-routes/account-routes.js +1931 -0
  30. package/lib/ui-routes/admin-config-routes.js +1233 -0
  31. package/lib/ui-routes/admin-entities-routes.js +2367 -0
  32. package/lib/ui-routes/oauth-routes.js +992 -0
  33. package/lib/utils/network.js +237 -0
  34. package/package.json +10 -10
  35. package/sbom.json +1 -1
  36. package/static/js/app.js +5 -5
  37. package/static/licenses.html +79 -19
  38. package/translations/de.mo +0 -0
  39. package/translations/de.po +97 -86
  40. package/translations/en.mo +0 -0
  41. package/translations/en.po +80 -75
  42. package/translations/et.mo +0 -0
  43. package/translations/et.po +96 -86
  44. package/translations/fr.mo +0 -0
  45. package/translations/fr.po +97 -86
  46. package/translations/ja.mo +0 -0
  47. package/translations/ja.po +96 -86
  48. package/translations/messages.pot +105 -91
  49. package/translations/nl.mo +0 -0
  50. package/translations/nl.po +98 -86
  51. package/translations/pl.mo +0 -0
  52. package/translations/pl.po +96 -86
  53. package/views/account/security.hbs +4 -4
  54. package/views/accounts/account.hbs +13 -13
  55. package/views/accounts/register/imap-server.hbs +12 -12
  56. package/views/config/document-store/pre-processing/index.hbs +4 -2
  57. package/views/config/oauth/app.hbs +6 -7
  58. package/views/config/oauth/index.hbs +2 -2
  59. package/views/config/service.hbs +3 -4
  60. package/views/dashboard.hbs +5 -7
  61. package/views/error.hbs +22 -7
  62. package/views/gateways/gateway.hbs +2 -2
  63. package/views/partials/add_account_modal.hbs +7 -10
  64. package/views/partials/document_store_header.hbs +1 -1
  65. package/views/partials/editor_scope_info.hbs +0 -1
  66. package/views/partials/oauth_config_header.hbs +1 -1
  67. package/views/partials/side_menu.hbs +3 -3
  68. package/views/partials/webhook_form.hbs +2 -2
  69. package/views/templates/index.hbs +1 -1
  70. package/views/templates/template.hbs +8 -8
  71. package/views/tokens/index.hbs +6 -6
  72. package/views/tokens/new.hbs +1 -1
  73. package/views/webhooks/index.hbs +4 -4
  74. package/views/webhooks/webhook.hbs +7 -7
  75. package/workers/api.js +148 -2436
  76. package/workers/smtp.js +2 -1
  77. package/lib/imapproxy/imap-core/test/client.js +0 -46
  78. package/lib/imapproxy/imap-core/test/fixtures/append.eml +0 -1196
  79. package/lib/imapproxy/imap-core/test/fixtures/chunks.js +0 -44
  80. package/lib/imapproxy/imap-core/test/fixtures/fix1.eml +0 -6
  81. package/lib/imapproxy/imap-core/test/fixtures/fix2.eml +0 -599
  82. package/lib/imapproxy/imap-core/test/fixtures/fix3.eml +0 -32
  83. package/lib/imapproxy/imap-core/test/fixtures/fix4.eml +0 -6
  84. package/lib/imapproxy/imap-core/test/fixtures/mimetorture.eml +0 -599
  85. package/lib/imapproxy/imap-core/test/fixtures/mimetorture.js +0 -2740
  86. package/lib/imapproxy/imap-core/test/fixtures/mimetorture.json +0 -1411
  87. package/lib/imapproxy/imap-core/test/fixtures/mimetree.js +0 -85
  88. package/lib/imapproxy/imap-core/test/fixtures/nodemailer.eml +0 -582
  89. package/lib/imapproxy/imap-core/test/fixtures/ryan_finnie_mime_torture.eml +0 -599
  90. package/lib/imapproxy/imap-core/test/fixtures/simple.eml +0 -42
  91. package/lib/imapproxy/imap-core/test/fixtures/simple.json +0 -164
  92. package/lib/imapproxy/imap-core/test/imap-compile-stream-test.js +0 -671
  93. package/lib/imapproxy/imap-core/test/imap-compiler-test.js +0 -272
  94. package/lib/imapproxy/imap-core/test/imap-indexer-test.js +0 -236
  95. package/lib/imapproxy/imap-core/test/imap-parser-test.js +0 -922
  96. package/lib/imapproxy/imap-core/test/memory-notifier.js +0 -129
  97. package/lib/imapproxy/imap-core/test/prepare.sh +0 -74
  98. package/lib/imapproxy/imap-core/test/protocol-test.js +0 -1756
  99. package/lib/imapproxy/imap-core/test/search-test.js +0 -1356
  100. package/lib/imapproxy/imap-core/test/test-client.js +0 -152
  101. package/lib/imapproxy/imap-core/test/test-server.js +0 -623
  102. package/lib/imapproxy/imap-core/test/tools-test.js +0 -22
  103. package/test/api-test.js +0 -899
  104. package/test/autoreply-test.js +0 -327
  105. package/test/bounce-test.js +0 -151
  106. package/test/complaint-test.js +0 -256
  107. package/test/fixtures/autoreply/LICENSE +0 -27
  108. package/test/fixtures/autoreply/rfc3834-01.eml +0 -23
  109. package/test/fixtures/autoreply/rfc3834-02.eml +0 -24
  110. package/test/fixtures/autoreply/rfc3834-03.eml +0 -26
  111. package/test/fixtures/autoreply/rfc3834-04.eml +0 -48
  112. package/test/fixtures/autoreply/rfc3834-05.eml +0 -19
  113. package/test/fixtures/autoreply/rfc3834-06.eml +0 -59
  114. package/test/fixtures/bounces/163.eml +0 -2521
  115. package/test/fixtures/bounces/fastmail.eml +0 -242
  116. package/test/fixtures/bounces/gmail.eml +0 -252
  117. package/test/fixtures/bounces/hotmail.eml +0 -655
  118. package/test/fixtures/bounces/mailru.eml +0 -121
  119. package/test/fixtures/bounces/outlook.eml +0 -1107
  120. package/test/fixtures/bounces/postfix.eml +0 -101
  121. package/test/fixtures/bounces/rambler.eml +0 -116
  122. package/test/fixtures/bounces/workmail.eml +0 -142
  123. package/test/fixtures/bounces/yahoo.eml +0 -139
  124. package/test/fixtures/bounces/zoho.eml +0 -83
  125. package/test/fixtures/bounces/zonemta.eml +0 -100
  126. package/test/fixtures/complaints/LICENSE +0 -27
  127. package/test/fixtures/complaints/amazonses.eml +0 -72
  128. package/test/fixtures/complaints/dmarc.eml +0 -59
  129. package/test/fixtures/complaints/hotmail.eml +0 -49
  130. package/test/fixtures/complaints/optout.eml +0 -40
  131. package/test/fixtures/complaints/standard-arf.eml +0 -68
  132. package/test/fixtures/complaints/yahoo.eml +0 -68
  133. package/test/oauth2-apps-test.js +0 -301
  134. package/test/sendonly-test.js +0 -160
  135. package/test/test-config.js +0 -34
  136. package/test/webhooks-server.js +0 -39
@@ -0,0 +1,237 @@
1
+ 'use strict';
2
+
3
+ const os = require('os');
4
+ const net = require('net');
5
+ const ipaddr = require('ipaddr.js');
6
+ const { reverse: dnsReverse } = require('dns').promises;
7
+ const { resolvePublicInterfaces } = require('pubface');
8
+ const googleCrawlerRanges = require('../../data/google-crawlers.json');
9
+ const settings = require('../settings');
10
+ const logger = require('../logger');
11
+ const { REDIS_PREFIX } = require('../consts');
12
+
13
+ // Build Google crawler IP range map for efficient lookup
14
+ const googleCrawlerMap = new Map();
15
+ for (let prefixEntry of googleCrawlerRanges.prefixes) {
16
+ for (let prefixKey of ['ipv6Prefix', 'ipv4Prefix']) {
17
+ if (prefixEntry[prefixKey]) {
18
+ let parsed = ipaddr.parseCIDR(prefixEntry[prefixKey]);
19
+ if (!googleCrawlerMap.has(prefixKey)) {
20
+ googleCrawlerMap.set(prefixKey, []);
21
+ }
22
+ googleCrawlerMap.get(prefixKey).push(parsed);
23
+ }
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Matches an IP address against a list of addresses or CIDR ranges
29
+ * @param {string} ip - IP address to check
30
+ * @param {string[]} addresses - Array of IP addresses or CIDR ranges
31
+ * @returns {boolean} True if IP matches any address in the list
32
+ */
33
+ function matchIp(ip, addresses) {
34
+ let parsed = ipaddr.parse(ip);
35
+ for (let addr of addresses) {
36
+ try {
37
+ let match;
38
+ if (/\/\d+$/.test(addr)) {
39
+ match = parsed.match(ipaddr.parseCIDR(addr));
40
+ } else {
41
+ match = parsed.toNormalizedString() === ipaddr.parse(addr).toNormalizedString();
42
+ }
43
+ if (match) {
44
+ return true;
45
+ }
46
+ } catch (err) {
47
+ logger.error({ msg: 'Failed to parse IP address', ip, addr, err });
48
+ }
49
+ }
50
+
51
+ return false;
52
+ }
53
+
54
+ /**
55
+ * Detects if a request is from an automated scanner (Google, Barracuda, etc.)
56
+ * @param {string} ip - IP address to check
57
+ * @returns {Promise<boolean>} True if request appears to be automated
58
+ */
59
+ async function detectAutomatedRequest(ip) {
60
+ let prefixKey;
61
+ if (net.isIPv4(ip)) {
62
+ prefixKey = 'ipv4Prefix';
63
+ } else if (net.isIPv6(ip)) {
64
+ prefixKey = 'ipv6Prefix';
65
+ } else {
66
+ return false;
67
+ }
68
+
69
+ const addr = ipaddr.parse(ip);
70
+
71
+ // Check if it is a Google security scanner
72
+ for (let prefixEntry of googleCrawlerMap.get(prefixKey)) {
73
+ if (addr.match(prefixEntry)) {
74
+ return true;
75
+ }
76
+ }
77
+
78
+ // Check known scanners via reverse DNS
79
+ let hostnames;
80
+ try {
81
+ hostnames = await dnsReverse(ip);
82
+ } catch (err) {
83
+ logger.trace({
84
+ msg: 'Failed to reverse resolve IP',
85
+ ip,
86
+ err
87
+ });
88
+ }
89
+
90
+ if (!hostnames || !hostnames.length) {
91
+ return false;
92
+ }
93
+
94
+ const hostname = []
95
+ .concat(hostnames || [])
96
+ .shift()
97
+ .toString()
98
+ .trim()
99
+ .toLowerCase();
100
+
101
+ // Barracuda, spfbl
102
+ if (/\bbarracuda\.com$|\bspfbl\.net$/gi.test(hostname)) {
103
+ return true;
104
+ }
105
+
106
+ return false;
107
+ }
108
+
109
+ /**
110
+ * Updates Redis with information about available public network interfaces
111
+ * @param {Object} redis - Redis client instance
112
+ * @returns {Promise<void>}
113
+ */
114
+ async function updatePublicInterfaces(redis) {
115
+ let interfaces = await resolvePublicInterfaces();
116
+
117
+ for (let iface of interfaces) {
118
+ if (!iface.localAddress) {
119
+ continue;
120
+ }
121
+
122
+ if (iface.defaultInterface) {
123
+ await redis.hset(`${REDIS_PREFIX}interfaces`, `default:${iface.family}`, iface.localAddress);
124
+ }
125
+
126
+ let existingEntry = await redis.hget(`${REDIS_PREFIX}interfaces`, iface.localAddress);
127
+ if (existingEntry) {
128
+ try {
129
+ existingEntry = JSON.parse(existingEntry);
130
+
131
+ iface.name = iface.name || existingEntry.name;
132
+
133
+ if (!iface.localAddress || !iface.ip || !iface.name) {
134
+ continue;
135
+ }
136
+ } catch (err) {
137
+ // ignore parsing errors
138
+ }
139
+ }
140
+
141
+ delete iface.defaultInterface;
142
+ await redis.hset(`${REDIS_PREFIX}interfaces`, iface.localAddress, JSON.stringify(iface));
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Gets the local address to use for outbound connections
148
+ * @param {Object} redis - Redis client instance
149
+ * @param {string} protocol - Protocol name (e.g., 'smtp', 'imap')
150
+ * @param {string} account - Account identifier
151
+ * @param {string} [hint] - Optional IP address hint
152
+ * @returns {Promise<Object>} Address information object
153
+ */
154
+ async function getLocalAddress(redis, protocol, account, hint) {
155
+ // Dynamic require to access tools.getServiceHostname() and tools.selectRendezvousAddress()
156
+ // These functions are defined in tools.js, which also imports from this module
157
+ const tools = require('../tools');
158
+
159
+ let existingAddresses = Object.values(os.networkInterfaces())
160
+ .flatMap(entry => entry)
161
+ .map(entry => entry.address);
162
+
163
+ if (hint) {
164
+ let parsedHint = ipaddr.parse(hint);
165
+ let normalizedHint = parsedHint.toNormalizedString();
166
+ let iface = await redis.hget(`${REDIS_PREFIX}interfaces`, normalizedHint);
167
+ try {
168
+ iface = iface ? JSON.parse(iface) : null;
169
+ } catch (err) {
170
+ // ignore parsing errors
171
+ }
172
+ if (iface && existingAddresses.includes(iface.localAddress)) {
173
+ iface.addressSelector = 'hint';
174
+ return iface;
175
+ }
176
+ }
177
+
178
+ let addressStrategy = await settings.get(`${protocol}Strategy`);
179
+ let localAddresses = []
180
+ .concat((await settings.get(`localAddresses`)) || [])
181
+ .filter(address => existingAddresses.includes(address))
182
+ .filter(address => net.isIPv4(address));
183
+ let localAddress;
184
+
185
+ let hostname = await tools.getServiceHostname(await settings.get('smtpEhloName'));
186
+
187
+ let addressSelector;
188
+
189
+ if (!localAddresses.length) {
190
+ addressSelector = 'default';
191
+ return { address: false, name: hostname, addressSelector };
192
+ }
193
+
194
+ if (localAddresses.length === 1) {
195
+ addressSelector = 'single';
196
+ localAddress = localAddresses[0];
197
+ } else {
198
+ switch (addressStrategy) {
199
+ case 'random':
200
+ addressSelector = 'random';
201
+ localAddress = localAddresses[Math.floor(Math.random() * localAddresses.length)];
202
+ break;
203
+ case 'dedicated':
204
+ addressSelector = 'dedicated';
205
+ localAddress = tools.selectRendezvousAddress(account, localAddresses);
206
+ break;
207
+ default:
208
+ addressSelector = 'unknown';
209
+ return { address: false, name: hostname, addressSelector };
210
+ }
211
+ }
212
+
213
+ if (!localAddress) {
214
+ addressSelector = 'unset';
215
+ return { address: false, name: hostname, addressSelector };
216
+ }
217
+
218
+ try {
219
+ let addressData = JSON.parse(await redis.hget(`${REDIS_PREFIX}interfaces`, localAddress));
220
+ addressData.name = addressData.name || hostname;
221
+ addressData.addressSelector = addressSelector;
222
+ return addressData;
223
+ } catch (err) {
224
+ logger.error({ msg: 'Failed to load address data', localAddress, err });
225
+ addressSelector = 'error';
226
+ return { address: false, name: hostname, addressSelector };
227
+ }
228
+ }
229
+
230
+ module.exports = {
231
+ resolvePublicInterfaces,
232
+ updatePublicInterfaces,
233
+ getLocalAddress,
234
+ matchIp,
235
+ detectAutomatedRequest,
236
+ googleCrawlerMap
237
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "emailengine-app",
3
- "version": "2.61.1",
3
+ "version": "2.61.3",
4
4
  "private": false,
5
5
  "productTitle": "EmailEngine",
6
6
  "description": "Email Sync Engine",
@@ -17,7 +17,7 @@
17
17
  "build-dist": "pkg --compress Brotli package.json && npm install && node winconf.js",
18
18
  "build-dist-fast": "pkg --debug package.json && npm install && node winconf.js",
19
19
  "licenses": "license-checker --excludePackages 'emailengine-app' --json | node list-generate.js > static/licenses.html",
20
- "gettext": "find ./views -name \"*.hbs\" -print0 | xargs -0 xgettext-template -L Handlebars -o translations/messages.pot --force-po && jsxgettext lib/routes-ui.js workers/api.js lib/tools.js lib/autodetect-imap-settings.js -j -o translations/messages.pot",
20
+ "gettext": "find ./views -name \"*.hbs\" -print0 | xargs -0 xgettext-template -L Handlebars -o translations/messages.pot --force-po && jsxgettext lib/routes-ui.js workers/api.js lib/tools.js lib/autodetect-imap-settings.js lib/ui-routes/account-routes.js lib/ui-routes/admin-config-routes.js lib/ui-routes/admin-entities-routes.js -j -o translations/messages.pot",
21
21
  "prepare-docker": "echo \"EE_DOCKER_LEGACY=$EE_DOCKER_LEGACY\" >> system.env && cat system.env",
22
22
  "update": "rm -rf node_modules package-lock.json && ncu -u && npm install && ./copy-static-files.sh && npm run licenses && npm run gettext",
23
23
  "test-gmail-api": "node lib/email-client/gmail-client.js --dbs.redis=redis://127.0.0.1/11",
@@ -61,14 +61,14 @@
61
61
  "@postalsys/ee-client": "1.3.0",
62
62
  "@postalsys/email-ai-tools": "1.11.1",
63
63
  "@postalsys/email-text-tools": "2.4.0",
64
- "@postalsys/gettext": "4.1.0",
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
69
  "ace-builds": "1.43.5",
70
70
  "base32.js": "0.1.0",
71
- "bullmq": "5.66.4",
71
+ "bullmq": "5.66.5",
72
72
  "compare-versions": "6.1.1",
73
73
  "dotenv": "17.2.3",
74
74
  "encoding-japanese": "2.2.0",
@@ -81,10 +81,10 @@
81
81
  "he": "1.2.0",
82
82
  "html-to-text": "9.0.5",
83
83
  "ical.js": "1.5.0",
84
- "iconv-lite": "0.7.1",
85
- "imapflow": "1.2.4",
84
+ "iconv-lite": "0.7.2",
85
+ "imapflow": "1.2.6",
86
86
  "ioredfour": "1.3.0-ioredis-07",
87
- "ioredis": "5.8.2",
87
+ "ioredis": "5.9.1",
88
88
  "ipaddr.js": "2.3.0",
89
89
  "joi": "17.13.3",
90
90
  "jquery": "3.7.1",
@@ -99,7 +99,7 @@
99
99
  "murmurhash": "2.0.1",
100
100
  "nanoid": "3.3.8",
101
101
  "nodemailer": "7.0.12",
102
- "pino": "10.1.0",
102
+ "pino": "10.1.1",
103
103
  "popper.js": "1.16.1",
104
104
  "prom-client": "15.1.3",
105
105
  "psl": "1.15.0",
@@ -111,7 +111,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.16.0",
114
+ "undici": "7.18.2",
115
115
  "xml2js": "0.6.2"
116
116
  },
117
117
  "devDependencies": {
@@ -128,7 +128,7 @@
128
128
  "prettier": "3.7.4",
129
129
  "resedit": "3.0.1",
130
130
  "spdx-satisfies": "6.0.0",
131
- "supertest": "7.1.4",
131
+ "supertest": "7.2.2",
132
132
  "xgettext-template": "5.0.0"
133
133
  },
134
134
  "engines": {