emailengine-app 2.68.1 → 2.69.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/deploy.yml +2 -0
- package/.github/workflows/release.yaml +4 -0
- package/CHANGELOG.md +40 -0
- package/config/default.toml +2 -0
- package/data/google-crawlers.json +7 -1
- package/lib/account.js +62 -25
- package/lib/api-routes/account-routes.js +493 -75
- package/lib/api-routes/blocklist-routes.js +337 -0
- package/lib/api-routes/delivery-test-routes.js +321 -0
- package/lib/api-routes/export-routes.js +1 -12
- package/lib/api-routes/gateway-routes.js +376 -0
- package/lib/api-routes/license-routes.js +142 -0
- package/lib/api-routes/mailbox-routes.js +318 -0
- package/lib/api-routes/message-routes.js +21 -129
- package/lib/api-routes/oauth2-app-routes.js +631 -0
- package/lib/api-routes/outbox-routes.js +173 -0
- package/lib/api-routes/pubsub-routes.js +98 -0
- package/lib/api-routes/route-helpers.js +45 -0
- package/lib/api-routes/settings-routes.js +331 -0
- package/lib/api-routes/stats-routes.js +77 -0
- package/lib/api-routes/submit-routes.js +472 -0
- package/lib/api-routes/template-routes.js +7 -55
- package/lib/api-routes/token-routes.js +297 -0
- package/lib/api-routes/webhook-route-routes.js +152 -0
- package/lib/email-client/gmail-client.js +14 -0
- package/lib/email-client/imap/mailbox.js +34 -11
- package/lib/email-client/imap/subconnection.js +20 -12
- package/lib/email-client/imap/sync-operations.js +130 -2
- package/lib/email-client/imap-client.js +116 -58
- package/lib/email-client/outlook-client.js +85 -13
- package/lib/export.js +60 -19
- package/lib/imapproxy/imap-core/lib/commands/starttls.js +18 -0
- package/lib/imapproxy/imap-core/lib/imap-command.js +6 -1
- package/lib/imapproxy/imap-core/lib/imap-connection.js +106 -23
- package/lib/imapproxy/imap-core/lib/imap-server.js +24 -0
- package/lib/imapproxy/imap-core/lib/imap-stream.js +26 -0
- package/lib/message-port-stream.js +113 -16
- package/lib/reject-worker-calls.js +42 -0
- package/lib/routes-ui.js +37 -8778
- package/lib/schemas.js +26 -1
- package/lib/tools.js +68 -0
- package/lib/ui-routes/account-routes.js +40 -210
- package/lib/ui-routes/admin-config-routes.js +913 -487
- package/lib/ui-routes/admin-entities-routes.js +1 -0
- package/lib/ui-routes/auth-routes.js +1339 -0
- package/lib/ui-routes/dashboard-routes.js +188 -0
- package/lib/ui-routes/document-store-routes.js +800 -0
- package/lib/ui-routes/export-routes.js +217 -0
- package/lib/ui-routes/internals-routes.js +354 -0
- package/lib/ui-routes/network-config-routes.js +759 -0
- package/lib/ui-routes/{oauth-routes.js → oauth-config-routes.js} +371 -91
- package/lib/ui-routes/route-helpers.js +316 -0
- package/lib/ui-routes/smtp-test-routes.js +236 -0
- package/lib/ui-routes/unsubscribe-routes.js +234 -0
- package/lib/webhook-request.js +36 -0
- package/package.json +8 -8
- package/sbom.json +1 -1
- package/server.js +214 -16
- package/static/licenses.html +12 -12
- package/translations/messages.pot +129 -149
- package/views/dashboard.hbs +7 -26
- package/views/internals/index.hbs +15 -0
- package/views/tokens/index.hbs +9 -0
- package/workers/api.js +198 -4401
- package/workers/export.js +87 -54
- package/workers/imap.js +29 -13
- package/workers/submit.js +20 -11
- package/workers/webhooks.js +6 -20
|
@@ -5,9 +5,12 @@ const { redis } = require('../db');
|
|
|
5
5
|
const { Account } = require('../account');
|
|
6
6
|
const getSecret = require('../get-secret');
|
|
7
7
|
const { oauth2Apps, SERVICE_ACCOUNT_PROVIDERS } = require('../oauth2-apps');
|
|
8
|
+
const settings = require('../settings');
|
|
9
|
+
const { autodetectImapSettings } = require('../autodetect-imap-settings');
|
|
8
10
|
const Boom = require('@hapi/boom');
|
|
9
11
|
const Joi = require('joi');
|
|
10
|
-
const { failAction } = require('../tools');
|
|
12
|
+
const { failAction, getSignedFormData, getLogs, verifyAccountInfo } = require('../tools');
|
|
13
|
+
const { handleError } = require('./route-helpers');
|
|
11
14
|
|
|
12
15
|
const {
|
|
13
16
|
settingsSchema,
|
|
@@ -21,7 +24,9 @@ const {
|
|
|
21
24
|
smtpSchema,
|
|
22
25
|
smtpUpdateSchema,
|
|
23
26
|
oauth2Schema,
|
|
24
|
-
oauth2UpdateSchema
|
|
27
|
+
oauth2UpdateSchema,
|
|
28
|
+
defaultAccountTypeSchema,
|
|
29
|
+
shortMailboxesSchema
|
|
25
30
|
} = require('../schemas');
|
|
26
31
|
|
|
27
32
|
const { REDIS_PREFIX, MAX_FORM_TTL, NONCE_BYTES } = require('../consts');
|
|
@@ -46,7 +51,9 @@ async function init(args) {
|
|
|
46
51
|
imapSchema: imapSchemaArg,
|
|
47
52
|
smtpSchema: smtpSchemaArg,
|
|
48
53
|
CORS_CONFIG,
|
|
49
|
-
AccountTypeSchema
|
|
54
|
+
AccountTypeSchema,
|
|
55
|
+
OAuth2ProviderSchema,
|
|
56
|
+
metrics
|
|
50
57
|
} = args;
|
|
51
58
|
|
|
52
59
|
// POST /v1/account - Create account
|
|
@@ -133,15 +140,7 @@ async function init(args) {
|
|
|
133
140
|
let result = await accountObject.create(request.payload);
|
|
134
141
|
return result;
|
|
135
142
|
} catch (err) {
|
|
136
|
-
|
|
137
|
-
if (Boom.isBoom(err)) {
|
|
138
|
-
throw err;
|
|
139
|
-
}
|
|
140
|
-
let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
|
|
141
|
-
if (err.code) {
|
|
142
|
-
error.output.payload.code = err.code;
|
|
143
|
-
}
|
|
144
|
-
throw error;
|
|
143
|
+
handleError(request, err);
|
|
145
144
|
}
|
|
146
145
|
},
|
|
147
146
|
|
|
@@ -281,15 +280,7 @@ async function init(args) {
|
|
|
281
280
|
|
|
282
281
|
return await accountObject.update(request.payload);
|
|
283
282
|
} catch (err) {
|
|
284
|
-
|
|
285
|
-
if (Boom.isBoom(err)) {
|
|
286
|
-
throw err;
|
|
287
|
-
}
|
|
288
|
-
let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
|
|
289
|
-
if (err.code) {
|
|
290
|
-
error.output.payload.code = err.code;
|
|
291
|
-
}
|
|
292
|
-
throw error;
|
|
283
|
+
handleError(request, err);
|
|
293
284
|
}
|
|
294
285
|
},
|
|
295
286
|
options: {
|
|
@@ -397,15 +388,7 @@ async function init(args) {
|
|
|
397
388
|
try {
|
|
398
389
|
return { reconnect: await accountObject.requestReconnect(request.payload) };
|
|
399
390
|
} catch (err) {
|
|
400
|
-
|
|
401
|
-
if (Boom.isBoom(err)) {
|
|
402
|
-
throw err;
|
|
403
|
-
}
|
|
404
|
-
let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
|
|
405
|
-
if (err.code) {
|
|
406
|
-
error.output.payload.code = err.code;
|
|
407
|
-
}
|
|
408
|
-
throw error;
|
|
391
|
+
handleError(request, err);
|
|
409
392
|
}
|
|
410
393
|
},
|
|
411
394
|
options: {
|
|
@@ -464,15 +447,7 @@ async function init(args) {
|
|
|
464
447
|
try {
|
|
465
448
|
return { sync: await accountObject.requestSync(request.payload) };
|
|
466
449
|
} catch (err) {
|
|
467
|
-
|
|
468
|
-
if (Boom.isBoom(err)) {
|
|
469
|
-
throw err;
|
|
470
|
-
}
|
|
471
|
-
let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
|
|
472
|
-
if (err.code) {
|
|
473
|
-
error.output.payload.code = err.code;
|
|
474
|
-
}
|
|
475
|
-
throw error;
|
|
450
|
+
handleError(request, err);
|
|
476
451
|
}
|
|
477
452
|
},
|
|
478
453
|
options: {
|
|
@@ -532,15 +507,7 @@ async function init(args) {
|
|
|
532
507
|
try {
|
|
533
508
|
return await accountObject.delete({ revoke: request.query.revoke });
|
|
534
509
|
} catch (err) {
|
|
535
|
-
|
|
536
|
-
if (Boom.isBoom(err)) {
|
|
537
|
-
throw err;
|
|
538
|
-
}
|
|
539
|
-
let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
|
|
540
|
-
if (err.code) {
|
|
541
|
-
error.output.payload.code = err.code;
|
|
542
|
-
}
|
|
543
|
-
throw error;
|
|
510
|
+
handleError(request, err);
|
|
544
511
|
}
|
|
545
512
|
},
|
|
546
513
|
options: {
|
|
@@ -608,15 +575,7 @@ async function init(args) {
|
|
|
608
575
|
try {
|
|
609
576
|
return { flush: await accountObject.flush(request.payload) };
|
|
610
577
|
} catch (err) {
|
|
611
|
-
|
|
612
|
-
if (Boom.isBoom(err)) {
|
|
613
|
-
throw err;
|
|
614
|
-
}
|
|
615
|
-
let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
|
|
616
|
-
if (err.code) {
|
|
617
|
-
error.output.payload.code = err.code;
|
|
618
|
-
}
|
|
619
|
-
throw error;
|
|
578
|
+
handleError(request, err);
|
|
620
579
|
}
|
|
621
580
|
},
|
|
622
581
|
options: {
|
|
@@ -678,15 +637,7 @@ async function init(args) {
|
|
|
678
637
|
|
|
679
638
|
return await accountObject.listAccounts(request.query.state, request.query.query, request.query.page, request.query.pageSize);
|
|
680
639
|
} catch (err) {
|
|
681
|
-
|
|
682
|
-
if (Boom.isBoom(err)) {
|
|
683
|
-
throw err;
|
|
684
|
-
}
|
|
685
|
-
let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
|
|
686
|
-
if (err.code) {
|
|
687
|
-
error.output.payload.code = err.code;
|
|
688
|
-
}
|
|
689
|
-
throw error;
|
|
640
|
+
handleError(request, err);
|
|
690
641
|
}
|
|
691
642
|
},
|
|
692
643
|
|
|
@@ -910,15 +861,7 @@ async function init(args) {
|
|
|
910
861
|
|
|
911
862
|
return result;
|
|
912
863
|
} catch (err) {
|
|
913
|
-
|
|
914
|
-
if (Boom.isBoom(err)) {
|
|
915
|
-
throw err;
|
|
916
|
-
}
|
|
917
|
-
let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
|
|
918
|
-
if (err.code) {
|
|
919
|
-
error.output.payload.code = err.code;
|
|
920
|
-
}
|
|
921
|
-
throw error;
|
|
864
|
+
handleError(request, err);
|
|
922
865
|
}
|
|
923
866
|
},
|
|
924
867
|
options: {
|
|
@@ -1057,6 +1000,481 @@ async function init(args) {
|
|
|
1057
1000
|
}
|
|
1058
1001
|
}
|
|
1059
1002
|
});
|
|
1003
|
+
|
|
1004
|
+
server.route({
|
|
1005
|
+
method: 'GET',
|
|
1006
|
+
path: '/v1/account/{account}/oauth-token',
|
|
1007
|
+
|
|
1008
|
+
async handler(request) {
|
|
1009
|
+
let enableOAuthTokensApi = await settings.get('enableOAuthTokensApi');
|
|
1010
|
+
if (!enableOAuthTokensApi) {
|
|
1011
|
+
let error = Boom.boomify(new Error('Disabled API endpoint'), { statusCode: 403 });
|
|
1012
|
+
error.output.payload.code = 'ApiEndpointDisabled';
|
|
1013
|
+
throw error;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
let accountObject = new Account({
|
|
1017
|
+
redis,
|
|
1018
|
+
account: request.params.account,
|
|
1019
|
+
call,
|
|
1020
|
+
secret: await getSecret(),
|
|
1021
|
+
timeout: request.headers['x-ee-timeout']
|
|
1022
|
+
});
|
|
1023
|
+
|
|
1024
|
+
try {
|
|
1025
|
+
const tokenData = await accountObject.getActiveAccessTokenData();
|
|
1026
|
+
|
|
1027
|
+
// Record metric if token was actually refreshed (not cached)
|
|
1028
|
+
if (!tokenData.cached) {
|
|
1029
|
+
const provider = tokenData.provider || 'unknown';
|
|
1030
|
+
metrics(request.logger, 'oauth2TokenRefresh', 'inc', { status: 'success', provider, statusCode: '200' });
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
return tokenData;
|
|
1034
|
+
} catch (err) {
|
|
1035
|
+
// Record failed token refresh
|
|
1036
|
+
const statusCode = String(err.statusCode || 0);
|
|
1037
|
+
metrics(request.logger, 'oauth2TokenRefresh', 'inc', { status: 'failure', provider: 'unknown', statusCode });
|
|
1038
|
+
|
|
1039
|
+
handleError(request, err);
|
|
1040
|
+
}
|
|
1041
|
+
},
|
|
1042
|
+
|
|
1043
|
+
options: {
|
|
1044
|
+
description: 'Get OAuth2 access token',
|
|
1045
|
+
notes: 'Get the active OAuth2 access token for an account. NB! This endpoint is disabled by default and needs activation on the Service configuration page.',
|
|
1046
|
+
tags: ['api', 'Account'],
|
|
1047
|
+
|
|
1048
|
+
plugins: {},
|
|
1049
|
+
|
|
1050
|
+
auth: {
|
|
1051
|
+
strategy: 'api-token',
|
|
1052
|
+
mode: 'required'
|
|
1053
|
+
},
|
|
1054
|
+
cors: CORS_CONFIG,
|
|
1055
|
+
|
|
1056
|
+
validate: {
|
|
1057
|
+
options: {
|
|
1058
|
+
stripUnknown: false,
|
|
1059
|
+
abortEarly: false,
|
|
1060
|
+
convert: true
|
|
1061
|
+
},
|
|
1062
|
+
failAction,
|
|
1063
|
+
params: Joi.object({
|
|
1064
|
+
account: accountIdSchema.required()
|
|
1065
|
+
})
|
|
1066
|
+
},
|
|
1067
|
+
|
|
1068
|
+
response: {
|
|
1069
|
+
schema: Joi.object({
|
|
1070
|
+
account: accountIdSchema.required(),
|
|
1071
|
+
user: Joi.string().max(256).required().example('user@example.com').description('Username'),
|
|
1072
|
+
accessToken: Joi.string().max(256).required().example('aGVsbG8gd29ybGQ=').description('Access Token').label('OAuthAccessToken'),
|
|
1073
|
+
provider: OAuth2ProviderSchema
|
|
1074
|
+
}).label('AccountTokenResponse'),
|
|
1075
|
+
failAction: 'log'
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
});
|
|
1079
|
+
|
|
1080
|
+
server.route({
|
|
1081
|
+
method: 'GET',
|
|
1082
|
+
path: '/v1/account/{account}/server-signatures',
|
|
1083
|
+
|
|
1084
|
+
async handler(request) {
|
|
1085
|
+
let accountObject = new Account({
|
|
1086
|
+
redis,
|
|
1087
|
+
account: request.params.account,
|
|
1088
|
+
call,
|
|
1089
|
+
secret: await getSecret(),
|
|
1090
|
+
timeout: request.headers['x-ee-timeout']
|
|
1091
|
+
});
|
|
1092
|
+
try {
|
|
1093
|
+
return await accountObject.listSignatures(request.query);
|
|
1094
|
+
} catch (err) {
|
|
1095
|
+
handleError(request, err);
|
|
1096
|
+
}
|
|
1097
|
+
},
|
|
1098
|
+
|
|
1099
|
+
options: {
|
|
1100
|
+
description: 'List Account Signatures',
|
|
1101
|
+
notes: 'Returns signatures associated with the account. Currently only Gmail is supported, and only "new message" signatures from the "sendAs" list are returned.',
|
|
1102
|
+
tags: ['api', 'Account'],
|
|
1103
|
+
|
|
1104
|
+
plugins: {},
|
|
1105
|
+
|
|
1106
|
+
auth: {
|
|
1107
|
+
strategy: 'api-token',
|
|
1108
|
+
mode: 'required'
|
|
1109
|
+
},
|
|
1110
|
+
cors: CORS_CONFIG,
|
|
1111
|
+
|
|
1112
|
+
validate: {
|
|
1113
|
+
options: {
|
|
1114
|
+
stripUnknown: false,
|
|
1115
|
+
abortEarly: false,
|
|
1116
|
+
convert: true
|
|
1117
|
+
},
|
|
1118
|
+
failAction,
|
|
1119
|
+
params: Joi.object({
|
|
1120
|
+
account: accountIdSchema.required()
|
|
1121
|
+
})
|
|
1122
|
+
},
|
|
1123
|
+
|
|
1124
|
+
response: {
|
|
1125
|
+
schema: Joi.object({
|
|
1126
|
+
signatures: Joi.array()
|
|
1127
|
+
.items(
|
|
1128
|
+
Joi.object({
|
|
1129
|
+
address: Joi.string().email().example('user@example.com').description('Email address associated with the signature').required(),
|
|
1130
|
+
signature: Joi.string().example('<div>Best regards,</div>').description('Signature HTML code').required()
|
|
1131
|
+
}).label('SignatureResponseItem')
|
|
1132
|
+
)
|
|
1133
|
+
.label('SignatureEntries')
|
|
1134
|
+
}).label('AccountSignaturesResponse'),
|
|
1135
|
+
failAction: 'log'
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
});
|
|
1139
|
+
|
|
1140
|
+
server.route({
|
|
1141
|
+
method: 'POST',
|
|
1142
|
+
path: '/v1/authentication/form',
|
|
1143
|
+
|
|
1144
|
+
async handler(request) {
|
|
1145
|
+
try {
|
|
1146
|
+
let { data, signature } = await getSignedFormData({
|
|
1147
|
+
account: request.payload.account,
|
|
1148
|
+
name: request.payload.name,
|
|
1149
|
+
email: request.payload.email,
|
|
1150
|
+
syncFrom: request.payload.syncFrom,
|
|
1151
|
+
notifyFrom: request.payload.notifyFrom,
|
|
1152
|
+
subconnections: request.payload.subconnections,
|
|
1153
|
+
redirectUrl: request.payload.redirectUrl,
|
|
1154
|
+
delegated: request.payload.delegated,
|
|
1155
|
+
path: request.payload.path && !request.payload.path.includes('*') ? request.payload.path : null,
|
|
1156
|
+
// identify request
|
|
1157
|
+
n: crypto.randomBytes(NONCE_BYTES).toString('base64url'),
|
|
1158
|
+
t: Date.now()
|
|
1159
|
+
});
|
|
1160
|
+
|
|
1161
|
+
let serviceUrl = await settings.get('serviceUrl');
|
|
1162
|
+
if (!serviceUrl) {
|
|
1163
|
+
let err = new Error('Service URL not set up');
|
|
1164
|
+
err.code = 'MissingServiceURLSetup';
|
|
1165
|
+
throw err;
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
let url = new URL(`accounts/new`, serviceUrl);
|
|
1169
|
+
|
|
1170
|
+
url.searchParams.append('data', data);
|
|
1171
|
+
if (signature) {
|
|
1172
|
+
url.searchParams.append('sig', signature);
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
let type = request.payload.type;
|
|
1176
|
+
|
|
1177
|
+
if (type && type !== 'imap') {
|
|
1178
|
+
let oauth2app = await oauth2Apps.get(type);
|
|
1179
|
+
if (!oauth2app || !oauth2app.enabled) {
|
|
1180
|
+
type = false;
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
if (!type) {
|
|
1185
|
+
let oauth2apps = (await oauth2Apps.list(0, 100)).apps.filter(app => app.includeInListing);
|
|
1186
|
+
if (!oauth2apps.length) {
|
|
1187
|
+
type = 'imap';
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
if (type) {
|
|
1192
|
+
url.searchParams.append('type', type);
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
return {
|
|
1196
|
+
url: url.href
|
|
1197
|
+
};
|
|
1198
|
+
} catch (err) {
|
|
1199
|
+
handleError(request, err);
|
|
1200
|
+
}
|
|
1201
|
+
},
|
|
1202
|
+
|
|
1203
|
+
options: {
|
|
1204
|
+
description: 'Generate authentication link',
|
|
1205
|
+
notes: 'Generates a redirect link to the hosted authentication form',
|
|
1206
|
+
tags: ['api', 'Account'],
|
|
1207
|
+
|
|
1208
|
+
plugins: {},
|
|
1209
|
+
|
|
1210
|
+
auth: {
|
|
1211
|
+
strategy: 'api-token',
|
|
1212
|
+
mode: 'required'
|
|
1213
|
+
},
|
|
1214
|
+
cors: CORS_CONFIG,
|
|
1215
|
+
|
|
1216
|
+
validate: {
|
|
1217
|
+
options: {
|
|
1218
|
+
stripUnknown: false,
|
|
1219
|
+
abortEarly: false,
|
|
1220
|
+
convert: true
|
|
1221
|
+
},
|
|
1222
|
+
failAction,
|
|
1223
|
+
|
|
1224
|
+
payload: Joi.object({
|
|
1225
|
+
account: Joi.string()
|
|
1226
|
+
.empty('')
|
|
1227
|
+
.trim()
|
|
1228
|
+
.max(256)
|
|
1229
|
+
.allow(null)
|
|
1230
|
+
.example('example')
|
|
1231
|
+
.default(null)
|
|
1232
|
+
.description(
|
|
1233
|
+
'Account ID. If set to `null`, a unique ID will be generated automatically. If you provide an existing account ID, the settings for that account will be updated instead'
|
|
1234
|
+
),
|
|
1235
|
+
|
|
1236
|
+
name: Joi.string().empty('').max(256).example('My Email Account').description('Display name for the account'),
|
|
1237
|
+
|
|
1238
|
+
email: Joi.string()
|
|
1239
|
+
.empty('')
|
|
1240
|
+
.email()
|
|
1241
|
+
.example('user@example.com')
|
|
1242
|
+
.description('Specifies the default email address for this account. Users can change it if needed.'),
|
|
1243
|
+
|
|
1244
|
+
delegated: Joi.boolean()
|
|
1245
|
+
.empty('')
|
|
1246
|
+
.truthy('Y', 'true', '1')
|
|
1247
|
+
.falsy('N', 'false', 0)
|
|
1248
|
+
.default(false)
|
|
1249
|
+
.description('If true, configures this account as a shared mailbox. Currently supported by MS365 OAuth2 accounts'),
|
|
1250
|
+
|
|
1251
|
+
syncFrom: accountSchemas.syncFrom,
|
|
1252
|
+
notifyFrom: accountSchemas.notifyFrom,
|
|
1253
|
+
|
|
1254
|
+
subconnections: accountSchemas.subconnections,
|
|
1255
|
+
|
|
1256
|
+
path: accountPathSchema.example(['*']).label('AccountFormPath'),
|
|
1257
|
+
redirectUrl: Joi.string()
|
|
1258
|
+
.empty('')
|
|
1259
|
+
.uri({ scheme: ['http', 'https'], allowRelative: false })
|
|
1260
|
+
.required()
|
|
1261
|
+
.example('https://myapp/account/settings.php')
|
|
1262
|
+
.description('After the authentication process is completed, the user is redirected to this URL'),
|
|
1263
|
+
|
|
1264
|
+
type: defaultAccountTypeSchema
|
|
1265
|
+
}).label('RequestAuthForm')
|
|
1266
|
+
},
|
|
1267
|
+
|
|
1268
|
+
response: {
|
|
1269
|
+
schema: Joi.object({
|
|
1270
|
+
url: Joi.string()
|
|
1271
|
+
.empty('')
|
|
1272
|
+
.uri({ scheme: ['http', 'https'], allowRelative: false })
|
|
1273
|
+
.required()
|
|
1274
|
+
.example('https://ee.example.com/accounts/new?data=eyJhY2NvdW50IjoiZXhh...L0W_BkFH5HW6Krwmr7c&type=imap')
|
|
1275
|
+
.description('Generated URL to the hosted authentication form')
|
|
1276
|
+
}).label('RequestAuthFormResponse'),
|
|
1277
|
+
failAction: 'log'
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
});
|
|
1281
|
+
|
|
1282
|
+
server.route({
|
|
1283
|
+
method: 'GET',
|
|
1284
|
+
path: '/v1/logs/{account}',
|
|
1285
|
+
|
|
1286
|
+
async handler(request) {
|
|
1287
|
+
return getLogs(redis, request.params.account);
|
|
1288
|
+
},
|
|
1289
|
+
options: {
|
|
1290
|
+
description: 'Return IMAP logs for an account',
|
|
1291
|
+
notes: 'Output is a downloadable text file',
|
|
1292
|
+
tags: ['api', 'Logs'],
|
|
1293
|
+
|
|
1294
|
+
auth: {
|
|
1295
|
+
strategy: 'api-token',
|
|
1296
|
+
mode: 'required'
|
|
1297
|
+
},
|
|
1298
|
+
cors: CORS_CONFIG,
|
|
1299
|
+
|
|
1300
|
+
plugins: {
|
|
1301
|
+
'hapi-swagger': {
|
|
1302
|
+
produces: ['text/plain']
|
|
1303
|
+
}
|
|
1304
|
+
},
|
|
1305
|
+
|
|
1306
|
+
validate: {
|
|
1307
|
+
options: {
|
|
1308
|
+
stripUnknown: false,
|
|
1309
|
+
abortEarly: false,
|
|
1310
|
+
convert: true
|
|
1311
|
+
},
|
|
1312
|
+
failAction,
|
|
1313
|
+
|
|
1314
|
+
params: Joi.object({
|
|
1315
|
+
account: accountIdSchema.required()
|
|
1316
|
+
})
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
});
|
|
1320
|
+
|
|
1321
|
+
server.route({
|
|
1322
|
+
method: 'POST',
|
|
1323
|
+
path: '/v1/verifyAccount',
|
|
1324
|
+
|
|
1325
|
+
async handler(request) {
|
|
1326
|
+
try {
|
|
1327
|
+
return await verifyAccountInfo(redis, request.payload, request.logger.child({ action: 'verify-account' }));
|
|
1328
|
+
} catch (err) {
|
|
1329
|
+
handleError(request, err);
|
|
1330
|
+
}
|
|
1331
|
+
},
|
|
1332
|
+
options: {
|
|
1333
|
+
description: 'Verify IMAP and SMTP settings',
|
|
1334
|
+
notes: 'Checks if can connect and authenticate using provided account info',
|
|
1335
|
+
tags: ['api', 'Account'],
|
|
1336
|
+
|
|
1337
|
+
plugins: {},
|
|
1338
|
+
|
|
1339
|
+
auth: {
|
|
1340
|
+
strategy: 'api-token',
|
|
1341
|
+
mode: 'required'
|
|
1342
|
+
},
|
|
1343
|
+
cors: CORS_CONFIG,
|
|
1344
|
+
|
|
1345
|
+
validate: {
|
|
1346
|
+
options: {
|
|
1347
|
+
stripUnknown: false,
|
|
1348
|
+
abortEarly: false,
|
|
1349
|
+
convert: true
|
|
1350
|
+
},
|
|
1351
|
+
failAction,
|
|
1352
|
+
|
|
1353
|
+
payload: Joi.object({
|
|
1354
|
+
mailboxes: Joi.boolean().example(false).description('Include mailbox listing in response').default(false).label('IncludeMailboxes'),
|
|
1355
|
+
imap: Joi.object(imapSchema).allow(false).description('IMAP configuration').label('ImapConfiguration'),
|
|
1356
|
+
smtp: Joi.object(smtpSchema).allow(false).description('SMTP configuration').label('SmtpConfiguration'),
|
|
1357
|
+
proxy: settingsSchema.proxyUrl,
|
|
1358
|
+
smtpEhloName: settingsSchema.smtpEhloName
|
|
1359
|
+
}).label('VerifyAccount')
|
|
1360
|
+
},
|
|
1361
|
+
response: {
|
|
1362
|
+
schema: Joi.object({
|
|
1363
|
+
imap: Joi.object({
|
|
1364
|
+
success: Joi.boolean().example(true).description('Was IMAP account verified').label('VerifyImapSuccess'),
|
|
1365
|
+
error: Joi.string()
|
|
1366
|
+
.example('Something went wrong')
|
|
1367
|
+
.description('Error messages for IMAP verification. Only present if success=false')
|
|
1368
|
+
.label('VerifyImapError'),
|
|
1369
|
+
code: Joi.string()
|
|
1370
|
+
.example('ERR_SSL_WRONG_VERSION_NUMBER')
|
|
1371
|
+
.description('Error code. Only present if success=false')
|
|
1372
|
+
.label('VerifyImapCode')
|
|
1373
|
+
}).label('VerifyImapResult'),
|
|
1374
|
+
smtp: Joi.object({
|
|
1375
|
+
success: Joi.boolean().example(true).description('Was SMTP account verified').label('VerifySmtpSuccess'),
|
|
1376
|
+
error: Joi.string()
|
|
1377
|
+
.example('Something went wrong')
|
|
1378
|
+
.description('Error messages for SMTP verification. Only present if success=false')
|
|
1379
|
+
.label('VerifySmtpError'),
|
|
1380
|
+
code: Joi.string()
|
|
1381
|
+
.example('ERR_SSL_WRONG_VERSION_NUMBER')
|
|
1382
|
+
.description('Error code. Only present if success=false')
|
|
1383
|
+
.label('VerifySmtpCode')
|
|
1384
|
+
}).label('VerifySmtpResult'),
|
|
1385
|
+
mailboxes: shortMailboxesSchema
|
|
1386
|
+
}).label('VerifyAccountResponse'),
|
|
1387
|
+
failAction: 'log'
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
});
|
|
1391
|
+
|
|
1392
|
+
server.route({
|
|
1393
|
+
method: 'GET',
|
|
1394
|
+
path: '/v1/autoconfig',
|
|
1395
|
+
|
|
1396
|
+
async handler(request) {
|
|
1397
|
+
try {
|
|
1398
|
+
let serverSettings = await autodetectImapSettings(request.query.email, request.app.gt);
|
|
1399
|
+
return serverSettings;
|
|
1400
|
+
} catch (err) {
|
|
1401
|
+
request.logger.error({ msg: 'API request failed', err });
|
|
1402
|
+
if (Boom.isBoom(err)) {
|
|
1403
|
+
throw err;
|
|
1404
|
+
}
|
|
1405
|
+
return { imap: false, smtp: false, _source: 'unknown' };
|
|
1406
|
+
}
|
|
1407
|
+
},
|
|
1408
|
+
|
|
1409
|
+
options: {
|
|
1410
|
+
description: 'Discover Email settings',
|
|
1411
|
+
notes: 'Try to discover IMAP and SMTP settings for an email account',
|
|
1412
|
+
tags: ['api', 'Settings'],
|
|
1413
|
+
|
|
1414
|
+
plugins: {},
|
|
1415
|
+
|
|
1416
|
+
auth: {
|
|
1417
|
+
strategy: 'api-token',
|
|
1418
|
+
mode: 'required'
|
|
1419
|
+
},
|
|
1420
|
+
cors: CORS_CONFIG,
|
|
1421
|
+
|
|
1422
|
+
validate: {
|
|
1423
|
+
options: {
|
|
1424
|
+
stripUnknown: false,
|
|
1425
|
+
abortEarly: false,
|
|
1426
|
+
convert: true
|
|
1427
|
+
},
|
|
1428
|
+
failAction,
|
|
1429
|
+
|
|
1430
|
+
query: Joi.object({
|
|
1431
|
+
email: Joi.string()
|
|
1432
|
+
.email()
|
|
1433
|
+
.required()
|
|
1434
|
+
.example('sender@example.com')
|
|
1435
|
+
.description('Email address to discover email settings for')
|
|
1436
|
+
.label('EmailAddress')
|
|
1437
|
+
}).label('AutodiscoverQuery')
|
|
1438
|
+
},
|
|
1439
|
+
|
|
1440
|
+
response: {
|
|
1441
|
+
schema: Joi.object({
|
|
1442
|
+
imap: Joi.object({
|
|
1443
|
+
auth: Joi.object({
|
|
1444
|
+
user: Joi.string().max(256).example('myuser@gmail.com').description('Account username')
|
|
1445
|
+
}).label('DetectedAuthenticationInfo'),
|
|
1446
|
+
|
|
1447
|
+
host: Joi.string().hostname().required().example('imap.gmail.com').description('Hostname to connect to'),
|
|
1448
|
+
port: Joi.number()
|
|
1449
|
+
.integer()
|
|
1450
|
+
.min(1)
|
|
1451
|
+
.max(64 * 1024)
|
|
1452
|
+
.required()
|
|
1453
|
+
.example(993)
|
|
1454
|
+
.description('Service port number'),
|
|
1455
|
+
secure: Joi.boolean().default(false).example(true).description('Should connection use TLS. Usually true for port 993')
|
|
1456
|
+
}).label('ResolvedServerSettings'),
|
|
1457
|
+
smtp: Joi.object({
|
|
1458
|
+
auth: Joi.object({
|
|
1459
|
+
user: Joi.string().max(256).example('myuser@gmail.com').description('Account username')
|
|
1460
|
+
}).label('DetectedAuthenticationInfo'),
|
|
1461
|
+
|
|
1462
|
+
host: Joi.string().hostname().required().example('imap.gmail.com').description('Hostname to connect to'),
|
|
1463
|
+
port: Joi.number()
|
|
1464
|
+
.integer()
|
|
1465
|
+
.min(1)
|
|
1466
|
+
.max(64 * 1024)
|
|
1467
|
+
.required()
|
|
1468
|
+
.example(993)
|
|
1469
|
+
.description('Service port number'),
|
|
1470
|
+
secure: Joi.boolean().default(false).example(true).description('Should connection use TLS. Usually true for port 993')
|
|
1471
|
+
}).label('DiscoveredServerSettings'),
|
|
1472
|
+
_source: Joi.string().example('srv').description('Source for the detected info')
|
|
1473
|
+
}).label('DiscoveredEmailSettings'),
|
|
1474
|
+
failAction: 'log'
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
});
|
|
1060
1478
|
}
|
|
1061
1479
|
|
|
1062
1480
|
module.exports = init;
|