emailengine-app 2.68.0 → 2.69.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 (74) hide show
  1. package/.github/codeql/codeql-config.yml +16 -0
  2. package/.github/workflows/codeql.yml +102 -0
  3. package/.github/workflows/deploy.yml +8 -0
  4. package/.github/workflows/release.yaml +4 -0
  5. package/.github/workflows/test.yml +3 -0
  6. package/CHANGELOG.md +49 -0
  7. package/SECURITY.md +80 -0
  8. package/SECURITY.txt +27 -0
  9. package/config/default.toml +2 -0
  10. package/data/google-crawlers.json +13 -1
  11. package/lib/account.js +62 -25
  12. package/lib/api-routes/account-routes.js +493 -75
  13. package/lib/api-routes/blocklist-routes.js +337 -0
  14. package/lib/api-routes/delivery-test-routes.js +321 -0
  15. package/lib/api-routes/export-routes.js +1 -12
  16. package/lib/api-routes/gateway-routes.js +376 -0
  17. package/lib/api-routes/license-routes.js +142 -0
  18. package/lib/api-routes/mailbox-routes.js +318 -0
  19. package/lib/api-routes/message-routes.js +21 -129
  20. package/lib/api-routes/oauth2-app-routes.js +631 -0
  21. package/lib/api-routes/outbox-routes.js +173 -0
  22. package/lib/api-routes/pubsub-routes.js +98 -0
  23. package/lib/api-routes/route-helpers.js +45 -0
  24. package/lib/api-routes/settings-routes.js +331 -0
  25. package/lib/api-routes/stats-routes.js +77 -0
  26. package/lib/api-routes/submit-routes.js +472 -0
  27. package/lib/api-routes/template-routes.js +7 -55
  28. package/lib/api-routes/token-routes.js +297 -0
  29. package/lib/api-routes/webhook-route-routes.js +152 -0
  30. package/lib/email-client/gmail-client.js +14 -0
  31. package/lib/email-client/imap/mailbox.js +34 -11
  32. package/lib/email-client/imap/subconnection.js +20 -12
  33. package/lib/email-client/imap/sync-operations.js +130 -2
  34. package/lib/email-client/imap-client.js +116 -58
  35. package/lib/email-client/outlook-client.js +85 -13
  36. package/lib/export.js +60 -19
  37. package/lib/imapproxy/imap-core/lib/commands/starttls.js +18 -0
  38. package/lib/imapproxy/imap-core/lib/imap-command.js +7 -2
  39. package/lib/imapproxy/imap-core/lib/imap-connection.js +113 -23
  40. package/lib/imapproxy/imap-core/lib/imap-server.js +25 -1
  41. package/lib/imapproxy/imap-core/lib/imap-stream.js +26 -0
  42. package/lib/imapproxy/imap-server.js +92 -29
  43. package/lib/message-port-stream.js +113 -16
  44. package/lib/reject-worker-calls.js +42 -0
  45. package/lib/routes-ui.js +37 -8778
  46. package/lib/schemas.js +26 -1
  47. package/lib/tools.js +73 -0
  48. package/lib/ui-routes/account-routes.js +40 -210
  49. package/lib/ui-routes/admin-config-routes.js +913 -487
  50. package/lib/ui-routes/admin-entities-routes.js +1 -0
  51. package/lib/ui-routes/auth-routes.js +1339 -0
  52. package/lib/ui-routes/dashboard-routes.js +188 -0
  53. package/lib/ui-routes/document-store-routes.js +800 -0
  54. package/lib/ui-routes/export-routes.js +217 -0
  55. package/lib/ui-routes/internals-routes.js +354 -0
  56. package/lib/ui-routes/network-config-routes.js +759 -0
  57. package/lib/ui-routes/{oauth-routes.js → oauth-config-routes.js} +371 -91
  58. package/lib/ui-routes/route-helpers.js +316 -0
  59. package/lib/ui-routes/smtp-test-routes.js +236 -0
  60. package/lib/ui-routes/unsubscribe-routes.js +234 -0
  61. package/lib/webhook-request.js +36 -0
  62. package/package.json +17 -17
  63. package/sbom.json +1 -1
  64. package/server.js +217 -19
  65. package/static/licenses.html +52 -182
  66. package/translations/messages.pot +131 -151
  67. package/views/dashboard.hbs +7 -26
  68. package/views/internals/index.hbs +15 -0
  69. package/views/tokens/index.hbs +9 -0
  70. package/workers/api.js +198 -4401
  71. package/workers/export.js +87 -54
  72. package/workers/imap.js +29 -13
  73. package/workers/submit.js +20 -11
  74. package/workers/webhooks.js +6 -20
@@ -0,0 +1,376 @@
1
+ 'use strict';
2
+
3
+ const Joi = require('joi');
4
+ const { redis } = require('../db');
5
+ const { Gateway } = require('../gateway');
6
+ const getSecret = require('../get-secret');
7
+ const { failAction } = require('../tools');
8
+ const { handleError } = require('./route-helpers');
9
+ const { lastErrorSchema } = require('../schemas');
10
+
11
+ async function init(args) {
12
+ const { server, call, CORS_CONFIG } = args;
13
+
14
+ server.route({
15
+ method: 'GET',
16
+ path: '/v1/gateways',
17
+
18
+ async handler(request) {
19
+ try {
20
+ let gatewayObject = new Gateway({ redis, gateway: request.params.gateway, call, secret: await getSecret() });
21
+
22
+ return await gatewayObject.listGateways(request.query.page, request.query.pageSize);
23
+ } catch (err) {
24
+ handleError(request, err);
25
+ }
26
+ },
27
+
28
+ options: {
29
+ description: 'List gateways',
30
+ notes: 'Lists registered gateways',
31
+ tags: ['api', 'SMTP Gateway'],
32
+
33
+ plugins: {},
34
+
35
+ auth: {
36
+ strategy: 'api-token',
37
+ mode: 'required'
38
+ },
39
+ cors: CORS_CONFIG,
40
+
41
+ validate: {
42
+ options: {
43
+ stripUnknown: false,
44
+ abortEarly: false,
45
+ convert: true
46
+ },
47
+ failAction,
48
+
49
+ query: Joi.object({
50
+ page: Joi.number()
51
+ .integer()
52
+ .min(0)
53
+ .max(1024 * 1024)
54
+ .default(0)
55
+ .example(0)
56
+ .description('Page number (zero indexed, so use 0 for first page)')
57
+ .label('PageNumber'),
58
+ pageSize: Joi.number().integer().min(1).max(1000).default(20).example(20).description('How many entries per page').label('PageSize')
59
+ }).label('GatewaysFilter')
60
+ },
61
+
62
+ response: {
63
+ schema: Joi.object({
64
+ total: Joi.number().integer().example(120).description('How many matching entries').label('TotalNumber'),
65
+ page: Joi.number().integer().example(0).description('Current page (0-based index)').label('PageNumber'),
66
+ pages: Joi.number().integer().example(24).description('Total page count').label('PagesNumber'),
67
+
68
+ gateways: Joi.array()
69
+ .items(
70
+ Joi.object({
71
+ gateway: Joi.string().max(256).required().example('example').description('Gateway ID'),
72
+ name: Joi.string().max(256).example('My Email Gateway').description('Display name for the gateway'),
73
+ deliveries: Joi.number().integer().empty('').example(100).description('Count of email deliveries using this gateway'),
74
+ lastUse: Joi.date().iso().example('2021-02-17T13:43:18.860Z').description('Last delivery time'),
75
+ lastError: lastErrorSchema.allow(null)
76
+ }).label('GatewayResponseItem')
77
+ )
78
+ .label('GatewayEntries')
79
+ }).label('GatewaysFilterResponse'),
80
+ failAction: 'log'
81
+ }
82
+ }
83
+ });
84
+
85
+ server.route({
86
+ method: 'GET',
87
+ path: '/v1/gateway/{gateway}',
88
+
89
+ async handler(request) {
90
+ let gatewayObject = new Gateway({ redis, gateway: request.params.gateway, call, secret: await getSecret() });
91
+ try {
92
+ let gatewayData = await gatewayObject.loadGatewayData();
93
+
94
+ // remove secrets
95
+ if (gatewayData.pass) {
96
+ gatewayData.pass = '******';
97
+ }
98
+
99
+ let result = {};
100
+
101
+ for (let key of ['gateway', 'name', 'host', 'port', 'user', 'pass', 'secure', 'deliveries', 'lastUse', 'lastError']) {
102
+ if (key in gatewayData) {
103
+ result[key] = gatewayData[key];
104
+ }
105
+ }
106
+
107
+ return result;
108
+ } catch (err) {
109
+ handleError(request, err);
110
+ }
111
+ },
112
+ options: {
113
+ description: 'Get gateway info',
114
+ notes: 'Returns stored information about the gateway. Passwords are not included.',
115
+ tags: ['api', 'SMTP Gateway'],
116
+
117
+ auth: {
118
+ strategy: 'api-token',
119
+ mode: 'required'
120
+ },
121
+ cors: CORS_CONFIG,
122
+
123
+ validate: {
124
+ options: {
125
+ stripUnknown: false,
126
+ abortEarly: false,
127
+ convert: true
128
+ },
129
+ failAction,
130
+
131
+ params: Joi.object({
132
+ gateway: Joi.string().max(256).required().example('example').description('Gateway ID')
133
+ })
134
+ },
135
+
136
+ response: {
137
+ schema: Joi.object({
138
+ gateway: Joi.string().max(256).required().example('example').description('Gateway ID'),
139
+
140
+ name: Joi.string().max(256).required().example('My Email Gateway').description('Display name for the gateway'),
141
+ deliveries: Joi.number().integer().empty('').example(100).description('Count of email deliveries using this gateway'),
142
+ lastUse: Joi.date().iso().example('2021-02-17T13:43:18.860Z').description('Last delivery time'),
143
+
144
+ user: Joi.string().empty('').trim().max(1024).description('SMTP authentication username').label('UserName'),
145
+ pass: Joi.string().empty('').max(1024).description('SMTP authentication password').label('Password'),
146
+
147
+ host: Joi.string().hostname().example('smtp.gmail.com').description('Hostname to connect to').label('Hostname'),
148
+ port: Joi.number()
149
+ .integer()
150
+ .min(1)
151
+ .max(64 * 1024)
152
+ .example(465)
153
+ .description('Service port number')
154
+ .label('Port'),
155
+
156
+ secure: Joi.boolean()
157
+ .truthy('Y', 'true', '1', 'on')
158
+ .falsy('N', 'false', 0, '')
159
+ .default(false)
160
+ .example(true)
161
+ .description('Should connection use TLS. Usually true for port 465')
162
+ .label('GatewayTlsOptions'),
163
+
164
+ lastError: lastErrorSchema.allow(null)
165
+ }).label('GatewayResponse'),
166
+ failAction: 'log'
167
+ }
168
+ }
169
+ });
170
+
171
+ server.route({
172
+ method: 'POST',
173
+ path: '/v1/gateway',
174
+
175
+ async handler(request) {
176
+ let gatewayObject = new Gateway({ redis, call, secret: await getSecret() });
177
+
178
+ try {
179
+ let result = await gatewayObject.create(request.payload);
180
+ return result;
181
+ } catch (err) {
182
+ handleError(request, err);
183
+ }
184
+ },
185
+
186
+ options: {
187
+ description: 'Register new gateway',
188
+ notes: 'Registers a new SMP gateway',
189
+ tags: ['api', 'SMTP Gateway'],
190
+
191
+ plugins: {},
192
+
193
+ auth: {
194
+ strategy: 'api-token',
195
+ mode: 'required'
196
+ },
197
+ cors: CORS_CONFIG,
198
+
199
+ validate: {
200
+ options: {
201
+ stripUnknown: false,
202
+ abortEarly: false,
203
+ convert: true
204
+ },
205
+ failAction,
206
+
207
+ payload: Joi.object({
208
+ gateway: Joi.string().empty('').trim().max(256).default(null).example('sendgun').description('Gateway ID').label('Gateway ID').required(),
209
+
210
+ name: Joi.string().empty('').max(256).example('John Smith').description('Account Name').label('Gateway Name').required(),
211
+
212
+ user: Joi.string().empty('').trim().default(null).max(1024).description('SMTP authentication username').label('UserName'),
213
+ pass: Joi.string().empty('').max(1024).default(null).description('SMTP authentication password').label('Password'),
214
+
215
+ host: Joi.string().hostname().example('smtp.gmail.com').description('Hostname to connect to').label('Hostname').required(),
216
+ port: Joi.number()
217
+ .integer()
218
+ .min(1)
219
+ .max(64 * 1024)
220
+ .example(465)
221
+ .description('Service port number')
222
+ .label('Port')
223
+ .required(),
224
+
225
+ secure: Joi.boolean()
226
+ .truthy('Y', 'true', '1', 'on')
227
+ .falsy('N', 'false', 0, '')
228
+ .default(false)
229
+ .example(true)
230
+ .description('Should connection use TLS. Usually true for port 465')
231
+ .label('GatewayCreateTlsOptions')
232
+ }).label('CreateGateway')
233
+ },
234
+
235
+ response: {
236
+ schema: Joi.object({
237
+ gateway: Joi.string().max(256).required().example('example').description('Gateway ID'),
238
+ state: Joi.string()
239
+ .required()
240
+ .valid('existing', 'new')
241
+ .example('new')
242
+ .description('Is the gateway new or updated existing')
243
+ .label('CreateGatewayState')
244
+ }).label('CreateGatewayResponse'),
245
+ failAction: 'log'
246
+ }
247
+ }
248
+ });
249
+
250
+ server.route({
251
+ method: 'PUT',
252
+ path: '/v1/gateway/edit/{gateway}',
253
+
254
+ async handler(request) {
255
+ let gatewayObject = new Gateway({ redis, gateway: request.params.gateway, call, secret: await getSecret() });
256
+
257
+ try {
258
+ return await gatewayObject.update(request.payload);
259
+ } catch (err) {
260
+ handleError(request, err);
261
+ }
262
+ },
263
+ options: {
264
+ description: 'Update gateway info',
265
+ notes: 'Updates gateway information',
266
+ tags: ['api', 'SMTP Gateway'],
267
+
268
+ plugins: {},
269
+
270
+ auth: {
271
+ strategy: 'api-token',
272
+ mode: 'required'
273
+ },
274
+ cors: CORS_CONFIG,
275
+
276
+ validate: {
277
+ options: {
278
+ stripUnknown: false,
279
+ abortEarly: false,
280
+ convert: true
281
+ },
282
+ failAction,
283
+
284
+ params: Joi.object({
285
+ gateway: Joi.string().max(256).required().example('example').description('Gateway ID')
286
+ }),
287
+
288
+ payload: Joi.object({
289
+ name: Joi.string().empty('').max(256).example('John Smith').description('Account Name').label('Gateway Name'),
290
+
291
+ user: Joi.string().empty('').trim().max(1024).allow(null).description('SMTP authentication username').label('UserName'),
292
+ pass: Joi.string().empty('').max(1024).allow(null).description('SMTP authentication password').label('Password'),
293
+
294
+ host: Joi.string().hostname().empty('').example('smtp.gmail.com').description('Hostname to connect to').label('Hostname'),
295
+ port: Joi.number()
296
+ .integer()
297
+ .min(1)
298
+ .empty('')
299
+ .max(64 * 1024)
300
+ .example(465)
301
+ .description('Service port number')
302
+ .label('Port'),
303
+
304
+ secure: Joi.boolean()
305
+ .truthy('Y', 'true', '1', 'on')
306
+ .falsy('N', 'false', 0, '')
307
+ .example(true)
308
+ .description('Should connection use TLS. Usually true for port 465')
309
+ .label('GatewayUpdateTlsOptions')
310
+ }).label('UpdateGateway')
311
+ },
312
+
313
+ response: {
314
+ schema: Joi.object({
315
+ gateway: Joi.string().max(256).required().example('example').description('Gateway ID')
316
+ }).label('UpdateGatewayResponse'),
317
+ failAction: 'log'
318
+ }
319
+ }
320
+ });
321
+
322
+ server.route({
323
+ method: 'DELETE',
324
+ path: '/v1/gateway/{gateway}',
325
+
326
+ async handler(request) {
327
+ let gatewayObject = new Gateway({
328
+ redis,
329
+ gateway: request.params.gateway,
330
+ secret: await getSecret()
331
+ });
332
+
333
+ try {
334
+ return await gatewayObject.delete();
335
+ } catch (err) {
336
+ handleError(request, err);
337
+ }
338
+ },
339
+ options: {
340
+ description: 'Remove SMTP gateway',
341
+ notes: 'Delete SMTP gateway data',
342
+ tags: ['api', 'SMTP Gateway'],
343
+
344
+ plugins: {},
345
+
346
+ auth: {
347
+ strategy: 'api-token',
348
+ mode: 'required'
349
+ },
350
+ cors: CORS_CONFIG,
351
+
352
+ validate: {
353
+ options: {
354
+ stripUnknown: false,
355
+ abortEarly: false,
356
+ convert: true
357
+ },
358
+ failAction,
359
+
360
+ params: Joi.object({
361
+ gateway: Joi.string().max(256).required().example('example').description('Gateway ID')
362
+ }).label('DeleteRequest')
363
+ },
364
+
365
+ response: {
366
+ schema: Joi.object({
367
+ gateway: Joi.string().max(256).required().example('example').description('Gateway ID'),
368
+ deleted: Joi.boolean().truthy('Y', 'true', '1').falsy('N', 'false', 0).default(true).description('Was the gateway deleted')
369
+ }).label('DeleteGatewayResponse'),
370
+ failAction: 'log'
371
+ }
372
+ }
373
+ });
374
+ }
375
+
376
+ module.exports = init;
@@ -0,0 +1,142 @@
1
+ 'use strict';
2
+
3
+ const Joi = require('joi');
4
+ const { failAction } = require('../tools');
5
+ const { handleError } = require('./route-helpers');
6
+ const { licenseSchema } = require('../schemas');
7
+
8
+ async function init(args) {
9
+ const { server, call, CORS_CONFIG } = args;
10
+
11
+ server.route({
12
+ method: 'GET',
13
+ path: '/v1/license',
14
+
15
+ async handler(request) {
16
+ try {
17
+ const licenseInfo = await call({ cmd: 'license', timeout: request.headers['x-ee-timeout'] });
18
+ if (!licenseInfo) {
19
+ let err = new Error('Failed to load license info');
20
+ err.statusCode = 403;
21
+ throw err;
22
+ }
23
+ return licenseInfo;
24
+ } catch (err) {
25
+ handleError(request, err);
26
+ }
27
+ },
28
+ options: {
29
+ description: 'Request license info',
30
+ notes: 'Get active license information',
31
+ tags: ['api', 'License'],
32
+
33
+ auth: {
34
+ strategy: 'api-token',
35
+ mode: 'required'
36
+ },
37
+ cors: CORS_CONFIG,
38
+
39
+ response: {
40
+ schema: licenseSchema.label('LicenseResponse'),
41
+ failAction: 'log'
42
+ }
43
+ }
44
+ });
45
+
46
+ server.route({
47
+ method: 'DELETE',
48
+ path: '/v1/license',
49
+
50
+ async handler(request) {
51
+ try {
52
+ const licenseInfo = await call({ cmd: 'removeLicense', timeout: request.headers['x-ee-timeout'] });
53
+ if (!licenseInfo) {
54
+ let err = new Error('Failed to clear license info');
55
+ err.statusCode = 403;
56
+ throw err;
57
+ }
58
+ return licenseInfo;
59
+ } catch (err) {
60
+ handleError(request, err);
61
+ }
62
+ },
63
+ options: {
64
+ description: 'Remove license',
65
+ notes: 'Remove registered active license',
66
+ tags: ['api', 'License'],
67
+
68
+ plugins: {},
69
+
70
+ auth: {
71
+ strategy: 'api-token',
72
+ mode: 'required'
73
+ },
74
+ cors: CORS_CONFIG,
75
+
76
+ response: {
77
+ schema: Joi.object({
78
+ active: Joi.boolean().example(false),
79
+ details: Joi.boolean().example(false),
80
+ type: Joi.string().example('SSPL-1.0-or-later')
81
+ }).label('EmptyLicenseResponse'),
82
+ failAction: 'log'
83
+ }
84
+ }
85
+ });
86
+
87
+ server.route({
88
+ method: 'POST',
89
+ path: '/v1/license',
90
+
91
+ async handler(request) {
92
+ try {
93
+ const licenseInfo = await call({ cmd: 'updateLicense', license: request.payload.license, timeout: request.headers['x-ee-timeout'] });
94
+ if (!licenseInfo) {
95
+ let err = new Error('Failed to update license. Check license file contents.');
96
+ err.statusCode = 403;
97
+ throw err;
98
+ }
99
+ return licenseInfo;
100
+ } catch (err) {
101
+ handleError(request, err);
102
+ }
103
+ },
104
+ options: {
105
+ description: 'Register a license',
106
+ notes: 'Set up a license for EmailEngine to unlock all features',
107
+ tags: ['api', 'License'],
108
+
109
+ plugins: {},
110
+
111
+ auth: {
112
+ strategy: 'api-token',
113
+ mode: 'required'
114
+ },
115
+ cors: CORS_CONFIG,
116
+
117
+ validate: {
118
+ options: {
119
+ stripUnknown: false,
120
+ abortEarly: false,
121
+ convert: true
122
+ },
123
+ failAction,
124
+
125
+ payload: Joi.object({
126
+ license: Joi.string()
127
+ .max(10 * 1024)
128
+ .required()
129
+ .example('-----BEGIN LICENSE-----\r\n...')
130
+ .description('License file')
131
+ }).label('RegisterLicense')
132
+ },
133
+
134
+ response: {
135
+ schema: licenseSchema.label('LicenseResponse'),
136
+ failAction: 'log'
137
+ }
138
+ }
139
+ });
140
+ }
141
+
142
+ module.exports = init;