emailengine-app 2.61.1 → 2.61.2
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 +9 -0
- package/data/google-crawlers.json +1 -1
- package/lib/account/account-state.js +248 -0
- package/lib/account.js +17 -178
- package/lib/api-routes/account-routes.js +1006 -0
- package/lib/api-routes/message-routes.js +1377 -0
- package/lib/consts.js +12 -2
- package/lib/email-client/base-client.js +282 -771
- package/lib/email-client/gmail/gmail-api.js +243 -0
- package/lib/email-client/gmail-client.js +145 -53
- package/lib/email-client/imap/mailbox.js +24 -698
- package/lib/email-client/imap/sync-operations.js +812 -0
- package/lib/email-client/imap-client.js +1 -1
- package/lib/email-client/message-builder.js +566 -0
- package/lib/email-client/notification-handler.js +314 -0
- package/lib/email-client/outlook/graph-api.js +326 -0
- package/lib/email-client/outlook-client.js +159 -113
- package/lib/email-client/smtp-pool-manager.js +196 -0
- package/lib/imapproxy/imap-server.js +3 -12
- package/lib/oauth/gmail.js +4 -4
- package/lib/oauth/mail-ru.js +30 -5
- package/lib/oauth/outlook.js +57 -3
- package/lib/oauth/pubsub/google.js +30 -11
- package/lib/oauth/scope-checker.js +202 -0
- package/lib/oauth2-apps.js +8 -4
- package/lib/redis-operations.js +484 -0
- package/lib/routes-ui.js +283 -2582
- package/lib/tools.js +4 -196
- package/lib/ui-routes/account-routes.js +1931 -0
- package/lib/ui-routes/admin-config-routes.js +1233 -0
- package/lib/ui-routes/admin-entities-routes.js +2367 -0
- package/lib/ui-routes/oauth-routes.js +992 -0
- package/lib/utils/network.js +237 -0
- package/package.json +9 -9
- package/sbom.json +1 -1
- package/static/js/app.js +5 -5
- package/static/licenses.html +78 -18
- package/translations/de.mo +0 -0
- package/translations/de.po +85 -82
- package/translations/en.mo +0 -0
- package/translations/en.po +63 -71
- package/translations/et.mo +0 -0
- package/translations/et.po +84 -82
- package/translations/fr.mo +0 -0
- package/translations/fr.po +85 -82
- package/translations/ja.mo +0 -0
- package/translations/ja.po +84 -82
- package/translations/messages.pot +74 -87
- package/translations/nl.mo +0 -0
- package/translations/nl.po +86 -82
- package/translations/pl.mo +0 -0
- package/translations/pl.po +84 -82
- package/views/account/security.hbs +4 -4
- package/views/accounts/account.hbs +13 -13
- package/views/accounts/register/imap-server.hbs +12 -12
- package/views/config/document-store/pre-processing/index.hbs +4 -2
- package/views/config/oauth/app.hbs +6 -7
- package/views/config/oauth/index.hbs +2 -2
- package/views/config/service.hbs +3 -4
- package/views/dashboard.hbs +5 -7
- package/views/error.hbs +22 -7
- package/views/gateways/gateway.hbs +2 -2
- package/views/partials/add_account_modal.hbs +7 -10
- package/views/partials/document_store_header.hbs +1 -1
- package/views/partials/editor_scope_info.hbs +0 -1
- package/views/partials/oauth_config_header.hbs +1 -1
- package/views/partials/side_menu.hbs +3 -3
- package/views/partials/webhook_form.hbs +2 -2
- package/views/templates/index.hbs +1 -1
- package/views/templates/template.hbs +8 -8
- package/views/tokens/index.hbs +6 -6
- package/views/tokens/new.hbs +1 -1
- package/views/webhooks/index.hbs +4 -4
- package/views/webhooks/webhook.hbs +7 -7
- package/workers/api.js +148 -2436
- package/workers/smtp.js +2 -1
- package/lib/imapproxy/imap-core/test/client.js +0 -46
- package/lib/imapproxy/imap-core/test/fixtures/append.eml +0 -1196
- package/lib/imapproxy/imap-core/test/fixtures/chunks.js +0 -44
- package/lib/imapproxy/imap-core/test/fixtures/fix1.eml +0 -6
- package/lib/imapproxy/imap-core/test/fixtures/fix2.eml +0 -599
- package/lib/imapproxy/imap-core/test/fixtures/fix3.eml +0 -32
- package/lib/imapproxy/imap-core/test/fixtures/fix4.eml +0 -6
- package/lib/imapproxy/imap-core/test/fixtures/mimetorture.eml +0 -599
- package/lib/imapproxy/imap-core/test/fixtures/mimetorture.js +0 -2740
- package/lib/imapproxy/imap-core/test/fixtures/mimetorture.json +0 -1411
- package/lib/imapproxy/imap-core/test/fixtures/mimetree.js +0 -85
- package/lib/imapproxy/imap-core/test/fixtures/nodemailer.eml +0 -582
- package/lib/imapproxy/imap-core/test/fixtures/ryan_finnie_mime_torture.eml +0 -599
- package/lib/imapproxy/imap-core/test/fixtures/simple.eml +0 -42
- package/lib/imapproxy/imap-core/test/fixtures/simple.json +0 -164
- package/lib/imapproxy/imap-core/test/imap-compile-stream-test.js +0 -671
- package/lib/imapproxy/imap-core/test/imap-compiler-test.js +0 -272
- package/lib/imapproxy/imap-core/test/imap-indexer-test.js +0 -236
- package/lib/imapproxy/imap-core/test/imap-parser-test.js +0 -922
- package/lib/imapproxy/imap-core/test/memory-notifier.js +0 -129
- package/lib/imapproxy/imap-core/test/prepare.sh +0 -74
- package/lib/imapproxy/imap-core/test/protocol-test.js +0 -1756
- package/lib/imapproxy/imap-core/test/search-test.js +0 -1356
- package/lib/imapproxy/imap-core/test/test-client.js +0 -152
- package/lib/imapproxy/imap-core/test/test-server.js +0 -623
- package/lib/imapproxy/imap-core/test/tools-test.js +0 -22
- package/test/api-test.js +0 -899
- package/test/autoreply-test.js +0 -327
- package/test/bounce-test.js +0 -151
- package/test/complaint-test.js +0 -256
- package/test/fixtures/autoreply/LICENSE +0 -27
- package/test/fixtures/autoreply/rfc3834-01.eml +0 -23
- package/test/fixtures/autoreply/rfc3834-02.eml +0 -24
- package/test/fixtures/autoreply/rfc3834-03.eml +0 -26
- package/test/fixtures/autoreply/rfc3834-04.eml +0 -48
- package/test/fixtures/autoreply/rfc3834-05.eml +0 -19
- package/test/fixtures/autoreply/rfc3834-06.eml +0 -59
- package/test/fixtures/bounces/163.eml +0 -2521
- package/test/fixtures/bounces/fastmail.eml +0 -242
- package/test/fixtures/bounces/gmail.eml +0 -252
- package/test/fixtures/bounces/hotmail.eml +0 -655
- package/test/fixtures/bounces/mailru.eml +0 -121
- package/test/fixtures/bounces/outlook.eml +0 -1107
- package/test/fixtures/bounces/postfix.eml +0 -101
- package/test/fixtures/bounces/rambler.eml +0 -116
- package/test/fixtures/bounces/workmail.eml +0 -142
- package/test/fixtures/bounces/yahoo.eml +0 -139
- package/test/fixtures/bounces/zoho.eml +0 -83
- package/test/fixtures/bounces/zonemta.eml +0 -100
- package/test/fixtures/complaints/LICENSE +0 -27
- package/test/fixtures/complaints/amazonses.eml +0 -72
- package/test/fixtures/complaints/dmarc.eml +0 -59
- package/test/fixtures/complaints/hotmail.eml +0 -49
- package/test/fixtures/complaints/optout.eml +0 -40
- package/test/fixtures/complaints/standard-arf.eml +0 -68
- package/test/fixtures/complaints/yahoo.eml +0 -68
- package/test/oauth2-apps-test.js +0 -301
- package/test/sendonly-test.js +0 -160
- package/test/test-config.js +0 -34
- package/test/webhooks-server.js +0 -39
package/lib/routes-ui.js
CHANGED
|
@@ -23,10 +23,10 @@ const {
|
|
|
23
23
|
formatByteSize,
|
|
24
24
|
getDuration,
|
|
25
25
|
parseSignedFormData,
|
|
26
|
-
updatePublicInterfaces,
|
|
27
26
|
hasEnvValue,
|
|
28
27
|
retryAgent
|
|
29
28
|
} = require('./tools');
|
|
29
|
+
const { updatePublicInterfaces } = require('./utils/network');
|
|
30
30
|
const packageData = require('../package.json');
|
|
31
31
|
const he = require('he');
|
|
32
32
|
const crypto = require('crypto');
|
|
@@ -42,7 +42,6 @@ const os = require('os');
|
|
|
42
42
|
const {
|
|
43
43
|
ADDRESS_STRATEGIES,
|
|
44
44
|
settingsSchema,
|
|
45
|
-
templateSchemas,
|
|
46
45
|
oauthCreateSchema,
|
|
47
46
|
accountIdSchema,
|
|
48
47
|
defaultAccountTypeSchema,
|
|
@@ -53,10 +52,7 @@ const fs = require('fs');
|
|
|
53
52
|
const pathlib = require('path');
|
|
54
53
|
const timezonesList = require('timezones-list').default;
|
|
55
54
|
const { Client: ElasticSearch } = require('@elastic/elasticsearch');
|
|
56
|
-
const { templates } = require('./templates');
|
|
57
|
-
const { webhooks } = require('./webhooks');
|
|
58
55
|
const { llmPreProcess } = require('./llm-pre-process');
|
|
59
|
-
const wellKnownServices = require('nodemailer/lib/well-known/services.json');
|
|
60
56
|
const { locales } = require('./translations');
|
|
61
57
|
const capa = require('./capa');
|
|
62
58
|
const exampleWebhookPayloads = require('./payload-examples-webhooks.json');
|
|
@@ -70,6 +66,8 @@ const base32 = require('base32.js');
|
|
|
70
66
|
const { simpleParser } = require('mailparser');
|
|
71
67
|
const libmime = require('libmime');
|
|
72
68
|
|
|
69
|
+
const adminEntitiesRoutes = require('./ui-routes/admin-entities-routes');
|
|
70
|
+
|
|
73
71
|
const {
|
|
74
72
|
DEFAULT_MAX_LOG_LINES,
|
|
75
73
|
PDKDF2_ITERATIONS,
|
|
@@ -356,17 +354,6 @@ const OKTA_OAUTH2_CLIENT_ID = readEnvValue('OKTA_OAUTH2_CLIENT_ID');
|
|
|
356
354
|
const OKTA_OAUTH2_CLIENT_SECRET = readEnvValue('OKTA_OAUTH2_CLIENT_SECRET');
|
|
357
355
|
const USE_OKTA_AUTH = !!(OKTA_OAUTH2_ISSUER && OKTA_OAUTH2_CLIENT_ID && OKTA_OAUTH2_CLIENT_SECRET);
|
|
358
356
|
|
|
359
|
-
const CODE_FORMATS = [
|
|
360
|
-
{
|
|
361
|
-
format: 'html',
|
|
362
|
-
name: 'HTML'
|
|
363
|
-
},
|
|
364
|
-
{
|
|
365
|
-
format: 'markdown',
|
|
366
|
-
name: 'Markdown'
|
|
367
|
-
}
|
|
368
|
-
];
|
|
369
|
-
|
|
370
357
|
const oauthUpdateSchema = {
|
|
371
358
|
app: Joi.string().empty('').max(255).example('gmail').label('Provider').required(),
|
|
372
359
|
|
|
@@ -870,6 +857,9 @@ async function getServerStatus(type) {
|
|
|
870
857
|
}
|
|
871
858
|
|
|
872
859
|
function applyRoutes(server, call) {
|
|
860
|
+
// Initialize admin entity routes (webhooks, templates, gateways, tokens)
|
|
861
|
+
adminEntitiesRoutes({ server, call });
|
|
862
|
+
|
|
873
863
|
const getDefaultPrompt = async () =>
|
|
874
864
|
await call({
|
|
875
865
|
cmd: 'openAiDefaultPrompt'
|
|
@@ -1181,7 +1171,7 @@ function applyRoutes(server, call) {
|
|
|
1181
1171
|
|
|
1182
1172
|
return h.redirect('/admin/config/webhooks');
|
|
1183
1173
|
} catch (err) {
|
|
1184
|
-
await request.flash({ type: 'danger', message: `
|
|
1174
|
+
await request.flash({ type: 'danger', message: `Couldn't save settings. Try again.` });
|
|
1185
1175
|
request.logger.error({ msg: 'Failed to update configuration', err });
|
|
1186
1176
|
|
|
1187
1177
|
return h.view(
|
|
@@ -1223,7 +1213,7 @@ function applyRoutes(server, call) {
|
|
|
1223
1213
|
});
|
|
1224
1214
|
}
|
|
1225
1215
|
|
|
1226
|
-
await request.flash({ type: 'danger', message: `
|
|
1216
|
+
await request.flash({ type: 'danger', message: `Couldn't save settings. Try again.` });
|
|
1227
1217
|
request.logger.error({ msg: 'Failed to update configuration', err });
|
|
1228
1218
|
|
|
1229
1219
|
return h
|
|
@@ -1375,7 +1365,7 @@ function applyRoutes(server, call) {
|
|
|
1375
1365
|
|
|
1376
1366
|
return h.redirect('/admin/config/service');
|
|
1377
1367
|
} catch (err) {
|
|
1378
|
-
await request.flash({ type: 'danger', message: `
|
|
1368
|
+
await request.flash({ type: 'danger', message: `Couldn't save settings. Try again.` });
|
|
1379
1369
|
request.logger.error({ msg: 'Failed to update configuration', err });
|
|
1380
1370
|
|
|
1381
1371
|
return h.view(
|
|
@@ -1427,7 +1417,7 @@ function applyRoutes(server, call) {
|
|
|
1427
1417
|
});
|
|
1428
1418
|
}
|
|
1429
1419
|
|
|
1430
|
-
await request.flash({ type: 'danger', message: `
|
|
1420
|
+
await request.flash({ type: 'danger', message: `Couldn't save settings. Try again.` });
|
|
1431
1421
|
request.logger.error({ msg: 'Failed to update configuration', err });
|
|
1432
1422
|
|
|
1433
1423
|
return h
|
|
@@ -1624,7 +1614,7 @@ return true;`
|
|
|
1624
1614
|
|
|
1625
1615
|
return h.redirect('/admin/config/ai');
|
|
1626
1616
|
} catch (err) {
|
|
1627
|
-
await request.flash({ type: 'danger', message: `
|
|
1617
|
+
await request.flash({ type: 'danger', message: `Couldn't save settings. Try again.` });
|
|
1628
1618
|
request.logger.error({ msg: 'Failed to update configuration', err });
|
|
1629
1619
|
|
|
1630
1620
|
let hasOpenAiAPIKey = !!(await settings.get('openAiAPIKey'));
|
|
@@ -1688,7 +1678,7 @@ return true;`
|
|
|
1688
1678
|
});
|
|
1689
1679
|
}
|
|
1690
1680
|
|
|
1691
|
-
await request.flash({ type: 'danger', message: `
|
|
1681
|
+
await request.flash({ type: 'danger', message: `Couldn't save settings. Try again.` });
|
|
1692
1682
|
request.logger.error({ msg: 'Failed to update configuration', err });
|
|
1693
1683
|
|
|
1694
1684
|
let hasOpenAiAPIKey = !!(await settings.get('openAiAPIKey'));
|
|
@@ -2045,7 +2035,7 @@ return true;`
|
|
|
2045
2035
|
|
|
2046
2036
|
return h.redirect('/admin/config/logging');
|
|
2047
2037
|
} catch (err) {
|
|
2048
|
-
await request.flash({ type: 'danger', message: `
|
|
2038
|
+
await request.flash({ type: 'danger', message: `Couldn't save settings. Try again.` });
|
|
2049
2039
|
request.logger.error({ msg: 'Failed to update configuration', err });
|
|
2050
2040
|
|
|
2051
2041
|
return h.view(
|
|
@@ -2080,7 +2070,7 @@ return true;`
|
|
|
2080
2070
|
});
|
|
2081
2071
|
}
|
|
2082
2072
|
|
|
2083
|
-
await request.flash({ type: 'danger', message: `
|
|
2073
|
+
await request.flash({ type: 'danger', message: `Couldn't save settings. Try again.` });
|
|
2084
2074
|
request.logger.error({ msg: 'Failed to update configuration', err });
|
|
2085
2075
|
|
|
2086
2076
|
return h
|
|
@@ -2512,11 +2502,11 @@ return true;`
|
|
|
2512
2502
|
try {
|
|
2513
2503
|
await oauth2Apps.del(request.payload.app);
|
|
2514
2504
|
|
|
2515
|
-
await request.flash({ type: 'info', message: `OAuth2
|
|
2505
|
+
await request.flash({ type: 'info', message: `OAuth2 app deleted` });
|
|
2516
2506
|
|
|
2517
2507
|
return h.redirect('/admin/config/oauth');
|
|
2518
2508
|
} catch (err) {
|
|
2519
|
-
await request.flash({ type: 'danger', message: `
|
|
2509
|
+
await request.flash({ type: 'danger', message: `Couldn't delete OAuth2 app. Try again.` });
|
|
2520
2510
|
request.logger.error({ msg: 'Failed to delete OAuth2 application', err, app: request.payload.app, remoteAddress: request.app.ip });
|
|
2521
2511
|
return h.redirect(`/admin/config/oauth/app/${request.payload.app}`);
|
|
2522
2512
|
}
|
|
@@ -2530,7 +2520,7 @@ return true;`
|
|
|
2530
2520
|
},
|
|
2531
2521
|
|
|
2532
2522
|
async failAction(request, h, err) {
|
|
2533
|
-
await request.flash({ type: 'danger', message: `
|
|
2523
|
+
await request.flash({ type: 'danger', message: `Couldn't delete OAuth2 app. Try again.` });
|
|
2534
2524
|
request.logger.error({ msg: 'Failed to delete delete the OAuth2 application', err });
|
|
2535
2525
|
|
|
2536
2526
|
return h.redirect('/admin/config/oauth').takeover();
|
|
@@ -2650,10 +2640,10 @@ return true;`
|
|
|
2650
2640
|
await call({ cmd: 'googlePubSub', app: oauth2App.id });
|
|
2651
2641
|
}
|
|
2652
2642
|
|
|
2653
|
-
await request.flash({ type: 'success', message: `OAuth2
|
|
2643
|
+
await request.flash({ type: 'success', message: `OAuth2 app created` });
|
|
2654
2644
|
return h.redirect(`/admin/config/oauth/app/${oauth2App.id}`);
|
|
2655
2645
|
} catch (err) {
|
|
2656
|
-
await request.flash({ type: 'danger', message: `
|
|
2646
|
+
await request.flash({ type: 'danger', message: `Couldn't register OAuth2 app. Try again.` });
|
|
2657
2647
|
request.logger.error({ msg: 'Failed to register OAuth2 app', err });
|
|
2658
2648
|
|
|
2659
2649
|
let { provider, baseScopes } = request.payload;
|
|
@@ -2733,7 +2723,7 @@ return true;`
|
|
|
2733
2723
|
});
|
|
2734
2724
|
}
|
|
2735
2725
|
|
|
2736
|
-
await request.flash({ type: 'danger', message: `
|
|
2726
|
+
await request.flash({ type: 'danger', message: `Couldn't register OAuth2 app. Try again.` });
|
|
2737
2727
|
request.logger.error({ msg: 'Failed to register OAuth2 app', err });
|
|
2738
2728
|
|
|
2739
2729
|
let { provider, baseScopes } = request.payload;
|
|
@@ -2840,2420 +2830,38 @@ return true;`
|
|
|
2840
2830
|
|
|
2841
2831
|
[`active${providerData.caseName}`]: true,
|
|
2842
2832
|
providerData,
|
|
2843
|
-
defaultRedirectUrl,
|
|
2844
|
-
|
|
2845
|
-
appData,
|
|
2846
|
-
|
|
2847
|
-
hasClientSecret: !!appData.clientSecret,
|
|
2848
|
-
hasServiceKey: !!appData.serviceKey,
|
|
2849
|
-
|
|
2850
|
-
pubSubApps:
|
|
2851
|
-
pubSubApps &&
|
|
2852
|
-
pubSubApps.apps &&
|
|
2853
|
-
pubSubApps.apps.map(app => {
|
|
2854
|
-
if (app.id === values.pubSubApp) {
|
|
2855
|
-
app.selected = true;
|
|
2856
|
-
}
|
|
2857
|
-
return app;
|
|
2858
|
-
}),
|
|
2859
|
-
|
|
2860
|
-
values,
|
|
2861
|
-
|
|
2862
|
-
baseScopesApi: values.baseScopes === 'api',
|
|
2863
|
-
baseScopesImap: values.baseScopes === 'imap' || !values.baseScopes,
|
|
2864
|
-
baseScopesPubsub: values.baseScopes === 'pubsub',
|
|
2865
|
-
|
|
2866
|
-
azureClouds: structuredClone(AZURE_CLOUDS).map(entry => {
|
|
2867
|
-
entry.selected = values.cloud === entry.id;
|
|
2868
|
-
return entry;
|
|
2869
|
-
}),
|
|
2870
|
-
|
|
2871
|
-
authorityCommon: values.authority === 'common',
|
|
2872
|
-
authorityOrganizations: values.authority === 'organizations',
|
|
2873
|
-
authorityConsumers: values.authority === 'consumers',
|
|
2874
|
-
authorityTenant: !!values.tenant
|
|
2875
|
-
},
|
|
2876
|
-
{
|
|
2877
|
-
layout: 'app'
|
|
2878
|
-
}
|
|
2879
|
-
);
|
|
2880
|
-
},
|
|
2881
|
-
|
|
2882
|
-
options: {
|
|
2883
|
-
validate: {
|
|
2884
|
-
options: {
|
|
2885
|
-
stripUnknown: true,
|
|
2886
|
-
abortEarly: false,
|
|
2887
|
-
convert: true
|
|
2888
|
-
},
|
|
2889
|
-
|
|
2890
|
-
async failAction(request, h /*, err*/) {
|
|
2891
|
-
return h.redirect('/admin/config/oauth').takeover();
|
|
2892
|
-
},
|
|
2893
|
-
|
|
2894
|
-
params: Joi.object({
|
|
2895
|
-
app: Joi.string().empty('').max(255).example('gmail').label('Provider').required()
|
|
2896
|
-
})
|
|
2897
|
-
}
|
|
2898
|
-
}
|
|
2899
|
-
});
|
|
2900
|
-
|
|
2901
|
-
server.route({
|
|
2902
|
-
method: 'POST',
|
|
2903
|
-
path: '/admin/config/oauth/edit',
|
|
2904
|
-
async handler(request, h) {
|
|
2905
|
-
let appData = await oauth2Apps.get(request.payload.app);
|
|
2906
|
-
if (!appData) {
|
|
2907
|
-
let error = Boom.boomify(new Error('Application was not found.'), { statusCode: 404 });
|
|
2908
|
-
throw error;
|
|
2909
|
-
}
|
|
2910
|
-
|
|
2911
|
-
try {
|
|
2912
|
-
let updates = Object.assign({}, request.payload);
|
|
2913
|
-
updates.extraScopes = updates.extraScopes
|
|
2914
|
-
.split(/\s+/)
|
|
2915
|
-
.map(scope => scope.trim())
|
|
2916
|
-
.filter(scope => scope);
|
|
2917
|
-
|
|
2918
|
-
updates.skipScopes = updates.skipScopes
|
|
2919
|
-
.split(/\s+/)
|
|
2920
|
-
.map(scope => scope.trim())
|
|
2921
|
-
.filter(scope => scope);
|
|
2922
|
-
|
|
2923
|
-
if (updates.authority === 'tenant') {
|
|
2924
|
-
updates.authority = updates.tenant;
|
|
2925
|
-
}
|
|
2926
|
-
delete updates.tenant;
|
|
2927
|
-
|
|
2928
|
-
let oauth2App = await oauth2Apps.update(appData.id, updates);
|
|
2929
|
-
if (!oauth2App || !oauth2App.id) {
|
|
2930
|
-
throw new Error('Unexpected result');
|
|
2931
|
-
}
|
|
2932
|
-
|
|
2933
|
-
if (oauth2App && oauth2App.pubsubUpdates && oauth2App.pubsubUpdates.pubSubSubscription) {
|
|
2934
|
-
await call({ cmd: 'googlePubSub', app: oauth2App.id });
|
|
2935
|
-
}
|
|
2936
|
-
|
|
2937
|
-
await request.flash({ type: 'success', message: `OAuth2 application was updated` });
|
|
2938
|
-
return h.redirect(`/admin/config/oauth/app/${oauth2App.id}`);
|
|
2939
|
-
} catch (err) {
|
|
2940
|
-
await request.flash({ type: 'danger', message: `Failed to update OAuth2 app` });
|
|
2941
|
-
request.logger.error({ msg: 'Failed to update OAuth2 app', app: request.payload.app, err });
|
|
2942
|
-
|
|
2943
|
-
let providerData = oauth2ProviderData(appData.provider, appData.cloud);
|
|
2944
|
-
|
|
2945
|
-
let serviceUrl = await settings.get('serviceUrl');
|
|
2946
|
-
let defaultRedirectUrl = `${serviceUrl}/oauth`;
|
|
2947
|
-
if (appData.provider === 'outlook') {
|
|
2948
|
-
defaultRedirectUrl = defaultRedirectUrl.replace(/^http:\/\/127\.0\.0\.1\b/i, 'http://localhost');
|
|
2949
|
-
}
|
|
2950
|
-
|
|
2951
|
-
let pubSubApps = await oauth2Apps.list(0, 1000, { pubsub: true });
|
|
2952
|
-
|
|
2953
|
-
return h.view(
|
|
2954
|
-
'config/oauth/edit',
|
|
2955
|
-
{
|
|
2956
|
-
pageTitle: 'OAuth2',
|
|
2957
|
-
menuConfig: true,
|
|
2958
|
-
menuConfigOauth: true,
|
|
2959
|
-
|
|
2960
|
-
[`active${providerData.caseName}`]: true,
|
|
2961
|
-
providerData,
|
|
2962
|
-
defaultRedirectUrl,
|
|
2963
|
-
appData,
|
|
2964
|
-
|
|
2965
|
-
hasClientSecret: !!appData.clientSecret,
|
|
2966
|
-
hasServiceKey: !!appData.serviceKey,
|
|
2967
|
-
|
|
2968
|
-
pubSubApps:
|
|
2969
|
-
pubSubApps &&
|
|
2970
|
-
pubSubApps.apps &&
|
|
2971
|
-
pubSubApps.apps.map(app => {
|
|
2972
|
-
if (app.id === request.payload.pubSubApp) {
|
|
2973
|
-
app.selected = true;
|
|
2974
|
-
}
|
|
2975
|
-
return app;
|
|
2976
|
-
}),
|
|
2977
|
-
|
|
2978
|
-
baseScopesApi: request.payload.baseScopes === 'api',
|
|
2979
|
-
baseScopesImap: request.payload.baseScopes === 'imap' || !request.payload.baseScopes,
|
|
2980
|
-
baseScopesPubsub: request.payload.baseScopes === 'pubsub',
|
|
2981
|
-
|
|
2982
|
-
azureClouds: structuredClone(AZURE_CLOUDS).map(entry => {
|
|
2983
|
-
entry.selected = request.payload.cloud === entry.id;
|
|
2984
|
-
return entry;
|
|
2985
|
-
}),
|
|
2986
|
-
|
|
2987
|
-
authorityCommon: request.payload.authority === 'common',
|
|
2988
|
-
authorityOrganizations: request.payload.authority === 'organizations',
|
|
2989
|
-
authorityConsumers: request.payload.authority === 'consumers',
|
|
2990
|
-
authorityTenant: request.payload.authority === 'tenant'
|
|
2991
|
-
},
|
|
2992
|
-
{
|
|
2993
|
-
layout: 'app'
|
|
2994
|
-
}
|
|
2995
|
-
);
|
|
2996
|
-
}
|
|
2997
|
-
},
|
|
2998
|
-
options: {
|
|
2999
|
-
validate: {
|
|
3000
|
-
options: {
|
|
3001
|
-
stripUnknown: true,
|
|
3002
|
-
abortEarly: false,
|
|
3003
|
-
convert: true
|
|
3004
|
-
},
|
|
3005
|
-
|
|
3006
|
-
async failAction(request, h, err) {
|
|
3007
|
-
let errors = {};
|
|
3008
|
-
|
|
3009
|
-
if (err.details) {
|
|
3010
|
-
err.details.forEach(detail => {
|
|
3011
|
-
if (!errors[detail.path]) {
|
|
3012
|
-
errors[detail.path] = detail.message;
|
|
3013
|
-
}
|
|
3014
|
-
});
|
|
3015
|
-
}
|
|
3016
|
-
|
|
3017
|
-
let appData = await oauth2Apps.get(request.payload.app);
|
|
3018
|
-
if (!appData) {
|
|
3019
|
-
await request.flash({ type: 'danger', message: `Application was not found.` });
|
|
3020
|
-
request.logger.error({ msg: 'Application was not found.', app: request.payload.app });
|
|
3021
|
-
return h.redirect('/admin').takeover();
|
|
3022
|
-
}
|
|
3023
|
-
|
|
3024
|
-
await request.flash({ type: 'danger', message: `Failed to update OAuth2 app` });
|
|
3025
|
-
request.logger.error({ msg: 'Failed to update OAuth2 app', err });
|
|
3026
|
-
|
|
3027
|
-
let { provider } = request.payload;
|
|
3028
|
-
if (!provider || !OAUTH_PROVIDERS.hasOwnProperty(provider)) {
|
|
3029
|
-
return h.redirect('/admin').takeover();
|
|
3030
|
-
}
|
|
3031
|
-
|
|
3032
|
-
let providerData = oauth2ProviderData(provider);
|
|
3033
|
-
|
|
3034
|
-
let serviceUrl = await settings.get('serviceUrl');
|
|
3035
|
-
let defaultRedirectUrl = `${serviceUrl}/oauth`;
|
|
3036
|
-
if (provider === 'outlook') {
|
|
3037
|
-
defaultRedirectUrl = defaultRedirectUrl.replace(/^http:\/\/127\.0\.0\.1\b/i, 'http://localhost');
|
|
3038
|
-
}
|
|
3039
|
-
|
|
3040
|
-
let pubSubApps = await oauth2Apps.list(0, 1000, { pubsub: true });
|
|
3041
|
-
|
|
3042
|
-
return h
|
|
3043
|
-
.view(
|
|
3044
|
-
'config/oauth/edit',
|
|
3045
|
-
{
|
|
3046
|
-
pageTitle: 'OAuth2',
|
|
3047
|
-
menuConfig: true,
|
|
3048
|
-
menuConfigOauth: true,
|
|
3049
|
-
|
|
3050
|
-
[`active${providerData.caseName}`]: true,
|
|
3051
|
-
providerData,
|
|
3052
|
-
defaultRedirectUrl,
|
|
3053
|
-
|
|
3054
|
-
appData,
|
|
3055
|
-
|
|
3056
|
-
hasClientSecret: !!appData.clientSecret,
|
|
3057
|
-
hasServiceKey: !!appData.serviceKey,
|
|
3058
|
-
|
|
3059
|
-
pubSubApps:
|
|
3060
|
-
pubSubApps &&
|
|
3061
|
-
pubSubApps.apps &&
|
|
3062
|
-
pubSubApps.apps.map(app => {
|
|
3063
|
-
if (app.id === request.payload.pubSubApp) {
|
|
3064
|
-
app.selected = true;
|
|
3065
|
-
}
|
|
3066
|
-
return app;
|
|
3067
|
-
}),
|
|
3068
|
-
|
|
3069
|
-
baseScopesApi: request.payload.baseScopes === 'api',
|
|
3070
|
-
baseScopesImap: request.payload.baseScopes === 'imap' || !request.payload.baseScopes,
|
|
3071
|
-
baseScopesPubsub: request.payload.baseScopes === 'pubsub',
|
|
3072
|
-
|
|
3073
|
-
azureClouds: structuredClone(AZURE_CLOUDS).map(entry => {
|
|
3074
|
-
entry.selected = request.payload.cloud === entry.id;
|
|
3075
|
-
return entry;
|
|
3076
|
-
}),
|
|
3077
|
-
|
|
3078
|
-
authorityCommon: request.payload.authority === 'common',
|
|
3079
|
-
authorityOrganizations: request.payload.authority === 'organizations',
|
|
3080
|
-
authorityConsumers: request.payload.authority === 'consumers',
|
|
3081
|
-
authorityTenant: request.payload.authority === 'tenant',
|
|
3082
|
-
|
|
3083
|
-
errors
|
|
3084
|
-
},
|
|
3085
|
-
{
|
|
3086
|
-
layout: 'app'
|
|
3087
|
-
}
|
|
3088
|
-
)
|
|
3089
|
-
.takeover();
|
|
3090
|
-
},
|
|
3091
|
-
|
|
3092
|
-
payload: Joi.object(oauthUpdateSchema)
|
|
3093
|
-
}
|
|
3094
|
-
}
|
|
3095
|
-
});
|
|
3096
|
-
|
|
3097
|
-
server.route({
|
|
3098
|
-
method: 'GET',
|
|
3099
|
-
path: '/admin/webhooks',
|
|
3100
|
-
async handler(request, h) {
|
|
3101
|
-
let data = await webhooks.list(request.query.page - 1, request.query.pageSize);
|
|
3102
|
-
|
|
3103
|
-
let nextPage = false;
|
|
3104
|
-
let prevPage = false;
|
|
3105
|
-
|
|
3106
|
-
if (request.query.account) {
|
|
3107
|
-
let accountObject = new Account({ redis, account: request.query.account });
|
|
3108
|
-
data.account = await accountObject.loadAccountData();
|
|
3109
|
-
}
|
|
3110
|
-
|
|
3111
|
-
let getPagingUrl = page => {
|
|
3112
|
-
let url = new URL(`admin/webhooks`, 'http://localhost');
|
|
3113
|
-
|
|
3114
|
-
if (page) {
|
|
3115
|
-
url.searchParams.append('page', page);
|
|
3116
|
-
}
|
|
3117
|
-
|
|
3118
|
-
if (request.query.pageSize !== DEFAULT_PAGE_SIZE) {
|
|
3119
|
-
url.searchParams.append('pageSize', request.query.pageSize);
|
|
3120
|
-
}
|
|
3121
|
-
|
|
3122
|
-
return url.pathname + url.search;
|
|
3123
|
-
};
|
|
3124
|
-
|
|
3125
|
-
if (data.pages > data.page + 1) {
|
|
3126
|
-
nextPage = getPagingUrl(data.page + 2);
|
|
3127
|
-
}
|
|
3128
|
-
|
|
3129
|
-
if (data.page > 0) {
|
|
3130
|
-
prevPage = getPagingUrl(data.page);
|
|
3131
|
-
}
|
|
3132
|
-
|
|
3133
|
-
let newLink = new URL('/admin/webhooks/new', 'http://localhost');
|
|
3134
|
-
|
|
3135
|
-
return h.view(
|
|
3136
|
-
'webhooks/index',
|
|
3137
|
-
{
|
|
3138
|
-
pageTitle: 'Webhook Routing',
|
|
3139
|
-
menuWebhooks: true,
|
|
3140
|
-
|
|
3141
|
-
newLink: newLink.pathname + newLink.search,
|
|
3142
|
-
|
|
3143
|
-
showPaging: data.pages > 1,
|
|
3144
|
-
nextPage,
|
|
3145
|
-
prevPage,
|
|
3146
|
-
firstPage: data.page === 0,
|
|
3147
|
-
pageLinks: new Array(data.pages || 1).fill(0).map((z, i) => ({
|
|
3148
|
-
url: getPagingUrl(i + 1),
|
|
3149
|
-
title: i + 1,
|
|
3150
|
-
active: i === data.page
|
|
3151
|
-
})),
|
|
3152
|
-
|
|
3153
|
-
webhooksEnabled: await settings.get('webhooksEnabled'),
|
|
3154
|
-
|
|
3155
|
-
webhooks: data.webhooks
|
|
3156
|
-
},
|
|
3157
|
-
{
|
|
3158
|
-
layout: 'app'
|
|
3159
|
-
}
|
|
3160
|
-
);
|
|
3161
|
-
},
|
|
3162
|
-
|
|
3163
|
-
options: {
|
|
3164
|
-
validate: {
|
|
3165
|
-
options: {
|
|
3166
|
-
stripUnknown: true,
|
|
3167
|
-
abortEarly: false,
|
|
3168
|
-
convert: true
|
|
3169
|
-
},
|
|
3170
|
-
|
|
3171
|
-
async failAction(request, h /*, err*/) {
|
|
3172
|
-
return h.redirect('/admin/webhooks').takeover();
|
|
3173
|
-
},
|
|
3174
|
-
|
|
3175
|
-
query: Joi.object({
|
|
3176
|
-
page: Joi.number().integer().min(1).max(1000000).default(1),
|
|
3177
|
-
pageSize: Joi.number().integer().min(1).max(250).default(DEFAULT_PAGE_SIZE)
|
|
3178
|
-
})
|
|
3179
|
-
}
|
|
3180
|
-
}
|
|
3181
|
-
});
|
|
3182
|
-
|
|
3183
|
-
server.route({
|
|
3184
|
-
method: 'GET',
|
|
3185
|
-
path: '/admin/webhooks/new',
|
|
3186
|
-
async handler(request, h) {
|
|
3187
|
-
const values = {
|
|
3188
|
-
name: '',
|
|
3189
|
-
description: '',
|
|
3190
|
-
|
|
3191
|
-
contentFnJson: JSON.stringify(`/*
|
|
3192
|
-
// The following example passes webhooks for new emails that appear in the Inbox of the user "testaccount".
|
|
3193
|
-
// NB! Gmail webhooks are always emitted from the "All Mail" folder, not the Inbox, so we need to check both the path and label values.
|
|
3194
|
-
|
|
3195
|
-
const isInbox = payload.path === 'INBOX' || payload.data?.labels?.includes('\\\\Inbox');
|
|
3196
|
-
if (payload.event === 'messageNew' && payload.account === 'testaccount' && isInbox) {
|
|
3197
|
-
return true;
|
|
3198
|
-
}
|
|
3199
|
-
*/`),
|
|
3200
|
-
contentMapJson: JSON.stringify(`// By default the output payload is returned unmodified.
|
|
3201
|
-
|
|
3202
|
-
return payload;`)
|
|
3203
|
-
};
|
|
3204
|
-
|
|
3205
|
-
return h.view(
|
|
3206
|
-
'webhooks/new',
|
|
3207
|
-
{
|
|
3208
|
-
pageTitle: 'Webhook Routing',
|
|
3209
|
-
menuWebhooks: true,
|
|
3210
|
-
values,
|
|
3211
|
-
|
|
3212
|
-
examplePayloadsJson: JSON.stringify(await getExampleWebhookPayloads()),
|
|
3213
|
-
notificationTypesJson: JSON.stringify(notificationTypes),
|
|
3214
|
-
scriptEnvJson: JSON.stringify((await settings.get('scriptEnv')) || '{}')
|
|
3215
|
-
},
|
|
3216
|
-
{
|
|
3217
|
-
layout: 'app'
|
|
3218
|
-
}
|
|
3219
|
-
);
|
|
3220
|
-
},
|
|
3221
|
-
|
|
3222
|
-
options: {
|
|
3223
|
-
validate: {
|
|
3224
|
-
options: {
|
|
3225
|
-
stripUnknown: true,
|
|
3226
|
-
abortEarly: false,
|
|
3227
|
-
convert: true
|
|
3228
|
-
},
|
|
3229
|
-
|
|
3230
|
-
async failAction(request, h /*, err*/) {
|
|
3231
|
-
return h.redirect('/admin/webhooks').takeover();
|
|
3232
|
-
},
|
|
3233
|
-
|
|
3234
|
-
query: Joi.object({})
|
|
3235
|
-
}
|
|
3236
|
-
}
|
|
3237
|
-
});
|
|
3238
|
-
|
|
3239
|
-
server.route({
|
|
3240
|
-
method: 'POST',
|
|
3241
|
-
path: '/admin/webhooks/new',
|
|
3242
|
-
async handler(request, h) {
|
|
3243
|
-
let contentFn, contentMap;
|
|
3244
|
-
try {
|
|
3245
|
-
if (request.payload.contentFnJson === '') {
|
|
3246
|
-
contentFn = null;
|
|
3247
|
-
} else {
|
|
3248
|
-
contentFn = JSON.parse(request.payload.contentFnJson);
|
|
3249
|
-
if (typeof contentFn !== 'string') {
|
|
3250
|
-
throw new Error('Invalid Format');
|
|
3251
|
-
}
|
|
3252
|
-
}
|
|
3253
|
-
} catch (err) {
|
|
3254
|
-
err.details = {
|
|
3255
|
-
contentFnJson: 'Invalid JSON'
|
|
3256
|
-
};
|
|
3257
|
-
throw err;
|
|
3258
|
-
}
|
|
3259
|
-
|
|
3260
|
-
try {
|
|
3261
|
-
if (request.payload.contentMapJson === '') {
|
|
3262
|
-
contentMap = null;
|
|
3263
|
-
} else {
|
|
3264
|
-
contentMap = JSON.parse(request.payload.contentMapJson);
|
|
3265
|
-
if (typeof contentMap !== 'string') {
|
|
3266
|
-
throw new Error('Invalid Format');
|
|
3267
|
-
}
|
|
3268
|
-
}
|
|
3269
|
-
} catch (err) {
|
|
3270
|
-
err.details = {
|
|
3271
|
-
contentMapJson: 'Invalid JSON'
|
|
3272
|
-
};
|
|
3273
|
-
throw err;
|
|
3274
|
-
}
|
|
3275
|
-
|
|
3276
|
-
let customHeaders = request.payload.customHeaders
|
|
3277
|
-
.split(/[\r\n]+/)
|
|
3278
|
-
.map(header => header.trim())
|
|
3279
|
-
.filter(header => header)
|
|
3280
|
-
.map(line => {
|
|
3281
|
-
let sep = line.indexOf(':');
|
|
3282
|
-
if (sep >= 0) {
|
|
3283
|
-
return {
|
|
3284
|
-
key: line.substring(0, sep).trim(),
|
|
3285
|
-
value: line.substring(sep + 1).trim()
|
|
3286
|
-
};
|
|
3287
|
-
}
|
|
3288
|
-
return {
|
|
3289
|
-
key: line,
|
|
3290
|
-
value: ''
|
|
3291
|
-
};
|
|
3292
|
-
});
|
|
3293
|
-
|
|
3294
|
-
try {
|
|
3295
|
-
let createRequest = await webhooks.create(
|
|
3296
|
-
{
|
|
3297
|
-
name: request.payload.name,
|
|
3298
|
-
description: request.payload.description,
|
|
3299
|
-
targetUrl: request.payload.targetUrl,
|
|
3300
|
-
enabled: request.payload.enabled,
|
|
3301
|
-
|
|
3302
|
-
customHeaders
|
|
3303
|
-
},
|
|
3304
|
-
{
|
|
3305
|
-
fn: contentFn,
|
|
3306
|
-
map: contentMap
|
|
3307
|
-
}
|
|
3308
|
-
);
|
|
3309
|
-
|
|
3310
|
-
await request.flash({ type: 'info', message: `Webhook routing was created` });
|
|
3311
|
-
return h.redirect(`/admin/webhooks/webhook/${createRequest.id}`);
|
|
3312
|
-
} catch (err) {
|
|
3313
|
-
await request.flash({ type: 'danger', message: `Failed to create webhook routing` });
|
|
3314
|
-
request.logger.error({ msg: 'Failed to create webhook routing', err });
|
|
3315
|
-
|
|
3316
|
-
return h.view(
|
|
3317
|
-
'webhooks/new',
|
|
3318
|
-
{
|
|
3319
|
-
pageTitle: 'Webhook Routing',
|
|
3320
|
-
menuWebhooks: true,
|
|
3321
|
-
errors: err.details,
|
|
3322
|
-
|
|
3323
|
-
examplePayloadsJson: JSON.stringify(await getExampleWebhookPayloads()),
|
|
3324
|
-
notificationTypesJson: JSON.stringify(notificationTypes),
|
|
3325
|
-
scriptEnvJson: JSON.stringify((await settings.get('scriptEnv')) || '{}')
|
|
3326
|
-
},
|
|
3327
|
-
{
|
|
3328
|
-
layout: 'app'
|
|
3329
|
-
}
|
|
3330
|
-
);
|
|
3331
|
-
}
|
|
3332
|
-
},
|
|
3333
|
-
options: {
|
|
3334
|
-
validate: {
|
|
3335
|
-
options: {
|
|
3336
|
-
stripUnknown: true,
|
|
3337
|
-
abortEarly: false,
|
|
3338
|
-
convert: true
|
|
3339
|
-
},
|
|
3340
|
-
|
|
3341
|
-
async failAction(request, h, err) {
|
|
3342
|
-
let errors = {};
|
|
3343
|
-
|
|
3344
|
-
if (err.details) {
|
|
3345
|
-
err.details.forEach(detail => {
|
|
3346
|
-
if (!errors[detail.path]) {
|
|
3347
|
-
errors[detail.path] = detail.message;
|
|
3348
|
-
}
|
|
3349
|
-
});
|
|
3350
|
-
}
|
|
3351
|
-
|
|
3352
|
-
await request.flash({ type: 'danger', message: `Failed to create webhook routing` });
|
|
3353
|
-
request.logger.error({ msg: 'Failed to create webhook routing', err });
|
|
3354
|
-
|
|
3355
|
-
return h
|
|
3356
|
-
.view(
|
|
3357
|
-
'templates/new',
|
|
3358
|
-
{
|
|
3359
|
-
pageTitle: 'Templates',
|
|
3360
|
-
menuTemplates: true,
|
|
3361
|
-
errors,
|
|
3362
|
-
|
|
3363
|
-
examplePayloadsJson: JSON.stringify(await getExampleWebhookPayloads()),
|
|
3364
|
-
notificationTypesJson: JSON.stringify(notificationTypes)
|
|
3365
|
-
},
|
|
3366
|
-
{
|
|
3367
|
-
layout: 'app'
|
|
3368
|
-
}
|
|
3369
|
-
)
|
|
3370
|
-
.takeover();
|
|
3371
|
-
},
|
|
3372
|
-
|
|
3373
|
-
payload: Joi.object({
|
|
3374
|
-
name: Joi.string().max(256).example('Transaction receipt').description('Name of the routing').label('RoutingName').required(),
|
|
3375
|
-
description: Joi.string()
|
|
3376
|
-
.allow('')
|
|
3377
|
-
.max(1024)
|
|
3378
|
-
.example('Something about the routing')
|
|
3379
|
-
.description('Optional description of the webhook routing')
|
|
3380
|
-
.label('RoutingDescription'),
|
|
3381
|
-
targetUrl: Joi.string()
|
|
3382
|
-
.uri({
|
|
3383
|
-
scheme: ['http', 'https'],
|
|
3384
|
-
allowRelative: false
|
|
3385
|
-
})
|
|
3386
|
-
.allow('')
|
|
3387
|
-
.default('')
|
|
3388
|
-
.example('https://myservice.com/imap/webhooks')
|
|
3389
|
-
.description('Webhook target URL'),
|
|
3390
|
-
enabled: Joi.boolean()
|
|
3391
|
-
.truthy('Y', 'true', '1', 'on')
|
|
3392
|
-
.falsy('N', 'false', 0, '')
|
|
3393
|
-
.default(false)
|
|
3394
|
-
.example(false)
|
|
3395
|
-
.description('Is the routing enabled'),
|
|
3396
|
-
customHeaders: Joi.string()
|
|
3397
|
-
.allow('')
|
|
3398
|
-
.trim()
|
|
3399
|
-
.max(10 * 1024)
|
|
3400
|
-
.description('Custom request headers'),
|
|
3401
|
-
contentFnJson: Joi.string()
|
|
3402
|
-
.max(1024 * 1024)
|
|
3403
|
-
.default('')
|
|
3404
|
-
.allow('')
|
|
3405
|
-
.trim()
|
|
3406
|
-
.description('Filter function'),
|
|
3407
|
-
contentMapJson: Joi.string()
|
|
3408
|
-
.max(1024 * 1024)
|
|
3409
|
-
.default('')
|
|
3410
|
-
.allow('')
|
|
3411
|
-
.trim()
|
|
3412
|
-
.description('Map function')
|
|
3413
|
-
})
|
|
3414
|
-
}
|
|
3415
|
-
}
|
|
3416
|
-
});
|
|
3417
|
-
|
|
3418
|
-
server.route({
|
|
3419
|
-
method: 'GET',
|
|
3420
|
-
path: '/admin/webhooks/webhook/{webhook}',
|
|
3421
|
-
async handler(request, h) {
|
|
3422
|
-
let webhook = await webhooks.get(request.params.webhook);
|
|
3423
|
-
if (!webhook) {
|
|
3424
|
-
let error = Boom.boomify(new Error('Webhook Route was not found.'), { statusCode: 404 });
|
|
3425
|
-
throw error;
|
|
3426
|
-
}
|
|
3427
|
-
|
|
3428
|
-
webhook.targetUrlShort = webhook.targetUrl ? new URL(webhook.targetUrl).hostname : false;
|
|
3429
|
-
|
|
3430
|
-
const errorLog = ((await webhooks.getErrorLog(webhook.id)) || []).map(entry => {
|
|
3431
|
-
if (entry.error && typeof entry.error === 'string') {
|
|
3432
|
-
entry.error = entry.error
|
|
3433
|
-
.replace(/\r?\n/g, '\n')
|
|
3434
|
-
.replace(/^\s+at\s+.*$/gm, '')
|
|
3435
|
-
.replace(/\n+/g, '\n')
|
|
3436
|
-
.trim()
|
|
3437
|
-
.replace(/(evalmachine.<anonymous>:)(\d+)/, (o, p, n) => p + (Number(n) - 1));
|
|
3438
|
-
}
|
|
3439
|
-
return entry;
|
|
3440
|
-
});
|
|
3441
|
-
|
|
3442
|
-
return h.view(
|
|
3443
|
-
'webhooks/webhook',
|
|
3444
|
-
{
|
|
3445
|
-
pageTitle: 'Webhook Routing',
|
|
3446
|
-
menuWebhooks: true,
|
|
3447
|
-
webhook,
|
|
3448
|
-
|
|
3449
|
-
errorLog
|
|
3450
|
-
},
|
|
3451
|
-
{
|
|
3452
|
-
layout: 'app'
|
|
3453
|
-
}
|
|
3454
|
-
);
|
|
3455
|
-
},
|
|
3456
|
-
|
|
3457
|
-
options: {
|
|
3458
|
-
validate: {
|
|
3459
|
-
options: {
|
|
3460
|
-
stripUnknown: true,
|
|
3461
|
-
abortEarly: false,
|
|
3462
|
-
convert: true
|
|
3463
|
-
},
|
|
3464
|
-
|
|
3465
|
-
async failAction(request, h /*, err*/) {
|
|
3466
|
-
return h.redirect('/admin/webhooks').takeover();
|
|
3467
|
-
},
|
|
3468
|
-
|
|
3469
|
-
params: Joi.object({
|
|
3470
|
-
webhook: Joi.string()
|
|
3471
|
-
.base64({ paddingRequired: false, urlSafe: true })
|
|
3472
|
-
.max(512)
|
|
3473
|
-
.example('AAAAAQAACnA')
|
|
3474
|
-
.required()
|
|
3475
|
-
.description('Webhook Route ID')
|
|
3476
|
-
})
|
|
3477
|
-
}
|
|
3478
|
-
}
|
|
3479
|
-
});
|
|
3480
|
-
|
|
3481
|
-
server.route({
|
|
3482
|
-
method: 'GET',
|
|
3483
|
-
path: '/admin/webhooks/webhook/{webhook}/edit',
|
|
3484
|
-
async handler(request, h) {
|
|
3485
|
-
let webhook = await webhooks.get(request.params.webhook);
|
|
3486
|
-
if (!webhook) {
|
|
3487
|
-
let error = Boom.boomify(new Error('Webhook Route not found.'), { statusCode: 404 });
|
|
3488
|
-
throw error;
|
|
3489
|
-
}
|
|
3490
|
-
|
|
3491
|
-
const values = {
|
|
3492
|
-
webhook: webhook.id,
|
|
3493
|
-
name: webhook.name,
|
|
3494
|
-
description: webhook.description,
|
|
3495
|
-
targetUrl: webhook.targetUrl,
|
|
3496
|
-
enabled: webhook.enabled,
|
|
3497
|
-
contentFnJson: JSON.stringify(webhook.content.fn || ''),
|
|
3498
|
-
contentMapJson: JSON.stringify(webhook.content.map || ''),
|
|
3499
|
-
|
|
3500
|
-
customHeaders: []
|
|
3501
|
-
.concat(webhook.customHeaders || [])
|
|
3502
|
-
.map(entry => `${entry.key}: ${entry.value}`.trim())
|
|
3503
|
-
.join('\n')
|
|
3504
|
-
};
|
|
3505
|
-
|
|
3506
|
-
return h.view(
|
|
3507
|
-
'webhooks/edit',
|
|
3508
|
-
{
|
|
3509
|
-
pageTitle: 'Webhook Routing',
|
|
3510
|
-
menuWebhooks: true,
|
|
3511
|
-
|
|
3512
|
-
webhook,
|
|
3513
|
-
|
|
3514
|
-
values,
|
|
3515
|
-
|
|
3516
|
-
examplePayloadsJson: JSON.stringify(await getExampleWebhookPayloads()),
|
|
3517
|
-
notificationTypesJson: JSON.stringify(notificationTypes),
|
|
3518
|
-
scriptEnvJson: JSON.stringify((await settings.get('scriptEnv')) || '{}')
|
|
3519
|
-
},
|
|
3520
|
-
{
|
|
3521
|
-
layout: 'app'
|
|
3522
|
-
}
|
|
3523
|
-
);
|
|
3524
|
-
},
|
|
3525
|
-
|
|
3526
|
-
options: {
|
|
3527
|
-
validate: {
|
|
3528
|
-
options: {
|
|
3529
|
-
stripUnknown: true,
|
|
3530
|
-
abortEarly: false,
|
|
3531
|
-
convert: true
|
|
3532
|
-
},
|
|
3533
|
-
|
|
3534
|
-
async failAction(request, h /*, err*/) {
|
|
3535
|
-
return h.redirect('/admin/webhooks').takeover();
|
|
3536
|
-
},
|
|
3537
|
-
|
|
3538
|
-
params: Joi.object({
|
|
3539
|
-
webhook: Joi.string()
|
|
3540
|
-
.base64({ paddingRequired: false, urlSafe: true })
|
|
3541
|
-
.max(512)
|
|
3542
|
-
.example('AAAAAQAACnA')
|
|
3543
|
-
.required()
|
|
3544
|
-
.description('Webhook Route ID')
|
|
3545
|
-
})
|
|
3546
|
-
}
|
|
3547
|
-
}
|
|
3548
|
-
});
|
|
3549
|
-
|
|
3550
|
-
server.route({
|
|
3551
|
-
method: 'POST',
|
|
3552
|
-
path: '/admin/webhooks/edit',
|
|
3553
|
-
async handler(request, h) {
|
|
3554
|
-
let contentFn, contentMap;
|
|
3555
|
-
try {
|
|
3556
|
-
if (request.payload.contentFnJson === '') {
|
|
3557
|
-
contentFn = null;
|
|
3558
|
-
} else {
|
|
3559
|
-
contentFn = JSON.parse(request.payload.contentFnJson);
|
|
3560
|
-
if (typeof contentFn !== 'string') {
|
|
3561
|
-
throw new Error('Invalid Format');
|
|
3562
|
-
}
|
|
3563
|
-
}
|
|
3564
|
-
} catch (err) {
|
|
3565
|
-
err.details = {
|
|
3566
|
-
contentFnJson: 'Invalid JSON'
|
|
3567
|
-
};
|
|
3568
|
-
throw err;
|
|
3569
|
-
}
|
|
3570
|
-
|
|
3571
|
-
try {
|
|
3572
|
-
if (request.payload.contentMapJson === '') {
|
|
3573
|
-
contentMap = null;
|
|
3574
|
-
} else {
|
|
3575
|
-
contentMap = JSON.parse(request.payload.contentMapJson);
|
|
3576
|
-
if (typeof contentMap !== 'string') {
|
|
3577
|
-
throw new Error('Invalid Format');
|
|
3578
|
-
}
|
|
3579
|
-
}
|
|
3580
|
-
} catch (err) {
|
|
3581
|
-
err.details = {
|
|
3582
|
-
contentMapJson: 'Invalid JSON'
|
|
3583
|
-
};
|
|
3584
|
-
throw err;
|
|
3585
|
-
}
|
|
3586
|
-
|
|
3587
|
-
let customHeaders = request.payload.customHeaders
|
|
3588
|
-
.split(/[\r\n]+/)
|
|
3589
|
-
.map(header => header.trim())
|
|
3590
|
-
.filter(header => header)
|
|
3591
|
-
.map(line => {
|
|
3592
|
-
let sep = line.indexOf(':');
|
|
3593
|
-
if (sep >= 0) {
|
|
3594
|
-
return {
|
|
3595
|
-
key: line.substring(0, sep).trim(),
|
|
3596
|
-
value: line.substring(sep + 1).trim()
|
|
3597
|
-
};
|
|
3598
|
-
}
|
|
3599
|
-
return {
|
|
3600
|
-
key: line,
|
|
3601
|
-
value: ''
|
|
3602
|
-
};
|
|
3603
|
-
});
|
|
3604
|
-
|
|
3605
|
-
try {
|
|
3606
|
-
await webhooks.update(
|
|
3607
|
-
request.payload.webhook,
|
|
3608
|
-
{
|
|
3609
|
-
name: request.payload.name,
|
|
3610
|
-
description: request.payload.description,
|
|
3611
|
-
targetUrl: request.payload.targetUrl,
|
|
3612
|
-
enabled: request.payload.enabled,
|
|
3613
|
-
|
|
3614
|
-
customHeaders
|
|
3615
|
-
},
|
|
3616
|
-
{
|
|
3617
|
-
fn: contentFn,
|
|
3618
|
-
map: contentMap
|
|
3619
|
-
}
|
|
3620
|
-
);
|
|
3621
|
-
|
|
3622
|
-
await request.flash({ type: 'info', message: `Webhook Route settings were updated` });
|
|
3623
|
-
return h.redirect(`/admin/webhooks/webhook/${request.payload.webhook}`);
|
|
3624
|
-
} catch (err) {
|
|
3625
|
-
await request.flash({ type: 'danger', message: `Failed to update Webhook Route` });
|
|
3626
|
-
request.logger.error({ msg: 'Failed to update Webhook Route', err });
|
|
3627
|
-
|
|
3628
|
-
let webhook = await webhooks.get(request.payload.webhook);
|
|
3629
|
-
if (!webhook) {
|
|
3630
|
-
let error = Boom.boomify(new Error('Webhook Route not found.'), { statusCode: 404 });
|
|
3631
|
-
throw error;
|
|
3632
|
-
}
|
|
3633
|
-
|
|
3634
|
-
return h.view(
|
|
3635
|
-
'webhooks/edit',
|
|
3636
|
-
{
|
|
3637
|
-
pageTitle: 'Webhook Routing',
|
|
3638
|
-
menuWebhooks: true,
|
|
3639
|
-
|
|
3640
|
-
webhook,
|
|
3641
|
-
|
|
3642
|
-
errors: err.details,
|
|
3643
|
-
|
|
3644
|
-
examplePayloadsJson: JSON.stringify(await getExampleWebhookPayloads()),
|
|
3645
|
-
notificationTypesJson: JSON.stringify(notificationTypes),
|
|
3646
|
-
scriptEnvJson: JSON.stringify((await settings.get('scriptEnv')) || '{}')
|
|
3647
|
-
},
|
|
3648
|
-
{
|
|
3649
|
-
layout: 'app'
|
|
3650
|
-
}
|
|
3651
|
-
);
|
|
3652
|
-
}
|
|
3653
|
-
},
|
|
3654
|
-
options: {
|
|
3655
|
-
validate: {
|
|
3656
|
-
options: {
|
|
3657
|
-
stripUnknown: true,
|
|
3658
|
-
abortEarly: false,
|
|
3659
|
-
convert: true
|
|
3660
|
-
},
|
|
3661
|
-
|
|
3662
|
-
async failAction(request, h, err) {
|
|
3663
|
-
let errors = {};
|
|
3664
|
-
|
|
3665
|
-
if (err.details) {
|
|
3666
|
-
err.details.forEach(detail => {
|
|
3667
|
-
if (!errors[detail.path]) {
|
|
3668
|
-
errors[detail.path] = detail.message;
|
|
3669
|
-
}
|
|
3670
|
-
});
|
|
3671
|
-
}
|
|
3672
|
-
|
|
3673
|
-
await request.flash({ type: 'danger', message: `Failed to update Webhook Route` });
|
|
3674
|
-
request.logger.error({ msg: 'Failed to update Webhook Route', err });
|
|
3675
|
-
|
|
3676
|
-
let webhook = await webhooks.get(request.payload.webhook);
|
|
3677
|
-
if (!webhook) {
|
|
3678
|
-
let error = Boom.boomify(new Error('Webhook Route not found.'), { statusCode: 404 });
|
|
3679
|
-
throw error;
|
|
3680
|
-
}
|
|
3681
|
-
|
|
3682
|
-
return h
|
|
3683
|
-
.view(
|
|
3684
|
-
'webhooks/edit',
|
|
3685
|
-
{
|
|
3686
|
-
pageTitle: 'Webhook Routing',
|
|
3687
|
-
menuWebhooks: true,
|
|
3688
|
-
|
|
3689
|
-
webhook,
|
|
3690
|
-
|
|
3691
|
-
errors,
|
|
3692
|
-
|
|
3693
|
-
examplePayloadsJson: JSON.stringify(await getExampleWebhookPayloads()),
|
|
3694
|
-
notificationTypesJson: JSON.stringify(notificationTypes),
|
|
3695
|
-
scriptEnvJson: JSON.stringify((await settings.get('scriptEnv')) || '{}')
|
|
3696
|
-
},
|
|
3697
|
-
{
|
|
3698
|
-
layout: 'app'
|
|
3699
|
-
}
|
|
3700
|
-
)
|
|
3701
|
-
.takeover();
|
|
3702
|
-
},
|
|
3703
|
-
|
|
3704
|
-
payload: Joi.object({
|
|
3705
|
-
webhook: Joi.string()
|
|
3706
|
-
.base64({ paddingRequired: false, urlSafe: true })
|
|
3707
|
-
.max(512)
|
|
3708
|
-
.example('AAAAAQAACnA')
|
|
3709
|
-
.required()
|
|
3710
|
-
.description('Webhook Route ID'),
|
|
3711
|
-
|
|
3712
|
-
name: Joi.string().max(256).example('Transaction receipt').description('Name of the routing').label('RoutingName').required(),
|
|
3713
|
-
description: Joi.string()
|
|
3714
|
-
.allow('')
|
|
3715
|
-
.max(1024)
|
|
3716
|
-
.example('Something about the routing')
|
|
3717
|
-
.description('Optional description of the webhook routing')
|
|
3718
|
-
.label('RoutingDescription'),
|
|
3719
|
-
targetUrl: Joi.string()
|
|
3720
|
-
.uri({
|
|
3721
|
-
scheme: ['http', 'https'],
|
|
3722
|
-
allowRelative: false
|
|
3723
|
-
})
|
|
3724
|
-
.allow('')
|
|
3725
|
-
.default('')
|
|
3726
|
-
.example('https://myservice.com/imap/webhooks')
|
|
3727
|
-
.description('Webhook target URL'),
|
|
3728
|
-
enabled: Joi.boolean()
|
|
3729
|
-
.truthy('Y', 'true', '1', 'on')
|
|
3730
|
-
.falsy('N', 'false', 0, '')
|
|
3731
|
-
.default(false)
|
|
3732
|
-
.example(false)
|
|
3733
|
-
.description('Is the routing enabled'),
|
|
3734
|
-
customHeaders: Joi.string()
|
|
3735
|
-
.allow('')
|
|
3736
|
-
.trim()
|
|
3737
|
-
.max(10 * 1024)
|
|
3738
|
-
.description('Custom request headers'),
|
|
3739
|
-
contentFnJson: Joi.string()
|
|
3740
|
-
.max(1024 * 1024)
|
|
3741
|
-
.default('')
|
|
3742
|
-
.allow('')
|
|
3743
|
-
.trim()
|
|
3744
|
-
.description('Filter function'),
|
|
3745
|
-
contentMapJson: Joi.string()
|
|
3746
|
-
.max(1024 * 1024)
|
|
3747
|
-
.default('')
|
|
3748
|
-
.allow('')
|
|
3749
|
-
.trim()
|
|
3750
|
-
.description('Map function')
|
|
3751
|
-
})
|
|
3752
|
-
}
|
|
3753
|
-
}
|
|
3754
|
-
});
|
|
3755
|
-
|
|
3756
|
-
server.route({
|
|
3757
|
-
method: 'POST',
|
|
3758
|
-
path: '/admin/webhooks/delete',
|
|
3759
|
-
async handler(request, h) {
|
|
3760
|
-
try {
|
|
3761
|
-
await webhooks.del(request.payload.webhook);
|
|
3762
|
-
|
|
3763
|
-
await request.flash({ type: 'info', message: `Webhook Route was deleted` });
|
|
3764
|
-
|
|
3765
|
-
let accountWebhooksLink = new URL('/admin/webhooks', 'http://localhost');
|
|
3766
|
-
|
|
3767
|
-
return h.redirect(accountWebhooksLink.pathname + accountWebhooksLink.search);
|
|
3768
|
-
} catch (err) {
|
|
3769
|
-
await request.flash({ type: 'danger', message: `Failed to delete the Webhook Route` });
|
|
3770
|
-
request.logger.error({ msg: 'Failed to delete Webhook Route', err, webhook: request.payload.webhook, remoteAddress: request.app.ip });
|
|
3771
|
-
return h.redirect(`/admin/webhooks/webhook/${request.payload.webhook}`);
|
|
3772
|
-
}
|
|
3773
|
-
},
|
|
3774
|
-
options: {
|
|
3775
|
-
validate: {
|
|
3776
|
-
options: {
|
|
3777
|
-
stripUnknown: true,
|
|
3778
|
-
abortEarly: false,
|
|
3779
|
-
convert: true
|
|
3780
|
-
},
|
|
3781
|
-
|
|
3782
|
-
async failAction(request, h, err) {
|
|
3783
|
-
await request.flash({ type: 'danger', message: `Failed to delete Webhook Route` });
|
|
3784
|
-
request.logger.error({ msg: 'Failed to delete delete Webhook Route', err });
|
|
3785
|
-
|
|
3786
|
-
return h.redirect('/admin/webhooks').takeover();
|
|
3787
|
-
},
|
|
3788
|
-
|
|
3789
|
-
payload: Joi.object({
|
|
3790
|
-
webhook: Joi.string()
|
|
3791
|
-
.base64({ paddingRequired: false, urlSafe: true })
|
|
3792
|
-
.max(512)
|
|
3793
|
-
.example('AAAAAQAACnA')
|
|
3794
|
-
.required()
|
|
3795
|
-
.description('Webhook Route ID')
|
|
3796
|
-
})
|
|
3797
|
-
}
|
|
3798
|
-
}
|
|
3799
|
-
});
|
|
3800
|
-
|
|
3801
|
-
server.route({
|
|
3802
|
-
method: 'GET',
|
|
3803
|
-
path: '/admin/templates',
|
|
3804
|
-
async handler(request, h) {
|
|
3805
|
-
let data = await templates.list(request.query.account, request.query.page - 1, request.query.pageSize);
|
|
3806
|
-
|
|
3807
|
-
let nextPage = false;
|
|
3808
|
-
let prevPage = false;
|
|
3809
|
-
|
|
3810
|
-
if (request.query.account) {
|
|
3811
|
-
let accountObject = new Account({ redis, account: request.query.account });
|
|
3812
|
-
data.account = await accountObject.loadAccountData();
|
|
3813
|
-
}
|
|
3814
|
-
|
|
3815
|
-
let getPagingUrl = page => {
|
|
3816
|
-
let url = new URL(`admin/templates`, 'http://localhost');
|
|
3817
|
-
url.searchParams.append('page', page);
|
|
3818
|
-
|
|
3819
|
-
if (request.query.account) {
|
|
3820
|
-
url.searchParams.append('account', request.query.account);
|
|
3821
|
-
}
|
|
3822
|
-
|
|
3823
|
-
if (request.query.pageSize !== DEFAULT_PAGE_SIZE) {
|
|
3824
|
-
url.searchParams.append('pageSize', request.query.pageSize);
|
|
3825
|
-
}
|
|
3826
|
-
|
|
3827
|
-
return url.pathname + url.search;
|
|
3828
|
-
};
|
|
3829
|
-
|
|
3830
|
-
if (data.pages > data.page + 1) {
|
|
3831
|
-
nextPage = getPagingUrl(data.page + 2);
|
|
3832
|
-
}
|
|
3833
|
-
|
|
3834
|
-
if (data.page > 0) {
|
|
3835
|
-
prevPage = getPagingUrl(data.page);
|
|
3836
|
-
}
|
|
3837
|
-
|
|
3838
|
-
let newLink = new URL('/admin/templates/new', 'http://localhost');
|
|
3839
|
-
if (request.query.account) {
|
|
3840
|
-
newLink.searchParams.append('account', request.query.account);
|
|
3841
|
-
}
|
|
3842
|
-
|
|
3843
|
-
return h.view(
|
|
3844
|
-
'templates/index',
|
|
3845
|
-
{
|
|
3846
|
-
pageTitle: 'Templates',
|
|
3847
|
-
menuTemplates: true,
|
|
3848
|
-
|
|
3849
|
-
account: data.account,
|
|
3850
|
-
newLink: newLink.pathname + newLink.search,
|
|
3851
|
-
|
|
3852
|
-
showPaging: data.pages > 1,
|
|
3853
|
-
nextPage,
|
|
3854
|
-
prevPage,
|
|
3855
|
-
firstPage: data.page === 0,
|
|
3856
|
-
pageLinks: new Array(data.pages || 1).fill(0).map((z, i) => ({
|
|
3857
|
-
url: getPagingUrl(i + 1),
|
|
3858
|
-
title: i + 1,
|
|
3859
|
-
active: i === data.page
|
|
3860
|
-
})),
|
|
3861
|
-
|
|
3862
|
-
templates: data.templates
|
|
3863
|
-
},
|
|
3864
|
-
{
|
|
3865
|
-
layout: 'app'
|
|
3866
|
-
}
|
|
3867
|
-
);
|
|
3868
|
-
},
|
|
3869
|
-
|
|
3870
|
-
options: {
|
|
3871
|
-
validate: {
|
|
3872
|
-
options: {
|
|
3873
|
-
stripUnknown: true,
|
|
3874
|
-
abortEarly: false,
|
|
3875
|
-
convert: true
|
|
3876
|
-
},
|
|
3877
|
-
|
|
3878
|
-
async failAction(request, h /*, err*/) {
|
|
3879
|
-
return h.redirect('/admin/templates').takeover();
|
|
3880
|
-
},
|
|
3881
|
-
|
|
3882
|
-
query: Joi.object({
|
|
3883
|
-
account: accountIdSchema.default(null),
|
|
3884
|
-
page: Joi.number().integer().min(1).max(1000000).default(1),
|
|
3885
|
-
pageSize: Joi.number().integer().min(1).max(250).default(DEFAULT_PAGE_SIZE)
|
|
3886
|
-
})
|
|
3887
|
-
}
|
|
3888
|
-
}
|
|
3889
|
-
});
|
|
3890
|
-
|
|
3891
|
-
server.route({
|
|
3892
|
-
method: 'GET',
|
|
3893
|
-
path: '/admin/templates/template/{template}',
|
|
3894
|
-
async handler(request, h) {
|
|
3895
|
-
let template = await templates.get(request.params.template);
|
|
3896
|
-
if (!template) {
|
|
3897
|
-
let error = Boom.boomify(new Error('Template not found.'), { statusCode: 404 });
|
|
3898
|
-
throw error;
|
|
3899
|
-
}
|
|
3900
|
-
|
|
3901
|
-
let account;
|
|
3902
|
-
if (template.account) {
|
|
3903
|
-
let accountObject = new Account({ redis, account: template.account });
|
|
3904
|
-
account = await accountObject.loadAccountData();
|
|
3905
|
-
}
|
|
3906
|
-
|
|
3907
|
-
let accountTemplatesLink = new URL('/admin/templates', 'http://localhost');
|
|
3908
|
-
if (account) {
|
|
3909
|
-
accountTemplatesLink.searchParams.append('account', account.account);
|
|
3910
|
-
}
|
|
3911
|
-
|
|
3912
|
-
return h.view(
|
|
3913
|
-
'templates/template',
|
|
3914
|
-
{
|
|
3915
|
-
pageTitle: 'Templates',
|
|
3916
|
-
menuTemplates: true,
|
|
3917
|
-
|
|
3918
|
-
account,
|
|
3919
|
-
|
|
3920
|
-
accountTemplatesLink: accountTemplatesLink.pathname + accountTemplatesLink.search,
|
|
3921
|
-
|
|
3922
|
-
format: CODE_FORMATS.find(entry => entry.format === template.format),
|
|
3923
|
-
|
|
3924
|
-
template
|
|
3925
|
-
},
|
|
3926
|
-
{
|
|
3927
|
-
layout: 'app'
|
|
3928
|
-
}
|
|
3929
|
-
);
|
|
3930
|
-
},
|
|
3931
|
-
|
|
3932
|
-
options: {
|
|
3933
|
-
validate: {
|
|
3934
|
-
options: {
|
|
3935
|
-
stripUnknown: true,
|
|
3936
|
-
abortEarly: false,
|
|
3937
|
-
convert: true
|
|
3938
|
-
},
|
|
3939
|
-
|
|
3940
|
-
async failAction(request, h /*, err*/) {
|
|
3941
|
-
return h.redirect('/admin/templates').takeover();
|
|
3942
|
-
},
|
|
3943
|
-
|
|
3944
|
-
params: Joi.object({
|
|
3945
|
-
template: Joi.string()
|
|
3946
|
-
.base64({ paddingRequired: false, urlSafe: true })
|
|
3947
|
-
.max(512)
|
|
3948
|
-
.example('AAAAAQAACnA')
|
|
3949
|
-
.required()
|
|
3950
|
-
.description('Template ID')
|
|
3951
|
-
})
|
|
3952
|
-
}
|
|
3953
|
-
}
|
|
3954
|
-
});
|
|
3955
|
-
|
|
3956
|
-
server.route({
|
|
3957
|
-
method: 'GET',
|
|
3958
|
-
path: '/admin/templates/template/{template}/edit',
|
|
3959
|
-
async handler(request, h) {
|
|
3960
|
-
let template = await templates.get(request.params.template);
|
|
3961
|
-
if (!template) {
|
|
3962
|
-
let error = Boom.boomify(new Error('Template not found.'), { statusCode: 404 });
|
|
3963
|
-
throw error;
|
|
3964
|
-
}
|
|
3965
|
-
|
|
3966
|
-
let account;
|
|
3967
|
-
if (template.account) {
|
|
3968
|
-
let accountObject = new Account({ redis, account: template.account });
|
|
3969
|
-
account = await accountObject.loadAccountData();
|
|
3970
|
-
}
|
|
3971
|
-
|
|
3972
|
-
let accountTemplatesLink = new URL('/admin/templates', 'http://localhost');
|
|
3973
|
-
if (account) {
|
|
3974
|
-
accountTemplatesLink.searchParams.append('account', account.account);
|
|
3975
|
-
}
|
|
3976
|
-
|
|
3977
|
-
const values = {
|
|
3978
|
-
template: template.id,
|
|
3979
|
-
name: template.name,
|
|
3980
|
-
description: template.description,
|
|
3981
|
-
subject: template.content.subject,
|
|
3982
|
-
format: template.format,
|
|
3983
|
-
previewText: template.content.previewText
|
|
3984
|
-
};
|
|
3985
|
-
|
|
3986
|
-
return h.view(
|
|
3987
|
-
'templates/edit',
|
|
3988
|
-
{
|
|
3989
|
-
pageTitle: 'Templates',
|
|
3990
|
-
menuTemplates: true,
|
|
3991
|
-
|
|
3992
|
-
account,
|
|
3993
|
-
|
|
3994
|
-
accountTemplatesLink: accountTemplatesLink.pathname + accountTemplatesLink.search,
|
|
3995
|
-
|
|
3996
|
-
template,
|
|
3997
|
-
|
|
3998
|
-
formats: CODE_FORMATS.map(format => Object.assign({ selected: format.format === values.format }, format)),
|
|
3999
|
-
|
|
4000
|
-
values,
|
|
4001
|
-
|
|
4002
|
-
contentHtmlJson: JSON.stringify(template.content.html || ''),
|
|
4003
|
-
contentTextJson: JSON.stringify(template.content.text || '')
|
|
4004
|
-
},
|
|
4005
|
-
{
|
|
4006
|
-
layout: 'app'
|
|
4007
|
-
}
|
|
4008
|
-
);
|
|
4009
|
-
},
|
|
4010
|
-
|
|
4011
|
-
options: {
|
|
4012
|
-
validate: {
|
|
4013
|
-
options: {
|
|
4014
|
-
stripUnknown: true,
|
|
4015
|
-
abortEarly: false,
|
|
4016
|
-
convert: true
|
|
4017
|
-
},
|
|
4018
|
-
|
|
4019
|
-
async failAction(request, h /*, err*/) {
|
|
4020
|
-
return h.redirect('/admin/templates').takeover();
|
|
4021
|
-
},
|
|
4022
|
-
|
|
4023
|
-
params: Joi.object({
|
|
4024
|
-
template: Joi.string()
|
|
4025
|
-
.base64({ paddingRequired: false, urlSafe: true })
|
|
4026
|
-
.max(512)
|
|
4027
|
-
.example('AAAAAQAACnA')
|
|
4028
|
-
.required()
|
|
4029
|
-
.description('Template ID')
|
|
4030
|
-
})
|
|
4031
|
-
}
|
|
4032
|
-
}
|
|
4033
|
-
});
|
|
4034
|
-
|
|
4035
|
-
server.route({
|
|
4036
|
-
method: 'POST',
|
|
4037
|
-
path: '/admin/templates/edit',
|
|
4038
|
-
async handler(request, h) {
|
|
4039
|
-
try {
|
|
4040
|
-
await templates.update(
|
|
4041
|
-
request.payload.template,
|
|
4042
|
-
{
|
|
4043
|
-
name: request.payload.name,
|
|
4044
|
-
description: request.payload.description,
|
|
4045
|
-
format: request.payload.format
|
|
4046
|
-
},
|
|
4047
|
-
{
|
|
4048
|
-
subject: request.payload.subject,
|
|
4049
|
-
html: request.payload.contentHtml,
|
|
4050
|
-
text: request.payload.contentText,
|
|
4051
|
-
previewText: request.payload.previewText
|
|
4052
|
-
}
|
|
4053
|
-
);
|
|
4054
|
-
|
|
4055
|
-
await request.flash({ type: 'info', message: `Template settings were updated` });
|
|
4056
|
-
return h.redirect(`/admin/templates/template/${request.payload.template}`);
|
|
4057
|
-
} catch (err) {
|
|
4058
|
-
await request.flash({ type: 'danger', message: `Failed to update template` });
|
|
4059
|
-
request.logger.error({ msg: 'Failed to update template', err });
|
|
4060
|
-
|
|
4061
|
-
let template = await templates.get(request.payload.template);
|
|
4062
|
-
if (!template) {
|
|
4063
|
-
let error = Boom.boomify(new Error('Template not found.'), { statusCode: 404 });
|
|
4064
|
-
throw error;
|
|
4065
|
-
}
|
|
4066
|
-
|
|
4067
|
-
let account;
|
|
4068
|
-
if (template.account) {
|
|
4069
|
-
let accountObject = new Account({ redis, account: template.account });
|
|
4070
|
-
account = await accountObject.loadAccountData();
|
|
4071
|
-
}
|
|
4072
|
-
|
|
4073
|
-
let accountTemplatesLink = new URL('/admin/templates', 'http://localhost');
|
|
4074
|
-
if (account) {
|
|
4075
|
-
accountTemplatesLink.searchParams.append('account', account.account);
|
|
4076
|
-
}
|
|
4077
|
-
|
|
4078
|
-
return h.view(
|
|
4079
|
-
'templates/edit',
|
|
4080
|
-
{
|
|
4081
|
-
pageTitle: 'Templates',
|
|
4082
|
-
menuTemplates: true,
|
|
4083
|
-
|
|
4084
|
-
account,
|
|
4085
|
-
|
|
4086
|
-
accountTemplatesLink: accountTemplatesLink.pathname + accountTemplatesLink.search,
|
|
4087
|
-
|
|
4088
|
-
template,
|
|
4089
|
-
|
|
4090
|
-
formats: CODE_FORMATS.map(format => Object.assign({ selected: format.format === request.payload.format }, format)),
|
|
4091
|
-
|
|
4092
|
-
errors: err.details,
|
|
4093
|
-
|
|
4094
|
-
contentHtmlJson: JSON.stringify(request.payload.contentHtml || ''),
|
|
4095
|
-
contentTextJson: JSON.stringify(request.payload.contentText || '')
|
|
4096
|
-
},
|
|
4097
|
-
{
|
|
4098
|
-
layout: 'app'
|
|
4099
|
-
}
|
|
4100
|
-
);
|
|
4101
|
-
}
|
|
4102
|
-
},
|
|
4103
|
-
options: {
|
|
4104
|
-
validate: {
|
|
4105
|
-
options: {
|
|
4106
|
-
stripUnknown: true,
|
|
4107
|
-
abortEarly: false,
|
|
4108
|
-
convert: true
|
|
4109
|
-
},
|
|
4110
|
-
|
|
4111
|
-
async failAction(request, h, err) {
|
|
4112
|
-
let errors = {};
|
|
4113
|
-
|
|
4114
|
-
if (err.details) {
|
|
4115
|
-
err.details.forEach(detail => {
|
|
4116
|
-
if (!errors[detail.path]) {
|
|
4117
|
-
errors[detail.path] = detail.message;
|
|
4118
|
-
}
|
|
4119
|
-
});
|
|
4120
|
-
}
|
|
4121
|
-
|
|
4122
|
-
await request.flash({ type: 'danger', message: `Failed to update template` });
|
|
4123
|
-
request.logger.error({ msg: 'Failed to update template', err });
|
|
4124
|
-
|
|
4125
|
-
let template = await templates.get(request.payload.template);
|
|
4126
|
-
if (!template) {
|
|
4127
|
-
let error = Boom.boomify(new Error('Template not found.'), { statusCode: 404 });
|
|
4128
|
-
throw error;
|
|
4129
|
-
}
|
|
4130
|
-
|
|
4131
|
-
let account;
|
|
4132
|
-
if (template.account) {
|
|
4133
|
-
let accountObject = new Account({ redis, account: template.account });
|
|
4134
|
-
account = await accountObject.loadAccountData();
|
|
4135
|
-
}
|
|
4136
|
-
|
|
4137
|
-
let accountTemplatesLink = new URL('/admin/templates', 'http://localhost');
|
|
4138
|
-
if (account) {
|
|
4139
|
-
accountTemplatesLink.searchParams.append('account', account.account);
|
|
4140
|
-
}
|
|
4141
|
-
|
|
4142
|
-
return h
|
|
4143
|
-
.view(
|
|
4144
|
-
'templates/edit',
|
|
4145
|
-
{
|
|
4146
|
-
pageTitle: 'Templates',
|
|
4147
|
-
menuTemplates: true,
|
|
4148
|
-
|
|
4149
|
-
account,
|
|
4150
|
-
|
|
4151
|
-
accountTemplatesLink: accountTemplatesLink.pathname + accountTemplatesLink.search,
|
|
4152
|
-
|
|
4153
|
-
template,
|
|
4154
|
-
|
|
4155
|
-
formats: CODE_FORMATS.map(format => Object.assign({ selected: format.format === request.payload.format }, format)),
|
|
4156
|
-
|
|
4157
|
-
errors,
|
|
4158
|
-
|
|
4159
|
-
contentHtmlJson: JSON.stringify(request.payload.contentHtml || ''),
|
|
4160
|
-
contentTextJson: JSON.stringify(request.payload.contentText || '')
|
|
4161
|
-
},
|
|
4162
|
-
{
|
|
4163
|
-
layout: 'app'
|
|
4164
|
-
}
|
|
4165
|
-
)
|
|
4166
|
-
.takeover();
|
|
4167
|
-
},
|
|
4168
|
-
|
|
4169
|
-
payload: Joi.object({
|
|
4170
|
-
template: Joi.string()
|
|
4171
|
-
.base64({ paddingRequired: false, urlSafe: true })
|
|
4172
|
-
.max(512)
|
|
4173
|
-
.example('AAAAAQAACnA')
|
|
4174
|
-
.required()
|
|
4175
|
-
.description('Template ID'),
|
|
4176
|
-
|
|
4177
|
-
name: Joi.string().max(256).example('Transaction receipt').description('Name of the template').label('TemplateName').required(),
|
|
4178
|
-
description: Joi.string()
|
|
4179
|
-
.allow('')
|
|
4180
|
-
.max(1024)
|
|
4181
|
-
.example('Something about the template')
|
|
4182
|
-
.description('Optional description of the template')
|
|
4183
|
-
.label('TemplateDescription'),
|
|
4184
|
-
format: Joi.string().valid('html', 'markdown').default('html').description('Markup language for HTML ("html" or "markdown")'),
|
|
4185
|
-
subject: templateSchemas.subject,
|
|
4186
|
-
contentText: templateSchemas.text,
|
|
4187
|
-
contentHtml: templateSchemas.html,
|
|
4188
|
-
previewText: templateSchemas.previewText
|
|
4189
|
-
})
|
|
4190
|
-
}
|
|
4191
|
-
}
|
|
4192
|
-
});
|
|
4193
|
-
|
|
4194
|
-
server.route({
|
|
4195
|
-
method: 'GET',
|
|
4196
|
-
path: '/admin/templates/new',
|
|
4197
|
-
async handler(request, h) {
|
|
4198
|
-
let account;
|
|
4199
|
-
if (request.query.account) {
|
|
4200
|
-
let accountObject = new Account({ redis, account: request.query.account });
|
|
4201
|
-
account = await accountObject.loadAccountData();
|
|
4202
|
-
}
|
|
4203
|
-
|
|
4204
|
-
let accountTemplatesLink = new URL('/admin/templates', 'http://localhost');
|
|
4205
|
-
if (account) {
|
|
4206
|
-
accountTemplatesLink.searchParams.append('account', account.account);
|
|
4207
|
-
}
|
|
4208
|
-
|
|
4209
|
-
const values = {
|
|
4210
|
-
account: request.query.account,
|
|
4211
|
-
name: '',
|
|
4212
|
-
description: '',
|
|
4213
|
-
subject: '',
|
|
4214
|
-
format: 'html',
|
|
4215
|
-
contentHtml: '',
|
|
4216
|
-
contentText: '',
|
|
4217
|
-
previewText: ''
|
|
4218
|
-
};
|
|
4219
|
-
|
|
4220
|
-
return h.view(
|
|
4221
|
-
'templates/new',
|
|
4222
|
-
{
|
|
4223
|
-
pageTitle: 'Templates',
|
|
4224
|
-
menuTemplates: true,
|
|
4225
|
-
|
|
4226
|
-
account,
|
|
4227
|
-
|
|
4228
|
-
accountTemplatesLink: accountTemplatesLink.pathname + accountTemplatesLink.search,
|
|
4229
|
-
|
|
4230
|
-
formats: CODE_FORMATS.map(format => Object.assign({ selected: format.format === values.format }, format)),
|
|
4231
|
-
|
|
4232
|
-
values,
|
|
4233
|
-
|
|
4234
|
-
contentHtmlJson: JSON.stringify(''),
|
|
4235
|
-
contentTextJson: JSON.stringify('')
|
|
4236
|
-
},
|
|
4237
|
-
{
|
|
4238
|
-
layout: 'app'
|
|
4239
|
-
}
|
|
4240
|
-
);
|
|
4241
|
-
},
|
|
4242
|
-
|
|
4243
|
-
options: {
|
|
4244
|
-
validate: {
|
|
4245
|
-
options: {
|
|
4246
|
-
stripUnknown: true,
|
|
4247
|
-
abortEarly: false,
|
|
4248
|
-
convert: true
|
|
4249
|
-
},
|
|
4250
|
-
|
|
4251
|
-
async failAction(request, h /*, err*/) {
|
|
4252
|
-
return h.redirect('/admin/templates').takeover();
|
|
4253
|
-
},
|
|
4254
|
-
|
|
4255
|
-
query: Joi.object({
|
|
4256
|
-
account: accountIdSchema.default(null)
|
|
4257
|
-
})
|
|
4258
|
-
}
|
|
4259
|
-
}
|
|
4260
|
-
});
|
|
4261
|
-
|
|
4262
|
-
server.route({
|
|
4263
|
-
method: 'POST',
|
|
4264
|
-
path: '/admin/templates/new',
|
|
4265
|
-
async handler(request, h) {
|
|
4266
|
-
try {
|
|
4267
|
-
let createRequest = await templates.create(
|
|
4268
|
-
request.payload.account,
|
|
4269
|
-
{
|
|
4270
|
-
name: request.payload.name,
|
|
4271
|
-
description: request.payload.description,
|
|
4272
|
-
format: request.payload.format
|
|
4273
|
-
},
|
|
4274
|
-
{
|
|
4275
|
-
subject: request.payload.subject,
|
|
4276
|
-
html: request.payload.contentHtml,
|
|
4277
|
-
text: request.payload.contentText,
|
|
4278
|
-
previewText: request.payload.previewText
|
|
4279
|
-
}
|
|
4280
|
-
);
|
|
4281
|
-
|
|
4282
|
-
await request.flash({ type: 'info', message: `Template was created` });
|
|
4283
|
-
return h.redirect(`/admin/templates/template/${createRequest.id}`);
|
|
4284
|
-
} catch (err) {
|
|
4285
|
-
await request.flash({ type: 'danger', message: `Failed to create template` });
|
|
4286
|
-
request.logger.error({ msg: 'Failed to create template', err });
|
|
4287
|
-
|
|
4288
|
-
let account;
|
|
4289
|
-
if (request.payload.account) {
|
|
4290
|
-
let accountObject = new Account({ redis, account: request.payload.account });
|
|
4291
|
-
account = await accountObject.loadAccountData();
|
|
4292
|
-
}
|
|
4293
|
-
|
|
4294
|
-
let accountTemplatesLink = new URL('/admin/templates', 'http://localhost');
|
|
4295
|
-
if (account) {
|
|
4296
|
-
accountTemplatesLink.searchParams.append('account', account.account);
|
|
4297
|
-
}
|
|
4298
|
-
|
|
4299
|
-
return h.view(
|
|
4300
|
-
'templates/new',
|
|
4301
|
-
{
|
|
4302
|
-
pageTitle: 'Templates',
|
|
4303
|
-
menuTemplates: true,
|
|
4304
|
-
|
|
4305
|
-
account,
|
|
4306
|
-
|
|
4307
|
-
accountTemplatesLink: accountTemplatesLink.pathname + accountTemplatesLink.search,
|
|
4308
|
-
|
|
4309
|
-
formats: CODE_FORMATS.map(format => Object.assign({ selected: format.format === request.payload.format }, format)),
|
|
4310
|
-
|
|
4311
|
-
errors: err.details,
|
|
4312
|
-
|
|
4313
|
-
contentHtmlJson: JSON.stringify(request.payload.contentHtml || ''),
|
|
4314
|
-
contentTextJson: JSON.stringify(request.payload.contentText || '')
|
|
4315
|
-
},
|
|
4316
|
-
{
|
|
4317
|
-
layout: 'app'
|
|
4318
|
-
}
|
|
4319
|
-
);
|
|
4320
|
-
}
|
|
4321
|
-
},
|
|
4322
|
-
options: {
|
|
4323
|
-
validate: {
|
|
4324
|
-
options: {
|
|
4325
|
-
stripUnknown: true,
|
|
4326
|
-
abortEarly: false,
|
|
4327
|
-
convert: true
|
|
4328
|
-
},
|
|
4329
|
-
|
|
4330
|
-
async failAction(request, h, err) {
|
|
4331
|
-
let errors = {};
|
|
4332
|
-
|
|
4333
|
-
if (err.details) {
|
|
4334
|
-
err.details.forEach(detail => {
|
|
4335
|
-
if (!errors[detail.path]) {
|
|
4336
|
-
errors[detail.path] = detail.message;
|
|
4337
|
-
}
|
|
4338
|
-
});
|
|
4339
|
-
}
|
|
4340
|
-
|
|
4341
|
-
await request.flash({ type: 'danger', message: `Failed to create template` });
|
|
4342
|
-
request.logger.error({ msg: 'Failed to create template', err });
|
|
4343
|
-
|
|
4344
|
-
let account;
|
|
4345
|
-
if (request.payload.account) {
|
|
4346
|
-
let accountObject = new Account({ redis, account: request.payload.account });
|
|
4347
|
-
account = await accountObject.loadAccountData();
|
|
4348
|
-
}
|
|
4349
|
-
|
|
4350
|
-
let accountTemplatesLink = new URL('/admin/templates', 'http://localhost');
|
|
4351
|
-
if (account) {
|
|
4352
|
-
accountTemplatesLink.searchParams.append('account', account.account);
|
|
4353
|
-
}
|
|
4354
|
-
|
|
4355
|
-
return h
|
|
4356
|
-
.view(
|
|
4357
|
-
'templates/new',
|
|
4358
|
-
{
|
|
4359
|
-
pageTitle: 'Templates',
|
|
4360
|
-
menuTemplates: true,
|
|
4361
|
-
|
|
4362
|
-
account,
|
|
4363
|
-
|
|
4364
|
-
accountTemplatesLink: accountTemplatesLink.pathname + accountTemplatesLink.search,
|
|
4365
|
-
|
|
4366
|
-
formats: CODE_FORMATS.map(format => Object.assign({ selected: format.format === request.payload.format }, format)),
|
|
4367
|
-
|
|
4368
|
-
errors,
|
|
4369
|
-
|
|
4370
|
-
contentHtmlJson: JSON.stringify(request.payload.contentHtml || ''),
|
|
4371
|
-
contentTextJson: JSON.stringify(request.payload.contentText || '')
|
|
4372
|
-
},
|
|
4373
|
-
{
|
|
4374
|
-
layout: 'app'
|
|
4375
|
-
}
|
|
4376
|
-
)
|
|
4377
|
-
.takeover();
|
|
4378
|
-
},
|
|
4379
|
-
|
|
4380
|
-
payload: Joi.object({
|
|
4381
|
-
account: accountIdSchema.default(null),
|
|
4382
|
-
|
|
4383
|
-
name: Joi.string().max(256).example('Transaction receipt').description('Name of the template').label('TemplateName').required(),
|
|
4384
|
-
description: Joi.string()
|
|
4385
|
-
.allow('')
|
|
4386
|
-
.max(1024)
|
|
4387
|
-
.example('Something about the template')
|
|
4388
|
-
.description('Optional description of the template')
|
|
4389
|
-
.label('TemplateDescription'),
|
|
4390
|
-
format: Joi.string().valid('html', 'markdown').default('html').description('Markup language for HTML ("html" or "markdown")'),
|
|
4391
|
-
subject: templateSchemas.subject,
|
|
4392
|
-
contentText: templateSchemas.text,
|
|
4393
|
-
contentHtml: templateSchemas.html,
|
|
4394
|
-
previewText: templateSchemas.previewText
|
|
4395
|
-
})
|
|
4396
|
-
}
|
|
4397
|
-
}
|
|
4398
|
-
});
|
|
4399
|
-
|
|
4400
|
-
server.route({
|
|
4401
|
-
method: 'POST',
|
|
4402
|
-
path: '/admin/templates/delete',
|
|
4403
|
-
async handler(request, h) {
|
|
4404
|
-
try {
|
|
4405
|
-
let templateResponse = await templates.del(request.payload.template);
|
|
4406
|
-
|
|
4407
|
-
await request.flash({ type: 'info', message: `Template was deleted` });
|
|
4408
|
-
|
|
4409
|
-
let accountTemplatesLink = new URL('/admin/templates', 'http://localhost');
|
|
4410
|
-
if (templateResponse && templateResponse.account) {
|
|
4411
|
-
accountTemplatesLink.searchParams.append('account', templateResponse.account);
|
|
4412
|
-
}
|
|
4413
|
-
|
|
4414
|
-
return h.redirect(accountTemplatesLink.pathname + accountTemplatesLink.search);
|
|
4415
|
-
} catch (err) {
|
|
4416
|
-
await request.flash({ type: 'danger', message: `Failed to delete the template` });
|
|
4417
|
-
request.logger.error({ msg: 'Failed to delete the template', err, template: request.payload.template, remoteAddress: request.app.ip });
|
|
4418
|
-
return h.redirect(`/admin/templates/template/${request.payload.template}`);
|
|
4419
|
-
}
|
|
4420
|
-
},
|
|
4421
|
-
options: {
|
|
4422
|
-
validate: {
|
|
4423
|
-
options: {
|
|
4424
|
-
stripUnknown: true,
|
|
4425
|
-
abortEarly: false,
|
|
4426
|
-
convert: true
|
|
4427
|
-
},
|
|
4428
|
-
|
|
4429
|
-
async failAction(request, h, err) {
|
|
4430
|
-
await request.flash({ type: 'danger', message: `Failed to delete the account` });
|
|
4431
|
-
request.logger.error({ msg: 'Failed to delete delete the account', err });
|
|
4432
|
-
|
|
4433
|
-
return h.redirect('/admin/templates').takeover();
|
|
4434
|
-
},
|
|
4435
|
-
|
|
4436
|
-
payload: Joi.object({
|
|
4437
|
-
template: Joi.string()
|
|
4438
|
-
.base64({ paddingRequired: false, urlSafe: true })
|
|
4439
|
-
.max(512)
|
|
4440
|
-
.example('AAAAAQAACnA')
|
|
4441
|
-
.required()
|
|
4442
|
-
.description('Template ID')
|
|
4443
|
-
})
|
|
4444
|
-
}
|
|
4445
|
-
}
|
|
4446
|
-
});
|
|
4447
|
-
|
|
4448
|
-
server.route({
|
|
4449
|
-
method: 'POST',
|
|
4450
|
-
path: '/admin/templates/test',
|
|
4451
|
-
async handler(request) {
|
|
4452
|
-
try {
|
|
4453
|
-
request.logger.info({ msg: 'Trying to send test message', payload: request.payload });
|
|
4454
|
-
|
|
4455
|
-
let template = await templates.get(request.payload.template);
|
|
4456
|
-
if (!template) {
|
|
4457
|
-
return {
|
|
4458
|
-
error: 'Template was not found'
|
|
4459
|
-
};
|
|
4460
|
-
}
|
|
4461
|
-
|
|
4462
|
-
let accountId = template.account || request.payload.account;
|
|
4463
|
-
if (!accountId) {
|
|
4464
|
-
return { error: 'Account ID not provided' };
|
|
4465
|
-
}
|
|
4466
|
-
|
|
4467
|
-
let accountObject = new Account({ redis, account: accountId, call, secret: await getSecret() });
|
|
4468
|
-
|
|
4469
|
-
let account;
|
|
4470
|
-
try {
|
|
4471
|
-
account = await accountObject.loadAccountData();
|
|
4472
|
-
} catch (err) {
|
|
4473
|
-
return {
|
|
4474
|
-
error: err.message
|
|
4475
|
-
};
|
|
4476
|
-
}
|
|
4477
|
-
|
|
4478
|
-
try {
|
|
4479
|
-
return await accountObject.queueMessage(
|
|
4480
|
-
{
|
|
4481
|
-
account: account.account,
|
|
4482
|
-
template: template.id,
|
|
4483
|
-
from: {
|
|
4484
|
-
name: account.name,
|
|
4485
|
-
address: account.email
|
|
4486
|
-
},
|
|
4487
|
-
to: [{ name: '', address: request.payload.to }],
|
|
4488
|
-
render: {
|
|
4489
|
-
params: request.payload.params || {}
|
|
4490
|
-
},
|
|
4491
|
-
copy: false,
|
|
4492
|
-
deliveryAttempts: 0
|
|
4493
|
-
},
|
|
4494
|
-
{ source: 'ui' }
|
|
4495
|
-
);
|
|
4496
|
-
} catch (err) {
|
|
4497
|
-
return {
|
|
4498
|
-
error: err.message
|
|
4499
|
-
};
|
|
4500
|
-
}
|
|
4501
|
-
} catch (err) {
|
|
4502
|
-
request.logger.error({ msg: 'Failed sending test message', err });
|
|
4503
|
-
return {
|
|
4504
|
-
success: false,
|
|
4505
|
-
error: err.message
|
|
4506
|
-
};
|
|
4507
|
-
}
|
|
4508
|
-
},
|
|
4509
|
-
options: {
|
|
4510
|
-
tags: ['test'],
|
|
4511
|
-
validate: {
|
|
4512
|
-
options: {
|
|
4513
|
-
stripUnknown: true,
|
|
4514
|
-
abortEarly: false,
|
|
4515
|
-
convert: true
|
|
4516
|
-
},
|
|
4517
|
-
|
|
4518
|
-
failAction,
|
|
4519
|
-
|
|
4520
|
-
payload: Joi.object({
|
|
4521
|
-
account: accountIdSchema.default(null),
|
|
4522
|
-
template: Joi.string()
|
|
4523
|
-
.base64({ paddingRequired: false, urlSafe: true })
|
|
4524
|
-
.max(512)
|
|
4525
|
-
.example('AAAAAQAACnA')
|
|
4526
|
-
.required()
|
|
4527
|
-
.description('Template ID'),
|
|
4528
|
-
to: Joi.string().email().required().description('Recipient address'),
|
|
4529
|
-
params: Joi.object().description('Optional handlebars values').unknown()
|
|
4530
|
-
})
|
|
4531
|
-
}
|
|
4532
|
-
}
|
|
4533
|
-
});
|
|
4534
|
-
|
|
4535
|
-
server.route({
|
|
4536
|
-
method: 'GET',
|
|
4537
|
-
path: '/admin/gateways',
|
|
4538
|
-
async handler(request, h) {
|
|
4539
|
-
let gatewayObject = new Gateway({ redis });
|
|
4540
|
-
|
|
4541
|
-
let gateways = await gatewayObject.listGateways(request.query.page - 1, request.query.pageSize);
|
|
4542
|
-
|
|
4543
|
-
if (gateways.pages < request.query.page) {
|
|
4544
|
-
request.query.page = gateways.pages;
|
|
4545
|
-
}
|
|
4546
|
-
|
|
4547
|
-
let nextPage = false;
|
|
4548
|
-
let prevPage = false;
|
|
4549
|
-
|
|
4550
|
-
let getPagingUrl = page => {
|
|
4551
|
-
let url = new URL(`admin/gateways`, 'http://localhost');
|
|
4552
|
-
url.searchParams.append('page', page);
|
|
4553
|
-
if (request.query.pageSize !== DEFAULT_PAGE_SIZE) {
|
|
4554
|
-
url.searchParams.append('pageSize', request.query.pageSize);
|
|
4555
|
-
}
|
|
4556
|
-
return url.pathname + url.search;
|
|
4557
|
-
};
|
|
4558
|
-
|
|
4559
|
-
if (gateways.pages > gateways.page + 1) {
|
|
4560
|
-
nextPage = getPagingUrl(gateways.page + 2);
|
|
4561
|
-
}
|
|
4562
|
-
|
|
4563
|
-
if (gateways.page > 0) {
|
|
4564
|
-
prevPage = getPagingUrl(gateways.page);
|
|
4565
|
-
}
|
|
4566
|
-
|
|
4567
|
-
return h.view(
|
|
4568
|
-
'gateways/index',
|
|
4569
|
-
{
|
|
4570
|
-
pageTitle: 'Email Gateways',
|
|
4571
|
-
menuGateways: true,
|
|
4572
|
-
|
|
4573
|
-
showPaging: gateways.pages > 1,
|
|
4574
|
-
nextPage,
|
|
4575
|
-
prevPage,
|
|
4576
|
-
firstPage: gateways.page === 0,
|
|
4577
|
-
pageLinks: new Array(gateways.pages || 1).fill(0).map((z, i) => ({
|
|
4578
|
-
url: getPagingUrl(i + 1),
|
|
4579
|
-
title: i + 1,
|
|
4580
|
-
active: i === gateways.page
|
|
4581
|
-
})),
|
|
4582
|
-
|
|
4583
|
-
gateways: gateways.gateways.map(entry => {
|
|
4584
|
-
let label = {};
|
|
4585
|
-
if (entry.deliveries && !entry.lastError) {
|
|
4586
|
-
label.type = 'success';
|
|
4587
|
-
label.name = 'Connected';
|
|
4588
|
-
} else if (entry.lastError) {
|
|
4589
|
-
label.type = 'danger';
|
|
4590
|
-
label.name = 'Error';
|
|
4591
|
-
label.error = entry.lastError.response;
|
|
4592
|
-
} else {
|
|
4593
|
-
label.type = 'info';
|
|
4594
|
-
label.name = 'Not used';
|
|
4595
|
-
}
|
|
4596
|
-
|
|
4597
|
-
return Object.assign(entry, {
|
|
4598
|
-
timeStr: entry.lastUse ? entry.lastUse.toISOString() : null,
|
|
4599
|
-
label
|
|
4600
|
-
});
|
|
4601
|
-
})
|
|
4602
|
-
},
|
|
4603
|
-
{
|
|
4604
|
-
layout: 'app'
|
|
4605
|
-
}
|
|
4606
|
-
);
|
|
4607
|
-
},
|
|
4608
|
-
|
|
4609
|
-
options: {
|
|
4610
|
-
validate: {
|
|
4611
|
-
options: {
|
|
4612
|
-
stripUnknown: true,
|
|
4613
|
-
abortEarly: false,
|
|
4614
|
-
convert: true
|
|
4615
|
-
},
|
|
4616
|
-
|
|
4617
|
-
async failAction(request, h /*, err*/) {
|
|
4618
|
-
return h.redirect('/admin/gateways').takeover();
|
|
4619
|
-
},
|
|
4620
|
-
|
|
4621
|
-
query: Joi.object({
|
|
4622
|
-
page: Joi.number().integer().min(1).max(1000000).default(1),
|
|
4623
|
-
pageSize: Joi.number().integer().min(1).max(250).default(DEFAULT_PAGE_SIZE)
|
|
4624
|
-
})
|
|
4625
|
-
}
|
|
4626
|
-
}
|
|
4627
|
-
});
|
|
4628
|
-
|
|
4629
|
-
server.route({
|
|
4630
|
-
method: 'GET',
|
|
4631
|
-
path: '/admin/gateways/new',
|
|
4632
|
-
async handler(request, h) {
|
|
4633
|
-
return h.view(
|
|
4634
|
-
'gateways/new',
|
|
4635
|
-
{
|
|
4636
|
-
pageTitle: 'Email Gateways',
|
|
4637
|
-
menuGateways: true,
|
|
4638
|
-
wellKnownServices: JSON.stringify(Object.keys(wellKnownServices).map(key => Object.assign({ key }, wellKnownServices[key])))
|
|
4639
|
-
},
|
|
4640
|
-
{
|
|
4641
|
-
layout: 'app'
|
|
4642
|
-
}
|
|
4643
|
-
);
|
|
4644
|
-
}
|
|
4645
|
-
});
|
|
4646
|
-
|
|
4647
|
-
server.route({
|
|
4648
|
-
method: 'GET',
|
|
4649
|
-
path: '/admin/gateways/gateway/{gateway}',
|
|
4650
|
-
async handler(request, h) {
|
|
4651
|
-
let gatewayObject = new Gateway({ gateway: request.params.gateway, redis, secret: await getSecret() });
|
|
4652
|
-
let gatewayData = await gatewayObject.loadGatewayData();
|
|
4653
|
-
|
|
4654
|
-
let label = {};
|
|
4655
|
-
if (gatewayData.deliveries && !gatewayData.lastError) {
|
|
4656
|
-
label.type = 'success';
|
|
4657
|
-
label.name = 'Connected';
|
|
4658
|
-
} else if (gatewayData.lastError) {
|
|
4659
|
-
label.type = 'danger';
|
|
4660
|
-
label.name = 'Error';
|
|
4661
|
-
label.error = gatewayData.lastError.response;
|
|
4662
|
-
} else {
|
|
4663
|
-
label.type = 'info';
|
|
4664
|
-
label.name = 'Not used';
|
|
4665
|
-
}
|
|
4666
|
-
|
|
4667
|
-
return h.view(
|
|
4668
|
-
'gateways/gateway',
|
|
4669
|
-
{
|
|
4670
|
-
pageTitle: 'Email Gateways',
|
|
4671
|
-
menuGateways: true,
|
|
4672
|
-
wellKnownServices: JSON.stringify(Object.keys(wellKnownServices).map(key => Object.assign({ key }, wellKnownServices[key]))),
|
|
4673
|
-
|
|
4674
|
-
gateway: gatewayData,
|
|
4675
|
-
label
|
|
4676
|
-
},
|
|
4677
|
-
{
|
|
4678
|
-
layout: 'app'
|
|
4679
|
-
}
|
|
4680
|
-
);
|
|
4681
|
-
},
|
|
4682
|
-
|
|
4683
|
-
options: {
|
|
4684
|
-
validate: {
|
|
4685
|
-
options: {
|
|
4686
|
-
stripUnknown: true,
|
|
4687
|
-
abortEarly: false,
|
|
4688
|
-
convert: true
|
|
4689
|
-
},
|
|
4690
|
-
|
|
4691
|
-
async failAction(request, h, err) {
|
|
4692
|
-
await request.flash({ type: 'danger', message: `Invalid gateway request: ${err.message}` });
|
|
4693
|
-
return h.redirect('/admin/gateways').takeover();
|
|
4694
|
-
},
|
|
4695
|
-
|
|
4696
|
-
params: Joi.object({
|
|
4697
|
-
gateway: Joi.string().max(256).required().example('sendgun').description('Gateway ID')
|
|
4698
|
-
})
|
|
4699
|
-
}
|
|
4700
|
-
}
|
|
4701
|
-
});
|
|
4702
|
-
|
|
4703
|
-
server.route({
|
|
4704
|
-
method: 'GET',
|
|
4705
|
-
path: '/admin/gateways/edit/{gateway}',
|
|
4706
|
-
async handler(request, h) {
|
|
4707
|
-
let gatewayObject = new Gateway({ gateway: request.params.gateway, redis, secret: await getSecret() });
|
|
4708
|
-
let gatewayData = await gatewayObject.loadGatewayData();
|
|
4709
|
-
|
|
4710
|
-
let hasSMTPPass = !!gatewayData.pass;
|
|
4711
|
-
delete gatewayData.pass;
|
|
4712
|
-
|
|
4713
|
-
return h.view(
|
|
4714
|
-
'gateways/edit',
|
|
4715
|
-
{
|
|
4716
|
-
pageTitle: 'Email Gateways',
|
|
4717
|
-
menuGateways: true,
|
|
4718
|
-
wellKnownServices: JSON.stringify(Object.keys(wellKnownServices).map(key => Object.assign({ key }, wellKnownServices[key]))),
|
|
4719
|
-
values: gatewayData,
|
|
4720
|
-
gatewayData,
|
|
4721
|
-
hasSMTPPass
|
|
4722
|
-
},
|
|
4723
|
-
{
|
|
4724
|
-
layout: 'app'
|
|
4725
|
-
}
|
|
4726
|
-
);
|
|
4727
|
-
},
|
|
4728
|
-
|
|
4729
|
-
options: {
|
|
4730
|
-
validate: {
|
|
4731
|
-
options: {
|
|
4732
|
-
stripUnknown: true,
|
|
4733
|
-
abortEarly: false,
|
|
4734
|
-
convert: true
|
|
4735
|
-
},
|
|
4736
|
-
|
|
4737
|
-
async failAction(request, h, err) {
|
|
4738
|
-
await request.flash({ type: 'danger', message: `Invalid gateway request: ${err.message}` });
|
|
4739
|
-
return h.redirect('/admin/gateways').takeover();
|
|
4740
|
-
},
|
|
4741
|
-
|
|
4742
|
-
params: Joi.object({
|
|
4743
|
-
gateway: Joi.string().max(256).required().example('sendgun').description('Gateway ID')
|
|
4744
|
-
})
|
|
4745
|
-
}
|
|
4746
|
-
}
|
|
4747
|
-
});
|
|
4748
|
-
|
|
4749
|
-
server.route({
|
|
4750
|
-
method: 'POST',
|
|
4751
|
-
path: '/admin/gateways/new',
|
|
4752
|
-
async handler(request, h) {
|
|
4753
|
-
try {
|
|
4754
|
-
let gatewayData = {
|
|
4755
|
-
gateway: request.payload.gateway || null,
|
|
4756
|
-
name: request.payload.name || null,
|
|
4757
|
-
host: request.payload.host || null,
|
|
4758
|
-
port: request.payload.port || null,
|
|
4759
|
-
secure: request.payload.secure || null,
|
|
4760
|
-
user: request.payload.user || null,
|
|
4761
|
-
pass: request.payload.pass || null,
|
|
4762
|
-
tls: {}
|
|
4763
|
-
};
|
|
4764
|
-
|
|
4765
|
-
let gatewayObject = new Gateway({ redis, secret: await getSecret() });
|
|
4766
|
-
let result = await gatewayObject.create(gatewayData);
|
|
4767
|
-
|
|
4768
|
-
if (result.state === 'new') {
|
|
4769
|
-
await request.flash({ type: 'success', message: `Added new SMTP gateway`, result });
|
|
4770
|
-
} else {
|
|
4771
|
-
await request.flash({ type: 'success', message: `Updated SMTP gateway`, result });
|
|
4772
|
-
}
|
|
4773
|
-
|
|
4774
|
-
return h.redirect(`/admin/gateways/gateway/${encodeURIComponent(result.gateway)}?state=${result.state}`);
|
|
4775
|
-
} catch (err) {
|
|
4776
|
-
await request.flash({ type: 'danger', message: `Failed to add new gateway` });
|
|
4777
|
-
request.logger.error({ msg: 'Failed to add new gateway', err });
|
|
4778
|
-
|
|
4779
|
-
return h.view(
|
|
4780
|
-
'gateways/new',
|
|
4781
|
-
{
|
|
4782
|
-
pageTitle: 'Email Gateways',
|
|
4783
|
-
menuGateways: true,
|
|
4784
|
-
wellKnownServices: JSON.stringify(Object.keys(wellKnownServices).map(key => Object.assign({ key }, wellKnownServices[key])))
|
|
4785
|
-
},
|
|
4786
|
-
{
|
|
4787
|
-
layout: 'app'
|
|
4788
|
-
}
|
|
4789
|
-
);
|
|
4790
|
-
}
|
|
4791
|
-
},
|
|
4792
|
-
|
|
4793
|
-
options: {
|
|
4794
|
-
validate: {
|
|
4795
|
-
options: {
|
|
4796
|
-
stripUnknown: true,
|
|
4797
|
-
abortEarly: false,
|
|
4798
|
-
convert: true
|
|
4799
|
-
},
|
|
4800
|
-
|
|
4801
|
-
async failAction(request, h, err) {
|
|
4802
|
-
let errors = {};
|
|
4803
|
-
|
|
4804
|
-
if (err.details) {
|
|
4805
|
-
err.details.forEach(detail => {
|
|
4806
|
-
if (!errors[detail.path]) {
|
|
4807
|
-
errors[detail.path] = detail.message;
|
|
4808
|
-
}
|
|
4809
|
-
});
|
|
4810
|
-
}
|
|
4811
|
-
|
|
4812
|
-
await request.flash({ type: 'danger', message: `Failed to add new gateway` });
|
|
4813
|
-
request.logger.error({ msg: 'Failed to add new gateway', err });
|
|
4814
|
-
|
|
4815
|
-
return h
|
|
4816
|
-
.view(
|
|
4817
|
-
'gateways/new',
|
|
4818
|
-
{
|
|
4819
|
-
pageTitle: 'Email Gateways',
|
|
4820
|
-
menuGateways: true,
|
|
4821
|
-
wellKnownServices: JSON.stringify(Object.keys(wellKnownServices).map(key => Object.assign({ key }, wellKnownServices[key]))),
|
|
4822
|
-
|
|
4823
|
-
errors
|
|
4824
|
-
},
|
|
4825
|
-
{
|
|
4826
|
-
layout: 'app'
|
|
4827
|
-
}
|
|
4828
|
-
)
|
|
4829
|
-
.takeover();
|
|
4830
|
-
},
|
|
4831
|
-
|
|
4832
|
-
payload: Joi.object({
|
|
4833
|
-
gateway: Joi.string().empty('').trim().max(256).default(null).example('sendgun').description('Gateway ID').label('Gateway ID'),
|
|
4834
|
-
|
|
4835
|
-
name: Joi.string().empty('').max(256).example('John Smith').description('Account Name').label('Gateway Name').required(),
|
|
4836
|
-
|
|
4837
|
-
user: Joi.string().empty('').trim().max(1024).default(null).label('UserName'),
|
|
4838
|
-
pass: Joi.string().empty('').max(1024).default(null).label('Password'),
|
|
4839
|
-
|
|
4840
|
-
host: Joi.string().hostname().example('smtp.gmail.com').description('Hostname to connect to').label('Hostname').required(),
|
|
4841
|
-
port: Joi.number()
|
|
4842
|
-
.integer()
|
|
4843
|
-
.min(1)
|
|
4844
|
-
.max(64 * 1024)
|
|
4845
|
-
.example(465)
|
|
4846
|
-
.description('Service port number')
|
|
4847
|
-
.label('Port')
|
|
4848
|
-
.required(),
|
|
4849
|
-
|
|
4850
|
-
secure: Joi.boolean()
|
|
4851
|
-
.truthy('Y', 'true', '1', 'on')
|
|
4852
|
-
.falsy('N', 'false', 0, '')
|
|
4853
|
-
.default(false)
|
|
4854
|
-
.example(true)
|
|
4855
|
-
.description('Should connection use TLS. Usually true for port 465')
|
|
4856
|
-
.label('TLS')
|
|
4857
|
-
})
|
|
4858
|
-
}
|
|
4859
|
-
}
|
|
4860
|
-
});
|
|
4861
|
-
|
|
4862
|
-
server.route({
|
|
4863
|
-
method: 'POST',
|
|
4864
|
-
path: '/admin/gateways/edit',
|
|
4865
|
-
async handler(request, h) {
|
|
4866
|
-
try {
|
|
4867
|
-
let gatewayData = {
|
|
4868
|
-
gateway: request.payload.gateway || null,
|
|
4869
|
-
name: request.payload.name || null,
|
|
4870
|
-
host: request.payload.host || null,
|
|
4871
|
-
port: request.payload.port || null,
|
|
4872
|
-
secure: request.payload.secure || null,
|
|
4873
|
-
user: request.payload.user || null
|
|
4874
|
-
};
|
|
4875
|
-
|
|
4876
|
-
if (request.payload.pass) {
|
|
4877
|
-
gatewayData.pass = request.payload.pass;
|
|
4878
|
-
}
|
|
4879
|
-
|
|
4880
|
-
if (!request.payload.user && !request.payload.pass) {
|
|
4881
|
-
gatewayData.pass = null;
|
|
4882
|
-
}
|
|
4883
|
-
|
|
4884
|
-
let gatewayObject = new Gateway({ gateway: request.payload.gateway, redis, secret: await getSecret() });
|
|
4885
|
-
let result = await gatewayObject.update(gatewayData);
|
|
4886
|
-
|
|
4887
|
-
await request.flash({ type: 'success', message: `Updated SMTP gateway`, result });
|
|
4888
|
-
|
|
4889
|
-
return h.redirect(`/admin/gateways/gateway/${encodeURIComponent(result.gateway)}`);
|
|
4890
|
-
} catch (err) {
|
|
4891
|
-
await request.flash({ type: 'danger', message: `Failed to update gateway` });
|
|
4892
|
-
request.logger.error({ msg: 'Failed to update gateway', err });
|
|
4893
|
-
|
|
4894
|
-
let gatewayObject = new Gateway({ gateway: request.payload.gateway, redis, secret: await getSecret() });
|
|
4895
|
-
let gatewayData = await gatewayObject.loadGatewayData();
|
|
4896
|
-
|
|
4897
|
-
let hasSMTPPass = !!gatewayData.pass;
|
|
4898
|
-
|
|
4899
|
-
return h.view(
|
|
4900
|
-
'gateways/edit',
|
|
4901
|
-
{
|
|
4902
|
-
pageTitle: 'Email Gateways',
|
|
4903
|
-
menuGateways: true,
|
|
4904
|
-
wellKnownServices: JSON.stringify(Object.keys(wellKnownServices).map(key => Object.assign({ key }, wellKnownServices[key]))),
|
|
4905
|
-
hasSMTPPass,
|
|
4906
|
-
gatewayData
|
|
4907
|
-
},
|
|
4908
|
-
{
|
|
4909
|
-
layout: 'app'
|
|
4910
|
-
}
|
|
4911
|
-
);
|
|
4912
|
-
}
|
|
4913
|
-
},
|
|
4914
|
-
|
|
4915
|
-
options: {
|
|
4916
|
-
validate: {
|
|
4917
|
-
options: {
|
|
4918
|
-
stripUnknown: true,
|
|
4919
|
-
abortEarly: false,
|
|
4920
|
-
convert: true
|
|
4921
|
-
},
|
|
4922
|
-
|
|
4923
|
-
async failAction(request, h, err) {
|
|
4924
|
-
let errors = {};
|
|
4925
|
-
|
|
4926
|
-
if (err.details) {
|
|
4927
|
-
err.details.forEach(detail => {
|
|
4928
|
-
if (!errors[detail.path]) {
|
|
4929
|
-
errors[detail.path] = detail.message;
|
|
4930
|
-
}
|
|
4931
|
-
});
|
|
4932
|
-
}
|
|
4933
|
-
|
|
4934
|
-
await request.flash({ type: 'danger', message: `Failed to update gateway` });
|
|
4935
|
-
request.logger.error({ msg: 'Failed to update gateway', err });
|
|
4936
|
-
|
|
4937
|
-
let gatewayObject = new Gateway({ gateway: request.payload.gateway, redis, secret: await getSecret() });
|
|
4938
|
-
let gatewayData = await gatewayObject.loadGatewayData();
|
|
4939
|
-
|
|
4940
|
-
let hasSMTPPass = !!gatewayData.pass;
|
|
4941
|
-
|
|
4942
|
-
return h
|
|
4943
|
-
.view(
|
|
4944
|
-
'gateways/edit',
|
|
4945
|
-
{
|
|
4946
|
-
pageTitle: 'Email Gateways',
|
|
4947
|
-
menuGateways: true,
|
|
4948
|
-
wellKnownServices: JSON.stringify(Object.keys(wellKnownServices).map(key => Object.assign({ key }, wellKnownServices[key]))),
|
|
4949
|
-
hasSMTPPass,
|
|
4950
|
-
gatewayData,
|
|
4951
|
-
|
|
4952
|
-
errors
|
|
4953
|
-
},
|
|
4954
|
-
{
|
|
4955
|
-
layout: 'app'
|
|
4956
|
-
}
|
|
4957
|
-
)
|
|
4958
|
-
.takeover();
|
|
4959
|
-
},
|
|
4960
|
-
|
|
4961
|
-
payload: Joi.object({
|
|
4962
|
-
gateway: Joi.string().empty('').trim().max(256).default(null).example('sendgun').description('Gateway ID').label('Gateway ID').required(),
|
|
4963
|
-
|
|
4964
|
-
name: Joi.string().empty('').max(256).example('John Smith').description('Account Name').label('Gateway Name').required(),
|
|
4965
|
-
|
|
4966
|
-
user: Joi.string().empty('').trim().max(1024).default(null).label('UserName'),
|
|
4967
|
-
pass: Joi.string().empty('').max(1024).default(null).label('Password'),
|
|
4968
|
-
|
|
4969
|
-
host: Joi.string().hostname().example('smtp.gmail.com').description('Hostname to connect to').label('Hostname').required(),
|
|
4970
|
-
port: Joi.number()
|
|
4971
|
-
.integer()
|
|
4972
|
-
.min(1)
|
|
4973
|
-
.max(64 * 1024)
|
|
4974
|
-
.example(465)
|
|
4975
|
-
.description('Service port number')
|
|
4976
|
-
.label('Port')
|
|
4977
|
-
.required(),
|
|
4978
|
-
|
|
4979
|
-
secure: Joi.boolean()
|
|
4980
|
-
.truthy('Y', 'true', '1', 'on')
|
|
4981
|
-
.falsy('N', 'false', 0, '')
|
|
4982
|
-
.default(false)
|
|
4983
|
-
.example(true)
|
|
4984
|
-
.description('Should connection use TLS. Usually true for port 465')
|
|
4985
|
-
.label('TLS')
|
|
4986
|
-
})
|
|
4987
|
-
}
|
|
4988
|
-
}
|
|
4989
|
-
});
|
|
4990
|
-
|
|
4991
|
-
server.route({
|
|
4992
|
-
method: 'POST',
|
|
4993
|
-
path: '/admin/gateways/test',
|
|
4994
|
-
async handler(request) {
|
|
4995
|
-
let { gateway, host, port, user, pass, secure } = request.payload;
|
|
4996
|
-
|
|
4997
|
-
try {
|
|
4998
|
-
if (user && !pass && gateway) {
|
|
4999
|
-
let gatewayObject = new Gateway({ gateway, redis, secret: await getSecret() });
|
|
5000
|
-
try {
|
|
5001
|
-
let gatewayData = await gatewayObject.loadGatewayData();
|
|
5002
|
-
if (gatewayData) {
|
|
5003
|
-
pass = gatewayData.pass || '';
|
|
5004
|
-
}
|
|
5005
|
-
} catch (err) {
|
|
5006
|
-
// ignore
|
|
5007
|
-
}
|
|
5008
|
-
}
|
|
5009
|
-
|
|
5010
|
-
let accountData = {
|
|
5011
|
-
smtp: {
|
|
5012
|
-
host,
|
|
5013
|
-
port,
|
|
5014
|
-
secure,
|
|
5015
|
-
auth:
|
|
5016
|
-
user || pass
|
|
5017
|
-
? {
|
|
5018
|
-
user,
|
|
5019
|
-
pass: pass || ''
|
|
5020
|
-
}
|
|
5021
|
-
: false
|
|
5022
|
-
}
|
|
5023
|
-
};
|
|
5024
|
-
|
|
5025
|
-
let verifyResult = await verifyAccountInfo(redis, accountData, request.logger.child({ gateway, action: 'verify-gateway' }));
|
|
5026
|
-
|
|
5027
|
-
if (verifyResult) {
|
|
5028
|
-
if (verifyResult.smtp && verifyResult.smtp.error && verifyResult.smtp.code) {
|
|
5029
|
-
switch (verifyResult.smtp.code) {
|
|
5030
|
-
case 'EDNS':
|
|
5031
|
-
verifyResult.smtp.error = request.app.gt.gettext('Server hostname was not found');
|
|
5032
|
-
break;
|
|
5033
|
-
case 'EAUTH':
|
|
5034
|
-
verifyResult.smtp.error = request.app.gt.gettext('Invalid username or password');
|
|
5035
|
-
break;
|
|
5036
|
-
case 'ESOCKET':
|
|
5037
|
-
if (/openssl/.test(verifyResult.smtp.error)) {
|
|
5038
|
-
verifyResult.smtp.error = request.app.gt.gettext('TLS protocol error');
|
|
5039
|
-
}
|
|
5040
|
-
break;
|
|
5041
|
-
}
|
|
5042
|
-
}
|
|
5043
|
-
}
|
|
5044
|
-
|
|
5045
|
-
return verifyResult.smtp;
|
|
5046
|
-
} catch (err) {
|
|
5047
|
-
request.logger.error({ msg: 'Failed posting request', host, port, user, pass: !!pass, err });
|
|
5048
|
-
return {
|
|
5049
|
-
success: false,
|
|
5050
|
-
error: err.message
|
|
5051
|
-
};
|
|
5052
|
-
}
|
|
5053
|
-
},
|
|
5054
|
-
options: {
|
|
5055
|
-
tags: ['test'],
|
|
5056
|
-
validate: {
|
|
5057
|
-
options: {
|
|
5058
|
-
stripUnknown: true,
|
|
5059
|
-
abortEarly: false,
|
|
5060
|
-
convert: true
|
|
5061
|
-
},
|
|
5062
|
-
|
|
5063
|
-
failAction,
|
|
5064
|
-
|
|
5065
|
-
payload: Joi.object({
|
|
5066
|
-
gateway: Joi.string().empty('').trim().max(256).example('sendgun').description('Gateway ID'),
|
|
5067
|
-
user: Joi.string().empty('').trim().max(1024).label('UserName'),
|
|
5068
|
-
pass: Joi.string().empty('').max(1024).label('Password'),
|
|
5069
|
-
host: Joi.string().hostname().example('smtp.gmail.com').description('Hostname to connect to').label('Hostname'),
|
|
5070
|
-
port: Joi.number()
|
|
5071
|
-
.integer()
|
|
5072
|
-
.min(1)
|
|
5073
|
-
.max(64 * 1024)
|
|
5074
|
-
.example(465)
|
|
5075
|
-
.description('Service port number')
|
|
5076
|
-
.label('Port'),
|
|
5077
|
-
secure: Joi.boolean()
|
|
5078
|
-
.truthy('Y', 'true', '1', 'on')
|
|
5079
|
-
.falsy('N', 'false', 0, '')
|
|
5080
|
-
.default(false)
|
|
5081
|
-
.example(true)
|
|
5082
|
-
.description('Should connection use TLS. Usually true for port 465')
|
|
5083
|
-
.label('TLS')
|
|
5084
|
-
})
|
|
5085
|
-
}
|
|
5086
|
-
}
|
|
5087
|
-
});
|
|
5088
|
-
|
|
5089
|
-
server.route({
|
|
5090
|
-
method: 'POST',
|
|
5091
|
-
path: '/admin/gateways/delete/{gateway}',
|
|
5092
|
-
async handler(request, h) {
|
|
5093
|
-
try {
|
|
5094
|
-
let gatewayObject = new Gateway({ redis, gateway: request.params.gateway, secret: await getSecret() });
|
|
5095
|
-
|
|
5096
|
-
let deleted = await gatewayObject.delete();
|
|
5097
|
-
if (deleted) {
|
|
5098
|
-
await request.flash({ type: 'info', message: `Gateway was deleted` });
|
|
5099
|
-
}
|
|
5100
|
-
|
|
5101
|
-
return h.redirect('/admin/gateways');
|
|
5102
|
-
} catch (err) {
|
|
5103
|
-
await request.flash({ type: 'danger', message: `Failed to delete the gateway` });
|
|
5104
|
-
request.logger.error({ msg: 'Failed to delete the gateway', err, gateway: request.payload.gateway, remoteAddress: request.app.ip });
|
|
5105
|
-
return h.redirect(`/admin/gateways/${request.params.gateway}`);
|
|
5106
|
-
}
|
|
5107
|
-
},
|
|
5108
|
-
options: {
|
|
5109
|
-
validate: {
|
|
5110
|
-
options: {
|
|
5111
|
-
stripUnknown: true,
|
|
5112
|
-
abortEarly: false,
|
|
5113
|
-
convert: true
|
|
5114
|
-
},
|
|
5115
|
-
|
|
5116
|
-
async failAction(request, h, err) {
|
|
5117
|
-
await request.flash({ type: 'danger', message: `Failed to delete the gateway` });
|
|
5118
|
-
request.logger.error({ msg: 'Failed to delete delete the gateway', err });
|
|
5119
|
-
|
|
5120
|
-
return h.redirect('/admin/gateways').takeover();
|
|
5121
|
-
},
|
|
5122
|
-
|
|
5123
|
-
params: Joi.object({
|
|
5124
|
-
gateway: Joi.string().max(256).required().example('sendgun').description('Gateway ID')
|
|
5125
|
-
})
|
|
5126
|
-
}
|
|
5127
|
-
}
|
|
5128
|
-
});
|
|
5129
|
-
|
|
5130
|
-
server.route({
|
|
5131
|
-
method: 'GET',
|
|
5132
|
-
path: '/admin/tokens',
|
|
5133
|
-
async handler(request, h) {
|
|
5134
|
-
let accountData;
|
|
5135
|
-
if (request.query.account) {
|
|
5136
|
-
let accountObject = new Account({ redis, account: request.query.account });
|
|
5137
|
-
accountData = await accountObject.loadAccountData();
|
|
5138
|
-
}
|
|
5139
|
-
|
|
5140
|
-
const data = await tokens.list(request.query.account, request.query.page - 1, request.query.pageSize);
|
|
5141
|
-
|
|
5142
|
-
data.tokens.forEach(entry => {
|
|
5143
|
-
entry.access = entry.access || {};
|
|
5144
|
-
entry.access.timeStr =
|
|
5145
|
-
entry.access && entry.access.time && typeof entry.access.time.toISOString === 'function' ? entry.access.time.toISOString() : null;
|
|
5146
|
-
entry.scopes = entry.scopes
|
|
5147
|
-
? entry.scopes.map((scope, i) => ({
|
|
5148
|
-
name: scope === '*' ? 'all scopes' : scope,
|
|
5149
|
-
first: !i
|
|
5150
|
-
}))
|
|
5151
|
-
: false;
|
|
5152
|
-
});
|
|
5153
|
-
|
|
5154
|
-
let nextPage = false;
|
|
5155
|
-
let prevPage = false;
|
|
5156
|
-
|
|
5157
|
-
let getPagingUrl = page => {
|
|
5158
|
-
let url = new URL(`admin/tokens`, 'http://localhost');
|
|
5159
|
-
|
|
5160
|
-
if (page) {
|
|
5161
|
-
url.searchParams.append('page', page);
|
|
5162
|
-
}
|
|
5163
|
-
|
|
5164
|
-
if (request.query.pageSize !== DEFAULT_PAGE_SIZE) {
|
|
5165
|
-
url.searchParams.append('pageSize', request.query.pageSize);
|
|
5166
|
-
}
|
|
5167
|
-
|
|
5168
|
-
return url.pathname + url.search;
|
|
5169
|
-
};
|
|
5170
|
-
|
|
5171
|
-
if (data.pages > data.page + 1) {
|
|
5172
|
-
nextPage = getPagingUrl(data.page + 2);
|
|
5173
|
-
}
|
|
5174
|
-
|
|
5175
|
-
if (data.page > 0) {
|
|
5176
|
-
prevPage = getPagingUrl(data.page);
|
|
5177
|
-
}
|
|
5178
|
-
|
|
5179
|
-
let newLink = new URL('/admin/tokens/new', 'http://localhost');
|
|
5180
|
-
if (request.query.account) {
|
|
5181
|
-
newLink.searchParams.append('account', request.query.account);
|
|
5182
|
-
}
|
|
5183
|
-
|
|
5184
|
-
return h.view(
|
|
5185
|
-
'tokens/index',
|
|
5186
|
-
{
|
|
5187
|
-
pageTitle: 'Access Tokens',
|
|
5188
|
-
menuTokens: true,
|
|
5189
|
-
data,
|
|
5190
|
-
|
|
5191
|
-
account: accountData,
|
|
5192
|
-
|
|
5193
|
-
showPaging: data.pages > 1,
|
|
5194
|
-
nextPage,
|
|
5195
|
-
prevPage,
|
|
5196
|
-
firstPage: data.page === 0,
|
|
5197
|
-
pageLinks: new Array(data.pages || 1).fill(0).map((z, i) => ({
|
|
5198
|
-
url: getPagingUrl(i + 1, request.query.state, request.query.query),
|
|
5199
|
-
title: i + 1,
|
|
5200
|
-
active: i === data.page
|
|
5201
|
-
})),
|
|
2833
|
+
defaultRedirectUrl,
|
|
5202
2834
|
|
|
5203
|
-
|
|
5204
|
-
},
|
|
5205
|
-
{
|
|
5206
|
-
layout: 'app'
|
|
5207
|
-
}
|
|
5208
|
-
);
|
|
5209
|
-
},
|
|
2835
|
+
appData,
|
|
5210
2836
|
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
options: {
|
|
5214
|
-
stripUnknown: true,
|
|
5215
|
-
abortEarly: false,
|
|
5216
|
-
convert: true
|
|
5217
|
-
},
|
|
2837
|
+
hasClientSecret: !!appData.clientSecret,
|
|
2838
|
+
hasServiceKey: !!appData.serviceKey,
|
|
5218
2839
|
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
2840
|
+
pubSubApps:
|
|
2841
|
+
pubSubApps &&
|
|
2842
|
+
pubSubApps.apps &&
|
|
2843
|
+
pubSubApps.apps.map(app => {
|
|
2844
|
+
if (app.id === values.pubSubApp) {
|
|
2845
|
+
app.selected = true;
|
|
2846
|
+
}
|
|
2847
|
+
return app;
|
|
2848
|
+
}),
|
|
5222
2849
|
|
|
5223
|
-
|
|
5224
|
-
account: accountIdSchema.default(null),
|
|
5225
|
-
page: Joi.number().integer().min(1).max(1000000).default(1),
|
|
5226
|
-
pageSize: Joi.number().integer().min(1).max(250).default(DEFAULT_PAGE_SIZE)
|
|
5227
|
-
})
|
|
5228
|
-
}
|
|
5229
|
-
}
|
|
5230
|
-
});
|
|
2850
|
+
values,
|
|
5231
2851
|
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
async handler(request, h) {
|
|
5236
|
-
let accountTokensLink = new URL('/admin/tokens', 'http://localhost');
|
|
2852
|
+
baseScopesApi: values.baseScopes === 'api',
|
|
2853
|
+
baseScopesImap: values.baseScopes === 'imap' || !values.baseScopes,
|
|
2854
|
+
baseScopesPubsub: values.baseScopes === 'pubsub',
|
|
5237
2855
|
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
accountTokensLink.searchParams.append('account', request.query.account);
|
|
5243
|
-
}
|
|
2856
|
+
azureClouds: structuredClone(AZURE_CLOUDS).map(entry => {
|
|
2857
|
+
entry.selected = values.cloud === entry.id;
|
|
2858
|
+
return entry;
|
|
2859
|
+
}),
|
|
5244
2860
|
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
|
|
5248
|
-
|
|
5249
|
-
menuTokens: true,
|
|
5250
|
-
values: {
|
|
5251
|
-
scopesAll: true,
|
|
5252
|
-
allAccounts: !request.query.account,
|
|
5253
|
-
account: request.query.account
|
|
5254
|
-
},
|
|
5255
|
-
account: accountData,
|
|
5256
|
-
accountTokensLink: accountTokensLink.pathname + accountTokensLink.search
|
|
2861
|
+
authorityCommon: values.authority === 'common',
|
|
2862
|
+
authorityOrganizations: values.authority === 'organizations',
|
|
2863
|
+
authorityConsumers: values.authority === 'consumers',
|
|
2864
|
+
authorityTenant: !!values.tenant
|
|
5257
2865
|
},
|
|
5258
2866
|
{
|
|
5259
2867
|
layout: 'app'
|
|
@@ -5270,11 +2878,11 @@ return payload;`)
|
|
|
5270
2878
|
},
|
|
5271
2879
|
|
|
5272
2880
|
async failAction(request, h /*, err*/) {
|
|
5273
|
-
return h.redirect('/admin/
|
|
2881
|
+
return h.redirect('/admin/config/oauth').takeover();
|
|
5274
2882
|
},
|
|
5275
2883
|
|
|
5276
|
-
|
|
5277
|
-
|
|
2884
|
+
params: Joi.object({
|
|
2885
|
+
app: Joi.string().empty('').max(255).example('gmail').label('Provider').required()
|
|
5278
2886
|
})
|
|
5279
2887
|
}
|
|
5280
2888
|
}
|
|
@@ -5282,74 +2890,99 @@ return payload;`)
|
|
|
5282
2890
|
|
|
5283
2891
|
server.route({
|
|
5284
2892
|
method: 'POST',
|
|
5285
|
-
path: '/admin/
|
|
2893
|
+
path: '/admin/config/oauth/edit',
|
|
2894
|
+
async handler(request, h) {
|
|
2895
|
+
let appData = await oauth2Apps.get(request.payload.app);
|
|
2896
|
+
if (!appData) {
|
|
2897
|
+
let error = Boom.boomify(new Error('Application was not found.'), { statusCode: 404 });
|
|
2898
|
+
throw error;
|
|
2899
|
+
}
|
|
5286
2900
|
|
|
5287
|
-
async handler(request) {
|
|
5288
2901
|
try {
|
|
5289
|
-
let
|
|
5290
|
-
|
|
5291
|
-
|
|
5292
|
-
|
|
5293
|
-
|
|
5294
|
-
|
|
2902
|
+
let updates = Object.assign({}, request.payload);
|
|
2903
|
+
updates.extraScopes = updates.extraScopes
|
|
2904
|
+
.split(/\s+/)
|
|
2905
|
+
.map(scope => scope.trim())
|
|
2906
|
+
.filter(scope => scope);
|
|
2907
|
+
|
|
2908
|
+
updates.skipScopes = updates.skipScopes
|
|
2909
|
+
.split(/\s+/)
|
|
2910
|
+
.map(scope => scope.trim())
|
|
2911
|
+
.filter(scope => scope);
|
|
5295
2912
|
|
|
5296
|
-
if (
|
|
5297
|
-
|
|
5298
|
-
await accountObject.loadAccountData();
|
|
5299
|
-
data.account = request.payload.account;
|
|
2913
|
+
if (updates.authority === 'tenant') {
|
|
2914
|
+
updates.authority = updates.tenant;
|
|
5300
2915
|
}
|
|
2916
|
+
delete updates.tenant;
|
|
5301
2917
|
|
|
5302
|
-
let
|
|
2918
|
+
let oauth2App = await oauth2Apps.update(appData.id, updates);
|
|
2919
|
+
if (!oauth2App || !oauth2App.id) {
|
|
2920
|
+
throw new Error('Unexpected result');
|
|
2921
|
+
}
|
|
5303
2922
|
|
|
5304
|
-
|
|
5305
|
-
|
|
5306
|
-
token
|
|
5307
|
-
};
|
|
5308
|
-
} catch (err) {
|
|
5309
|
-
request.logger.error({ msg: 'Failed to generate token', err, remoteAddress: request.app.ip, description: request.payload.description });
|
|
5310
|
-
if (Boom.isBoom(err)) {
|
|
5311
|
-
return Object.assign({ success: false }, err.output.payload);
|
|
2923
|
+
if (oauth2App && oauth2App.pubsubUpdates && oauth2App.pubsubUpdates.pubSubSubscription) {
|
|
2924
|
+
await call({ cmd: 'googlePubSub', app: oauth2App.id });
|
|
5312
2925
|
}
|
|
5313
|
-
return { success: false, error: err.code || 'Error', message: err.message };
|
|
5314
|
-
}
|
|
5315
|
-
},
|
|
5316
|
-
options: {
|
|
5317
|
-
validate: {
|
|
5318
|
-
options: {
|
|
5319
|
-
stripUnknown: true,
|
|
5320
|
-
abortEarly: false,
|
|
5321
|
-
convert: true
|
|
5322
|
-
},
|
|
5323
2926
|
|
|
5324
|
-
|
|
2927
|
+
await request.flash({ type: 'success', message: `OAuth2 app saved` });
|
|
2928
|
+
return h.redirect(`/admin/config/oauth/app/${oauth2App.id}`);
|
|
2929
|
+
} catch (err) {
|
|
2930
|
+
await request.flash({ type: 'danger', message: `Couldn't save OAuth2 app. Try again.` });
|
|
2931
|
+
request.logger.error({ msg: 'Failed to update OAuth2 app', app: request.payload.app, err });
|
|
5325
2932
|
|
|
5326
|
-
|
|
5327
|
-
description: Joi.string().empty('').trim().max(1024).required().example('Token description').description('Token description'),
|
|
5328
|
-
scopes: Joi.array()
|
|
5329
|
-
.items(Joi.string().valid('*', 'api', 'metrics', 'smtp', 'imap-proxy'))
|
|
5330
|
-
.required()
|
|
5331
|
-
.label('Scopes'),
|
|
5332
|
-
account: accountIdSchema.default(null)
|
|
5333
|
-
})
|
|
5334
|
-
}
|
|
5335
|
-
}
|
|
5336
|
-
});
|
|
2933
|
+
let providerData = oauth2ProviderData(appData.provider, appData.cloud);
|
|
5337
2934
|
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
|
|
5341
|
-
|
|
5342
|
-
try {
|
|
5343
|
-
let deleted = await tokens.delete(request.payload.token, { remoteAddress: request.app.ip });
|
|
5344
|
-
if (deleted) {
|
|
5345
|
-
await request.flash({ type: 'info', message: `Access token was deleted` });
|
|
2935
|
+
let serviceUrl = await settings.get('serviceUrl');
|
|
2936
|
+
let defaultRedirectUrl = `${serviceUrl}/oauth`;
|
|
2937
|
+
if (appData.provider === 'outlook') {
|
|
2938
|
+
defaultRedirectUrl = defaultRedirectUrl.replace(/^http:\/\/127\.0\.0\.1\b/i, 'http://localhost');
|
|
5346
2939
|
}
|
|
5347
2940
|
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
|
|
5352
|
-
|
|
2941
|
+
let pubSubApps = await oauth2Apps.list(0, 1000, { pubsub: true });
|
|
2942
|
+
|
|
2943
|
+
return h.view(
|
|
2944
|
+
'config/oauth/edit',
|
|
2945
|
+
{
|
|
2946
|
+
pageTitle: 'OAuth2',
|
|
2947
|
+
menuConfig: true,
|
|
2948
|
+
menuConfigOauth: true,
|
|
2949
|
+
|
|
2950
|
+
[`active${providerData.caseName}`]: true,
|
|
2951
|
+
providerData,
|
|
2952
|
+
defaultRedirectUrl,
|
|
2953
|
+
appData,
|
|
2954
|
+
|
|
2955
|
+
hasClientSecret: !!appData.clientSecret,
|
|
2956
|
+
hasServiceKey: !!appData.serviceKey,
|
|
2957
|
+
|
|
2958
|
+
pubSubApps:
|
|
2959
|
+
pubSubApps &&
|
|
2960
|
+
pubSubApps.apps &&
|
|
2961
|
+
pubSubApps.apps.map(app => {
|
|
2962
|
+
if (app.id === request.payload.pubSubApp) {
|
|
2963
|
+
app.selected = true;
|
|
2964
|
+
}
|
|
2965
|
+
return app;
|
|
2966
|
+
}),
|
|
2967
|
+
|
|
2968
|
+
baseScopesApi: request.payload.baseScopes === 'api',
|
|
2969
|
+
baseScopesImap: request.payload.baseScopes === 'imap' || !request.payload.baseScopes,
|
|
2970
|
+
baseScopesPubsub: request.payload.baseScopes === 'pubsub',
|
|
2971
|
+
|
|
2972
|
+
azureClouds: structuredClone(AZURE_CLOUDS).map(entry => {
|
|
2973
|
+
entry.selected = request.payload.cloud === entry.id;
|
|
2974
|
+
return entry;
|
|
2975
|
+
}),
|
|
2976
|
+
|
|
2977
|
+
authorityCommon: request.payload.authority === 'common',
|
|
2978
|
+
authorityOrganizations: request.payload.authority === 'organizations',
|
|
2979
|
+
authorityConsumers: request.payload.authority === 'consumers',
|
|
2980
|
+
authorityTenant: request.payload.authority === 'tenant'
|
|
2981
|
+
},
|
|
2982
|
+
{
|
|
2983
|
+
layout: 'app'
|
|
2984
|
+
}
|
|
2985
|
+
);
|
|
5353
2986
|
}
|
|
5354
2987
|
},
|
|
5355
2988
|
options: {
|
|
@@ -5361,17 +2994,98 @@ return payload;`)
|
|
|
5361
2994
|
},
|
|
5362
2995
|
|
|
5363
2996
|
async failAction(request, h, err) {
|
|
5364
|
-
|
|
5365
|
-
|
|
2997
|
+
let errors = {};
|
|
2998
|
+
|
|
2999
|
+
if (err.details) {
|
|
3000
|
+
err.details.forEach(detail => {
|
|
3001
|
+
if (!errors[detail.path]) {
|
|
3002
|
+
errors[detail.path] = detail.message;
|
|
3003
|
+
}
|
|
3004
|
+
});
|
|
3005
|
+
}
|
|
3006
|
+
|
|
3007
|
+
let appData = await oauth2Apps.get(request.payload.app);
|
|
3008
|
+
if (!appData) {
|
|
3009
|
+
await request.flash({ type: 'danger', message: `Application not found` });
|
|
3010
|
+
request.logger.error({ msg: 'Application was not found.', app: request.payload.app });
|
|
3011
|
+
return h.redirect('/admin').takeover();
|
|
3012
|
+
}
|
|
3013
|
+
|
|
3014
|
+
await request.flash({ type: 'danger', message: `Couldn't save OAuth2 app. Try again.` });
|
|
3015
|
+
request.logger.error({ msg: 'Failed to update OAuth2 app', err });
|
|
3016
|
+
|
|
3017
|
+
let { provider } = request.payload;
|
|
3018
|
+
if (!provider || !OAUTH_PROVIDERS.hasOwnProperty(provider)) {
|
|
3019
|
+
return h.redirect('/admin').takeover();
|
|
3020
|
+
}
|
|
3021
|
+
|
|
3022
|
+
let providerData = oauth2ProviderData(provider);
|
|
3023
|
+
|
|
3024
|
+
let serviceUrl = await settings.get('serviceUrl');
|
|
3025
|
+
let defaultRedirectUrl = `${serviceUrl}/oauth`;
|
|
3026
|
+
if (provider === 'outlook') {
|
|
3027
|
+
defaultRedirectUrl = defaultRedirectUrl.replace(/^http:\/\/127\.0\.0\.1\b/i, 'http://localhost');
|
|
3028
|
+
}
|
|
3029
|
+
|
|
3030
|
+
let pubSubApps = await oauth2Apps.list(0, 1000, { pubsub: true });
|
|
3031
|
+
|
|
3032
|
+
return h
|
|
3033
|
+
.view(
|
|
3034
|
+
'config/oauth/edit',
|
|
3035
|
+
{
|
|
3036
|
+
pageTitle: 'OAuth2',
|
|
3037
|
+
menuConfig: true,
|
|
3038
|
+
menuConfigOauth: true,
|
|
3039
|
+
|
|
3040
|
+
[`active${providerData.caseName}`]: true,
|
|
3041
|
+
providerData,
|
|
3042
|
+
defaultRedirectUrl,
|
|
3043
|
+
|
|
3044
|
+
appData,
|
|
3045
|
+
|
|
3046
|
+
hasClientSecret: !!appData.clientSecret,
|
|
3047
|
+
hasServiceKey: !!appData.serviceKey,
|
|
3048
|
+
|
|
3049
|
+
pubSubApps:
|
|
3050
|
+
pubSubApps &&
|
|
3051
|
+
pubSubApps.apps &&
|
|
3052
|
+
pubSubApps.apps.map(app => {
|
|
3053
|
+
if (app.id === request.payload.pubSubApp) {
|
|
3054
|
+
app.selected = true;
|
|
3055
|
+
}
|
|
3056
|
+
return app;
|
|
3057
|
+
}),
|
|
3058
|
+
|
|
3059
|
+
baseScopesApi: request.payload.baseScopes === 'api',
|
|
3060
|
+
baseScopesImap: request.payload.baseScopes === 'imap' || !request.payload.baseScopes,
|
|
3061
|
+
baseScopesPubsub: request.payload.baseScopes === 'pubsub',
|
|
3062
|
+
|
|
3063
|
+
azureClouds: structuredClone(AZURE_CLOUDS).map(entry => {
|
|
3064
|
+
entry.selected = request.payload.cloud === entry.id;
|
|
3065
|
+
return entry;
|
|
3066
|
+
}),
|
|
5366
3067
|
|
|
5367
|
-
|
|
3068
|
+
authorityCommon: request.payload.authority === 'common',
|
|
3069
|
+
authorityOrganizations: request.payload.authority === 'organizations',
|
|
3070
|
+
authorityConsumers: request.payload.authority === 'consumers',
|
|
3071
|
+
authorityTenant: request.payload.authority === 'tenant',
|
|
3072
|
+
|
|
3073
|
+
errors
|
|
3074
|
+
},
|
|
3075
|
+
{
|
|
3076
|
+
layout: 'app'
|
|
3077
|
+
}
|
|
3078
|
+
)
|
|
3079
|
+
.takeover();
|
|
5368
3080
|
},
|
|
5369
3081
|
|
|
5370
|
-
payload: Joi.object(
|
|
3082
|
+
payload: Joi.object(oauthUpdateSchema)
|
|
5371
3083
|
}
|
|
5372
3084
|
}
|
|
5373
3085
|
});
|
|
5374
3086
|
|
|
3087
|
+
// Webhook, template, gateway, and token routes are in admin-entities-routes.js
|
|
3088
|
+
|
|
5375
3089
|
server.route({
|
|
5376
3090
|
method: 'GET',
|
|
5377
3091
|
path: '/admin/config/license',
|
|
@@ -5424,12 +3138,12 @@ return payload;`)
|
|
|
5424
3138
|
}
|
|
5425
3139
|
|
|
5426
3140
|
if (licenseInfo.active) {
|
|
5427
|
-
await request.flash({ type: 'info', message: `License
|
|
3141
|
+
await request.flash({ type: 'info', message: `License activated` });
|
|
5428
3142
|
}
|
|
5429
3143
|
|
|
5430
3144
|
return h.redirect('/admin/config/license');
|
|
5431
3145
|
} catch (err) {
|
|
5432
|
-
await request.flash({ type: 'danger', message: `
|
|
3146
|
+
await request.flash({ type: 'danger', message: `Couldn't register license. Check the key and try again.` });
|
|
5433
3147
|
request.logger.error({ msg: 'Failed to register license key', err });
|
|
5434
3148
|
|
|
5435
3149
|
return h.view(
|
|
@@ -5471,7 +3185,7 @@ return payload;`)
|
|
|
5471
3185
|
});
|
|
5472
3186
|
}
|
|
5473
3187
|
|
|
5474
|
-
await request.flash({ type: 'danger', message: `
|
|
3188
|
+
await request.flash({ type: 'danger', message: `Couldn't register license. Check the key and try again.` });
|
|
5475
3189
|
request.logger.error({ msg: 'Failed to register license key', err });
|
|
5476
3190
|
|
|
5477
3191
|
return h
|
|
@@ -5519,12 +3233,12 @@ return payload;`)
|
|
|
5519
3233
|
err.statusCode = 403;
|
|
5520
3234
|
throw err;
|
|
5521
3235
|
} else {
|
|
5522
|
-
await request.flash({ type: 'info', message: `License
|
|
3236
|
+
await request.flash({ type: 'info', message: `License removed` });
|
|
5523
3237
|
}
|
|
5524
3238
|
|
|
5525
3239
|
return h.redirect('/admin/config/license');
|
|
5526
3240
|
} catch (err) {
|
|
5527
|
-
await request.flash({ type: 'danger', message: `
|
|
3241
|
+
await request.flash({ type: 'danger', message: `Couldn't remove license. Try again.` });
|
|
5528
3242
|
request.logger.error({ msg: 'Failed to unregister license key', err, token: request.payload.token, remoteAddress: request.app.ip });
|
|
5529
3243
|
return h.redirect('/admin/config/license');
|
|
5530
3244
|
}
|
|
@@ -5538,7 +3252,7 @@ return payload;`)
|
|
|
5538
3252
|
},
|
|
5539
3253
|
|
|
5540
3254
|
async failAction(request, h, err) {
|
|
5541
|
-
await request.flash({ type: 'danger', message: `
|
|
3255
|
+
await request.flash({ type: 'danger', message: `Couldn't remove license. Try again.` });
|
|
5542
3256
|
request.logger.error({ msg: 'Failed to unregister license key', err });
|
|
5543
3257
|
|
|
5544
3258
|
return h.redirect('/admin/config/license').takeover();
|
|
@@ -5601,8 +3315,8 @@ ${Buffer.from(data.content, 'base64url').toString('base64')}
|
|
|
5601
3315
|
}
|
|
5602
3316
|
|
|
5603
3317
|
if (licenseInfo.active) {
|
|
5604
|
-
await request.flash({ type: 'info', message: `Trial
|
|
5605
|
-
return { success: true, message: `Trial
|
|
3318
|
+
await request.flash({ type: 'info', message: `Trial activated` });
|
|
3319
|
+
return { success: true, message: `Trial activated` };
|
|
5606
3320
|
}
|
|
5607
3321
|
|
|
5608
3322
|
throw new Error('Failed to activate provisioned trial license');
|
|
@@ -5765,7 +3479,7 @@ ${Buffer.from(data.content, 'base64url').toString('base64')}
|
|
|
5765
3479
|
return h.redirect('/admin');
|
|
5766
3480
|
}
|
|
5767
3481
|
} catch (err) {
|
|
5768
|
-
await request.flash({ type: 'danger', message: err.responseText || `
|
|
3482
|
+
await request.flash({ type: 'danger', message: err.responseText || `Sign-in failed. Check your password and try again.` });
|
|
5769
3483
|
request.logger.error({ msg: 'Failed to authenticate', err });
|
|
5770
3484
|
|
|
5771
3485
|
let errors = err.details;
|
|
@@ -5805,7 +3519,7 @@ ${Buffer.from(data.content, 'base64url').toString('base64')}
|
|
|
5805
3519
|
});
|
|
5806
3520
|
}
|
|
5807
3521
|
|
|
5808
|
-
await request.flash({ type: 'danger', message: `
|
|
3522
|
+
await request.flash({ type: 'danger', message: `Sign-in failed. Check your password and try again.` });
|
|
5809
3523
|
request.logger.error({ msg: 'Failed to authenticate', err });
|
|
5810
3524
|
|
|
5811
3525
|
return h
|
|
@@ -5914,7 +3628,7 @@ ${Buffer.from(data.content, 'base64url').toString('base64')}
|
|
|
5914
3628
|
|
|
5915
3629
|
let totpSeed = await settings.get('totpSeed');
|
|
5916
3630
|
if (!totpSeed) {
|
|
5917
|
-
await request.flash({ type: 'danger', message: `
|
|
3631
|
+
await request.flash({ type: 'danger', message: `Start two-factor auth setup first` });
|
|
5918
3632
|
return h.redirect(`/admin/login`);
|
|
5919
3633
|
}
|
|
5920
3634
|
|
|
@@ -5954,7 +3668,7 @@ ${Buffer.from(data.content, 'base64url').toString('base64')}
|
|
|
5954
3668
|
} catch (err) {
|
|
5955
3669
|
if (!err.details || !err.details.code) {
|
|
5956
3670
|
// skip error message if code is invalid
|
|
5957
|
-
await request.flash({ type: 'danger', message: err.responseText || `
|
|
3671
|
+
await request.flash({ type: 'danger', message: err.responseText || `Verification failed. Check your code and try again.` });
|
|
5958
3672
|
}
|
|
5959
3673
|
|
|
5960
3674
|
request.logger.error({ msg: 'Failed to verify login', err });
|
|
@@ -5993,7 +3707,7 @@ ${Buffer.from(data.content, 'base64url').toString('base64')}
|
|
|
5993
3707
|
});
|
|
5994
3708
|
}
|
|
5995
3709
|
|
|
5996
|
-
await request.flash({ type: 'danger', message: `
|
|
3710
|
+
await request.flash({ type: 'danger', message: `Verification failed. Check your code and try again.` });
|
|
5997
3711
|
request.logger.error({ msg: 'Failed to verify login', err });
|
|
5998
3712
|
|
|
5999
3713
|
return h
|
|
@@ -6112,7 +3826,7 @@ ${Buffer.from(data.content, 'base64url').toString('base64')}
|
|
|
6112
3826
|
try {
|
|
6113
3827
|
let totpSeed = await settings.get('totpSeed');
|
|
6114
3828
|
if (!totpSeed) {
|
|
6115
|
-
await request.flash({ type: 'danger', message: `
|
|
3829
|
+
await request.flash({ type: 'danger', message: `Start two-factor auth setup first` });
|
|
6116
3830
|
return h.redirect(`/admin/account/security`);
|
|
6117
3831
|
}
|
|
6118
3832
|
|
|
@@ -6124,7 +3838,7 @@ ${Buffer.from(data.content, 'base64url').toString('base64')}
|
|
|
6124
3838
|
});
|
|
6125
3839
|
|
|
6126
3840
|
if (!verified) {
|
|
6127
|
-
await request.flash({ type: 'danger', message: `
|
|
3841
|
+
await request.flash({ type: 'danger', message: `Invalid verification code` });
|
|
6128
3842
|
return h.redirect(`/admin/account/security`);
|
|
6129
3843
|
}
|
|
6130
3844
|
|
|
@@ -6140,10 +3854,10 @@ ${Buffer.from(data.content, 'base64url').toString('base64')}
|
|
|
6140
3854
|
}
|
|
6141
3855
|
}
|
|
6142
3856
|
|
|
6143
|
-
await request.flash({ type: 'success', message: `Two-factor
|
|
3857
|
+
await request.flash({ type: 'success', message: `Two-factor auth enabled` });
|
|
6144
3858
|
return h.redirect(`/admin/account/security`);
|
|
6145
3859
|
} catch (err) {
|
|
6146
|
-
await request.flash({ type: 'danger', message: `
|
|
3860
|
+
await request.flash({ type: 'danger', message: `Couldn't enable two-factor auth. Try again.` });
|
|
6147
3861
|
request.logger.error({ msg: 'Failed to enable 2FA', err, remoteAddress: request.app.ip });
|
|
6148
3862
|
return h.redirect(`/admin/account/security`);
|
|
6149
3863
|
}
|
|
@@ -6157,7 +3871,7 @@ ${Buffer.from(data.content, 'base64url').toString('base64')}
|
|
|
6157
3871
|
},
|
|
6158
3872
|
|
|
6159
3873
|
async failAction(request, h, err) {
|
|
6160
|
-
await request.flash({ type: 'danger', message: `
|
|
3874
|
+
await request.flash({ type: 'danger', message: `Couldn't enable two-factor auth. Try again.` });
|
|
6161
3875
|
request.logger.error({ msg: 'Failed to enable 2FA', err });
|
|
6162
3876
|
|
|
6163
3877
|
return h.redirect('/admin').takeover();
|
|
@@ -6183,10 +3897,10 @@ ${Buffer.from(data.content, 'base64url').toString('base64')}
|
|
|
6183
3897
|
await settings.set('totpEnabled', false);
|
|
6184
3898
|
await settings.set('totpSeed', false);
|
|
6185
3899
|
|
|
6186
|
-
await request.flash({ type: 'info', message: `Two-factor
|
|
3900
|
+
await request.flash({ type: 'info', message: `Two-factor auth disabled` });
|
|
6187
3901
|
return h.redirect(`/admin/account/security`);
|
|
6188
3902
|
} catch (err) {
|
|
6189
|
-
await request.flash({ type: 'danger', message: `
|
|
3903
|
+
await request.flash({ type: 'danger', message: `Couldn't disable two-factor auth. Try again.` });
|
|
6190
3904
|
request.logger.error({ msg: 'Failed to enable 2FA', err, remoteAddress: request.app.ip });
|
|
6191
3905
|
return h.redirect(`/admin/account/security`);
|
|
6192
3906
|
}
|
|
@@ -6200,7 +3914,7 @@ ${Buffer.from(data.content, 'base64url').toString('base64')}
|
|
|
6200
3914
|
},
|
|
6201
3915
|
|
|
6202
3916
|
async failAction(request, h, err) {
|
|
6203
|
-
await request.flash({ type: 'danger', message: `
|
|
3917
|
+
await request.flash({ type: 'danger', message: `Couldn't disable two-factor auth. Try again.` });
|
|
6204
3918
|
request.logger.error({ msg: 'Failed to disable 2FA', err });
|
|
6205
3919
|
|
|
6206
3920
|
return h.redirect('/admin').takeover();
|
|
@@ -6233,7 +3947,7 @@ ${Buffer.from(data.content, 'base64url').toString('base64')}
|
|
|
6233
3947
|
await request.flash({ type: 'info', message: `User logged out` });
|
|
6234
3948
|
return h.redirect('/');
|
|
6235
3949
|
} catch (err) {
|
|
6236
|
-
await request.flash({ type: 'danger', message: `
|
|
3950
|
+
await request.flash({ type: 'danger', message: `Couldn't log out sessions. Try again.` });
|
|
6237
3951
|
request.logger.error({ msg: 'Failed to log out user sessions', err, remoteAddress: request.app.ip });
|
|
6238
3952
|
return h.redirect(`/admin/account/security`);
|
|
6239
3953
|
}
|
|
@@ -6247,7 +3961,7 @@ ${Buffer.from(data.content, 'base64url').toString('base64')}
|
|
|
6247
3961
|
},
|
|
6248
3962
|
|
|
6249
3963
|
async failAction(request, h, err) {
|
|
6250
|
-
await request.flash({ type: 'danger', message: `
|
|
3964
|
+
await request.flash({ type: 'danger', message: `Couldn't log out sessions. Try again.` });
|
|
6251
3965
|
request.logger.error({ msg: 'Failed to log out user sessions', err });
|
|
6252
3966
|
|
|
6253
3967
|
return h.redirect('/admin').takeover();
|
|
@@ -6336,16 +4050,16 @@ ${Buffer.from(data.content, 'base64url').toString('base64')}
|
|
|
6336
4050
|
}
|
|
6337
4051
|
|
|
6338
4052
|
if (!hasExistingPassword) {
|
|
6339
|
-
await request.flash({ type: 'info', message: `
|
|
4053
|
+
await request.flash({ type: 'info', message: `Password saved` });
|
|
6340
4054
|
|
|
6341
4055
|
return h.redirect('/admin');
|
|
6342
4056
|
}
|
|
6343
4057
|
|
|
6344
|
-
await request.flash({ type: 'info', message: `
|
|
4058
|
+
await request.flash({ type: 'info', message: `Password updated` });
|
|
6345
4059
|
|
|
6346
4060
|
return h.redirect('/admin/account/password');
|
|
6347
4061
|
} catch (err) {
|
|
6348
|
-
await request.flash({ type: 'danger', message: `
|
|
4062
|
+
await request.flash({ type: 'danger', message: `Couldn't update password. Try again.` });
|
|
6349
4063
|
request.logger.error({ msg: 'Failed to update password', err });
|
|
6350
4064
|
|
|
6351
4065
|
let username = (request.auth && request.auth.credentials && request.auth.credentials.user) || 'admin';
|
|
@@ -6386,7 +4100,7 @@ ${Buffer.from(data.content, 'base64url').toString('base64')}
|
|
|
6386
4100
|
});
|
|
6387
4101
|
}
|
|
6388
4102
|
|
|
6389
|
-
await request.flash({ type: 'danger', message: `
|
|
4103
|
+
await request.flash({ type: 'danger', message: `Couldn't update password. Try again.` });
|
|
6390
4104
|
request.logger.error({ msg: 'Failed to update account password', err });
|
|
6391
4105
|
|
|
6392
4106
|
let username = (request.auth && request.auth.credentials && request.auth.credentials.user) || 'admin';
|
|
@@ -6705,12 +4419,8 @@ ${Buffer.from(data.content, 'base64url').toString('base64')}
|
|
|
6705
4419
|
|
|
6706
4420
|
const nonce = data.n || crypto.randomBytes(NONCE_BYTES).toString('base64url');
|
|
6707
4421
|
|
|
6708
|
-
// store account data
|
|
6709
|
-
await redis
|
|
6710
|
-
.multi()
|
|
6711
|
-
.set(`${REDIS_PREFIX}account:add:${nonce}`, JSON.stringify(accountData))
|
|
6712
|
-
.expire(`${REDIS_PREFIX}account:add:${nonce}`, Math.floor(MAX_FORM_TTL / 1000))
|
|
6713
|
-
.exec();
|
|
4422
|
+
// store account data with atomic SET + EX
|
|
4423
|
+
await redis.set(`${REDIS_PREFIX}account:add:${nonce}`, JSON.stringify(accountData), 'EX', Math.floor(MAX_FORM_TTL / 1000));
|
|
6714
4424
|
|
|
6715
4425
|
// Generate the url that will be used for the consent dialog.
|
|
6716
4426
|
|
|
@@ -6790,7 +4500,7 @@ ${Buffer.from(data.content, 'base64url').toString('base64')}
|
|
|
6790
4500
|
|
|
6791
4501
|
async failAction(request, h, err) {
|
|
6792
4502
|
request.logger.error({ msg: 'Failed to validate request arguments', err });
|
|
6793
|
-
let error = Boom.boomify(new Error(request.app.gt.gettext('
|
|
4503
|
+
let error = Boom.boomify(new Error(request.app.gt.gettext('Invalid request. Check your input and try again.')), { statusCode: 400 });
|
|
6794
4504
|
if (err.code) {
|
|
6795
4505
|
error.output.payload.code = err.code;
|
|
6796
4506
|
}
|
|
@@ -6823,7 +4533,7 @@ ${Buffer.from(data.content, 'base64url').toString('base64')}
|
|
|
6823
4533
|
|
|
6824
4534
|
async failAction(request, h, err) {
|
|
6825
4535
|
request.logger.error({ msg: 'Failed to validate request arguments', err });
|
|
6826
|
-
let error = Boom.boomify(new Error(request.app.gt.gettext('
|
|
4536
|
+
let error = Boom.boomify(new Error(request.app.gt.gettext('Invalid request. Check your input and try again.')), { statusCode: 400 });
|
|
6827
4537
|
if (err.code) {
|
|
6828
4538
|
error.output.payload.code = err.code;
|
|
6829
4539
|
}
|
|
@@ -6920,7 +4630,7 @@ ${Buffer.from(data.content, 'base64url').toString('base64')}
|
|
|
6920
4630
|
});
|
|
6921
4631
|
}
|
|
6922
4632
|
|
|
6923
|
-
await request.flash({ type: 'danger', message: request.app.gt.gettext('
|
|
4633
|
+
await request.flash({ type: 'danger', message: request.app.gt.gettext(`Couldn't set up account. Try again.`) });
|
|
6924
4634
|
request.logger.error({ msg: 'Failed to process account', err });
|
|
6925
4635
|
|
|
6926
4636
|
return h
|
|
@@ -7185,7 +4895,7 @@ ${Buffer.from(data.content, 'base64url').toString('base64')}
|
|
|
7185
4895
|
});
|
|
7186
4896
|
}
|
|
7187
4897
|
|
|
7188
|
-
await request.flash({ type: 'danger', message: request.app.gt.gettext('
|
|
4898
|
+
await request.flash({ type: 'danger', message: request.app.gt.gettext(`Couldn't set up account. Try again.`) });
|
|
7189
4899
|
request.logger.error({ msg: 'Failed to process account', err });
|
|
7190
4900
|
|
|
7191
4901
|
return h
|
|
@@ -7491,12 +5201,12 @@ ${Buffer.from(data.content, 'base64url').toString('base64')}
|
|
|
7491
5201
|
|
|
7492
5202
|
let deleted = await accountObject.delete();
|
|
7493
5203
|
if (deleted) {
|
|
7494
|
-
await request.flash({ type: 'info', message: `Account
|
|
5204
|
+
await request.flash({ type: 'info', message: `Account deleted` });
|
|
7495
5205
|
}
|
|
7496
5206
|
|
|
7497
5207
|
return h.redirect('/admin/accounts');
|
|
7498
5208
|
} catch (err) {
|
|
7499
|
-
await request.flash({ type: 'danger', message: `
|
|
5209
|
+
await request.flash({ type: 'danger', message: `Couldn't delete account. Try again.` });
|
|
7500
5210
|
request.logger.error({ msg: 'Failed to delete the account', err, account: request.payload.account, remoteAddress: request.app.ip });
|
|
7501
5211
|
return h.redirect(`/admin/accounts/${request.params.account}`);
|
|
7502
5212
|
}
|
|
@@ -7510,7 +5220,7 @@ ${Buffer.from(data.content, 'base64url').toString('base64')}
|
|
|
7510
5220
|
},
|
|
7511
5221
|
|
|
7512
5222
|
async failAction(request, h, err) {
|
|
7513
|
-
await request.flash({ type: 'danger', message: `
|
|
5223
|
+
await request.flash({ type: 'danger', message: `Couldn't delete account. Try again.` });
|
|
7514
5224
|
request.logger.error({ msg: 'Failed to delete delete the account', err });
|
|
7515
5225
|
|
|
7516
5226
|
return h.redirect('/admin/accounts').takeover();
|
|
@@ -7712,7 +5422,7 @@ ${Buffer.from(data.content, 'base64url').toString('base64')}
|
|
|
7712
5422
|
let authData = await settings.get('authData');
|
|
7713
5423
|
let hasExistingPassword = !!(authData && authData.password);
|
|
7714
5424
|
if (!hasExistingPassword) {
|
|
7715
|
-
await request.flash({ type: 'info', message: `
|
|
5425
|
+
await request.flash({ type: 'info', message: `Set a password to access messages` });
|
|
7716
5426
|
return h.redirect('/admin/account/password');
|
|
7717
5427
|
}
|
|
7718
5428
|
|
|
@@ -7721,7 +5431,7 @@ ${Buffer.from(data.content, 'base64url').toString('base64')}
|
|
|
7721
5431
|
if (request.cookieAuth) {
|
|
7722
5432
|
request.cookieAuth.clear();
|
|
7723
5433
|
}
|
|
7724
|
-
await request.flash({ type: 'info', message: `
|
|
5434
|
+
await request.flash({ type: 'info', message: `Sign in again to continue` });
|
|
7725
5435
|
return h.redirect('/admin/login?next=' + encodeURIComponent('/admin/accounts/{account}/browse'));
|
|
7726
5436
|
}
|
|
7727
5437
|
|
|
@@ -7743,7 +5453,7 @@ ${Buffer.from(data.content, 'base64url').toString('base64')}
|
|
|
7743
5453
|
|
|
7744
5454
|
const canReadMail = (accountData.imap || accountData.oauth2) && !(accountData.imap && accountData.imap.disabled) && !DISABLE_MESSAGE_BROWSER;
|
|
7745
5455
|
if (!canReadMail) {
|
|
7746
|
-
await request.flash({ type: 'danger', message: `Mail access is disabled for
|
|
5456
|
+
await request.flash({ type: 'danger', message: `Mail access is disabled for this account` });
|
|
7747
5457
|
return h.redirect(`/admin/accounts/${request.params.account}`);
|
|
7748
5458
|
}
|
|
7749
5459
|
|
|
@@ -7948,7 +5658,7 @@ ${Buffer.from(data.content, 'base64url').toString('base64')}
|
|
|
7948
5658
|
|
|
7949
5659
|
return h.redirect(`/admin/accounts/${request.params.account}`);
|
|
7950
5660
|
} catch (err) {
|
|
7951
|
-
await request.flash({ type: 'danger', message: `
|
|
5661
|
+
await request.flash({ type: 'danger', message: `Couldn't save account settings. Try again.` });
|
|
7952
5662
|
request.logger.error({ msg: 'Failed to update account settings', err, account: request.params.account });
|
|
7953
5663
|
|
|
7954
5664
|
let accountObject = new Account({ redis, account: request.params.account, call, secret: await getSecret() });
|
|
@@ -7997,7 +5707,7 @@ ${Buffer.from(data.content, 'base64url').toString('base64')}
|
|
|
7997
5707
|
});
|
|
7998
5708
|
}
|
|
7999
5709
|
|
|
8000
|
-
await request.flash({ type: 'danger', message: `
|
|
5710
|
+
await request.flash({ type: 'danger', message: `Couldn't save settings. Try again.` });
|
|
8001
5711
|
request.logger.error({ msg: 'Failed to update configuration', err });
|
|
8002
5712
|
|
|
8003
5713
|
let accountObject = new Account({ redis, account: request.params.account, call, secret: await getSecret() });
|
|
@@ -8171,7 +5881,7 @@ ${Buffer.from(data.content, 'base64url').toString('base64')}
|
|
|
8171
5881
|
|
|
8172
5882
|
return h.redirect('/admin/config/document-store');
|
|
8173
5883
|
} catch (err) {
|
|
8174
|
-
await request.flash({ type: 'danger', message: `
|
|
5884
|
+
await request.flash({ type: 'danger', message: `Couldn't save settings. Try again.` });
|
|
8175
5885
|
request.logger.error({ msg: 'Failed to update configuration', err });
|
|
8176
5886
|
|
|
8177
5887
|
let hasDocumentStorePassword = !!(await settings.get('documentStorePassword'));
|
|
@@ -8211,7 +5921,7 @@ ${Buffer.from(data.content, 'base64url').toString('base64')}
|
|
|
8211
5921
|
});
|
|
8212
5922
|
}
|
|
8213
5923
|
|
|
8214
|
-
await request.flash({ type: 'danger', message: `
|
|
5924
|
+
await request.flash({ type: 'danger', message: `Couldn't save settings. Try again.` });
|
|
8215
5925
|
request.logger.error({ msg: 'Failed to update configuration', err });
|
|
8216
5926
|
|
|
8217
5927
|
let hasDocumentStorePassword = !!(await settings.get('documentStorePassword'));
|
|
@@ -8284,7 +5994,7 @@ ${Buffer.from(data.content, 'base64url').toString('base64')}
|
|
|
8284
5994
|
|
|
8285
5995
|
return h.redirect('/admin/config/document-store/chat');
|
|
8286
5996
|
} catch (err) {
|
|
8287
|
-
await request.flash({ type: 'danger', message: `
|
|
5997
|
+
await request.flash({ type: 'danger', message: `Couldn't save settings. Try again.` });
|
|
8288
5998
|
request.logger.error({ msg: 'Failed to update configuration', err });
|
|
8289
5999
|
|
|
8290
6000
|
return h.view(
|
|
@@ -8325,7 +6035,7 @@ ${Buffer.from(data.content, 'base64url').toString('base64')}
|
|
|
8325
6035
|
});
|
|
8326
6036
|
}
|
|
8327
6037
|
|
|
8328
|
-
await request.flash({ type: 'danger', message: `
|
|
6038
|
+
await request.flash({ type: 'danger', message: `Couldn't save settings. Try again.` });
|
|
8329
6039
|
request.logger.error({ msg: 'Failed to update configuration', err });
|
|
8330
6040
|
|
|
8331
6041
|
return h
|
|
@@ -8440,10 +6150,10 @@ return payload;`
|
|
|
8440
6150
|
documentStorePreProcessingMap: contentMap
|
|
8441
6151
|
});
|
|
8442
6152
|
|
|
8443
|
-
await request.flash({ type: 'info', message: `
|
|
6153
|
+
await request.flash({ type: 'info', message: `Document Store rules saved` });
|
|
8444
6154
|
return h.redirect(`/admin/config/document-store/pre-processing`);
|
|
8445
6155
|
} catch (err) {
|
|
8446
|
-
await request.flash({ type: 'danger', message: `
|
|
6156
|
+
await request.flash({ type: 'danger', message: `Couldn't save Document Store rules. Try again.` });
|
|
8447
6157
|
request.logger.error({ msg: 'Failed to update Document Store pre-processing rules', err });
|
|
8448
6158
|
|
|
8449
6159
|
return h.view(
|
|
@@ -8483,7 +6193,7 @@ return payload;`
|
|
|
8483
6193
|
});
|
|
8484
6194
|
}
|
|
8485
6195
|
|
|
8486
|
-
await request.flash({ type: 'danger', message: `
|
|
6196
|
+
await request.flash({ type: 'danger', message: `Couldn't save Document Store rules. Try again.` });
|
|
8487
6197
|
request.logger.error({ msg: 'Failed to update Document Store pre-processing rules', err });
|
|
8488
6198
|
|
|
8489
6199
|
return h
|
|
@@ -8653,7 +6363,7 @@ return payload;`
|
|
|
8653
6363
|
if (Boom.isBoom(err)) {
|
|
8654
6364
|
await request.flash({ type: 'danger', message: err.message });
|
|
8655
6365
|
} else {
|
|
8656
|
-
await request.flash({ type: 'danger', message: err.responseText || `
|
|
6366
|
+
await request.flash({ type: 'danger', message: err.responseText || `Couldn't create mapping. Try again.` });
|
|
8657
6367
|
}
|
|
8658
6368
|
request.logger.error({ msg: 'Failed to create mapping', err });
|
|
8659
6369
|
|
|
@@ -8691,7 +6401,7 @@ return payload;`
|
|
|
8691
6401
|
});
|
|
8692
6402
|
}
|
|
8693
6403
|
|
|
8694
|
-
await request.flash({ type: 'danger', message: `
|
|
6404
|
+
await request.flash({ type: 'danger', message: `Couldn't create mapping. Try again.` });
|
|
8695
6405
|
request.logger.error({ msg: 'Failed to create mapping', err });
|
|
8696
6406
|
|
|
8697
6407
|
return h
|
|
@@ -8901,7 +6611,7 @@ return payload;`
|
|
|
8901
6611
|
|
|
8902
6612
|
return h.redirect('/admin/config/network');
|
|
8903
6613
|
} catch (err) {
|
|
8904
|
-
await request.flash({ type: 'danger', message: `
|
|
6614
|
+
await request.flash({ type: 'danger', message: `Couldn't save settings. Try again.` });
|
|
8905
6615
|
request.logger.error({ msg: 'Failed to update configuration', err });
|
|
8906
6616
|
|
|
8907
6617
|
let smtpStrategies = ADDRESS_STRATEGIES.map(entry => Object.assign({ selected: request.payload.smtpStrategy === entry.key }, entry));
|
|
@@ -8945,7 +6655,7 @@ return payload;`
|
|
|
8945
6655
|
});
|
|
8946
6656
|
}
|
|
8947
6657
|
|
|
8948
|
-
await request.flash({ type: 'danger', message: `
|
|
6658
|
+
await request.flash({ type: 'danger', message: `Couldn't save settings. Try again.` });
|
|
8949
6659
|
request.logger.error({ msg: 'Failed to update configuration', err });
|
|
8950
6660
|
|
|
8951
6661
|
let smtpStrategies = ADDRESS_STRATEGIES.map(entry => Object.assign({ selected: request.payload.smtpStrategy === entry.key }, entry));
|
|
@@ -9002,10 +6712,10 @@ return payload;`
|
|
|
9002
6712
|
|
|
9003
6713
|
await redis.hdel(`${REDIS_PREFIX}interfaces`, localAddress);
|
|
9004
6714
|
|
|
9005
|
-
await request.flash({ type: 'info', message: `Address
|
|
6715
|
+
await request.flash({ type: 'info', message: `Address removed` });
|
|
9006
6716
|
return h.redirect('/admin/config/network');
|
|
9007
6717
|
} catch (err) {
|
|
9008
|
-
await request.flash({ type: 'danger', message: `
|
|
6718
|
+
await request.flash({ type: 'danger', message: `Couldn't delete address. Try again.` });
|
|
9009
6719
|
request.logger.error({ msg: 'Failed to delete address', err, localAddress: request.payload.localAddress, remoteAddress: request.app.ip });
|
|
9010
6720
|
return h.redirect('/admin/config/network');
|
|
9011
6721
|
}
|
|
@@ -9019,7 +6729,7 @@ return payload;`
|
|
|
9019
6729
|
},
|
|
9020
6730
|
|
|
9021
6731
|
async failAction(request, h, err) {
|
|
9022
|
-
await request.flash({ type: 'danger', message: `
|
|
6732
|
+
await request.flash({ type: 'danger', message: `Couldn't delete address. Try again.` });
|
|
9023
6733
|
request.logger.error({ msg: 'Failed to delete address', err });
|
|
9024
6734
|
|
|
9025
6735
|
return h.redirect('/admin/config/network').takeover();
|
|
@@ -9114,7 +6824,7 @@ return payload;`
|
|
|
9114
6824
|
|
|
9115
6825
|
return h.redirect('/admin/config/imap-proxy');
|
|
9116
6826
|
} catch (err) {
|
|
9117
|
-
await request.flash({ type: 'danger', message: `
|
|
6827
|
+
await request.flash({ type: 'danger', message: `Couldn't save settings. Try again.` });
|
|
9118
6828
|
request.logger.error({ msg: 'Failed to update configuration', err });
|
|
9119
6829
|
|
|
9120
6830
|
let availableAddresses = new Set(
|
|
@@ -9166,7 +6876,7 @@ return payload;`
|
|
|
9166
6876
|
});
|
|
9167
6877
|
}
|
|
9168
6878
|
|
|
9169
|
-
await request.flash({ type: 'danger', message: `
|
|
6879
|
+
await request.flash({ type: 'danger', message: `Couldn't save settings. Try again.` });
|
|
9170
6880
|
request.logger.error({ msg: 'Failed to update configuration', err });
|
|
9171
6881
|
|
|
9172
6882
|
let availableAddresses = new Set(
|
|
@@ -9288,7 +6998,7 @@ return payload;`
|
|
|
9288
6998
|
|
|
9289
6999
|
return h.redirect('/admin/config/smtp');
|
|
9290
7000
|
} catch (err) {
|
|
9291
|
-
await request.flash({ type: 'danger', message: `
|
|
7001
|
+
await request.flash({ type: 'danger', message: `Couldn't save settings. Try again.` });
|
|
9292
7002
|
request.logger.error({ msg: 'Failed to update configuration', err });
|
|
9293
7003
|
|
|
9294
7004
|
let availableAddresses = new Set(
|
|
@@ -9340,7 +7050,7 @@ return payload;`
|
|
|
9340
7050
|
});
|
|
9341
7051
|
}
|
|
9342
7052
|
|
|
9343
|
-
await request.flash({ type: 'danger', message: `
|
|
7053
|
+
await request.flash({ type: 'danger', message: `Couldn't save settings. Try again.` });
|
|
9344
7054
|
request.logger.error({ msg: 'Failed to update configuration', err });
|
|
9345
7055
|
|
|
9346
7056
|
let availableAddresses = new Set(
|
|
@@ -9742,7 +7452,7 @@ ${now}`,
|
|
|
9742
7452
|
|
|
9743
7453
|
async failAction(request, h, err) {
|
|
9744
7454
|
request.logger.error({ msg: 'Failed to validate request arguments', err });
|
|
9745
|
-
let error = Boom.boomify(new Error(request.app.gt.gettext('
|
|
7455
|
+
let error = Boom.boomify(new Error(request.app.gt.gettext('Invalid request. Check your input and try again.')), { statusCode: 400 });
|
|
9746
7456
|
if (err.code) {
|
|
9747
7457
|
error.output.payload.code = err.code;
|
|
9748
7458
|
}
|
|
@@ -9844,7 +7554,7 @@ ${now}`,
|
|
|
9844
7554
|
}
|
|
9845
7555
|
);
|
|
9846
7556
|
} catch (err) {
|
|
9847
|
-
await request.flash({ type: 'danger', message: request.app.gt.gettext('
|
|
7557
|
+
await request.flash({ type: 'danger', message: request.app.gt.gettext(`Couldn't process request. Try again.`) });
|
|
9848
7558
|
request.logger.error({ msg: 'Failed to process subscription request', err });
|
|
9849
7559
|
|
|
9850
7560
|
return h.view(
|
|
@@ -9879,7 +7589,7 @@ ${now}`,
|
|
|
9879
7589
|
});
|
|
9880
7590
|
}
|
|
9881
7591
|
|
|
9882
|
-
await request.flash({ type: 'danger', message: request.app.gt.gettext('
|
|
7592
|
+
await request.flash({ type: 'danger', message: request.app.gt.gettext(`Couldn't process request. Try again.`) });
|
|
9883
7593
|
request.logger.error({ msg: 'Failed to process subscription request', err });
|
|
9884
7594
|
|
|
9885
7595
|
return h
|
|
@@ -9916,15 +7626,8 @@ ${now}`,
|
|
|
9916
7626
|
|
|
9917
7627
|
let defaultLocale = (await settings.get('locale')) || 'en';
|
|
9918
7628
|
|
|
9919
|
-
let percentFormatter;
|
|
9920
7629
|
let bytesFormatter;
|
|
9921
7630
|
|
|
9922
|
-
let percentFormatterOpts = {
|
|
9923
|
-
style: 'percent',
|
|
9924
|
-
minimumFractionDigits: 2,
|
|
9925
|
-
maximumFractionDigits: 2
|
|
9926
|
-
};
|
|
9927
|
-
|
|
9928
7631
|
let bytesFormatterOpts = {
|
|
9929
7632
|
style: 'unit',
|
|
9930
7633
|
unit: 'byte',
|
|
@@ -9933,10 +7636,8 @@ ${now}`,
|
|
|
9933
7636
|
};
|
|
9934
7637
|
|
|
9935
7638
|
try {
|
|
9936
|
-
percentFormatter = new Intl.NumberFormat(defaultLocale, percentFormatterOpts);
|
|
9937
7639
|
bytesFormatter = new Intl.NumberFormat(defaultLocale, bytesFormatterOpts);
|
|
9938
7640
|
} catch (err) {
|
|
9939
|
-
percentFormatter = new Intl.NumberFormat('en-US', percentFormatterOpts);
|
|
9940
7641
|
bytesFormatter = new Intl.NumberFormat('en-US', bytesFormatterOpts);
|
|
9941
7642
|
}
|
|
9942
7643
|
|
|
@@ -10029,12 +7730,12 @@ ${now}`,
|
|
|
10029
7730
|
try {
|
|
10030
7731
|
let killed = await call({ cmd: 'kill-thread', thread: request.payload.thread });
|
|
10031
7732
|
if (killed) {
|
|
10032
|
-
await request.flash({ type: 'info', message: `
|
|
7733
|
+
await request.flash({ type: 'info', message: `Worker stopped` });
|
|
10033
7734
|
}
|
|
10034
7735
|
|
|
10035
7736
|
return h.redirect('/admin/internals');
|
|
10036
7737
|
} catch (err) {
|
|
10037
|
-
await request.flash({ type: 'danger', message: `
|
|
7738
|
+
await request.flash({ type: 'danger', message: `Couldn't stop worker. Try again.` });
|
|
10038
7739
|
request.logger.error({ msg: 'Failed to kill thread', err, thread: request.payload.thread, remoteAddress: request.app.ip });
|
|
10039
7740
|
return h.redirect('/admin/internals');
|
|
10040
7741
|
}
|
|
@@ -10048,7 +7749,7 @@ ${now}`,
|
|
|
10048
7749
|
},
|
|
10049
7750
|
|
|
10050
7751
|
async failAction(request, h, err) {
|
|
10051
|
-
await request.flash({ type: 'danger', message: `
|
|
7752
|
+
await request.flash({ type: 'danger', message: `Couldn't stop worker. Try again.` });
|
|
10052
7753
|
request.logger.error({ msg: 'Failed to kill thread', err });
|
|
10053
7754
|
|
|
10054
7755
|
return h.redirect('/admin/internals').takeover();
|
|
@@ -10086,7 +7787,7 @@ ${now}`,
|
|
|
10086
7787
|
.header('Pragma', 'no-cache')
|
|
10087
7788
|
.code(200);
|
|
10088
7789
|
} catch (err) {
|
|
10089
|
-
await request.flash({ type: 'danger', message: `
|
|
7790
|
+
await request.flash({ type: 'danger', message: `Couldn't create snapshot. Try again.` });
|
|
10090
7791
|
request.logger.error({ msg: 'Failed to generate snapshot', err, thread: request.payload.thread, remoteAddress: request.app.ip });
|
|
10091
7792
|
return h.redirect('/admin/internals');
|
|
10092
7793
|
}
|
|
@@ -10100,7 +7801,7 @@ ${now}`,
|
|
|
10100
7801
|
},
|
|
10101
7802
|
|
|
10102
7803
|
async failAction(request, h, err) {
|
|
10103
|
-
await request.flash({ type: 'danger', message: `
|
|
7804
|
+
await request.flash({ type: 'danger', message: `Couldn't create snapshot. Try again.` });
|
|
10104
7805
|
request.logger.error({ msg: 'Failed to generate snapshot', err });
|
|
10105
7806
|
|
|
10106
7807
|
return h.redirect('/admin/internals').takeover();
|
|
@@ -10124,12 +7825,12 @@ ${now}`,
|
|
|
10124
7825
|
const threadInfo = threads.find(t => t.threadId === threadId);
|
|
10125
7826
|
|
|
10126
7827
|
if (!threadInfo) {
|
|
10127
|
-
await request.flash({ type: 'danger', message:
|
|
7828
|
+
await request.flash({ type: 'danger', message: `Worker not found` });
|
|
10128
7829
|
return h.redirect('/admin/internals');
|
|
10129
7830
|
}
|
|
10130
7831
|
|
|
10131
7832
|
if (threadInfo.type !== 'imap') {
|
|
10132
|
-
await request.flash({ type: 'warning', message:
|
|
7833
|
+
await request.flash({ type: 'warning', message: `Only email workers have assigned accounts` });
|
|
10133
7834
|
return h.redirect('/admin/internals');
|
|
10134
7835
|
}
|
|
10135
7836
|
|