emailengine-app 2.68.1 → 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.
- package/.github/workflows/deploy.yml +2 -0
- package/.github/workflows/release.yaml +4 -0
- package/CHANGELOG.md +40 -0
- package/config/default.toml +2 -0
- package/data/google-crawlers.json +7 -1
- package/lib/account.js +62 -25
- package/lib/api-routes/account-routes.js +493 -75
- package/lib/api-routes/blocklist-routes.js +337 -0
- package/lib/api-routes/delivery-test-routes.js +321 -0
- package/lib/api-routes/export-routes.js +1 -12
- package/lib/api-routes/gateway-routes.js +376 -0
- package/lib/api-routes/license-routes.js +142 -0
- package/lib/api-routes/mailbox-routes.js +318 -0
- package/lib/api-routes/message-routes.js +21 -129
- package/lib/api-routes/oauth2-app-routes.js +631 -0
- package/lib/api-routes/outbox-routes.js +173 -0
- package/lib/api-routes/pubsub-routes.js +98 -0
- package/lib/api-routes/route-helpers.js +45 -0
- package/lib/api-routes/settings-routes.js +331 -0
- package/lib/api-routes/stats-routes.js +77 -0
- package/lib/api-routes/submit-routes.js +472 -0
- package/lib/api-routes/template-routes.js +7 -55
- package/lib/api-routes/token-routes.js +297 -0
- package/lib/api-routes/webhook-route-routes.js +152 -0
- package/lib/email-client/gmail-client.js +14 -0
- package/lib/email-client/imap/mailbox.js +34 -11
- package/lib/email-client/imap/subconnection.js +20 -12
- package/lib/email-client/imap/sync-operations.js +130 -2
- package/lib/email-client/imap-client.js +116 -58
- package/lib/email-client/outlook-client.js +85 -13
- package/lib/export.js +60 -19
- package/lib/imapproxy/imap-core/lib/commands/starttls.js +18 -0
- package/lib/imapproxy/imap-core/lib/imap-command.js +6 -1
- package/lib/imapproxy/imap-core/lib/imap-connection.js +106 -23
- package/lib/imapproxy/imap-core/lib/imap-server.js +24 -0
- package/lib/imapproxy/imap-core/lib/imap-stream.js +26 -0
- package/lib/message-port-stream.js +113 -16
- package/lib/reject-worker-calls.js +42 -0
- package/lib/routes-ui.js +37 -8778
- package/lib/schemas.js +26 -1
- package/lib/tools.js +68 -0
- package/lib/ui-routes/account-routes.js +40 -210
- package/lib/ui-routes/admin-config-routes.js +913 -487
- package/lib/ui-routes/admin-entities-routes.js +1 -0
- package/lib/ui-routes/auth-routes.js +1339 -0
- package/lib/ui-routes/dashboard-routes.js +188 -0
- package/lib/ui-routes/document-store-routes.js +800 -0
- package/lib/ui-routes/export-routes.js +217 -0
- package/lib/ui-routes/internals-routes.js +354 -0
- package/lib/ui-routes/network-config-routes.js +759 -0
- package/lib/ui-routes/{oauth-routes.js → oauth-config-routes.js} +371 -91
- package/lib/ui-routes/route-helpers.js +316 -0
- package/lib/ui-routes/smtp-test-routes.js +236 -0
- package/lib/ui-routes/unsubscribe-routes.js +234 -0
- package/lib/webhook-request.js +36 -0
- package/package.json +8 -8
- package/sbom.json +1 -1
- package/server.js +214 -16
- package/static/licenses.html +12 -12
- package/translations/messages.pot +129 -149
- package/views/dashboard.hbs +7 -26
- package/views/internals/index.hbs +15 -0
- package/views/tokens/index.hbs +9 -0
- package/workers/api.js +198 -4401
- package/workers/export.js +87 -54
- package/workers/imap.js +29 -13
- package/workers/submit.js +20 -11
- package/workers/webhooks.js +6 -20
|
@@ -0,0 +1,759 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Admin UI routes for network, SMTP server, IMAP proxy, and browser config pages
|
|
4
|
+
// (/admin/config/{network*,imap-proxy,smtp*,browser}). Extracted verbatim from
|
|
5
|
+
// lib/routes-ui.js. The network page also reloads/deletes the autodetected public
|
|
6
|
+
// interfaces; the SMTP/IMAP-proxy pages manage the built-in MSA and IMAP proxy servers
|
|
7
|
+
// (including on-demand TLS certificate provisioning).
|
|
8
|
+
|
|
9
|
+
const Joi = require('joi');
|
|
10
|
+
const os = require('os');
|
|
11
|
+
const { parentPort } = require('worker_threads');
|
|
12
|
+
|
|
13
|
+
const settings = require('../settings');
|
|
14
|
+
const { redis } = require('../db');
|
|
15
|
+
const { REDIS_PREFIX } = require('../consts');
|
|
16
|
+
const { failAction, getServiceHostname } = require('../tools');
|
|
17
|
+
const { ADDRESS_STRATEGIES, settingsSchema } = require('../schemas');
|
|
18
|
+
const { updatePublicInterfaces } = require('../utils/network');
|
|
19
|
+
const { getServerStatus, cachedTemplates } = require('./route-helpers');
|
|
20
|
+
|
|
21
|
+
const configSmtpSchema = {
|
|
22
|
+
smtpServerEnabled: settingsSchema.smtpServerEnabled.default(false),
|
|
23
|
+
smtpServerPassword: settingsSchema.smtpServerPassword.default(null),
|
|
24
|
+
smtpServerAuthEnabled: settingsSchema.smtpServerAuthEnabled.default(false),
|
|
25
|
+
smtpServerPort: settingsSchema.smtpServerPort,
|
|
26
|
+
smtpServerHost: settingsSchema.smtpServerHost.default('0.0.0.0'),
|
|
27
|
+
smtpServerProxy: settingsSchema.smtpServerProxy.default(false),
|
|
28
|
+
smtpServerTLSEnabled: settingsSchema.smtpServerTLSEnabled.default(false)
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const configImapProxySchema = {
|
|
32
|
+
imapProxyServerEnabled: settingsSchema.imapProxyServerEnabled.default(false),
|
|
33
|
+
imapProxyServerPassword: settingsSchema.imapProxyServerPassword.default(null),
|
|
34
|
+
imapProxyServerPort: settingsSchema.imapProxyServerPort,
|
|
35
|
+
imapProxyServerHost: settingsSchema.imapProxyServerHost.default('0.0.0.0'),
|
|
36
|
+
imapProxyServerProxy: settingsSchema.imapProxyServerProxy.default(false),
|
|
37
|
+
imapProxyServerTLSEnabled: settingsSchema.imapProxyServerTLSEnabled.default(false)
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
async function listPublicInterfaces(selectedAddresses) {
|
|
41
|
+
let existingAddresses = Object.values(os.networkInterfaces())
|
|
42
|
+
.flatMap(entry => entry)
|
|
43
|
+
.map(entry => entry.address);
|
|
44
|
+
|
|
45
|
+
let entries = await redis.hgetall(`${REDIS_PREFIX}interfaces`);
|
|
46
|
+
|
|
47
|
+
let defaultInterfaces = {};
|
|
48
|
+
|
|
49
|
+
let addresses = Object.keys(entries)
|
|
50
|
+
.map(key => {
|
|
51
|
+
if (/^default:/.test(key)) {
|
|
52
|
+
let family = key.split(':').pop();
|
|
53
|
+
defaultInterfaces[family] = entries[key];
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let entry = entries[key];
|
|
58
|
+
try {
|
|
59
|
+
return JSON.parse(entry);
|
|
60
|
+
} catch (err) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
.filter(entry => entry && entry.family === 'IPv4')
|
|
65
|
+
.map(entry => entry);
|
|
66
|
+
|
|
67
|
+
addresses.forEach(address => {
|
|
68
|
+
if (address.localAddress === defaultInterfaces[address.family]) {
|
|
69
|
+
address.defaultInterface = true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (selectedAddresses && selectedAddresses.includes(address.localAddress)) {
|
|
73
|
+
address.checked = true;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (!existingAddresses.includes(address.localAddress)) {
|
|
77
|
+
address.notice = 'This address was not found from the current interface listing and will not be used for connections';
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
return addresses.sort((a, b) => {
|
|
82
|
+
if (a.family !== b.family) {
|
|
83
|
+
return a.family.localeCompare(b.family);
|
|
84
|
+
}
|
|
85
|
+
if (a.defaultInterface) {
|
|
86
|
+
return -1;
|
|
87
|
+
}
|
|
88
|
+
if (b.defaultInterface) {
|
|
89
|
+
return 1;
|
|
90
|
+
}
|
|
91
|
+
return (a.name || a.ip).localeCompare(b.name || b.ip);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function init(args) {
|
|
96
|
+
const { server, call } = args;
|
|
97
|
+
|
|
98
|
+
server.route({
|
|
99
|
+
method: 'GET',
|
|
100
|
+
path: '/admin/config/network',
|
|
101
|
+
async handler(request, h) {
|
|
102
|
+
let smtpStrategy = (await settings.get('smtpStrategy')) || 'default';
|
|
103
|
+
let imapStrategy = (await settings.get('imapStrategy')) || 'default';
|
|
104
|
+
|
|
105
|
+
let proxyEnabled = await settings.get('proxyEnabled');
|
|
106
|
+
let proxyUrl = await settings.get('proxyUrl');
|
|
107
|
+
let smtpEhloName = await settings.get('smtpEhloName');
|
|
108
|
+
let httpProxyEnabled = await settings.get('httpProxyEnabled');
|
|
109
|
+
let httpProxyUrl = await settings.get('httpProxyUrl');
|
|
110
|
+
|
|
111
|
+
let localAddresses = [].concat((await settings.get('localAddresses')) || []);
|
|
112
|
+
|
|
113
|
+
let smtpStrategies = ADDRESS_STRATEGIES.map(entry => Object.assign({ selected: smtpStrategy === entry.key }, entry));
|
|
114
|
+
let imapStrategies = ADDRESS_STRATEGIES.map(entry => Object.assign({ selected: imapStrategy === entry.key }, entry));
|
|
115
|
+
|
|
116
|
+
return h.view(
|
|
117
|
+
'config/network',
|
|
118
|
+
{
|
|
119
|
+
pageTitle: 'Network',
|
|
120
|
+
menuConfig: true,
|
|
121
|
+
menuConfigNetwork: true,
|
|
122
|
+
|
|
123
|
+
smtpStrategies,
|
|
124
|
+
imapStrategies,
|
|
125
|
+
|
|
126
|
+
values: {
|
|
127
|
+
proxyEnabled,
|
|
128
|
+
proxyUrl,
|
|
129
|
+
smtpEhloName,
|
|
130
|
+
httpProxyEnabled,
|
|
131
|
+
httpProxyUrl
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
addresses: await listPublicInterfaces(localAddresses),
|
|
135
|
+
addressListTemplate: cachedTemplates.addressList,
|
|
136
|
+
defaultSmtpEhloName: await getServiceHostname()
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
layout: 'app'
|
|
140
|
+
}
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
server.route({
|
|
146
|
+
method: 'POST',
|
|
147
|
+
path: '/admin/config/network/reload',
|
|
148
|
+
async handler(request) {
|
|
149
|
+
try {
|
|
150
|
+
await updatePublicInterfaces(redis);
|
|
151
|
+
|
|
152
|
+
let localAddresses = [].concat((await settings.get('localAddresses')) || []);
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
success: true,
|
|
156
|
+
addresses: await listPublicInterfaces(localAddresses)
|
|
157
|
+
};
|
|
158
|
+
} catch (err) {
|
|
159
|
+
request.logger.error({ msg: 'Failed loading public IP addresses', err });
|
|
160
|
+
return {
|
|
161
|
+
success: false,
|
|
162
|
+
error: err.message
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
options: {
|
|
167
|
+
validate: {
|
|
168
|
+
options: {
|
|
169
|
+
stripUnknown: true,
|
|
170
|
+
abortEarly: false,
|
|
171
|
+
convert: true
|
|
172
|
+
},
|
|
173
|
+
|
|
174
|
+
failAction
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
server.route({
|
|
180
|
+
method: 'POST',
|
|
181
|
+
path: '/admin/config/network',
|
|
182
|
+
async handler(request, h) {
|
|
183
|
+
try {
|
|
184
|
+
for (let key of [
|
|
185
|
+
'smtpStrategy',
|
|
186
|
+
'imapStrategy',
|
|
187
|
+
'localAddresses',
|
|
188
|
+
'proxyUrl',
|
|
189
|
+
'smtpEhloName',
|
|
190
|
+
'proxyEnabled',
|
|
191
|
+
'httpProxyEnabled',
|
|
192
|
+
'httpProxyUrl'
|
|
193
|
+
]) {
|
|
194
|
+
await settings.set(key, request.payload[key]);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Notify all workers (including this one) about the settings change; each reloads
|
|
198
|
+
// its HTTP proxy agent via the 'settings' message handler.
|
|
199
|
+
if (parentPort) {
|
|
200
|
+
parentPort.postMessage({ cmd: 'settings', data: request.payload });
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
await request.flash({ type: 'info', message: `Configuration updated` });
|
|
204
|
+
|
|
205
|
+
return h.redirect('/admin/config/network');
|
|
206
|
+
} catch (err) {
|
|
207
|
+
await request.flash({ type: 'danger', message: `Couldn't save settings. Try again.` });
|
|
208
|
+
request.logger.error({ msg: 'Failed to update configuration', err });
|
|
209
|
+
|
|
210
|
+
let smtpStrategies = ADDRESS_STRATEGIES.map(entry => Object.assign({ selected: request.payload.smtpStrategy === entry.key }, entry));
|
|
211
|
+
let imapStrategies = ADDRESS_STRATEGIES.map(entry => Object.assign({ selected: request.payload.imapStrategy === entry.key }, entry));
|
|
212
|
+
|
|
213
|
+
return h.view(
|
|
214
|
+
'config/network',
|
|
215
|
+
{
|
|
216
|
+
pageTitle: 'Network',
|
|
217
|
+
menuConfig: true,
|
|
218
|
+
menuConfigNetwork: true,
|
|
219
|
+
smtpStrategies,
|
|
220
|
+
imapStrategies,
|
|
221
|
+
|
|
222
|
+
addresses: await listPublicInterfaces(request.payload.localAddresses),
|
|
223
|
+
addressListTemplate: cachedTemplates.addressList,
|
|
224
|
+
defaultSmtpEhloName: await getServiceHostname()
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
layout: 'app'
|
|
228
|
+
}
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
options: {
|
|
233
|
+
validate: {
|
|
234
|
+
options: {
|
|
235
|
+
stripUnknown: true,
|
|
236
|
+
abortEarly: false,
|
|
237
|
+
convert: true
|
|
238
|
+
},
|
|
239
|
+
|
|
240
|
+
async failAction(request, h, err) {
|
|
241
|
+
let errors = {};
|
|
242
|
+
|
|
243
|
+
if (err.details) {
|
|
244
|
+
err.details.forEach(detail => {
|
|
245
|
+
if (!errors[detail.path]) {
|
|
246
|
+
errors[detail.path] = detail.message;
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
await request.flash({ type: 'danger', message: `Couldn't save settings. Try again.` });
|
|
252
|
+
request.logger.error({ msg: 'Failed to update configuration', err });
|
|
253
|
+
|
|
254
|
+
let smtpStrategies = ADDRESS_STRATEGIES.map(entry => Object.assign({ selected: request.payload.smtpStrategy === entry.key }, entry));
|
|
255
|
+
let imapStrategies = ADDRESS_STRATEGIES.map(entry => Object.assign({ selected: request.payload.imapStrategy === entry.key }, entry));
|
|
256
|
+
|
|
257
|
+
return h
|
|
258
|
+
.view(
|
|
259
|
+
'config/network',
|
|
260
|
+
{
|
|
261
|
+
pageTitle: 'Network',
|
|
262
|
+
menuConfig: true,
|
|
263
|
+
menuConfigNetwork: true,
|
|
264
|
+
smtpStrategies,
|
|
265
|
+
imapStrategies,
|
|
266
|
+
|
|
267
|
+
addresses: await listPublicInterfaces(request.payload.localAddresses),
|
|
268
|
+
addressListTemplate: cachedTemplates.addressList,
|
|
269
|
+
defaultSmtpEhloName: await getServiceHostname(),
|
|
270
|
+
|
|
271
|
+
errors
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
layout: 'app'
|
|
275
|
+
}
|
|
276
|
+
)
|
|
277
|
+
.takeover();
|
|
278
|
+
},
|
|
279
|
+
payload: Joi.object({
|
|
280
|
+
imapStrategy: settingsSchema.imapStrategy.default('default'),
|
|
281
|
+
smtpStrategy: settingsSchema.smtpStrategy.default('default'),
|
|
282
|
+
localAddresses: settingsSchema.localAddresses.default([]),
|
|
283
|
+
|
|
284
|
+
proxyUrl: settingsSchema.proxyUrl,
|
|
285
|
+
smtpEhloName: settingsSchema.smtpEhloName,
|
|
286
|
+
proxyEnabled: settingsSchema.proxyEnabled,
|
|
287
|
+
|
|
288
|
+
httpProxyEnabled: settingsSchema.httpProxyEnabled,
|
|
289
|
+
httpProxyUrl: settingsSchema.httpProxyUrl
|
|
290
|
+
})
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
server.route({
|
|
296
|
+
method: 'POST',
|
|
297
|
+
path: '/admin/config/network/delete',
|
|
298
|
+
async handler(request, h) {
|
|
299
|
+
try {
|
|
300
|
+
let localAddress = request.payload.localAddress;
|
|
301
|
+
let localAddresses = [].concat((await settings.get('localAddresses')) || []);
|
|
302
|
+
if (localAddresses.includes(localAddress)) {
|
|
303
|
+
let list = new Set(localAddresses);
|
|
304
|
+
list.delete(localAddress);
|
|
305
|
+
localAddresses = Array.from(list);
|
|
306
|
+
await settings.set('localAddresses', localAddresses);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
await redis.hdel(`${REDIS_PREFIX}interfaces`, localAddress);
|
|
310
|
+
|
|
311
|
+
await request.flash({ type: 'info', message: `Address removed` });
|
|
312
|
+
return h.redirect('/admin/config/network');
|
|
313
|
+
} catch (err) {
|
|
314
|
+
await request.flash({ type: 'danger', message: `Couldn't delete address. Try again.` });
|
|
315
|
+
request.logger.error({ msg: 'Failed to delete address', err, localAddress: request.payload.localAddress, remoteAddress: request.app.ip });
|
|
316
|
+
return h.redirect('/admin/config/network');
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
options: {
|
|
320
|
+
validate: {
|
|
321
|
+
options: {
|
|
322
|
+
stripUnknown: true,
|
|
323
|
+
abortEarly: false,
|
|
324
|
+
convert: true
|
|
325
|
+
},
|
|
326
|
+
|
|
327
|
+
async failAction(request, h, err) {
|
|
328
|
+
await request.flash({ type: 'danger', message: `Couldn't delete address. Try again.` });
|
|
329
|
+
request.logger.error({ msg: 'Failed to delete address', err });
|
|
330
|
+
|
|
331
|
+
return h.redirect('/admin/config/network').takeover();
|
|
332
|
+
},
|
|
333
|
+
|
|
334
|
+
payload: Joi.object({
|
|
335
|
+
localAddress: Joi.string().ip({
|
|
336
|
+
version: ['ipv4', 'ipv6'],
|
|
337
|
+
cidr: 'forbidden'
|
|
338
|
+
})
|
|
339
|
+
})
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
server.route({
|
|
345
|
+
method: 'GET',
|
|
346
|
+
path: '/admin/config/imap-proxy',
|
|
347
|
+
async handler(request, h) {
|
|
348
|
+
let values = {
|
|
349
|
+
imapProxyServerEnabled: await settings.get('imapProxyServerEnabled'),
|
|
350
|
+
imapProxyServerPassword: await settings.get('imapProxyServerPassword'),
|
|
351
|
+
imapProxyServerPort: await settings.get('imapProxyServerPort'),
|
|
352
|
+
imapProxyServerHost: await settings.get('imapProxyServerHost'),
|
|
353
|
+
imapProxyServerProxy: await settings.get('imapProxyServerProxy'),
|
|
354
|
+
imapProxyServerTLSEnabled: await settings.get('imapProxyServerTLSEnabled')
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
let availableAddresses = new Set(
|
|
358
|
+
Object.values(os.networkInterfaces())
|
|
359
|
+
.flatMap(entry => entry)
|
|
360
|
+
.map(entry => entry.address)
|
|
361
|
+
);
|
|
362
|
+
availableAddresses.add('0.0.0.0');
|
|
363
|
+
|
|
364
|
+
let hostname = await h.serviceDomain();
|
|
365
|
+
let certificateData = await h.getCertificate();
|
|
366
|
+
|
|
367
|
+
return h.view(
|
|
368
|
+
'config/imap-proxy',
|
|
369
|
+
{
|
|
370
|
+
pageTitle: 'IMAP Proxy',
|
|
371
|
+
menuConfig: true,
|
|
372
|
+
menuConfigImapProxy: true,
|
|
373
|
+
|
|
374
|
+
values,
|
|
375
|
+
|
|
376
|
+
serverState: await getServerStatus('imapProxy'),
|
|
377
|
+
availableAddresses: Array.from(availableAddresses).join(','),
|
|
378
|
+
|
|
379
|
+
serviceDomain: hostname,
|
|
380
|
+
serviceUrl: await settings.get('serviceUrl'),
|
|
381
|
+
certificateData
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
layout: 'app'
|
|
385
|
+
}
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
server.route({
|
|
391
|
+
method: 'POST',
|
|
392
|
+
path: '/admin/config/imap-proxy',
|
|
393
|
+
async handler(request, h) {
|
|
394
|
+
try {
|
|
395
|
+
let existingSetup = {};
|
|
396
|
+
let hasServerChanges = false;
|
|
397
|
+
|
|
398
|
+
const systemKeys = ['imapProxyServerEnabled', 'imapProxyServerPort', 'imapProxyServerHost', 'imapProxyServerTLSEnabled'];
|
|
399
|
+
for (let key of systemKeys) {
|
|
400
|
+
existingSetup[key] = await settings.get(key);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
for (let key of Object.keys(request.payload)) {
|
|
404
|
+
await settings.set(key, request.payload[key]);
|
|
405
|
+
if (systemKeys.includes(key) && request.payload[key] !== existingSetup[key]) {
|
|
406
|
+
hasServerChanges = true;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
await request.flash({ type: 'info', message: `Configuration updated` });
|
|
411
|
+
|
|
412
|
+
if (hasServerChanges) {
|
|
413
|
+
// request server restart
|
|
414
|
+
try {
|
|
415
|
+
await call({ cmd: 'imapProxyReload' });
|
|
416
|
+
} catch (err) {
|
|
417
|
+
request.logger.error({ msg: 'Reload request failed', action: 'request_reload_imap_proxy', err });
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return h.redirect('/admin/config/imap-proxy');
|
|
422
|
+
} catch (err) {
|
|
423
|
+
await request.flash({ type: 'danger', message: `Couldn't save settings. Try again.` });
|
|
424
|
+
request.logger.error({ msg: 'Failed to update configuration', err });
|
|
425
|
+
|
|
426
|
+
let availableAddresses = new Set(
|
|
427
|
+
Object.values(os.networkInterfaces())
|
|
428
|
+
.flatMap(entry => entry)
|
|
429
|
+
.map(entry => entry.address)
|
|
430
|
+
);
|
|
431
|
+
availableAddresses.add('0.0.0.0');
|
|
432
|
+
|
|
433
|
+
let hostname = await h.serviceDomain();
|
|
434
|
+
let certificateData = await h.getCertificate();
|
|
435
|
+
|
|
436
|
+
return h.view(
|
|
437
|
+
'config/imap-proxy',
|
|
438
|
+
{
|
|
439
|
+
pageTitle: 'IMAP Proxy',
|
|
440
|
+
menuConfig: true,
|
|
441
|
+
menuConfigImapProxy: true,
|
|
442
|
+
|
|
443
|
+
serverState: await getServerStatus('imap'),
|
|
444
|
+
availableAddresses: Array.from(availableAddresses).join(','),
|
|
445
|
+
|
|
446
|
+
serviceDomain: hostname,
|
|
447
|
+
serviceUrl: await settings.get('serviceUrl'),
|
|
448
|
+
certificateData
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
layout: 'app'
|
|
452
|
+
}
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
},
|
|
456
|
+
options: {
|
|
457
|
+
validate: {
|
|
458
|
+
options: {
|
|
459
|
+
stripUnknown: true,
|
|
460
|
+
abortEarly: false,
|
|
461
|
+
convert: true
|
|
462
|
+
},
|
|
463
|
+
|
|
464
|
+
async failAction(request, h, err) {
|
|
465
|
+
let errors = {};
|
|
466
|
+
|
|
467
|
+
if (err.details) {
|
|
468
|
+
err.details.forEach(detail => {
|
|
469
|
+
if (!errors[detail.path]) {
|
|
470
|
+
errors[detail.path] = detail.message;
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
await request.flash({ type: 'danger', message: `Couldn't save settings. Try again.` });
|
|
476
|
+
request.logger.error({ msg: 'Failed to update configuration', err });
|
|
477
|
+
|
|
478
|
+
let availableAddresses = new Set(
|
|
479
|
+
Object.values(os.networkInterfaces())
|
|
480
|
+
.flatMap(entry => entry)
|
|
481
|
+
.map(entry => entry.address)
|
|
482
|
+
);
|
|
483
|
+
availableAddresses.add('0.0.0.0');
|
|
484
|
+
|
|
485
|
+
let hostname = await h.serviceDomain();
|
|
486
|
+
let certificateData = await h.getCertificate();
|
|
487
|
+
|
|
488
|
+
return h
|
|
489
|
+
.view(
|
|
490
|
+
'config/imap-proxy',
|
|
491
|
+
{
|
|
492
|
+
pageTitle: 'IMAP Proxy',
|
|
493
|
+
menuConfig: true,
|
|
494
|
+
menuConfigImapProxy: true,
|
|
495
|
+
|
|
496
|
+
serverState: await getServerStatus('imapProxy'),
|
|
497
|
+
availableAddresses: Array.from(availableAddresses).join(','),
|
|
498
|
+
|
|
499
|
+
serviceDomain: hostname,
|
|
500
|
+
serviceUrl: await settings.get('serviceUrl'),
|
|
501
|
+
certificateData,
|
|
502
|
+
|
|
503
|
+
errors
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
layout: 'app'
|
|
507
|
+
}
|
|
508
|
+
)
|
|
509
|
+
.takeover();
|
|
510
|
+
},
|
|
511
|
+
|
|
512
|
+
payload: Joi.object(configImapProxySchema)
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
server.route({
|
|
518
|
+
method: 'GET',
|
|
519
|
+
path: '/admin/config/smtp',
|
|
520
|
+
async handler(request, h) {
|
|
521
|
+
let values = {
|
|
522
|
+
smtpServerEnabled: await settings.get('smtpServerEnabled'),
|
|
523
|
+
smtpServerPassword: await settings.get('smtpServerPassword'),
|
|
524
|
+
smtpServerAuthEnabled: await settings.get('smtpServerAuthEnabled'),
|
|
525
|
+
smtpServerPort: await settings.get('smtpServerPort'),
|
|
526
|
+
smtpServerHost: await settings.get('smtpServerHost'),
|
|
527
|
+
smtpServerProxy: await settings.get('smtpServerProxy'),
|
|
528
|
+
smtpServerTLSEnabled: await settings.get('smtpServerTLSEnabled')
|
|
529
|
+
};
|
|
530
|
+
|
|
531
|
+
let availableAddresses = new Set(
|
|
532
|
+
Object.values(os.networkInterfaces())
|
|
533
|
+
.flatMap(entry => entry)
|
|
534
|
+
.map(entry => entry.address)
|
|
535
|
+
);
|
|
536
|
+
availableAddresses.add('0.0.0.0');
|
|
537
|
+
|
|
538
|
+
let hostname = await h.serviceDomain();
|
|
539
|
+
let certificateData = await h.getCertificate();
|
|
540
|
+
|
|
541
|
+
return h.view(
|
|
542
|
+
'config/smtp',
|
|
543
|
+
{
|
|
544
|
+
pageTitle: 'SMTP Interface',
|
|
545
|
+
menuConfig: true,
|
|
546
|
+
menuConfigSmtp: true,
|
|
547
|
+
|
|
548
|
+
values,
|
|
549
|
+
|
|
550
|
+
serverState: await getServerStatus('smtp'),
|
|
551
|
+
availableAddresses: Array.from(availableAddresses).join(','),
|
|
552
|
+
|
|
553
|
+
serviceDomain: hostname,
|
|
554
|
+
serviceUrl: await settings.get('serviceUrl'),
|
|
555
|
+
certificateData
|
|
556
|
+
},
|
|
557
|
+
{
|
|
558
|
+
layout: 'app'
|
|
559
|
+
}
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
server.route({
|
|
565
|
+
method: 'POST',
|
|
566
|
+
path: '/admin/config/smtp',
|
|
567
|
+
async handler(request, h) {
|
|
568
|
+
try {
|
|
569
|
+
let existingSetup = {};
|
|
570
|
+
let hasServerChanges = false;
|
|
571
|
+
|
|
572
|
+
const systemKeys = ['smtpServerEnabled', 'smtpServerPort', 'smtpServerHost', 'smtpServerTLSEnabled'];
|
|
573
|
+
for (let key of systemKeys) {
|
|
574
|
+
existingSetup[key] = await settings.get(key);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
for (let key of Object.keys(request.payload)) {
|
|
578
|
+
await settings.set(key, request.payload[key]);
|
|
579
|
+
if (systemKeys.includes(key) && request.payload[key] !== existingSetup[key]) {
|
|
580
|
+
hasServerChanges = true;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
await request.flash({ type: 'info', message: `Configuration updated` });
|
|
585
|
+
|
|
586
|
+
if (hasServerChanges) {
|
|
587
|
+
// request server restart
|
|
588
|
+
try {
|
|
589
|
+
await call({ cmd: 'smtpReload' });
|
|
590
|
+
} catch (err) {
|
|
591
|
+
request.logger.error({ msg: 'Reload request failed', action: 'request_reload_smtp', err });
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
return h.redirect('/admin/config/smtp');
|
|
596
|
+
} catch (err) {
|
|
597
|
+
await request.flash({ type: 'danger', message: `Couldn't save settings. Try again.` });
|
|
598
|
+
request.logger.error({ msg: 'Failed to update configuration', err });
|
|
599
|
+
|
|
600
|
+
let availableAddresses = new Set(
|
|
601
|
+
Object.values(os.networkInterfaces())
|
|
602
|
+
.flatMap(entry => entry)
|
|
603
|
+
.map(entry => entry.address)
|
|
604
|
+
);
|
|
605
|
+
availableAddresses.add('0.0.0.0');
|
|
606
|
+
|
|
607
|
+
let hostname = await h.serviceDomain();
|
|
608
|
+
let certificateData = await h.getCertificate();
|
|
609
|
+
|
|
610
|
+
return h.view(
|
|
611
|
+
'config/smtp',
|
|
612
|
+
{
|
|
613
|
+
pageTitle: 'SMTP Interface',
|
|
614
|
+
menuConfig: true,
|
|
615
|
+
menuConfigSmtp: true,
|
|
616
|
+
|
|
617
|
+
serverState: await getServerStatus('smtp'),
|
|
618
|
+
availableAddresses: Array.from(availableAddresses).join(','),
|
|
619
|
+
|
|
620
|
+
serviceDomain: hostname,
|
|
621
|
+
serviceUrl: await settings.get('serviceUrl'),
|
|
622
|
+
certificateData
|
|
623
|
+
},
|
|
624
|
+
{
|
|
625
|
+
layout: 'app'
|
|
626
|
+
}
|
|
627
|
+
);
|
|
628
|
+
}
|
|
629
|
+
},
|
|
630
|
+
options: {
|
|
631
|
+
validate: {
|
|
632
|
+
options: {
|
|
633
|
+
stripUnknown: true,
|
|
634
|
+
abortEarly: false,
|
|
635
|
+
convert: true
|
|
636
|
+
},
|
|
637
|
+
|
|
638
|
+
async failAction(request, h, err) {
|
|
639
|
+
let errors = {};
|
|
640
|
+
|
|
641
|
+
if (err.details) {
|
|
642
|
+
err.details.forEach(detail => {
|
|
643
|
+
if (!errors[detail.path]) {
|
|
644
|
+
errors[detail.path] = detail.message;
|
|
645
|
+
}
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
await request.flash({ type: 'danger', message: `Couldn't save settings. Try again.` });
|
|
650
|
+
request.logger.error({ msg: 'Failed to update configuration', err });
|
|
651
|
+
|
|
652
|
+
let availableAddresses = new Set(
|
|
653
|
+
Object.values(os.networkInterfaces())
|
|
654
|
+
.flatMap(entry => entry)
|
|
655
|
+
.map(entry => entry.address)
|
|
656
|
+
);
|
|
657
|
+
availableAddresses.add('0.0.0.0');
|
|
658
|
+
|
|
659
|
+
let hostname = await h.serviceDomain();
|
|
660
|
+
let certificateData = await h.getCertificate();
|
|
661
|
+
|
|
662
|
+
return h
|
|
663
|
+
.view(
|
|
664
|
+
'config/smtp',
|
|
665
|
+
{
|
|
666
|
+
pageTitle: 'SMTP Interface',
|
|
667
|
+
menuConfig: true,
|
|
668
|
+
menuConfigSmtp: true,
|
|
669
|
+
|
|
670
|
+
serverState: await getServerStatus('smtp'),
|
|
671
|
+
availableAddresses: Array.from(availableAddresses).join(','),
|
|
672
|
+
|
|
673
|
+
serviceDomain: hostname,
|
|
674
|
+
serviceUrl: await settings.get('serviceUrl'),
|
|
675
|
+
certificateData,
|
|
676
|
+
|
|
677
|
+
errors
|
|
678
|
+
},
|
|
679
|
+
{
|
|
680
|
+
layout: 'app'
|
|
681
|
+
}
|
|
682
|
+
)
|
|
683
|
+
.takeover();
|
|
684
|
+
},
|
|
685
|
+
|
|
686
|
+
payload: Joi.object(configSmtpSchema)
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
server.route({
|
|
692
|
+
method: 'POST',
|
|
693
|
+
path: '/admin/config/smtp/certificate',
|
|
694
|
+
async handler(request, h) {
|
|
695
|
+
try {
|
|
696
|
+
let certificateData = await h.getCertificate(true);
|
|
697
|
+
if (!certificateData) {
|
|
698
|
+
throw new Error(`Failed to provision a ceritifcate`);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
return {
|
|
702
|
+
success: true,
|
|
703
|
+
domain: certificateData.domain,
|
|
704
|
+
fingerprint: certificateData.fingerprint,
|
|
705
|
+
altNames: certificateData.altNames,
|
|
706
|
+
validTo: certificateData.validTo && certificateData.validTo.toISOString(),
|
|
707
|
+
label: certificateData.label
|
|
708
|
+
};
|
|
709
|
+
} catch (err) {
|
|
710
|
+
request.logger.error({ msg: 'Failed to request syncing', err });
|
|
711
|
+
return {
|
|
712
|
+
success: false,
|
|
713
|
+
error: err.message
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
server.route({
|
|
720
|
+
method: 'POST',
|
|
721
|
+
path: '/admin/config/browser',
|
|
722
|
+
async handler(request) {
|
|
723
|
+
for (let key of ['serviceUrl', 'language', 'timezone']) {
|
|
724
|
+
if (request.payload[key]) {
|
|
725
|
+
let existingValue = await settings.get(key);
|
|
726
|
+
if (existingValue === null) {
|
|
727
|
+
await settings.set(key, request.payload[key]);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
return { success: true };
|
|
732
|
+
},
|
|
733
|
+
options: {
|
|
734
|
+
validate: {
|
|
735
|
+
options: {
|
|
736
|
+
stripUnknown: true,
|
|
737
|
+
abortEarly: false,
|
|
738
|
+
convert: true
|
|
739
|
+
},
|
|
740
|
+
|
|
741
|
+
failAction,
|
|
742
|
+
|
|
743
|
+
payload: Joi.object({
|
|
744
|
+
serviceUrl: settingsSchema.serviceUrl.empty('').allow(false),
|
|
745
|
+
|
|
746
|
+
language: Joi.string()
|
|
747
|
+
.empty('')
|
|
748
|
+
.lowercase()
|
|
749
|
+
.regex(/^[a-z0-9]{1,5}([-_][a-z0-9]{1,15})?$/)
|
|
750
|
+
.allow(false),
|
|
751
|
+
|
|
752
|
+
timezone: Joi.string().empty('').allow(false).max(255)
|
|
753
|
+
})
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
module.exports = init;
|