emailengine-app 2.68.1 → 2.70.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 +8 -3
- package/.github/workflows/release.yaml +6 -0
- package/CHANGELOG.md +59 -0
- package/Gruntfile.js +3 -1
- package/config/default.toml +2 -0
- package/data/google-crawlers.json +7 -1
- package/getswagger.sh +40 -4
- package/gettext-extract.js +163 -0
- package/lib/account.js +135 -72
- package/lib/api-routes/account-routes.js +684 -106
- package/lib/api-routes/blocklist-routes.js +344 -0
- package/lib/api-routes/chat-routes.js +32 -14
- package/lib/api-routes/delivery-test-routes.js +346 -0
- package/lib/api-routes/export-routes.js +28 -14
- package/lib/api-routes/gateway-routes.js +427 -0
- package/lib/api-routes/license-routes.js +156 -0
- package/lib/api-routes/mailbox-routes.js +344 -0
- package/lib/api-routes/message-routes.js +221 -187
- package/lib/api-routes/oauth2-app-routes.js +697 -0
- package/lib/api-routes/outbox-routes.js +185 -0
- package/lib/api-routes/pubsub-routes.js +102 -0
- package/lib/api-routes/route-helpers.js +58 -0
- package/lib/api-routes/settings-routes.js +357 -0
- package/lib/api-routes/stats-routes.js +111 -0
- package/lib/api-routes/submit-routes.js +461 -0
- package/lib/api-routes/template-routes.js +60 -75
- package/lib/api-routes/token-routes.js +297 -0
- package/lib/api-routes/webhook-route-routes.js +181 -0
- package/lib/autodetect-imap-settings.js +0 -2
- package/lib/consts.js +5 -0
- package/lib/email-client/base-client.js +28 -6
- package/lib/email-client/gmail-client.js +133 -112
- package/lib/email-client/imap/mailbox.js +34 -11
- package/lib/email-client/imap/subconnection.js +20 -13
- package/lib/email-client/imap/sync-operations.js +131 -3
- package/lib/email-client/imap-client.js +152 -75
- package/lib/email-client/notification-handler.js +1 -4
- package/lib/email-client/outlook-client.js +134 -75
- package/lib/export.js +97 -20
- package/lib/feature-flags.js +2 -2
- package/lib/gateway.js +4 -9
- package/lib/get-raw-email.js +5 -5
- 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 -24
- package/lib/imapproxy/imap-core/lib/imap-server.js +24 -0
- package/lib/imapproxy/imap-core/lib/imap-stream.js +26 -0
- package/lib/logger.js +24 -21
- package/lib/message-port-stream.js +113 -16
- 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/reject-worker-calls.js +42 -0
- package/lib/routes-ui.js +37 -8778
- package/lib/schemas.js +429 -84
- 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 +70 -4
- package/lib/ui-routes/account-routes.js +45 -212
- package/lib/ui-routes/admin-config-routes.js +928 -489
- 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} +369 -91
- package/lib/ui-routes/route-helpers.js +314 -0
- package/lib/ui-routes/smtp-test-routes.js +236 -0
- package/lib/ui-routes/unsubscribe-routes.js +232 -0
- package/lib/webhook-request.js +36 -0
- package/lib/webhooks.js +8 -4
- package/package.json +13 -12
- package/sbom.json +1 -1
- package/server.js +222 -39
- package/static/licenses.html +160 -300
- package/translations/messages.pot +112 -132
- package/update-info.sh +19 -1
- package/views/config/logging.hbs +48 -0
- package/views/dashboard.hbs +7 -26
- package/views/internals/index.hbs +15 -0
- package/views/tokens/index.hbs +9 -0
- package/workers/api.js +200 -4424
- package/workers/documents.js +2 -22
- package/workers/export.js +103 -104
- package/workers/imap-proxy.js +3 -23
- package/workers/imap.js +32 -36
- package/workers/smtp.js +2 -22
- package/workers/submit.js +26 -35
- package/workers/webhooks.js +9 -43
package/lib/schemas.js
CHANGED
|
@@ -5,6 +5,7 @@ const config = require('@zone-eu/wild-config');
|
|
|
5
5
|
const { getByteSize } = require('./tools');
|
|
6
6
|
const { locales } = require('./translations');
|
|
7
7
|
const { LEGACY_KEYS, OAUTH_PROVIDERS } = require('./oauth2-apps');
|
|
8
|
+
const { ACCOUNT_STATES } = require('./account/account-state');
|
|
8
9
|
const { validateConfig: validateExternalAccountConfig } = require('./oauth/external-account-config');
|
|
9
10
|
|
|
10
11
|
// Shared Joi custom validator for the gmailService external_account credential
|
|
@@ -47,6 +48,10 @@ const ADDRESS_STRATEGIES = [
|
|
|
47
48
|
|
|
48
49
|
const accountIdSchema = Joi.string().empty('').trim().max(256).example('user123').description('Unique identifier for the email account');
|
|
49
50
|
|
|
51
|
+
// All states that the account state field can expose over the API. In addition to the state
|
|
52
|
+
// machine states, 'disconnected' and 'paused' are client-level display states set by imap-client
|
|
53
|
+
const ACCOUNT_DISPLAY_STATES = [...Object.values(ACCOUNT_STATES), 'disconnected', 'paused'];
|
|
54
|
+
|
|
50
55
|
// Allowed configuration keys
|
|
51
56
|
const settingsSchema = {
|
|
52
57
|
/* ────────────── Webhooks ────────────── */
|
|
@@ -400,6 +405,23 @@ const settingsSchema = {
|
|
|
400
405
|
maxLogLines: Joi.number().integer().min(0).max(1000000).default(10000).description('Maximum number of log entries to retain per account')
|
|
401
406
|
}).label('LogSettings'),
|
|
402
407
|
|
|
408
|
+
/* ────────────── Error Reporting ────────────── */
|
|
409
|
+
|
|
410
|
+
sentryEnabled: Joi.boolean()
|
|
411
|
+
.truthy('Y', 'true', '1', 'on')
|
|
412
|
+
.falsy('N', 'false', 0, '')
|
|
413
|
+
.example(false)
|
|
414
|
+
.description(
|
|
415
|
+
'Send unhandled error reports to a Sentry server. Applied at runtime, no restart needed. Ignored when the SENTRY_DSN environment variable is set'
|
|
416
|
+
),
|
|
417
|
+
|
|
418
|
+
sentryDsn: Joi.string()
|
|
419
|
+
.uri({ scheme: ['http', 'https'], allowRelative: false })
|
|
420
|
+
.allow('', null)
|
|
421
|
+
.example('https://public-key@sentry.example.com/1')
|
|
422
|
+
.description('Sentry DSN to send error reports to. If empty, reports go to the Sentry instance run by the EmailEngine developers')
|
|
423
|
+
.label('SentryDsn'),
|
|
424
|
+
|
|
403
425
|
/* ────────────── Local Address Strategy ────────────── */
|
|
404
426
|
|
|
405
427
|
imapStrategy: Joi.string()
|
|
@@ -932,10 +954,27 @@ const attachmentSchema = Joi.object({
|
|
|
932
954
|
method: Joi.string().example('REQUEST').description('Calendar method (REQUEST, REPLY, CANCEL, etc.) for iCalendar attachments')
|
|
933
955
|
}).label('AttachmentEntry');
|
|
934
956
|
|
|
935
|
-
|
|
957
|
+
// Response-side address entry. Mail servers can return name-only entries (empty address) and
|
|
958
|
+
// group-syntax artifacts (e.g. "undisclosed-recipients@") that are not valid email addresses,
|
|
959
|
+
// so this is laxer than the request-side addressSchema.
|
|
960
|
+
const responseAddressSchema = Joi.object({
|
|
961
|
+
name: Joi.string().trim().empty('').max(256).example('Some Name').description('Display name for the email address'),
|
|
962
|
+
address: Joi.string().allow('').example('user@example.com').description('Email address (can be empty for name-only entries)')
|
|
963
|
+
})
|
|
964
|
+
.description('Email address with optional display name')
|
|
965
|
+
.label('EmailAddressResponse');
|
|
966
|
+
|
|
967
|
+
const AddressListSchema = Joi.array().items(responseAddressSchema.label('RcptAddressEntry')).description('List of email addresses').label('AddressList');
|
|
936
968
|
|
|
969
|
+
// Request-side sender address - keeps strict email validation
|
|
937
970
|
const fromAddressSchema = addressSchema.example({ name: 'From Me', address: 'sender@example.com' }).description('Sender email address').label('FromAddress');
|
|
938
971
|
|
|
972
|
+
// Response-side sender address - lax, see responseAddressSchema
|
|
973
|
+
const responseFromAddressSchema = responseAddressSchema
|
|
974
|
+
.example({ name: 'From Me', address: 'sender@example.com' })
|
|
975
|
+
.description('Sender email address')
|
|
976
|
+
.label('FromAddressResponse');
|
|
977
|
+
|
|
939
978
|
const emailIdSchema = Joi.string()
|
|
940
979
|
.max(256)
|
|
941
980
|
.example('1278455344230334865')
|
|
@@ -947,23 +986,56 @@ const threadIdSchema = Joi.string()
|
|
|
947
986
|
.description('Thread identifier for email conversations (when supported by the email server)')
|
|
948
987
|
.label('MessageThreadId');
|
|
949
988
|
|
|
989
|
+
const messageSpecialUseSchema = Joi.string()
|
|
990
|
+
.example('\\Sent')
|
|
991
|
+
.valid('\\Drafts', '\\Junk', '\\Sent', '\\Trash', '\\Inbox')
|
|
992
|
+
.description('Special folder type where this message is stored')
|
|
993
|
+
.label('MessageSpecialUse');
|
|
994
|
+
|
|
995
|
+
const messageCategorySchema = Joi.string()
|
|
996
|
+
.example('primary')
|
|
997
|
+
.valid('primary', 'social', 'promotions', 'updates', 'forums')
|
|
998
|
+
.description('Gmail inbox tab/category of the message (Gmail API accounts only)')
|
|
999
|
+
.label('MessageCategory');
|
|
1000
|
+
|
|
1001
|
+
const bounceListSchema = Joi.array()
|
|
1002
|
+
.items(
|
|
1003
|
+
Joi.object({
|
|
1004
|
+
message: Joi.string().max(256).required().example('AAAAAQAACnA').description('EmailEngine identifier of the bounce notification'),
|
|
1005
|
+
recipient: Joi.string().email().example('recipient@example.com').description('Email address that bounced'),
|
|
1006
|
+
action: Joi.string().example('failed').description('Bounce action (failed, delayed, etc.)').label('BounceAction'),
|
|
1007
|
+
response: Joi.object({
|
|
1008
|
+
message: Joi.string().example('550 5.1.1 No such user').description('Error message from the receiving server'),
|
|
1009
|
+
status: Joi.string().example('5.1.1').description('SMTP status code')
|
|
1010
|
+
}).label('BounceResponse'),
|
|
1011
|
+
date: Joi.date().iso().example('2021-03-22T13:13:31.000Z').description('When the bounce was detected')
|
|
1012
|
+
}).label('BounceEntry')
|
|
1013
|
+
)
|
|
1014
|
+
.description('Bounce notifications detected for this message (IMAP accounts only)')
|
|
1015
|
+
.label('BounceList');
|
|
1016
|
+
|
|
950
1017
|
const messageEntrySchema = Joi.object({
|
|
951
1018
|
id: Joi.string().example('AAAAAgAACrI').description('EmailEngine message identifier').label('MessageEntryId'),
|
|
952
|
-
uid: Joi.number().integer().example(12345).description('IMAP UID (unique identifier within the mailbox)').label('MessageUid'),
|
|
1019
|
+
uid: Joi.number().integer().example(12345).description('IMAP UID (unique identifier within the mailbox). IMAP accounts only').label('MessageUid'),
|
|
953
1020
|
emailId: emailIdSchema,
|
|
954
1021
|
threadId: threadIdSchema,
|
|
1022
|
+
path: Joi.string()
|
|
1023
|
+
.example('INBOX')
|
|
1024
|
+
.description(
|
|
1025
|
+
'Mailbox folder path that contains this message. Returned when listing a virtual folder like "\\\\All" or when extended info is requested'
|
|
1026
|
+
),
|
|
955
1027
|
date: Joi.date().iso().example('2021-03-22T13:13:31.000Z').description('Date when the message was received by the mail server'),
|
|
956
1028
|
draft: Joi.boolean().example(false).description('Whether this message is a draft'),
|
|
957
1029
|
unseen: Joi.boolean().example(true).description('Whether this message is unread'),
|
|
958
1030
|
flagged: Joi.boolean().example(true).description('Whether this message is flagged/starred'),
|
|
1031
|
+
answered: Joi.boolean().example(false).description('Whether this message has been answered (IMAP accounts only)'),
|
|
959
1032
|
size: Joi.number().integer().example(1040).description('Message size in bytes'),
|
|
960
1033
|
subject: Joi.string().allow('').example('What a wonderful message').description('Message subject line'),
|
|
961
1034
|
|
|
962
|
-
from:
|
|
1035
|
+
from: responseFromAddressSchema,
|
|
963
1036
|
replyTo: AddressListSchema,
|
|
964
1037
|
to: AddressListSchema,
|
|
965
1038
|
cc: AddressListSchema,
|
|
966
|
-
bcc: AddressListSchema,
|
|
967
1039
|
messageId: Joi.string().example('<test123@example.com>').description('Message-ID header value'),
|
|
968
1040
|
inReplyTo: Joi.string().example('<7JBUMt0WOn+_==MOkaCOQ@mail.gmail.com>').description('Message-ID of the message this is replying to'),
|
|
969
1041
|
|
|
@@ -982,29 +1054,30 @@ const messageEntrySchema = Joi.object({
|
|
|
982
1054
|
.label('TextEncodedSize')
|
|
983
1055
|
}).label('TextInfo'),
|
|
984
1056
|
|
|
985
|
-
preview: Joi.string().description('Short preview of the message content')
|
|
986
|
-
}).label('MessageListEntry');
|
|
1057
|
+
preview: Joi.string().description('Short preview of the message content'),
|
|
987
1058
|
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
.
|
|
991
|
-
|
|
992
|
-
|
|
1059
|
+
category: messageCategorySchema,
|
|
1060
|
+
messageSpecialUse: messageSpecialUseSchema,
|
|
1061
|
+
isAutoReply: Joi.boolean().example(false).description('Whether this message appears to be an automatic reply (out of office, vacation responder, etc.)'),
|
|
1062
|
+
bounces: bounceListSchema
|
|
1063
|
+
}).label('MessageListEntry');
|
|
993
1064
|
|
|
994
1065
|
const messageDetailsSchema = Joi.object({
|
|
995
1066
|
id: Joi.string().example('AAAAAgAACrI').description('EmailEngine message identifier').label('MessageEntryId'),
|
|
996
|
-
uid: Joi.number().integer().example(12345).description('IMAP UID (unique identifier within the mailbox)').label('MessageUid'),
|
|
1067
|
+
uid: Joi.number().integer().example(12345).description('IMAP UID (unique identifier within the mailbox). IMAP accounts only').label('MessageUid'),
|
|
997
1068
|
emailId: emailIdSchema,
|
|
998
1069
|
threadId: threadIdSchema,
|
|
1070
|
+
path: Joi.string().example('INBOX').description('Mailbox folder path that contains this message. Not returned for Gmail API accounts'),
|
|
999
1071
|
date: Joi.date().iso().example('2021-03-22T13:13:31.000Z').description('Date when the message was received by the mail server'),
|
|
1000
1072
|
draft: Joi.boolean().example(false).description('Whether this message is a draft'),
|
|
1001
1073
|
unseen: Joi.boolean().example(true).description('Whether this message is unread'),
|
|
1002
1074
|
flagged: Joi.boolean().example(true).description('Whether this message is flagged/starred'),
|
|
1003
|
-
|
|
1075
|
+
answered: Joi.boolean().example(false).description('Whether this message has been answered (IMAP accounts only)'),
|
|
1076
|
+
size: Joi.number().integer().example(1040).description('Message size in bytes. Not returned for MS Graph API accounts'),
|
|
1004
1077
|
subject: Joi.string().allow('').example('What a wonderful message').description('Message subject line'),
|
|
1005
1078
|
|
|
1006
|
-
from:
|
|
1007
|
-
sender:
|
|
1079
|
+
from: responseFromAddressSchema,
|
|
1080
|
+
sender: responseFromAddressSchema,
|
|
1008
1081
|
|
|
1009
1082
|
to: AddressListSchema,
|
|
1010
1083
|
|
|
@@ -1024,9 +1097,7 @@ const messageDetailsSchema = Joi.object({
|
|
|
1024
1097
|
headers: Joi.object()
|
|
1025
1098
|
.example({ from: ['From Me <sender@example.com>'], subject: ['What a wonderful message'] })
|
|
1026
1099
|
.label('MessageHeaders')
|
|
1027
|
-
.description(
|
|
1028
|
-
'Raw email headers as key-value pairs (arrays contain multiple values for headers that appear multiple times). Not available for MS Graph API.'
|
|
1029
|
-
)
|
|
1100
|
+
.description('Raw email headers as key-value pairs (arrays contain multiple values for headers that appear multiple times)')
|
|
1030
1101
|
.unknown(),
|
|
1031
1102
|
|
|
1032
1103
|
text: Joi.object({
|
|
@@ -1039,100 +1110,136 @@ const messageDetailsSchema = Joi.object({
|
|
|
1039
1110
|
.label('TextDetailEncodedSize'),
|
|
1040
1111
|
plain: Joi.string().example('Hello from myself!').description('Plain text version of the message'),
|
|
1041
1112
|
html: Joi.string().example('<p>Hello from myself!</p>').description('HTML version of the message'),
|
|
1042
|
-
hasMore: Joi.boolean()
|
|
1113
|
+
hasMore: Joi.boolean()
|
|
1114
|
+
.example(false)
|
|
1115
|
+
.description('Whether the message content was truncated (true if more content is available via separate API call)'),
|
|
1116
|
+
webSafe: Joi.boolean()
|
|
1117
|
+
.example(true)
|
|
1118
|
+
.description('Whether the HTML content has been processed into a web-safe version (set when webSafeHtml was requested)')
|
|
1043
1119
|
}).label('TextInfoDetails'),
|
|
1044
1120
|
|
|
1045
|
-
bounces:
|
|
1046
|
-
.items(
|
|
1047
|
-
Joi.object({
|
|
1048
|
-
message: Joi.string().max(256).required().example('AAAAAQAACnA').description('EmailEngine identifier of the bounce notification'),
|
|
1049
|
-
recipient: Joi.string().email().example('recipient@example.com').description('Email address that bounced'),
|
|
1050
|
-
action: Joi.string().example('failed').description('Bounce action (failed, delayed, etc.)').label('BounceAction'),
|
|
1051
|
-
response: Joi.object({
|
|
1052
|
-
message: Joi.string().example('550 5.1.1 No such user').description('Error message from the receiving server'),
|
|
1053
|
-
status: Joi.string().example('5.1.1').description('SMTP status code')
|
|
1054
|
-
}).label('BounceResponse'),
|
|
1055
|
-
date: Joi.date().iso().example('2021-03-22T13:13:31.000Z').description('When the bounce was detected')
|
|
1056
|
-
}).label('BounceEntry')
|
|
1057
|
-
)
|
|
1058
|
-
.label('BounceList'),
|
|
1121
|
+
bounces: bounceListSchema,
|
|
1059
1122
|
|
|
1060
1123
|
isAutoReply: Joi.boolean().example(false).description('Whether this message appears to be an automatic reply (out of office, vacation responder, etc.)'),
|
|
1061
1124
|
|
|
1125
|
+
preview: Joi.string().description('Short preview of the message content (Gmail API and MS Graph API accounts only)'),
|
|
1126
|
+
category: messageCategorySchema,
|
|
1127
|
+
|
|
1062
1128
|
specialUse: Joi.string()
|
|
1063
1129
|
.example('\\Sent')
|
|
1064
1130
|
.valid('\\All', '\\Archive', '\\Drafts', '\\Flagged', '\\Junk', '\\Sent', '\\Trash', '\\Inbox')
|
|
1065
|
-
.description('Special folder type of the mailbox containing this message')
|
|
1131
|
+
.description('Special folder type of the mailbox containing this message (IMAP accounts only)')
|
|
1066
1132
|
.label('MailboxSpecialUse'),
|
|
1067
1133
|
messageSpecialUse: messageSpecialUseSchema
|
|
1068
1134
|
}).label('MessageDetails');
|
|
1069
1135
|
|
|
1136
|
+
// Combined Document Store query echoed back by search endpoints when the (hidden) exposeQuery
|
|
1137
|
+
// query parameter is enabled. Free-form object, mirrors whatever was sent to the backing index
|
|
1138
|
+
const documentStoreQuerySchema = Joi.object()
|
|
1139
|
+
.unknown()
|
|
1140
|
+
.description('Combined Document Store query. Only present when the exposeQuery query parameter is enabled')
|
|
1141
|
+
.label('ExposedDocumentStoreQuery')
|
|
1142
|
+
.meta({ swaggerHidden: true });
|
|
1143
|
+
|
|
1070
1144
|
const messageListSchema = Joi.object({
|
|
1071
1145
|
total: Joi.number()
|
|
1072
1146
|
.integer()
|
|
1073
1147
|
.example(120)
|
|
1074
|
-
.description(
|
|
1148
|
+
.description(
|
|
1149
|
+
'Total number of messages matching the query (exact for IMAP, approximate for Gmail API, can be omitted for MS Graph API and Outlook search results)'
|
|
1150
|
+
)
|
|
1075
1151
|
.label('TotalNumber'),
|
|
1076
1152
|
page: Joi.number().integer().example(0).description('Current page number (zero-based)').label('PageNumber'),
|
|
1077
|
-
pages: Joi.number()
|
|
1153
|
+
pages: Joi.number()
|
|
1154
|
+
.integer()
|
|
1155
|
+
.example(24)
|
|
1156
|
+
.description('Total number of pages available (exact for IMAP, approximate for Gmail API, can be omitted for MS Graph API and Outlook search results)')
|
|
1157
|
+
.label('PagesNumber'),
|
|
1078
1158
|
nextPageCursor: Joi.string()
|
|
1079
1159
|
.allow(null)
|
|
1080
1160
|
.example('imap_kcQIji3UobDDTxc')
|
|
1081
1161
|
.description('Cursor for fetching the next page (null when no more pages)')
|
|
1082
1162
|
.label('NextPageCursor'),
|
|
1083
1163
|
prevPageCursor: Joi.string().allow(null).example('imap_kcQIji3UobDDTxc').description('Cursor for fetching the previous page').label('PrevPageCursor'),
|
|
1084
|
-
messages: Joi.array().items(messageEntrySchema).label('PageMessages')
|
|
1164
|
+
messages: Joi.array().items(messageEntrySchema).label('PageMessages'),
|
|
1165
|
+
documentStoreQuery: documentStoreQuerySchema
|
|
1085
1166
|
}).label('MessageList');
|
|
1086
1167
|
|
|
1168
|
+
// Fields shared by both mailbox listing item shapes (the full listing and the short listing in
|
|
1169
|
+
// the account verification response). Joi schema instances are immutable, so the same field
|
|
1170
|
+
// instances are safely referenced from both objects
|
|
1171
|
+
const mailboxBaseFields = {
|
|
1172
|
+
path: Joi.string().required().example('Kalender/S&APw-nnip&AOQ-evad').description('Full path to the mailbox').label('MailboxPath'),
|
|
1173
|
+
delimiter: Joi.string()
|
|
1174
|
+
.allow(null)
|
|
1175
|
+
.example('/')
|
|
1176
|
+
.description('Hierarchy delimiter character used in paths (can be null when the server uses a NIL delimiter)'),
|
|
1177
|
+
parentPath: Joi.string()
|
|
1178
|
+
.allow('')
|
|
1179
|
+
.example('Kalender')
|
|
1180
|
+
.description('Path to the parent mailbox. Not set (or empty) for root level mailboxes')
|
|
1181
|
+
.label('MailboxParentPath'),
|
|
1182
|
+
name: Joi.string().required().example('Sünnipäevad').description('Display name of the mailbox').label('MailboxName'),
|
|
1183
|
+
listed: Joi.boolean().example(true).description('Whether this mailbox appears in LIST command results').label('MailboxListed'),
|
|
1184
|
+
subscribed: Joi.boolean().example(true).description('Whether the user is subscribed to this mailbox').label('MailboxSubscribed'),
|
|
1185
|
+
specialUse: Joi.string()
|
|
1186
|
+
.example('\\Sent')
|
|
1187
|
+
.valid('\\All', '\\Archive', '\\Drafts', '\\Flagged', '\\Junk', '\\Sent', '\\Trash', '\\Inbox')
|
|
1188
|
+
.description('Special folder type (Inbox, Sent, Drafts, etc.)')
|
|
1189
|
+
.label('MailboxSpecialUse'),
|
|
1190
|
+
specialUseSource: Joi.string()
|
|
1191
|
+
.example('extension')
|
|
1192
|
+
.valid('user', 'extension', 'name')
|
|
1193
|
+
.description('How the special use was determined: "user" (set via API), "extension" (server-provided), or "name" (guessed from folder name)')
|
|
1194
|
+
.label('MailboxSpecialUseSource'),
|
|
1195
|
+
noInferiors: Joi.boolean().example(false).description('Whether this mailbox can contain child mailboxes').label('MailboxNoInferiors')
|
|
1196
|
+
};
|
|
1197
|
+
|
|
1087
1198
|
const mailboxesSchema = Joi.array()
|
|
1088
1199
|
.items(
|
|
1089
1200
|
Joi.object({
|
|
1090
|
-
path:
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
.
|
|
1103
|
-
.
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
noInferiors:
|
|
1107
|
-
messages: Joi.number()
|
|
1108
|
-
|
|
1201
|
+
path: mailboxBaseFields.path,
|
|
1202
|
+
id: Joi.string()
|
|
1203
|
+
.example('AAMkAGVmMDEzMTM4LTZmYWUtNDdkNC1hMDZiLTU1OGY5OTZhYmY4OAAuAAAAAAAiQ8W967B7TKBjgx9rVEURAQAiIsqMbYjsT5e-T7KzowPTAAAAAAEMAAA=')
|
|
1204
|
+
.description('Stable folder ID (Gmail API and MS Graph API accounts only)')
|
|
1205
|
+
.label('MailboxId'),
|
|
1206
|
+
delimiter: mailboxBaseFields.delimiter,
|
|
1207
|
+
parentPath: mailboxBaseFields.parentPath,
|
|
1208
|
+
name: mailboxBaseFields.name,
|
|
1209
|
+
listed: mailboxBaseFields.listed,
|
|
1210
|
+
subscribed: mailboxBaseFields.subscribed,
|
|
1211
|
+
noSelect: Joi.boolean()
|
|
1212
|
+
.example(false)
|
|
1213
|
+
.description('Whether this mailbox can not be selected (virtual folders like "\\\\All")')
|
|
1214
|
+
.label('MailboxNoSelect'),
|
|
1215
|
+
specialUse: mailboxBaseFields.specialUse,
|
|
1216
|
+
specialUseSource: mailboxBaseFields.specialUseSource,
|
|
1217
|
+
noInferiors: mailboxBaseFields.noInferiors,
|
|
1218
|
+
messages: Joi.number()
|
|
1219
|
+
.integer()
|
|
1220
|
+
.allow(false)
|
|
1221
|
+
.example(120)
|
|
1222
|
+
.description('Total number of messages in the mailbox. False if the count is not known')
|
|
1223
|
+
.label('MailboxMessages'),
|
|
1224
|
+
uidNext: Joi.number()
|
|
1225
|
+
.integer()
|
|
1226
|
+
.allow(false)
|
|
1227
|
+
.example(121)
|
|
1228
|
+
.description('Next UID value that will be assigned. False if the value is not known')
|
|
1229
|
+
.label('MailboxUidNext'),
|
|
1109
1230
|
status: Joi.object({
|
|
1110
1231
|
messages: Joi.number().integer().example(120).description('Message count from STATUS command').label('StatusMessages'),
|
|
1111
|
-
unseen: Joi.number().integer().example(120).description('Unread message count from STATUS command').label('StatusUnseenMessages')
|
|
1232
|
+
unseen: Joi.number().integer().example(120).description('Unread message count from STATUS command').label('StatusUnseenMessages'),
|
|
1233
|
+
error: Joi.string().description('Error message if the STATUS command failed for this mailbox').label('StatusError')
|
|
1112
1234
|
})
|
|
1235
|
+
.unknown()
|
|
1113
1236
|
.description('Additional mailbox statistics')
|
|
1114
1237
|
.label('MailboxResponseStatus')
|
|
1115
1238
|
}).label('MailboxResponseItem')
|
|
1116
1239
|
)
|
|
1117
1240
|
.label('MailboxesList');
|
|
1118
1241
|
|
|
1119
|
-
const shortMailboxesSchema = Joi.array()
|
|
1120
|
-
.items(
|
|
1121
|
-
Joi.object({
|
|
1122
|
-
path: Joi.string().required().example('Kalender/S&APw-nnip&AOQ-evad').description('Full path to the mailbox').label('MailboxPath'),
|
|
1123
|
-
delimiter: Joi.string().example('/').description('Hierarchy delimiter character used in paths'),
|
|
1124
|
-
parentPath: Joi.string().required().example('Kalender').description('Path to the parent mailbox').label('MailboxParentPath'),
|
|
1125
|
-
name: Joi.string().required().example('Sünnipäevad').description('Display name of the mailbox').label('MailboxName'),
|
|
1126
|
-
listed: Joi.boolean().example(true).description('Whether this mailbox appears in LIST command results').label('MailboxListed'),
|
|
1127
|
-
subscribed: Joi.boolean().example(true).description('Whether the user is subscribed to this mailbox').label('MailboxSubscribed'),
|
|
1128
|
-
specialUse: Joi.string()
|
|
1129
|
-
.example('\\Sent')
|
|
1130
|
-
.valid('\\All', '\\Archive', '\\Drafts', '\\Flagged', '\\Junk', '\\Sent', '\\Trash', '\\Inbox')
|
|
1131
|
-
.description('Special folder type (Inbox, Sent, Drafts, etc.)')
|
|
1132
|
-
.label('MailboxSpecialUse')
|
|
1133
|
-
}).label('MailboxShortResponseItem')
|
|
1134
|
-
)
|
|
1135
|
-
.label('ShortMailboxes');
|
|
1242
|
+
const shortMailboxesSchema = Joi.array().items(Joi.object(mailboxBaseFields).label('MailboxShortResponseItem')).label('ShortMailboxes');
|
|
1136
1243
|
|
|
1137
1244
|
const licenseSchema = Joi.object({
|
|
1138
1245
|
active: Joi.boolean().example(true).description('Whether a valid license is currently active'),
|
|
@@ -1142,7 +1249,10 @@ const licenseSchema = Joi.object({
|
|
|
1142
1249
|
key: Joi.string().hex().example('1edf01e35e75ed3425808eba').description('License key'),
|
|
1143
1250
|
licensedTo: Joi.string().example('Kreata OÜ').description('Organization or individual the license is issued to'),
|
|
1144
1251
|
hostname: Joi.string().example('emailengine.example.com').description('Licensed hostname or environment'),
|
|
1145
|
-
created: Joi.date().example('2021-10-13T07:47:42.695Z').description('License creation date')
|
|
1252
|
+
created: Joi.date().example('2021-10-13T07:47:42.695Z').description('License creation date'),
|
|
1253
|
+
trial: Joi.boolean().example(false).description('Whether this is a trial license'),
|
|
1254
|
+
lt: Joi.boolean().example(false).description('Whether this is a lifetime license'),
|
|
1255
|
+
expires: Joi.date().iso().example('2026-01-01T00:00:00.000Z').description('License expiration time, if the license is time-limited')
|
|
1146
1256
|
})
|
|
1147
1257
|
.allow(false)
|
|
1148
1258
|
.label('LicenseDetails'),
|
|
@@ -1153,8 +1263,11 @@ const lastErrorSchema = Joi.object({
|
|
|
1153
1263
|
response: Joi.string()
|
|
1154
1264
|
.example('Token request failed for gmail (refresh_token, HTTP 400): invalid_grant - Token has been expired or revoked.')
|
|
1155
1265
|
.description('Human-readable error message'),
|
|
1266
|
+
description: Joi.string().example('Authentication failed').description('Additional details about the error'),
|
|
1156
1267
|
serverResponseCode: Joi.string().example('OauthRenewError').description('Error code or classification'),
|
|
1157
1268
|
tokenRequest: Joi.object({
|
|
1269
|
+
url: Joi.string().example('https://oauth2.googleapis.com/token').description('OAuth2 token endpoint URL').label('OAuthTokenUrl'),
|
|
1270
|
+
method: Joi.string().example('post').description('HTTP method used for the token request').label('OAuthTokenMethod'),
|
|
1158
1271
|
grant: Joi.string()
|
|
1159
1272
|
.valid('refresh_token', 'authorization_code')
|
|
1160
1273
|
.example('refresh_token')
|
|
@@ -1166,6 +1279,42 @@ const lastErrorSchema = Joi.object({
|
|
|
1166
1279
|
.example('1023289917884-h3nu00e9cb7h252e24c23sv19l8k57ah.apps.googleusercontent.com')
|
|
1167
1280
|
.description('OAuth2 client ID used for authentication')
|
|
1168
1281
|
.label('OAuthClientId'),
|
|
1282
|
+
clientSecret: Joi.string()
|
|
1283
|
+
.example('GOCSPX...V-Da')
|
|
1284
|
+
.description('Partially masked OAuth2 client secret, included when the OAuth2 server indicates an invalid client secret')
|
|
1285
|
+
.label('OAuthClientSecretPartial'),
|
|
1286
|
+
authority: Joi.string().example('common').description('Microsoft tenant configuration (Outlook applications only)').label('OAuthAuthority'),
|
|
1287
|
+
serviceClient: Joi.string()
|
|
1288
|
+
.example('113328088575113530522')
|
|
1289
|
+
.description('Service client ID (2-legged OAuth2 applications only)')
|
|
1290
|
+
.label('OAuthServiceClient'),
|
|
1291
|
+
signer: Joi.string().example('serviceKey').description('Signing method used for service account token requests').label('OAuthSigner'),
|
|
1292
|
+
googleProjectId: Joi.string()
|
|
1293
|
+
.example('project-name-425411')
|
|
1294
|
+
.description('Google Cloud project ID (Gmail service applications only)')
|
|
1295
|
+
.label('OAuthGoogleProjectId'),
|
|
1296
|
+
serviceClientEmail: Joi.string()
|
|
1297
|
+
.example('service-account@project-name-425411.iam.gserviceaccount.com')
|
|
1298
|
+
.description('Service account email (Gmail service applications only)')
|
|
1299
|
+
.label('OAuthServiceClientEmail'),
|
|
1300
|
+
code: Joi.string()
|
|
1301
|
+
.example('AAABhaBPHscAAAAH')
|
|
1302
|
+
.description('Value of the authorization code or error code related to the failure')
|
|
1303
|
+
.label('OAuthTokenErrorCode'),
|
|
1304
|
+
flag: Joi.object({
|
|
1305
|
+
message: Joi.string().example('Please verify the OAuth2 application credentials').description('Description of the application-level problem'),
|
|
1306
|
+
code: Joi.string().example('INVALID_CLIENT_SECRET').description('Machine-readable code of the application-level problem'),
|
|
1307
|
+
url: Joi.string().example('https://console.developers.google.com/').description('Link with instructions to resolve the problem')
|
|
1308
|
+
})
|
|
1309
|
+
.unknown()
|
|
1310
|
+
.description('Application-level error flag raised by this failure')
|
|
1311
|
+
.label('OAuthTokenErrorFlag'),
|
|
1312
|
+
userFlag: Joi.object({
|
|
1313
|
+
message: Joi.string().example('The grant for this account has been revoked').description('Description of the account-level problem')
|
|
1314
|
+
})
|
|
1315
|
+
.unknown()
|
|
1316
|
+
.description('Account-level error flag raised by this failure')
|
|
1317
|
+
.label('OAuthTokenUserFlag'),
|
|
1169
1318
|
scopes: Joi.array()
|
|
1170
1319
|
.items(Joi.string().example('https://mail.google.com/').label('ScopeEntry').description('OAuth2 permission scope'))
|
|
1171
1320
|
.description('Requested OAuth2 permission scopes')
|
|
@@ -1285,6 +1434,23 @@ const searchSchema = Joi.object({
|
|
|
1285
1434
|
|
|
1286
1435
|
gmailRaw: Joi.string().max(1024).example('has:attachment in:unread').description('Gmail search syntax (only works with Gmail accounts)'),
|
|
1287
1436
|
|
|
1437
|
+
labels: Joi.object({
|
|
1438
|
+
has: Joi.array()
|
|
1439
|
+
.items(Joi.string().max(128))
|
|
1440
|
+
.single()
|
|
1441
|
+
.description('Match messages that contain ALL of these labels/categories. Supported for Gmail and MS Graph (Outlook) accounts only.')
|
|
1442
|
+
.example(['Horizon'])
|
|
1443
|
+
.label('HasLabels'),
|
|
1444
|
+
not: Joi.array()
|
|
1445
|
+
.items(Joi.string().max(128))
|
|
1446
|
+
.single()
|
|
1447
|
+
.description('Match messages that contain NONE of these labels/categories. Supported for Gmail and MS Graph (Outlook) accounts only.')
|
|
1448
|
+
.example(['Horizon'])
|
|
1449
|
+
.label('NotLabels')
|
|
1450
|
+
})
|
|
1451
|
+
.description('Filter by Gmail labels or MS Graph (Outlook) categories. Not supported for generic IMAP accounts.')
|
|
1452
|
+
.label('LabelFilter'),
|
|
1453
|
+
|
|
1288
1454
|
emailIds: Joi.array()
|
|
1289
1455
|
.items(emailIdSchema)
|
|
1290
1456
|
.single()
|
|
@@ -1855,6 +2021,13 @@ const ipSchema = Joi.string()
|
|
|
1855
2021
|
})
|
|
1856
2022
|
.example('127.0.0.1');
|
|
1857
2023
|
|
|
2024
|
+
const tokenIdSchema = Joi.string()
|
|
2025
|
+
.length(64)
|
|
2026
|
+
.hex()
|
|
2027
|
+
.example('1bc12baf7f0d5e51fe0a4e0eda06e1be5b8d6cc2c66b95dc0fbe4d2e9f5d5e1a')
|
|
2028
|
+
.description('Token identifier (SHA-256 hash of the token)')
|
|
2029
|
+
.label('TokenId');
|
|
2030
|
+
|
|
1858
2031
|
const accountCountersSchema = Joi.object({
|
|
1859
2032
|
events: Joi.object()
|
|
1860
2033
|
.unknown()
|
|
@@ -1885,7 +2058,7 @@ const defaultAccountTypeSchema = Joi.string()
|
|
|
1885
2058
|
const outboxEntrySchema = Joi.object({
|
|
1886
2059
|
queueId: Joi.string().example('1869c5692565f756b33').description('Unique queue entry identifier'),
|
|
1887
2060
|
account: accountIdSchema.required(),
|
|
1888
|
-
source: Joi.string().example('smtp').valid('smtp', 'api').description('How this message entered the queue').label('OutboxSource'),
|
|
2061
|
+
source: Joi.string().example('smtp').valid('smtp', 'api', 'test', 'ui').description('How this message entered the queue').label('OutboxSource'),
|
|
1889
2062
|
|
|
1890
2063
|
messageId: Joi.string().max(996).example('<test123@example.com>').description('Message-ID header value'),
|
|
1891
2064
|
envelope: Joi.object({
|
|
@@ -1896,25 +2069,50 @@ const outboxEntrySchema = Joi.object({
|
|
|
1896
2069
|
.label('OutboxEnvelope'),
|
|
1897
2070
|
|
|
1898
2071
|
subject: Joi.string()
|
|
1899
|
-
.allow('')
|
|
2072
|
+
.allow('', null)
|
|
1900
2073
|
.max(10 * 1024)
|
|
1901
2074
|
.example('What a wonderful message')
|
|
1902
2075
|
.description('Email subject line'),
|
|
1903
2076
|
|
|
2077
|
+
gateway: Joi.string().allow(null).max(256).example(null).description('Gateway ID used for delivery, or null when no gateway is set'),
|
|
2078
|
+
proxy: Joi.string().allow(null).example(null).description('Proxy URL used for delivery, or null when no proxy is set'),
|
|
2079
|
+
localAddress: Joi.string().allow(null).example(null).description('Local IP address used for delivery, or null when not set'),
|
|
2080
|
+
idempotencyKey: Joi.string().allow(null).max(1024).description('Idempotency key provided when the message was submitted'),
|
|
2081
|
+
useStructuredFormat: Joi.boolean().description('Whether delivery errors for this message are reported in a structured format'),
|
|
2082
|
+
|
|
1904
2083
|
created: Joi.date().iso().example('2021-02-17T13:43:18.860Z').description('When this message was added to the queue'),
|
|
1905
2084
|
scheduled: Joi.date().iso().example('2021-02-17T13:43:18.860Z').description('Scheduled delivery time'),
|
|
1906
|
-
nextAttempt: Joi.date()
|
|
2085
|
+
nextAttempt: Joi.date()
|
|
2086
|
+
.iso()
|
|
2087
|
+
.allow(false)
|
|
2088
|
+
.example('2021-02-17T13:43:18.860Z')
|
|
2089
|
+
.description(
|
|
2090
|
+
'Next delivery attempt time (approximate, computed from the retry backoff without jitter). False when all delivery attempts have been used up'
|
|
2091
|
+
),
|
|
1907
2092
|
|
|
1908
2093
|
attemptsMade: Joi.number().integer().example(3).description('Number of delivery attempts made'),
|
|
1909
2094
|
attempts: Joi.number().integer().example(3).description('Maximum delivery attempts before marking as failed'),
|
|
1910
2095
|
|
|
1911
2096
|
progress: Joi.object({
|
|
1912
|
-
status: Joi.string()
|
|
1913
|
-
|
|
2097
|
+
status: Joi.string()
|
|
2098
|
+
.valid('queued', 'processing', 'smtp-starting', 'smtp-completed', 'submitted', 'error')
|
|
2099
|
+
.example('queued')
|
|
2100
|
+
.description('Current delivery status')
|
|
2101
|
+
.label('OutboxStatus'),
|
|
2102
|
+
response: Joi.string().example('250 Message Accepted').description('SMTP server response (when status is "submitted" or "smtp-completed")'),
|
|
2103
|
+
messageId: Joi.string().example('<test123@example.com>').description('Message-ID header value of the sent message (when status is "smtp-completed")'),
|
|
2104
|
+
originalMessageId: Joi.string()
|
|
2105
|
+
.example('<test123@example.com>')
|
|
2106
|
+
.description('Original Message-ID header value if the server assigned a new one (when status is "smtp-completed")'),
|
|
1914
2107
|
error: Joi.object({
|
|
1915
2108
|
message: Joi.string().example('Authentication failed').description('Error description'),
|
|
1916
2109
|
code: Joi.string().example('EAUTH').description('Error code'),
|
|
1917
|
-
statusCode: Joi.
|
|
2110
|
+
statusCode: Joi.number().integer().example(502).description('SMTP response code'),
|
|
2111
|
+
networkRouting: Joi.object()
|
|
2112
|
+
.unknown()
|
|
2113
|
+
.allow(null)
|
|
2114
|
+
.description('Network routing information for the failed delivery attempt')
|
|
2115
|
+
.label('OutboxErrorNetworkRouting')
|
|
1918
2116
|
})
|
|
1919
2117
|
.label('OutboxListProgressError')
|
|
1920
2118
|
.description('Error details (when status is "error")')
|
|
@@ -2011,7 +2209,7 @@ const exportRequestSchema = Joi.object({
|
|
|
2011
2209
|
.valid('plain', 'html', '*')
|
|
2012
2210
|
.default('*')
|
|
2013
2211
|
.example('*')
|
|
2014
|
-
.description('Text content to include: "plain", "html", "*" (both)
|
|
2212
|
+
.description('Text content to include: "plain", "html", or "*" (both)')
|
|
2015
2213
|
.label('ExportTextType'),
|
|
2016
2214
|
maxBytes: Joi.number()
|
|
2017
2215
|
.integer()
|
|
@@ -2058,7 +2256,9 @@ const exportStatusSchema = Joi.object({
|
|
|
2058
2256
|
progress: exportProgressSchema,
|
|
2059
2257
|
created: Joi.date().iso().example('2024-01-15T10:30:00Z').description('When export was created'),
|
|
2060
2258
|
expiresAt: Joi.date().iso().example('2024-01-16T10:30:00Z').description('When export file expires'),
|
|
2061
|
-
truncated: Joi.boolean()
|
|
2259
|
+
truncated: Joi.boolean()
|
|
2260
|
+
.example(true)
|
|
2261
|
+
.description('Whether the export was truncated due to message count or size limits. Only present (as true) for truncated exports'),
|
|
2062
2262
|
error: Joi.string().allow(null).description('Error message if export failed')
|
|
2063
2263
|
}).label('ExportStatus');
|
|
2064
2264
|
|
|
@@ -2087,8 +2287,146 @@ const exportIdSchema = Joi.string()
|
|
|
2087
2287
|
.description('Export job identifier')
|
|
2088
2288
|
.label('ExportId');
|
|
2089
2289
|
|
|
2290
|
+
// Boom-style error payloads returned by failed API requests. These schemas are used for API
|
|
2291
|
+
// documentation only - they are rendered as error response examples in Swagger UI through the
|
|
2292
|
+
// plugins['hapi-swagger'].responses route option and are never used to validate or modify
|
|
2293
|
+
// actual responses at runtime.
|
|
2294
|
+
|
|
2295
|
+
// Builds one documented error response: a response description (shown next to the status code
|
|
2296
|
+
// in Swagger UI) paired with a labeled Joi schema for the Boom-style error payload. The
|
|
2297
|
+
// optional extra fields document values that EmailEngine adds to the base
|
|
2298
|
+
// { statusCode, error, message } payload for that error type.
|
|
2299
|
+
const errorResponseEntry = ({ statusCode, statusText, messageExample, label, title, description, fields }) => ({
|
|
2300
|
+
description,
|
|
2301
|
+
schema: Joi.object({
|
|
2302
|
+
statusCode: Joi.number().integer().example(statusCode).description('HTTP status code'),
|
|
2303
|
+
error: Joi.string().example(statusText).description('HTTP status text'),
|
|
2304
|
+
message: Joi.string().example(messageExample).description('Error message'),
|
|
2305
|
+
...fields
|
|
2306
|
+
})
|
|
2307
|
+
.label(label)
|
|
2308
|
+
.description(title)
|
|
2309
|
+
});
|
|
2310
|
+
|
|
2311
|
+
const errorResponseSchemas = {
|
|
2312
|
+
400: errorResponseEntry({
|
|
2313
|
+
statusCode: 400,
|
|
2314
|
+
statusText: 'Bad Request',
|
|
2315
|
+
messageExample: 'Invalid input',
|
|
2316
|
+
label: 'ValidationErrorResponse',
|
|
2317
|
+
title: 'Validation error',
|
|
2318
|
+
description: 'Invalid input - the path parameters, query arguments, or request payload did not pass validation',
|
|
2319
|
+
fields: {
|
|
2320
|
+
fields: Joi.array()
|
|
2321
|
+
.items(
|
|
2322
|
+
Joi.object({
|
|
2323
|
+
message: Joi.string().example('"pageSize" must be less than or equal to 1000').description('Description of the validation failure'),
|
|
2324
|
+
key: Joi.string().example('pageSize').description('Name of the input field that failed validation')
|
|
2325
|
+
}).label('ValidationErrorField')
|
|
2326
|
+
)
|
|
2327
|
+
.description('List of input fields that failed validation')
|
|
2328
|
+
.label('ValidationErrorFields')
|
|
2329
|
+
}
|
|
2330
|
+
}),
|
|
2331
|
+
|
|
2332
|
+
401: errorResponseEntry({
|
|
2333
|
+
statusCode: 401,
|
|
2334
|
+
statusText: 'Unauthorized',
|
|
2335
|
+
messageExample: 'Missing authentication',
|
|
2336
|
+
label: 'AuthenticationErrorResponse',
|
|
2337
|
+
title: 'Authentication error',
|
|
2338
|
+
description: 'Authentication failed - the access token is missing, invalid, or was not accepted'
|
|
2339
|
+
}),
|
|
2340
|
+
|
|
2341
|
+
403: errorResponseEntry({
|
|
2342
|
+
statusCode: 403,
|
|
2343
|
+
statusText: 'Forbidden',
|
|
2344
|
+
messageExample: 'Unauthorized scope',
|
|
2345
|
+
label: 'AccessDeniedErrorResponse',
|
|
2346
|
+
title: 'Access denied error',
|
|
2347
|
+
description: 'Access denied - the access token does not grant permission for this operation (token scope, account, or IP address restriction)'
|
|
2348
|
+
}),
|
|
2349
|
+
|
|
2350
|
+
404: errorResponseEntry({
|
|
2351
|
+
statusCode: 404,
|
|
2352
|
+
statusText: 'Not Found',
|
|
2353
|
+
messageExample: 'Requested message was not found',
|
|
2354
|
+
label: 'NotFoundErrorResponse',
|
|
2355
|
+
title: 'Not found error',
|
|
2356
|
+
description: 'Not found - the requested entity (account, message, mailbox, etc.) does not exist',
|
|
2357
|
+
fields: {
|
|
2358
|
+
code: Joi.string().example('MessageNotFound').description('Machine-readable error code')
|
|
2359
|
+
}
|
|
2360
|
+
}),
|
|
2361
|
+
|
|
2362
|
+
422: errorResponseEntry({
|
|
2363
|
+
statusCode: 422,
|
|
2364
|
+
statusText: 'Unprocessable Entity',
|
|
2365
|
+
messageExample: 'The mail server does not support the requested operation',
|
|
2366
|
+
label: 'UnprocessableErrorResponse',
|
|
2367
|
+
title: 'Unprocessable request error',
|
|
2368
|
+
description: 'Unprocessable request - the request is well-formed, but the email server cannot perform the requested operation',
|
|
2369
|
+
fields: {
|
|
2370
|
+
code: Joi.string().example('MissingServerExtension').description('Machine-readable error code')
|
|
2371
|
+
}
|
|
2372
|
+
}),
|
|
2373
|
+
|
|
2374
|
+
429: errorResponseEntry({
|
|
2375
|
+
statusCode: 429,
|
|
2376
|
+
statusText: 'Too Many Requests',
|
|
2377
|
+
messageExample: 'Rate limit exceeded',
|
|
2378
|
+
label: 'RateLimitErrorResponse',
|
|
2379
|
+
title: 'Rate limit error',
|
|
2380
|
+
description: 'Rate limit exceeded - too many requests were made with this access token',
|
|
2381
|
+
fields: {
|
|
2382
|
+
ttl: Joi.number().integer().example(30).description('Seconds until the rate limit window resets')
|
|
2383
|
+
}
|
|
2384
|
+
}),
|
|
2385
|
+
|
|
2386
|
+
500: errorResponseEntry({
|
|
2387
|
+
statusCode: 500,
|
|
2388
|
+
statusText: 'Internal Server Error',
|
|
2389
|
+
messageExample: 'An internal server error occurred',
|
|
2390
|
+
label: 'InternalErrorResponse',
|
|
2391
|
+
title: 'Internal server error',
|
|
2392
|
+
description: 'Internal server error - an unexpected error occurred while processing the request',
|
|
2393
|
+
fields: {
|
|
2394
|
+
code: Joi.string().example('InternalError').description('Machine-readable error code, if available'),
|
|
2395
|
+
details: Joi.any().description('Additional error details, if available')
|
|
2396
|
+
}
|
|
2397
|
+
}),
|
|
2398
|
+
|
|
2399
|
+
503: errorResponseEntry({
|
|
2400
|
+
statusCode: 503,
|
|
2401
|
+
statusText: 'Service Unavailable',
|
|
2402
|
+
messageExample: 'No active handler for requested account. Try again later.',
|
|
2403
|
+
label: 'ServiceUnavailableErrorResponse',
|
|
2404
|
+
title: 'Service unavailable error',
|
|
2405
|
+
description: 'Service unavailable - the worker processing this account is not available, try again later',
|
|
2406
|
+
fields: {
|
|
2407
|
+
code: Joi.string().example('WorkerNotAvailable').description('Machine-readable error code')
|
|
2408
|
+
}
|
|
2409
|
+
})
|
|
2410
|
+
};
|
|
2411
|
+
|
|
2412
|
+
// Builds the value for the plugins['hapi-swagger'].responses route option from a list of HTTP
|
|
2413
|
+
// error status codes, so that error responses are rendered in Swagger UI in addition to the
|
|
2414
|
+
// 200 response that hapi-swagger picks up from the route's response.schema option. This is
|
|
2415
|
+
// documentation only and does not affect request processing in any way.
|
|
2416
|
+
function errorResponses(...statusCodes) {
|
|
2417
|
+
let responses = {};
|
|
2418
|
+
for (let statusCode of statusCodes) {
|
|
2419
|
+
if (!errorResponseSchemas[statusCode]) {
|
|
2420
|
+
throw new Error(`Missing error response schema for HTTP status code ${statusCode}`);
|
|
2421
|
+
}
|
|
2422
|
+
responses[statusCode] = errorResponseSchemas[statusCode];
|
|
2423
|
+
}
|
|
2424
|
+
return responses;
|
|
2425
|
+
}
|
|
2426
|
+
|
|
2090
2427
|
module.exports = {
|
|
2091
2428
|
ADDRESS_STRATEGIES,
|
|
2429
|
+
ACCOUNT_DISPLAY_STATES,
|
|
2092
2430
|
|
|
2093
2431
|
settingsSchema,
|
|
2094
2432
|
accountSchemas,
|
|
@@ -2104,6 +2442,7 @@ module.exports = {
|
|
|
2104
2442
|
messageEntrySchema,
|
|
2105
2443
|
messageDetailsSchema,
|
|
2106
2444
|
messageListSchema,
|
|
2445
|
+
documentStoreQuerySchema,
|
|
2107
2446
|
mailboxesSchema,
|
|
2108
2447
|
shortMailboxesSchema,
|
|
2109
2448
|
licenseSchema,
|
|
@@ -2117,11 +2456,16 @@ module.exports = {
|
|
|
2117
2456
|
tokenRestrictionsSchema,
|
|
2118
2457
|
accountIdSchema,
|
|
2119
2458
|
ipSchema,
|
|
2459
|
+
tokenIdSchema,
|
|
2120
2460
|
accountCountersSchema,
|
|
2121
2461
|
accountPathSchema,
|
|
2122
2462
|
messageSpecialUseSchema,
|
|
2123
2463
|
defaultAccountTypeSchema,
|
|
2124
2464
|
fromAddressSchema,
|
|
2465
|
+
responseAddressSchema,
|
|
2466
|
+
responseFromAddressSchema,
|
|
2467
|
+
emailIdSchema,
|
|
2468
|
+
threadIdSchema,
|
|
2125
2469
|
outboxEntrySchema,
|
|
2126
2470
|
googleProjectIdSchema,
|
|
2127
2471
|
googleTopicNameSchema,
|
|
@@ -2135,7 +2479,8 @@ module.exports = {
|
|
|
2135
2479
|
exportListSchema,
|
|
2136
2480
|
exportProgressSchema,
|
|
2137
2481
|
exportIdSchema,
|
|
2138
|
-
pubSubErrorSchema
|
|
2482
|
+
pubSubErrorSchema,
|
|
2483
|
+
errorResponses
|
|
2139
2484
|
};
|
|
2140
2485
|
|
|
2141
2486
|
/*
|