emailengine-app 2.69.0 → 2.71.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 +6 -3
- package/.github/workflows/release.yaml +2 -0
- package/.github/workflows/test.yml +73 -12
- package/.ncurc.js +3 -3
- package/CHANGELOG.md +37 -0
- package/Gruntfile.js +21 -23
- package/bin/emailengine.js +8 -1
- package/config/default.toml +5 -0
- package/config/test.toml +5 -0
- package/data/google-crawlers.json +1 -1
- package/getswagger.sh +44 -4
- package/gettext-extract.js +163 -0
- package/lib/account.js +104 -72
- package/lib/api-routes/account-routes.js +231 -71
- package/lib/api-routes/blocklist-routes.js +25 -18
- package/lib/api-routes/chat-routes.js +32 -14
- package/lib/api-routes/delivery-test-routes.js +30 -5
- package/lib/api-routes/export-routes.js +27 -2
- package/lib/api-routes/gateway-routes.js +63 -12
- package/lib/api-routes/license-routes.js +18 -4
- package/lib/api-routes/mailbox-routes.js +33 -7
- package/lib/api-routes/message-routes.js +291 -145
- package/lib/api-routes/oauth2-app-routes.js +90 -24
- package/lib/api-routes/outbox-routes.js +16 -4
- package/lib/api-routes/pubsub-routes.js +8 -4
- package/lib/api-routes/route-helpers.js +14 -1
- package/lib/api-routes/settings-routes.js +51 -25
- package/lib/api-routes/stats-routes.js +37 -3
- package/lib/api-routes/submit-routes.js +31 -42
- package/lib/api-routes/template-routes.js +54 -21
- package/lib/api-routes/token-routes.js +67 -67
- package/lib/api-routes/webhook-route-routes.js +37 -8
- package/lib/autodetect-imap-settings.js +0 -2
- package/lib/consts.js +5 -0
- package/lib/document-store.js +22 -1
- package/lib/email-client/base-client.js +31 -8
- package/lib/email-client/gmail-client.js +119 -112
- package/lib/email-client/imap/mailbox.js +2 -2
- package/lib/email-client/imap/subconnection.js +0 -1
- package/lib/email-client/imap/sync-operations.js +1 -1
- package/lib/email-client/imap-client.js +36 -17
- package/lib/email-client/notification-handler.js +3 -6
- package/lib/email-client/outlook-client.js +49 -62
- package/lib/export.js +49 -1
- package/lib/feature-flags.js +8 -2
- package/lib/gateway.js +4 -9
- package/lib/get-raw-email.js +5 -5
- package/lib/imapproxy/imap-core/lib/imap-connection.js +0 -1
- package/lib/license-beacon.js +367 -0
- package/lib/logger.js +35 -22
- package/lib/metrics-collector.js +0 -2
- package/lib/oauth2-apps.js +13 -4
- package/lib/outbox.js +24 -40
- package/lib/redis-operations.js +1 -1
- package/lib/routes-ui.js +2 -1
- package/lib/schemas.js +403 -83
- package/lib/sentry.js +139 -0
- package/lib/settings.js +9 -3
- package/lib/stream-encrypt.js +1 -1
- package/lib/templates.js +1 -1
- package/lib/tokens.js +5 -3
- package/lib/tools.js +28 -6
- package/lib/ui-routes/account-routes.js +7 -4
- package/lib/ui-routes/admin-config-routes.js +20 -6
- package/lib/ui-routes/document-store-routes.js +7 -1
- package/lib/ui-routes/oauth-config-routes.js +0 -2
- package/lib/ui-routes/route-helpers.js +0 -2
- package/lib/ui-routes/unsubscribe-routes.js +0 -2
- package/lib/webhooks.js +8 -4
- package/package.json +23 -19
- package/sbom.json +1 -1
- package/server.js +38 -31
- package/static/licenses.html +171 -391
- package/translations/de.mo +0 -0
- package/translations/de.po +154 -142
- package/translations/et.mo +0 -0
- package/translations/et.po +129 -131
- package/translations/fr.mo +0 -0
- package/translations/fr.po +133 -136
- package/translations/ja.mo +0 -0
- package/translations/ja.po +126 -129
- package/translations/messages.pot +107 -107
- package/translations/nl.mo +0 -0
- package/translations/nl.po +128 -130
- package/translations/pl.mo +0 -0
- package/translations/pl.po +125 -128
- package/update-info.sh +19 -1
- package/views/config/logging.hbs +48 -0
- package/views/dashboard.hbs +22 -0
- package/workers/api.js +33 -37
- package/workers/documents.js +2 -22
- package/workers/export.js +73 -92
- package/workers/imap-proxy.js +3 -23
- package/workers/imap.js +2 -22
- package/workers/smtp.js +2 -22
- package/workers/submit.js +6 -24
- package/workers/webhooks.js +2 -22
|
@@ -4,7 +4,7 @@ const crypto = require('crypto');
|
|
|
4
4
|
const { redis } = require('../db');
|
|
5
5
|
const { Account } = require('../account');
|
|
6
6
|
const getSecret = require('../get-secret');
|
|
7
|
-
const { oauth2Apps, SERVICE_ACCOUNT_PROVIDERS } = require('../oauth2-apps');
|
|
7
|
+
const { oauth2Apps, LEGACY_KEYS, SERVICE_ACCOUNT_PROVIDERS } = require('../oauth2-apps');
|
|
8
8
|
const settings = require('../settings');
|
|
9
9
|
const { autodetectImapSettings } = require('../autodetect-imap-settings');
|
|
10
10
|
const Boom = require('@hapi/boom');
|
|
@@ -26,11 +26,42 @@ const {
|
|
|
26
26
|
oauth2Schema,
|
|
27
27
|
oauth2UpdateSchema,
|
|
28
28
|
defaultAccountTypeSchema,
|
|
29
|
-
shortMailboxesSchema
|
|
29
|
+
shortMailboxesSchema,
|
|
30
|
+
ACCOUNT_DISPLAY_STATES,
|
|
31
|
+
errorResponses
|
|
30
32
|
} = require('../schemas');
|
|
31
33
|
|
|
32
34
|
const { REDIS_PREFIX, MAX_FORM_TTL, NONCE_BYTES } = require('../consts');
|
|
33
35
|
|
|
36
|
+
// OAuth2 scope list stored on the account (accountData.oauth2.scope). Reused with a per-route
|
|
37
|
+
// description/label where the same value is exposed under a different field name
|
|
38
|
+
const accountOauth2ScopesSchema = Joi.array()
|
|
39
|
+
.items(Joi.string().example('https://mail.google.com/').label('AccountScopeEntry'))
|
|
40
|
+
.description('OAuth2 scopes granted for this account')
|
|
41
|
+
.label('AccountOauth2Scopes');
|
|
42
|
+
|
|
43
|
+
// Per-protocol result block for the account verification response - the IMAP and SMTP results
|
|
44
|
+
// carry the same fields built by verifyAccountInfo, only labels, wording, and the server
|
|
45
|
+
// response example differ
|
|
46
|
+
const verifyResultSchema = (proto, responseTextExample) =>
|
|
47
|
+
Joi.object({
|
|
48
|
+
success: Joi.boolean().example(true).description(`Was ${proto.toUpperCase()} account verified`).label(`Verify${proto}Success`),
|
|
49
|
+
error: Joi.string()
|
|
50
|
+
.example('Something went wrong')
|
|
51
|
+
.description(`Error messages for ${proto.toUpperCase()} verification. Only present if success=false`)
|
|
52
|
+
.label(`Verify${proto}Error`),
|
|
53
|
+
code: Joi.string().example('ERR_SSL_WRONG_VERSION_NUMBER').description('Error code. Only present if success=false').label(`Verify${proto}Code`),
|
|
54
|
+
statusCode: Joi.number()
|
|
55
|
+
.integer()
|
|
56
|
+
.example(500)
|
|
57
|
+
.description('HTTP-style status code for the error. Only present if success=false')
|
|
58
|
+
.label(`Verify${proto}StatusCode`),
|
|
59
|
+
responseText: Joi.string()
|
|
60
|
+
.example(responseTextExample)
|
|
61
|
+
.description('Server response for the failed verification. Only present if success=false')
|
|
62
|
+
.label(`Verify${proto}ResponseText`)
|
|
63
|
+
}).label(`Verify${proto}Result`);
|
|
64
|
+
|
|
34
65
|
/**
|
|
35
66
|
* Validates that delegation fields are only used with OAuth2 accounts.
|
|
36
67
|
*/
|
|
@@ -149,7 +180,11 @@ async function init(args) {
|
|
|
149
180
|
notes: 'Registers new IMAP account to be synced',
|
|
150
181
|
tags: ['api', 'Account'],
|
|
151
182
|
|
|
152
|
-
plugins: {
|
|
183
|
+
plugins: {
|
|
184
|
+
'hapi-swagger': {
|
|
185
|
+
responses: errorResponses(400, 401, 403, 404, 429, 500)
|
|
186
|
+
}
|
|
187
|
+
},
|
|
153
188
|
|
|
154
189
|
auth: {
|
|
155
190
|
strategy: 'api-token',
|
|
@@ -247,13 +282,19 @@ async function init(args) {
|
|
|
247
282
|
|
|
248
283
|
response: {
|
|
249
284
|
schema: Joi.object({
|
|
250
|
-
account: accountIdSchema
|
|
285
|
+
account: accountIdSchema,
|
|
251
286
|
state: Joi.string()
|
|
252
|
-
.required()
|
|
253
287
|
.valid('existing', 'new')
|
|
254
288
|
.example('new')
|
|
255
|
-
.description('Is the account new or updated existing')
|
|
256
|
-
.label('CreateAccountState')
|
|
289
|
+
.description('Is the account new or updated existing. Not present when a redirect URL is returned')
|
|
290
|
+
.label('CreateAccountState'),
|
|
291
|
+
redirect: Joi.string()
|
|
292
|
+
.uri()
|
|
293
|
+
.example('https://emailengine.example.com/oauth?account=example')
|
|
294
|
+
.description(
|
|
295
|
+
'OAuth2 authorization URL. Returned instead of account and state when the request used oauth2.authorize=true - send the user to this URL to complete the OAuth2 flow'
|
|
296
|
+
)
|
|
297
|
+
.label('CreateAccountRedirect')
|
|
257
298
|
}).label('CreateAccountResponse'),
|
|
258
299
|
failAction: 'log'
|
|
259
300
|
}
|
|
@@ -288,7 +329,11 @@ async function init(args) {
|
|
|
288
329
|
notes: 'Updates account information',
|
|
289
330
|
tags: ['api', 'Account'],
|
|
290
331
|
|
|
291
|
-
plugins: {
|
|
332
|
+
plugins: {
|
|
333
|
+
'hapi-swagger': {
|
|
334
|
+
responses: errorResponses(400, 401, 403, 404, 429, 500)
|
|
335
|
+
}
|
|
336
|
+
},
|
|
292
337
|
|
|
293
338
|
auth: {
|
|
294
339
|
strategy: 'api-token',
|
|
@@ -396,7 +441,11 @@ async function init(args) {
|
|
|
396
441
|
notes: 'Requests connection to be reconnected',
|
|
397
442
|
tags: ['api', 'Account'],
|
|
398
443
|
|
|
399
|
-
plugins: {
|
|
444
|
+
plugins: {
|
|
445
|
+
'hapi-swagger': {
|
|
446
|
+
responses: errorResponses(400, 401, 403, 404, 429, 500)
|
|
447
|
+
}
|
|
448
|
+
},
|
|
400
449
|
|
|
401
450
|
auth: {
|
|
402
451
|
strategy: 'api-token',
|
|
@@ -455,7 +504,11 @@ async function init(args) {
|
|
|
455
504
|
notes: 'Immediately trigger account syncing for IMAP accounts',
|
|
456
505
|
tags: ['api', 'Account'],
|
|
457
506
|
|
|
458
|
-
plugins: {
|
|
507
|
+
plugins: {
|
|
508
|
+
'hapi-swagger': {
|
|
509
|
+
responses: errorResponses(400, 401, 403, 404, 429, 500)
|
|
510
|
+
}
|
|
511
|
+
},
|
|
459
512
|
|
|
460
513
|
auth: {
|
|
461
514
|
strategy: 'api-token',
|
|
@@ -516,7 +569,11 @@ async function init(args) {
|
|
|
516
569
|
|
|
517
570
|
tags: ['api', 'Account'],
|
|
518
571
|
|
|
519
|
-
plugins: {
|
|
572
|
+
plugins: {
|
|
573
|
+
'hapi-swagger': {
|
|
574
|
+
responses: errorResponses(400, 401, 403, 404, 429, 500)
|
|
575
|
+
}
|
|
576
|
+
},
|
|
520
577
|
|
|
521
578
|
auth: {
|
|
522
579
|
strategy: 'api-token',
|
|
@@ -583,7 +640,11 @@ async function init(args) {
|
|
|
583
640
|
notes: 'Deletes all email indexes from Redis and ElasticSearch and re-creates the index for that account. You can only run a single flush operation at a time, so you must wait until the previous flush has finished before initiating a new one.',
|
|
584
641
|
tags: ['api', 'Account'],
|
|
585
642
|
|
|
586
|
-
plugins: {
|
|
643
|
+
plugins: {
|
|
644
|
+
'hapi-swagger': {
|
|
645
|
+
responses: errorResponses(400, 401, 403, 404, 429, 500)
|
|
646
|
+
}
|
|
647
|
+
},
|
|
587
648
|
|
|
588
649
|
auth: {
|
|
589
650
|
strategy: 'api-token',
|
|
@@ -646,7 +707,11 @@ async function init(args) {
|
|
|
646
707
|
notes: 'Lists registered accounts',
|
|
647
708
|
tags: ['api', 'Account'],
|
|
648
709
|
|
|
649
|
-
plugins: {
|
|
710
|
+
plugins: {
|
|
711
|
+
'hapi-swagger': {
|
|
712
|
+
responses: errorResponses(400, 401, 403, 429, 500)
|
|
713
|
+
}
|
|
714
|
+
},
|
|
650
715
|
|
|
651
716
|
auth: {
|
|
652
717
|
strategy: 'api-token',
|
|
@@ -673,7 +738,7 @@ async function init(args) {
|
|
|
673
738
|
.label('PageNumber'),
|
|
674
739
|
pageSize: Joi.number().integer().min(1).max(1000).default(20).example(20).description('How many entries per page').label('PageSize'),
|
|
675
740
|
state: Joi.string()
|
|
676
|
-
.valid(
|
|
741
|
+
.valid(...ACCOUNT_DISPLAY_STATES)
|
|
677
742
|
.example('connected')
|
|
678
743
|
.description('Filter accounts by state')
|
|
679
744
|
.label('AccountState'),
|
|
@@ -687,17 +752,27 @@ async function init(args) {
|
|
|
687
752
|
page: Joi.number().integer().example(0).description('Current page (0-based index)').label('PageNumber'),
|
|
688
753
|
pages: Joi.number().integer().example(24).description('Total page count').label('PagesNumber'),
|
|
689
754
|
|
|
755
|
+
query: Joi.string()
|
|
756
|
+
.allow(false)
|
|
757
|
+
.example(false)
|
|
758
|
+
.description('Search term used for filtering, or false when no filter was applied')
|
|
759
|
+
.label('AccountsQueryFilter'),
|
|
760
|
+
state: Joi.string()
|
|
761
|
+
.example('*')
|
|
762
|
+
.description('Account state filter used for the listing, or "*" when no filter was applied')
|
|
763
|
+
.label('AccountsStateFilter'),
|
|
764
|
+
|
|
690
765
|
accounts: Joi.array()
|
|
691
766
|
.items(
|
|
692
767
|
Joi.object({
|
|
693
768
|
account: accountIdSchema.required(),
|
|
694
|
-
name: Joi.string().max(256).example('My Email Account').description('Display name for the account'),
|
|
769
|
+
name: Joi.string().allow('').max(256).example('My Email Account').description('Display name for the account'),
|
|
695
770
|
email: Joi.string().empty('').email().example('user@example.com').description('Default email address of the account'),
|
|
696
771
|
type: AccountTypeSchema,
|
|
697
772
|
app: Joi.string().max(256).example('AAABhaBPHscAAAAH').description('OAuth2 application ID'),
|
|
698
773
|
state: Joi.string()
|
|
699
774
|
.required()
|
|
700
|
-
.valid(
|
|
775
|
+
.valid(...ACCOUNT_DISPLAY_STATES)
|
|
701
776
|
.example('connected')
|
|
702
777
|
.description('Account state')
|
|
703
778
|
.label('AccountListState'),
|
|
@@ -713,8 +788,11 @@ async function init(args) {
|
|
|
713
788
|
|
|
714
789
|
counters: accountCountersSchema,
|
|
715
790
|
|
|
716
|
-
syncTime: Joi.date().iso().example('2021-02-17T13:43:18.860Z').description('Last sync time'),
|
|
717
|
-
lastError: lastErrorSchema.allow(null)
|
|
791
|
+
syncTime: Joi.date().iso().example('2021-02-17T13:43:18.860Z').description('Last sync time (IMAP accounts only)'),
|
|
792
|
+
lastError: lastErrorSchema.allow(null),
|
|
793
|
+
delegationError: Joi.string()
|
|
794
|
+
.example('Delegated account was not found')
|
|
795
|
+
.description('Error message if the delegated account could not be resolved')
|
|
718
796
|
}).label('AccountResponseItem')
|
|
719
797
|
)
|
|
720
798
|
.label('AccountEntries')
|
|
@@ -817,16 +895,15 @@ async function init(args) {
|
|
|
817
895
|
oauth2App = await oauth2Apps.get(accountData.oauth2.provider);
|
|
818
896
|
|
|
819
897
|
if (oauth2App) {
|
|
898
|
+
result.type = oauth2App.provider;
|
|
820
899
|
// Check if account is already marked as send-only
|
|
821
900
|
if (accountData.sendOnly) {
|
|
822
901
|
result.sendOnly = true;
|
|
823
|
-
} else {
|
|
824
|
-
result.type = oauth2App.provider;
|
|
825
902
|
}
|
|
826
903
|
if (oauth2App.id !== oauth2App.provider) {
|
|
827
904
|
result.app = oauth2App.id;
|
|
828
905
|
}
|
|
829
|
-
result.baseScopes = oauth2App.
|
|
906
|
+
result.baseScopes = oauth2App.baseScopes || 'imap';
|
|
830
907
|
} else {
|
|
831
908
|
result.type = 'oauth2';
|
|
832
909
|
}
|
|
@@ -869,6 +946,12 @@ async function init(args) {
|
|
|
869
946
|
notes: 'Returns stored information about the account. Passwords are not included.',
|
|
870
947
|
tags: ['api', 'Account'],
|
|
871
948
|
|
|
949
|
+
plugins: {
|
|
950
|
+
'hapi-swagger': {
|
|
951
|
+
responses: errorResponses(400, 401, 403, 404, 429, 500)
|
|
952
|
+
}
|
|
953
|
+
},
|
|
954
|
+
|
|
872
955
|
auth: {
|
|
873
956
|
strategy: 'api-token',
|
|
874
957
|
mode: 'required'
|
|
@@ -901,7 +984,7 @@ async function init(args) {
|
|
|
901
984
|
schema: Joi.object({
|
|
902
985
|
account: accountIdSchema.required(),
|
|
903
986
|
|
|
904
|
-
name: Joi.string().max(256).example('My Email Account').description('Display name for the account'),
|
|
987
|
+
name: Joi.string().allow('').max(256).example('My Email Account').description('Display name for the account'),
|
|
905
988
|
email: Joi.string().empty('').email().example('user@example.com').description('Default email address of the account'),
|
|
906
989
|
|
|
907
990
|
copy: Joi.boolean().example(true).description('Copy submitted messages to Sent folder'),
|
|
@@ -917,6 +1000,7 @@ async function init(args) {
|
|
|
917
1000
|
subconnections: accountSchemas.subconnections,
|
|
918
1001
|
|
|
919
1002
|
webhooks: Joi.string()
|
|
1003
|
+
.allow('')
|
|
920
1004
|
.uri({
|
|
921
1005
|
scheme: ['http', 'https'],
|
|
922
1006
|
allowRelative: false
|
|
@@ -930,10 +1014,22 @@ async function init(args) {
|
|
|
930
1014
|
|
|
931
1015
|
smtp: Joi.object(smtpSchema).description('SMTP configuration').label('SMTPResponse'),
|
|
932
1016
|
|
|
933
|
-
oauth2: Joi.object(oauth2Schema)
|
|
1017
|
+
oauth2: Joi.object(oauth2Schema)
|
|
1018
|
+
.keys({
|
|
1019
|
+
scope: accountOauth2ScopesSchema,
|
|
1020
|
+
tokenType: Joi.string().example('Bearer').description('OAuth2 token type'),
|
|
1021
|
+
generated: Joi.date().iso().example('2021-03-22T13:13:31.000Z').description('When the current access token was generated'),
|
|
1022
|
+
refreshTokenGenerated: Joi.date()
|
|
1023
|
+
.iso()
|
|
1024
|
+
.example('2021-03-22T13:13:31.000Z')
|
|
1025
|
+
.description('When the current refresh token was generated'),
|
|
1026
|
+
userFlag: Joi.object().unknown().description('Account-level OAuth2 error flag, if any').label('AccountOauth2UserFlag')
|
|
1027
|
+
})
|
|
1028
|
+
.description('OAuth2 configuration')
|
|
1029
|
+
.label('Oauth2Response'),
|
|
934
1030
|
|
|
935
1031
|
state: Joi.string()
|
|
936
|
-
.valid(
|
|
1032
|
+
.valid(...ACCOUNT_DISPLAY_STATES)
|
|
937
1033
|
.example('connected')
|
|
938
1034
|
.description('Informational account state')
|
|
939
1035
|
.label('AccountInfoState'),
|
|
@@ -949,11 +1045,30 @@ async function init(args) {
|
|
|
949
1045
|
description: Joi.string().example('Authentication failed').description('Error information'),
|
|
950
1046
|
responseCode: Joi.number().integer().example(500).description('Error status code'),
|
|
951
1047
|
code: Joi.string().example('EAUTH').description('Error type identifier'),
|
|
952
|
-
command: Joi.string().example('AUTH PLAIN').description('SMTP command that failed')
|
|
1048
|
+
command: Joi.string().example('AUTH PLAIN').description('SMTP command that failed'),
|
|
1049
|
+
networkRouting: Joi.object()
|
|
1050
|
+
.unknown()
|
|
1051
|
+
.allow(null)
|
|
1052
|
+
.description('Network routing information for the delivery attempt')
|
|
1053
|
+
.label('SMTPStatusNetworkRouting')
|
|
953
1054
|
})
|
|
954
|
-
.
|
|
1055
|
+
.allow(null)
|
|
1056
|
+
.description('Information about the last SMTP connection attempt. Null when no SMTP connection has been attempted')
|
|
955
1057
|
.label('SMTPInfoStatus'),
|
|
956
1058
|
|
|
1059
|
+
syncError: Joi.object({
|
|
1060
|
+
path: Joi.string().example('INBOX').description('Mailbox folder path where the error occurred'),
|
|
1061
|
+
time: Joi.date().iso().example('2021-07-08T07:06:34.336Z').description('When the error occurred'),
|
|
1062
|
+
error: Joi.string().example('Failed to open mailbox').description('Error message')
|
|
1063
|
+
})
|
|
1064
|
+
.unknown()
|
|
1065
|
+
.description('Information about the last mailbox sync error (IMAP accounts only)')
|
|
1066
|
+
.label('AccountSyncError'),
|
|
1067
|
+
|
|
1068
|
+
connections: Joi.number().integer().example(2).description('Number of open IMAP connections for this account (IMAP accounts only)'),
|
|
1069
|
+
|
|
1070
|
+
sendOnly: Joi.boolean().example(false).description('Whether this is a send-only account that does not sync messages'),
|
|
1071
|
+
|
|
957
1072
|
webhooksCustomHeaders: settingsSchema.webhooksCustomHeaders.label('AccountWebhooksCustomHeaders'),
|
|
958
1073
|
|
|
959
1074
|
locale: Joi.string().empty('').max(100).example('fr').description('Optional locale'),
|
|
@@ -982,17 +1097,23 @@ async function init(args) {
|
|
|
982
1097
|
'Account quota information if query argument quota=true. This value will be false if the server does not provide quota information.'
|
|
983
1098
|
),
|
|
984
1099
|
|
|
985
|
-
syncTime: Joi.date().iso().example('2021-02-17T13:43:18.860Z').description('Last sync time'),
|
|
1100
|
+
syncTime: Joi.date().iso().example('2021-02-17T13:43:18.860Z').description('Last sync time (IMAP accounts only)'),
|
|
986
1101
|
|
|
987
1102
|
outlookSubscription: Joi.object({
|
|
988
1103
|
id: Joi.string().description('Microsoft Graph subscription ID'),
|
|
989
|
-
expirationDateTime: Joi.date().iso().description('When the subscription expires'),
|
|
1104
|
+
expirationDateTime: Joi.date().iso().allow(null).description('When the subscription expires'),
|
|
990
1105
|
state: Joi.object({
|
|
991
|
-
state: Joi.string().valid('creating', 'created', 'error').description('Subscription state'),
|
|
1106
|
+
state: Joi.string().valid('creating', 'created', 'renewing', 'error').description('Subscription state'),
|
|
992
1107
|
time: Joi.number().description('Timestamp of last state change'),
|
|
993
|
-
error: Joi.string().description('Error message if state is error')
|
|
994
|
-
|
|
995
|
-
|
|
1108
|
+
error: Joi.string().allow(null).description('Error message if state is error, null after a successful renewal'),
|
|
1109
|
+
retryCount: Joi.number().integer().description('How many times the subscription renewal has been retried'),
|
|
1110
|
+
createRetryCount: Joi.number().integer().description('How many times the subscription creation has been retried')
|
|
1111
|
+
})
|
|
1112
|
+
.unknown()
|
|
1113
|
+
.description('Current subscription state')
|
|
1114
|
+
})
|
|
1115
|
+
.unknown()
|
|
1116
|
+
.description('Microsoft Graph subscription details (Outlook accounts only)'),
|
|
996
1117
|
|
|
997
1118
|
lastError: lastErrorSchema.allow(null)
|
|
998
1119
|
}).label('AccountResponse'),
|
|
@@ -1024,6 +1145,19 @@ async function init(args) {
|
|
|
1024
1145
|
try {
|
|
1025
1146
|
const tokenData = await accountObject.getActiveAccessTokenData();
|
|
1026
1147
|
|
|
1148
|
+
// Account data stores the OAuth2 app ID as the provider value. Resolve it to the
|
|
1149
|
+
// provider name and expose the app ID separately. Legacy app IDs already equal
|
|
1150
|
+
// their provider name, so skip the lookup for these.
|
|
1151
|
+
if (tokenData.provider && !LEGACY_KEYS.includes(tokenData.provider)) {
|
|
1152
|
+
let oauth2App = await oauth2Apps.get(tokenData.provider);
|
|
1153
|
+
if (oauth2App) {
|
|
1154
|
+
if (oauth2App.id !== oauth2App.provider) {
|
|
1155
|
+
tokenData.app = oauth2App.id;
|
|
1156
|
+
}
|
|
1157
|
+
tokenData.provider = oauth2App.provider;
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1027
1161
|
// Record metric if token was actually refreshed (not cached)
|
|
1028
1162
|
if (!tokenData.cached) {
|
|
1029
1163
|
const provider = tokenData.provider || 'unknown';
|
|
@@ -1045,7 +1179,11 @@ async function init(args) {
|
|
|
1045
1179
|
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
1180
|
tags: ['api', 'Account'],
|
|
1047
1181
|
|
|
1048
|
-
plugins: {
|
|
1182
|
+
plugins: {
|
|
1183
|
+
'hapi-swagger': {
|
|
1184
|
+
responses: errorResponses(400, 401, 403, 404, 429, 500)
|
|
1185
|
+
}
|
|
1186
|
+
},
|
|
1049
1187
|
|
|
1050
1188
|
auth: {
|
|
1051
1189
|
strategy: 'api-token',
|
|
@@ -1069,8 +1207,16 @@ async function init(args) {
|
|
|
1069
1207
|
schema: Joi.object({
|
|
1070
1208
|
account: accountIdSchema.required(),
|
|
1071
1209
|
user: Joi.string().max(256).required().example('user@example.com').description('Username'),
|
|
1072
|
-
accessToken: Joi.string()
|
|
1073
|
-
|
|
1210
|
+
accessToken: Joi.string()
|
|
1211
|
+
.max(4 * 4096)
|
|
1212
|
+
.example('aGVsbG8gd29ybGQ=')
|
|
1213
|
+
.description('Access Token. Can be missing if the external authentication server provided password-based credentials')
|
|
1214
|
+
.label('OAuthAccessToken'),
|
|
1215
|
+
provider: OAuth2ProviderSchema,
|
|
1216
|
+
app: Joi.string().max(256).example('AAABhaBPHscAAAAH').description('OAuth2 application ID'),
|
|
1217
|
+
registeredScopes: accountOauth2ScopesSchema.description('OAuth2 scopes registered for this account').label('RegisteredScopes'),
|
|
1218
|
+
expires: Joi.date().iso().example('2021-03-22T13:13:31.000Z').description('When the access token expires'),
|
|
1219
|
+
cached: Joi.boolean().example(false).description('Whether the token was returned from cache or was freshly renewed')
|
|
1074
1220
|
}).label('AccountTokenResponse'),
|
|
1075
1221
|
failAction: 'log'
|
|
1076
1222
|
}
|
|
@@ -1101,7 +1247,11 @@ async function init(args) {
|
|
|
1101
1247
|
notes: 'Returns signatures associated with the account. Currently only Gmail is supported, and only "new message" signatures from the "sendAs" list are returned.',
|
|
1102
1248
|
tags: ['api', 'Account'],
|
|
1103
1249
|
|
|
1104
|
-
plugins: {
|
|
1250
|
+
plugins: {
|
|
1251
|
+
'hapi-swagger': {
|
|
1252
|
+
responses: errorResponses(400, 401, 403, 404, 429, 500)
|
|
1253
|
+
}
|
|
1254
|
+
},
|
|
1105
1255
|
|
|
1106
1256
|
auth: {
|
|
1107
1257
|
strategy: 'api-token',
|
|
@@ -1130,7 +1280,10 @@ async function init(args) {
|
|
|
1130
1280
|
signature: Joi.string().example('<div>Best regards,</div>').description('Signature HTML code').required()
|
|
1131
1281
|
}).label('SignatureResponseItem')
|
|
1132
1282
|
)
|
|
1133
|
-
.label('SignatureEntries')
|
|
1283
|
+
.label('SignatureEntries'),
|
|
1284
|
+
signaturesSupported: Joi.boolean()
|
|
1285
|
+
.example(true)
|
|
1286
|
+
.description('Whether the account type supports listing signatures (currently Gmail API accounts only)')
|
|
1134
1287
|
}).label('AccountSignaturesResponse'),
|
|
1135
1288
|
failAction: 'log'
|
|
1136
1289
|
}
|
|
@@ -1205,7 +1358,11 @@ async function init(args) {
|
|
|
1205
1358
|
notes: 'Generates a redirect link to the hosted authentication form',
|
|
1206
1359
|
tags: ['api', 'Account'],
|
|
1207
1360
|
|
|
1208
|
-
plugins: {
|
|
1361
|
+
plugins: {
|
|
1362
|
+
'hapi-swagger': {
|
|
1363
|
+
responses: errorResponses(400, 401, 403, 404, 429, 500)
|
|
1364
|
+
}
|
|
1365
|
+
},
|
|
1209
1366
|
|
|
1210
1367
|
auth: {
|
|
1211
1368
|
strategy: 'api-token',
|
|
@@ -1299,7 +1456,8 @@ async function init(args) {
|
|
|
1299
1456
|
|
|
1300
1457
|
plugins: {
|
|
1301
1458
|
'hapi-swagger': {
|
|
1302
|
-
produces: ['text/plain']
|
|
1459
|
+
produces: ['text/plain'],
|
|
1460
|
+
responses: errorResponses(400, 401, 403, 429, 500)
|
|
1303
1461
|
}
|
|
1304
1462
|
},
|
|
1305
1463
|
|
|
@@ -1334,7 +1492,11 @@ async function init(args) {
|
|
|
1334
1492
|
notes: 'Checks if can connect and authenticate using provided account info',
|
|
1335
1493
|
tags: ['api', 'Account'],
|
|
1336
1494
|
|
|
1337
|
-
plugins: {
|
|
1495
|
+
plugins: {
|
|
1496
|
+
'hapi-swagger': {
|
|
1497
|
+
responses: errorResponses(400, 401, 403, 429, 500)
|
|
1498
|
+
}
|
|
1499
|
+
},
|
|
1338
1500
|
|
|
1339
1501
|
auth: {
|
|
1340
1502
|
strategy: 'api-token',
|
|
@@ -1360,28 +1522,8 @@ async function init(args) {
|
|
|
1360
1522
|
},
|
|
1361
1523
|
response: {
|
|
1362
1524
|
schema: Joi.object({
|
|
1363
|
-
imap:
|
|
1364
|
-
|
|
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'),
|
|
1525
|
+
imap: verifyResultSchema('Imap', 'NO [AUTHENTICATIONFAILED] Invalid credentials'),
|
|
1526
|
+
smtp: verifyResultSchema('Smtp', '535 Authentication failed'),
|
|
1385
1527
|
mailboxes: shortMailboxesSchema
|
|
1386
1528
|
}).label('VerifyAccountResponse'),
|
|
1387
1529
|
failAction: 'log'
|
|
@@ -1411,7 +1553,11 @@ async function init(args) {
|
|
|
1411
1553
|
notes: 'Try to discover IMAP and SMTP settings for an email account',
|
|
1412
1554
|
tags: ['api', 'Settings'],
|
|
1413
1555
|
|
|
1414
|
-
plugins: {
|
|
1556
|
+
plugins: {
|
|
1557
|
+
'hapi-swagger': {
|
|
1558
|
+
responses: errorResponses(400, 401, 403, 429, 500)
|
|
1559
|
+
}
|
|
1560
|
+
},
|
|
1415
1561
|
|
|
1416
1562
|
auth: {
|
|
1417
1563
|
strategy: 'api-token',
|
|
@@ -1444,31 +1590,45 @@ async function init(args) {
|
|
|
1444
1590
|
user: Joi.string().max(256).example('myuser@gmail.com').description('Account username')
|
|
1445
1591
|
}).label('DetectedAuthenticationInfo'),
|
|
1446
1592
|
|
|
1447
|
-
host: Joi.string().hostname().
|
|
1593
|
+
host: Joi.string().hostname().example('imap.gmail.com').description('Hostname to connect to'),
|
|
1448
1594
|
port: Joi.number()
|
|
1449
1595
|
.integer()
|
|
1450
1596
|
.min(1)
|
|
1451
1597
|
.max(64 * 1024)
|
|
1452
|
-
.required()
|
|
1453
1598
|
.example(993)
|
|
1454
1599
|
.description('Service port number'),
|
|
1455
1600
|
secure: Joi.boolean().default(false).example(true).description('Should connection use TLS. Usually true for port 993')
|
|
1456
|
-
})
|
|
1601
|
+
})
|
|
1602
|
+
.allow(false)
|
|
1603
|
+
.description('Discovered IMAP settings. False if IMAP settings were not found')
|
|
1604
|
+
.label('ResolvedServerSettings'),
|
|
1457
1605
|
smtp: Joi.object({
|
|
1458
1606
|
auth: Joi.object({
|
|
1459
1607
|
user: Joi.string().max(256).example('myuser@gmail.com').description('Account username')
|
|
1460
1608
|
}).label('DetectedAuthenticationInfo'),
|
|
1461
1609
|
|
|
1462
|
-
host: Joi.string().hostname().
|
|
1610
|
+
host: Joi.string().hostname().example('smtp.gmail.com').description('Hostname to connect to'),
|
|
1463
1611
|
port: Joi.number()
|
|
1464
1612
|
.integer()
|
|
1465
1613
|
.min(1)
|
|
1466
1614
|
.max(64 * 1024)
|
|
1467
|
-
.
|
|
1468
|
-
.example(993)
|
|
1615
|
+
.example(465)
|
|
1469
1616
|
.description('Service port number'),
|
|
1470
|
-
secure: Joi.boolean().default(false).example(true).description('Should connection use TLS. Usually true for port
|
|
1471
|
-
})
|
|
1617
|
+
secure: Joi.boolean().default(false).example(true).description('Should connection use TLS. Usually true for port 465')
|
|
1618
|
+
})
|
|
1619
|
+
.allow(false)
|
|
1620
|
+
.description('Discovered SMTP settings. False if SMTP settings were not found')
|
|
1621
|
+
.label('DiscoveredServerSettings'),
|
|
1622
|
+
appPassword: Joi.object({
|
|
1623
|
+
required: Joi.boolean().example(true).description('Whether the provider requires an app password'),
|
|
1624
|
+
provider: Joi.string().example('Gmail').description('Provider name'),
|
|
1625
|
+
instructions: Joi.string()
|
|
1626
|
+
.example('Use an app password instead of the regular account password')
|
|
1627
|
+
.description('Instructions for setting up an app password')
|
|
1628
|
+
})
|
|
1629
|
+
.unknown()
|
|
1630
|
+
.description('App password requirements for the provider, if known')
|
|
1631
|
+
.label('DiscoveredAppPasswordInfo'),
|
|
1472
1632
|
_source: Joi.string().example('srv').description('Source for the detected info')
|
|
1473
1633
|
}).label('DiscoveredEmailSettings'),
|
|
1474
1634
|
failAction: 'log'
|
|
@@ -8,7 +8,7 @@ const getSecret = require('../get-secret');
|
|
|
8
8
|
const { lists } = require('../lists');
|
|
9
9
|
const { failAction } = require('../tools');
|
|
10
10
|
const { handleError } = require('./route-helpers');
|
|
11
|
-
const { accountIdSchema } = require('../schemas');
|
|
11
|
+
const { accountIdSchema, errorResponses } = require('../schemas');
|
|
12
12
|
const { REDIS_PREFIX } = require('../consts');
|
|
13
13
|
|
|
14
14
|
async function init(args) {
|
|
@@ -31,7 +31,11 @@ async function init(args) {
|
|
|
31
31
|
notes: 'List blocklists with blocked addresses',
|
|
32
32
|
tags: ['api', 'Blocklists'],
|
|
33
33
|
|
|
34
|
-
plugins: {
|
|
34
|
+
plugins: {
|
|
35
|
+
'hapi-swagger': {
|
|
36
|
+
responses: errorResponses(400, 401, 403, 429, 500)
|
|
37
|
+
}
|
|
38
|
+
},
|
|
35
39
|
|
|
36
40
|
auth: {
|
|
37
41
|
strategy: 'api-token',
|
|
@@ -97,7 +101,11 @@ async function init(args) {
|
|
|
97
101
|
notes: 'List blocked addresses for a list',
|
|
98
102
|
tags: ['api', 'Blocklists'],
|
|
99
103
|
|
|
100
|
-
plugins: {
|
|
104
|
+
plugins: {
|
|
105
|
+
'hapi-swagger': {
|
|
106
|
+
responses: errorResponses(400, 401, 403, 404, 429, 500)
|
|
107
|
+
}
|
|
108
|
+
},
|
|
101
109
|
|
|
102
110
|
auth: {
|
|
103
111
|
strategy: 'api-token',
|
|
@@ -145,10 +153,10 @@ async function init(args) {
|
|
|
145
153
|
.items(
|
|
146
154
|
Joi.object({
|
|
147
155
|
recipient: Joi.string().email().example('user@example.com').description('Listed email address').required(),
|
|
148
|
-
account: accountIdSchema.required()
|
|
156
|
+
account: accountIdSchema.required(),
|
|
149
157
|
messageId: Joi.string().example('<test123@example.com>').description('Message ID'),
|
|
150
158
|
source: Joi.string().example('api').description('Which mechanism was used to add the entry'),
|
|
151
|
-
reason: Joi.string().example('
|
|
159
|
+
reason: Joi.string().example('block').description('Why this entry was added'),
|
|
152
160
|
remoteAddress: Joi.string()
|
|
153
161
|
.ip({
|
|
154
162
|
version: ['ipv4', 'ipv6'],
|
|
@@ -203,18 +211,7 @@ async function init(args) {
|
|
|
203
211
|
added: !!added
|
|
204
212
|
};
|
|
205
213
|
} catch (err) {
|
|
206
|
-
|
|
207
|
-
if (Boom.isBoom(err)) {
|
|
208
|
-
throw err;
|
|
209
|
-
}
|
|
210
|
-
let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
|
|
211
|
-
if (err.code) {
|
|
212
|
-
error.output.payload.code = err.code;
|
|
213
|
-
}
|
|
214
|
-
if (err.details) {
|
|
215
|
-
error.output.payload.details = err.details;
|
|
216
|
-
}
|
|
217
|
-
throw error;
|
|
214
|
+
handleError(request, err);
|
|
218
215
|
}
|
|
219
216
|
},
|
|
220
217
|
options: {
|
|
@@ -222,6 +219,12 @@ async function init(args) {
|
|
|
222
219
|
notes: 'Add an email address to a blocklist',
|
|
223
220
|
tags: ['api', 'Blocklists'],
|
|
224
221
|
|
|
222
|
+
plugins: {
|
|
223
|
+
'hapi-swagger': {
|
|
224
|
+
responses: errorResponses(400, 401, 403, 404, 429, 500)
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
|
|
225
228
|
auth: {
|
|
226
229
|
strategy: 'api-token',
|
|
227
230
|
mode: 'required'
|
|
@@ -294,7 +297,11 @@ async function init(args) {
|
|
|
294
297
|
notes: 'Delete a blocked email address from a list',
|
|
295
298
|
tags: ['api', 'Blocklists'],
|
|
296
299
|
|
|
297
|
-
plugins: {
|
|
300
|
+
plugins: {
|
|
301
|
+
'hapi-swagger': {
|
|
302
|
+
responses: errorResponses(400, 401, 403, 404, 429, 500)
|
|
303
|
+
}
|
|
304
|
+
},
|
|
298
305
|
|
|
299
306
|
auth: {
|
|
300
307
|
strategy: 'api-token',
|