emailengine-app 2.61.1 → 2.61.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/data/google-crawlers.json +1 -1
- package/lib/account/account-state.js +248 -0
- package/lib/account.js +45 -193
- package/lib/api-routes/account-routes.js +1023 -0
- package/lib/api-routes/message-routes.js +1377 -0
- package/lib/consts.js +12 -2
- package/lib/email-client/base-client.js +282 -771
- package/lib/email-client/gmail/gmail-api.js +243 -0
- package/lib/email-client/gmail-client.js +145 -53
- package/lib/email-client/imap/mailbox.js +24 -698
- package/lib/email-client/imap/sync-operations.js +812 -0
- package/lib/email-client/imap-client.js +1 -1
- package/lib/email-client/message-builder.js +566 -0
- package/lib/email-client/notification-handler.js +314 -0
- package/lib/email-client/outlook/graph-api.js +326 -0
- package/lib/email-client/outlook-client.js +159 -113
- package/lib/email-client/smtp-pool-manager.js +196 -0
- package/lib/imapproxy/imap-server.js +3 -12
- package/lib/oauth/gmail.js +4 -4
- package/lib/oauth/mail-ru.js +30 -5
- package/lib/oauth/outlook.js +57 -3
- package/lib/oauth/pubsub/google.js +30 -11
- package/lib/oauth/scope-checker.js +202 -0
- package/lib/oauth2-apps.js +8 -4
- package/lib/redis-operations.js +484 -0
- package/lib/routes-ui.js +283 -2582
- package/lib/tools.js +4 -196
- package/lib/ui-routes/account-routes.js +1931 -0
- package/lib/ui-routes/admin-config-routes.js +1233 -0
- package/lib/ui-routes/admin-entities-routes.js +2367 -0
- package/lib/ui-routes/oauth-routes.js +992 -0
- package/lib/utils/network.js +237 -0
- package/package.json +10 -10
- package/sbom.json +1 -1
- package/static/js/app.js +5 -5
- package/static/licenses.html +79 -19
- package/translations/de.mo +0 -0
- package/translations/de.po +97 -86
- package/translations/en.mo +0 -0
- package/translations/en.po +80 -75
- package/translations/et.mo +0 -0
- package/translations/et.po +96 -86
- package/translations/fr.mo +0 -0
- package/translations/fr.po +97 -86
- package/translations/ja.mo +0 -0
- package/translations/ja.po +96 -86
- package/translations/messages.pot +105 -91
- package/translations/nl.mo +0 -0
- package/translations/nl.po +98 -86
- package/translations/pl.mo +0 -0
- package/translations/pl.po +96 -86
- package/views/account/security.hbs +4 -4
- package/views/accounts/account.hbs +13 -13
- package/views/accounts/register/imap-server.hbs +12 -12
- package/views/config/document-store/pre-processing/index.hbs +4 -2
- package/views/config/oauth/app.hbs +6 -7
- package/views/config/oauth/index.hbs +2 -2
- package/views/config/service.hbs +3 -4
- package/views/dashboard.hbs +5 -7
- package/views/error.hbs +22 -7
- package/views/gateways/gateway.hbs +2 -2
- package/views/partials/add_account_modal.hbs +7 -10
- package/views/partials/document_store_header.hbs +1 -1
- package/views/partials/editor_scope_info.hbs +0 -1
- package/views/partials/oauth_config_header.hbs +1 -1
- package/views/partials/side_menu.hbs +3 -3
- package/views/partials/webhook_form.hbs +2 -2
- package/views/templates/index.hbs +1 -1
- package/views/templates/template.hbs +8 -8
- package/views/tokens/index.hbs +6 -6
- package/views/tokens/new.hbs +1 -1
- package/views/webhooks/index.hbs +4 -4
- package/views/webhooks/webhook.hbs +7 -7
- package/workers/api.js +148 -2436
- package/workers/smtp.js +2 -1
- package/lib/imapproxy/imap-core/test/client.js +0 -46
- package/lib/imapproxy/imap-core/test/fixtures/append.eml +0 -1196
- package/lib/imapproxy/imap-core/test/fixtures/chunks.js +0 -44
- package/lib/imapproxy/imap-core/test/fixtures/fix1.eml +0 -6
- package/lib/imapproxy/imap-core/test/fixtures/fix2.eml +0 -599
- package/lib/imapproxy/imap-core/test/fixtures/fix3.eml +0 -32
- package/lib/imapproxy/imap-core/test/fixtures/fix4.eml +0 -6
- package/lib/imapproxy/imap-core/test/fixtures/mimetorture.eml +0 -599
- package/lib/imapproxy/imap-core/test/fixtures/mimetorture.js +0 -2740
- package/lib/imapproxy/imap-core/test/fixtures/mimetorture.json +0 -1411
- package/lib/imapproxy/imap-core/test/fixtures/mimetree.js +0 -85
- package/lib/imapproxy/imap-core/test/fixtures/nodemailer.eml +0 -582
- package/lib/imapproxy/imap-core/test/fixtures/ryan_finnie_mime_torture.eml +0 -599
- package/lib/imapproxy/imap-core/test/fixtures/simple.eml +0 -42
- package/lib/imapproxy/imap-core/test/fixtures/simple.json +0 -164
- package/lib/imapproxy/imap-core/test/imap-compile-stream-test.js +0 -671
- package/lib/imapproxy/imap-core/test/imap-compiler-test.js +0 -272
- package/lib/imapproxy/imap-core/test/imap-indexer-test.js +0 -236
- package/lib/imapproxy/imap-core/test/imap-parser-test.js +0 -922
- package/lib/imapproxy/imap-core/test/memory-notifier.js +0 -129
- package/lib/imapproxy/imap-core/test/prepare.sh +0 -74
- package/lib/imapproxy/imap-core/test/protocol-test.js +0 -1756
- package/lib/imapproxy/imap-core/test/search-test.js +0 -1356
- package/lib/imapproxy/imap-core/test/test-client.js +0 -152
- package/lib/imapproxy/imap-core/test/test-server.js +0 -623
- package/lib/imapproxy/imap-core/test/tools-test.js +0 -22
- package/test/api-test.js +0 -899
- package/test/autoreply-test.js +0 -327
- package/test/bounce-test.js +0 -151
- package/test/complaint-test.js +0 -256
- package/test/fixtures/autoreply/LICENSE +0 -27
- package/test/fixtures/autoreply/rfc3834-01.eml +0 -23
- package/test/fixtures/autoreply/rfc3834-02.eml +0 -24
- package/test/fixtures/autoreply/rfc3834-03.eml +0 -26
- package/test/fixtures/autoreply/rfc3834-04.eml +0 -48
- package/test/fixtures/autoreply/rfc3834-05.eml +0 -19
- package/test/fixtures/autoreply/rfc3834-06.eml +0 -59
- package/test/fixtures/bounces/163.eml +0 -2521
- package/test/fixtures/bounces/fastmail.eml +0 -242
- package/test/fixtures/bounces/gmail.eml +0 -252
- package/test/fixtures/bounces/hotmail.eml +0 -655
- package/test/fixtures/bounces/mailru.eml +0 -121
- package/test/fixtures/bounces/outlook.eml +0 -1107
- package/test/fixtures/bounces/postfix.eml +0 -101
- package/test/fixtures/bounces/rambler.eml +0 -116
- package/test/fixtures/bounces/workmail.eml +0 -142
- package/test/fixtures/bounces/yahoo.eml +0 -139
- package/test/fixtures/bounces/zoho.eml +0 -83
- package/test/fixtures/bounces/zonemta.eml +0 -100
- package/test/fixtures/complaints/LICENSE +0 -27
- package/test/fixtures/complaints/amazonses.eml +0 -72
- package/test/fixtures/complaints/dmarc.eml +0 -59
- package/test/fixtures/complaints/hotmail.eml +0 -49
- package/test/fixtures/complaints/optout.eml +0 -40
- package/test/fixtures/complaints/standard-arf.eml +0 -68
- package/test/fixtures/complaints/yahoo.eml +0 -68
- package/test/oauth2-apps-test.js +0 -301
- package/test/sendonly-test.js +0 -160
- package/test/test-config.js +0 -34
- package/test/webhooks-server.js +0 -39
|
@@ -0,0 +1,1023 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
const { redis } = require('../db');
|
|
5
|
+
const { Account } = require('../account');
|
|
6
|
+
const getSecret = require('../get-secret');
|
|
7
|
+
const { oauth2Apps } = require('../oauth2-apps');
|
|
8
|
+
const Boom = require('@hapi/boom');
|
|
9
|
+
const Joi = require('joi');
|
|
10
|
+
const { failAction } = require('../tools');
|
|
11
|
+
|
|
12
|
+
const {
|
|
13
|
+
settingsSchema,
|
|
14
|
+
accountSchemas,
|
|
15
|
+
accountIdSchema,
|
|
16
|
+
accountCountersSchema,
|
|
17
|
+
accountPathSchema,
|
|
18
|
+
lastErrorSchema,
|
|
19
|
+
imapSchema,
|
|
20
|
+
imapUpdateSchema,
|
|
21
|
+
smtpSchema,
|
|
22
|
+
smtpUpdateSchema,
|
|
23
|
+
oauth2Schema,
|
|
24
|
+
oauth2UpdateSchema
|
|
25
|
+
} = require('../schemas');
|
|
26
|
+
|
|
27
|
+
const { REDIS_PREFIX, MAX_FORM_TTL, NONCE_BYTES } = require('../consts');
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Validates that delegation fields are only used with OAuth2 accounts.
|
|
31
|
+
*/
|
|
32
|
+
function validateDelegationFields(payload) {
|
|
33
|
+
const auth = payload.oauth2?.auth;
|
|
34
|
+
const hasDelegation = auth?.delegatedUser || auth?.delegatedAccount;
|
|
35
|
+
if (hasDelegation && !payload.oauth2?.provider) {
|
|
36
|
+
throw Boom.badRequest('Delegation fields (delegatedUser, delegatedAccount) require oauth2.provider to be set');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function init(args) {
|
|
41
|
+
const {
|
|
42
|
+
server,
|
|
43
|
+
call,
|
|
44
|
+
documentsQueue,
|
|
45
|
+
oauth2Schema: oauth2SchemaArg,
|
|
46
|
+
imapSchema: imapSchemaArg,
|
|
47
|
+
smtpSchema: smtpSchemaArg,
|
|
48
|
+
CORS_CONFIG,
|
|
49
|
+
AccountTypeSchema
|
|
50
|
+
} = args;
|
|
51
|
+
|
|
52
|
+
// POST /v1/account - Create account
|
|
53
|
+
server.route({
|
|
54
|
+
method: 'POST',
|
|
55
|
+
path: '/v1/account',
|
|
56
|
+
|
|
57
|
+
async handler(request) {
|
|
58
|
+
let accountObject = new Account({
|
|
59
|
+
redis,
|
|
60
|
+
call,
|
|
61
|
+
secret: await getSecret(),
|
|
62
|
+
timeout: request.headers['x-ee-timeout']
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
if (request.payload.oauth2 && request.payload.oauth2.authorize) {
|
|
67
|
+
// redirect to OAuth2 consent screen
|
|
68
|
+
|
|
69
|
+
const oAuth2Client = await oauth2Apps.getClient(request.payload.oauth2.provider);
|
|
70
|
+
const nonce = crypto.randomBytes(NONCE_BYTES).toString('base64url');
|
|
71
|
+
|
|
72
|
+
const accountData = request.payload;
|
|
73
|
+
|
|
74
|
+
if (accountData.oauth2.redirectUrl) {
|
|
75
|
+
accountData._meta = {
|
|
76
|
+
redirectUrl: accountData.oauth2.redirectUrl
|
|
77
|
+
};
|
|
78
|
+
delete accountData.oauth2.redirectUrl;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
delete accountData.oauth2.authorize; // do not store this property
|
|
82
|
+
// store account data
|
|
83
|
+
await redis
|
|
84
|
+
.multi()
|
|
85
|
+
.set(`${REDIS_PREFIX}account:add:${nonce}`, JSON.stringify(accountData))
|
|
86
|
+
.expire(`${REDIS_PREFIX}account:add:${nonce}`, Math.floor(MAX_FORM_TTL / 1000))
|
|
87
|
+
.exec();
|
|
88
|
+
|
|
89
|
+
// Generate the url that will be used for the consent dialog.
|
|
90
|
+
let authorizeUrl;
|
|
91
|
+
switch (oAuth2Client.provider) {
|
|
92
|
+
case 'gmail': {
|
|
93
|
+
let requestData = {
|
|
94
|
+
state: `account:add:${nonce}`
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
if (accountData.email) {
|
|
98
|
+
requestData.email = accountData.email;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
authorizeUrl = oAuth2Client.generateAuthUrl(requestData);
|
|
102
|
+
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
case 'outlook':
|
|
107
|
+
case 'mailRu':
|
|
108
|
+
authorizeUrl = oAuth2Client.generateAuthUrl({
|
|
109
|
+
state: `account:add:${nonce}`
|
|
110
|
+
});
|
|
111
|
+
break;
|
|
112
|
+
|
|
113
|
+
default: {
|
|
114
|
+
let error = Boom.boomify(new Error('Unknown OAuth provider'), { statusCode: 400 });
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
redirect: authorizeUrl
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Validate delegation fields are only used with OAuth2 provider
|
|
125
|
+
validateDelegationFields(request.payload);
|
|
126
|
+
|
|
127
|
+
let result = await accountObject.create(request.payload);
|
|
128
|
+
return result;
|
|
129
|
+
} catch (err) {
|
|
130
|
+
request.logger.error({ msg: 'API request failed', err });
|
|
131
|
+
if (Boom.isBoom(err)) {
|
|
132
|
+
throw err;
|
|
133
|
+
}
|
|
134
|
+
let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
|
|
135
|
+
if (err.code) {
|
|
136
|
+
error.output.payload.code = err.code;
|
|
137
|
+
}
|
|
138
|
+
throw error;
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
options: {
|
|
143
|
+
description: 'Register new account',
|
|
144
|
+
notes: 'Registers new IMAP account to be synced',
|
|
145
|
+
tags: ['api', 'Account'],
|
|
146
|
+
|
|
147
|
+
plugins: {},
|
|
148
|
+
|
|
149
|
+
auth: {
|
|
150
|
+
strategy: 'api-token',
|
|
151
|
+
mode: 'required'
|
|
152
|
+
},
|
|
153
|
+
cors: CORS_CONFIG,
|
|
154
|
+
|
|
155
|
+
validate: {
|
|
156
|
+
options: {
|
|
157
|
+
stripUnknown: false,
|
|
158
|
+
abortEarly: false,
|
|
159
|
+
convert: true
|
|
160
|
+
},
|
|
161
|
+
failAction,
|
|
162
|
+
|
|
163
|
+
payload: Joi.object({
|
|
164
|
+
account: Joi.string()
|
|
165
|
+
.empty('')
|
|
166
|
+
.trim()
|
|
167
|
+
.max(256)
|
|
168
|
+
.allow(null)
|
|
169
|
+
.example('example')
|
|
170
|
+
.description(
|
|
171
|
+
'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'
|
|
172
|
+
)
|
|
173
|
+
.required(),
|
|
174
|
+
|
|
175
|
+
name: Joi.string().max(256).required().example('My Email Account').description('Display name for the account'),
|
|
176
|
+
email: Joi.string().empty('').email().example('user@example.com').description('Default email address of the account'),
|
|
177
|
+
|
|
178
|
+
path: accountPathSchema.example(['*']).label('AccountPath'),
|
|
179
|
+
|
|
180
|
+
subconnections: accountSchemas.subconnections,
|
|
181
|
+
|
|
182
|
+
webhooks: Joi.string()
|
|
183
|
+
.uri({
|
|
184
|
+
scheme: ['http', 'https'],
|
|
185
|
+
allowRelative: false
|
|
186
|
+
})
|
|
187
|
+
.allow('')
|
|
188
|
+
.example('https://myservice.com/imap/webhooks')
|
|
189
|
+
.description('Account-specific webhook URL'),
|
|
190
|
+
|
|
191
|
+
copy: Joi.boolean()
|
|
192
|
+
.allow(null)
|
|
193
|
+
.example(null)
|
|
194
|
+
.description('Copy submitted messages to Sent folder. Set to `null` to unset and use provider specific default.'),
|
|
195
|
+
|
|
196
|
+
logs: Joi.boolean().example(false).description('Store recent logs').default(false),
|
|
197
|
+
|
|
198
|
+
notifyFrom: accountSchemas.notifyFrom.default('now'),
|
|
199
|
+
syncFrom: accountSchemas.syncFrom.default(null),
|
|
200
|
+
|
|
201
|
+
proxy: settingsSchema.proxyUrl,
|
|
202
|
+
smtpEhloName: settingsSchema.smtpEhloName,
|
|
203
|
+
|
|
204
|
+
imapIndexer: accountSchemas.imapIndexer,
|
|
205
|
+
|
|
206
|
+
imap: Joi.object(imapSchemaArg).allow(false).description('IMAP configuration').label('ImapConfiguration'),
|
|
207
|
+
|
|
208
|
+
smtp: Joi.object(smtpSchemaArg).allow(false).description('SMTP configuration').label('SmtpConfiguration'),
|
|
209
|
+
|
|
210
|
+
oauth2: Joi.object(oauth2SchemaArg).allow(false).description('OAuth2 configuration').label('OAuth2'),
|
|
211
|
+
|
|
212
|
+
webhooksCustomHeaders: settingsSchema.webhooksCustomHeaders.label('AccountWebhooksCustomHeaders'),
|
|
213
|
+
|
|
214
|
+
locale: Joi.string().empty('').max(100).example('fr').description('Optional locale'),
|
|
215
|
+
tz: Joi.string().empty('').max(100).example('Europe/Tallinn').description('Optional timezone')
|
|
216
|
+
})
|
|
217
|
+
.label('CreateAccount')
|
|
218
|
+
.example({
|
|
219
|
+
account: 'example',
|
|
220
|
+
name: 'Nyan Cat',
|
|
221
|
+
email: 'nyan.cat@example.com',
|
|
222
|
+
imap: {
|
|
223
|
+
auth: {
|
|
224
|
+
user: 'nyan.cat',
|
|
225
|
+
pass: 'sercretpass'
|
|
226
|
+
},
|
|
227
|
+
host: 'mail.example.com',
|
|
228
|
+
port: 993,
|
|
229
|
+
secure: true
|
|
230
|
+
},
|
|
231
|
+
smtp: {
|
|
232
|
+
auth: {
|
|
233
|
+
user: 'nyan.cat',
|
|
234
|
+
pass: 'secretpass'
|
|
235
|
+
},
|
|
236
|
+
host: 'mail.example.com',
|
|
237
|
+
port: 465,
|
|
238
|
+
secure: true
|
|
239
|
+
}
|
|
240
|
+
})
|
|
241
|
+
},
|
|
242
|
+
|
|
243
|
+
response: {
|
|
244
|
+
schema: Joi.object({
|
|
245
|
+
account: accountIdSchema.required(),
|
|
246
|
+
state: Joi.string()
|
|
247
|
+
.required()
|
|
248
|
+
.valid('existing', 'new')
|
|
249
|
+
.example('new')
|
|
250
|
+
.description('Is the account new or updated existing')
|
|
251
|
+
.label('CreateAccountState')
|
|
252
|
+
}).label('CreateAccountResponse'),
|
|
253
|
+
failAction: 'log'
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
// PUT /v1/account/{account} - Update account
|
|
259
|
+
server.route({
|
|
260
|
+
method: 'PUT',
|
|
261
|
+
path: '/v1/account/{account}',
|
|
262
|
+
|
|
263
|
+
async handler(request) {
|
|
264
|
+
let accountObject = new Account({
|
|
265
|
+
redis,
|
|
266
|
+
account: request.params.account,
|
|
267
|
+
call,
|
|
268
|
+
secret: await getSecret(),
|
|
269
|
+
timeout: request.headers['x-ee-timeout']
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
try {
|
|
273
|
+
// Validate delegation fields are only used with OAuth2 provider
|
|
274
|
+
validateDelegationFields(request.payload);
|
|
275
|
+
|
|
276
|
+
return await accountObject.update(request.payload);
|
|
277
|
+
} catch (err) {
|
|
278
|
+
request.logger.error({ msg: 'API request failed', err });
|
|
279
|
+
if (Boom.isBoom(err)) {
|
|
280
|
+
throw err;
|
|
281
|
+
}
|
|
282
|
+
let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
|
|
283
|
+
if (err.code) {
|
|
284
|
+
error.output.payload.code = err.code;
|
|
285
|
+
}
|
|
286
|
+
throw error;
|
|
287
|
+
}
|
|
288
|
+
},
|
|
289
|
+
options: {
|
|
290
|
+
description: 'Update account info',
|
|
291
|
+
notes: 'Updates account information',
|
|
292
|
+
tags: ['api', 'Account'],
|
|
293
|
+
|
|
294
|
+
plugins: {},
|
|
295
|
+
|
|
296
|
+
auth: {
|
|
297
|
+
strategy: 'api-token',
|
|
298
|
+
mode: 'required'
|
|
299
|
+
},
|
|
300
|
+
cors: CORS_CONFIG,
|
|
301
|
+
|
|
302
|
+
validate: {
|
|
303
|
+
options: {
|
|
304
|
+
stripUnknown: false,
|
|
305
|
+
abortEarly: false,
|
|
306
|
+
convert: true
|
|
307
|
+
},
|
|
308
|
+
failAction,
|
|
309
|
+
|
|
310
|
+
params: Joi.object({
|
|
311
|
+
account: accountIdSchema.required()
|
|
312
|
+
}),
|
|
313
|
+
|
|
314
|
+
payload: Joi.object({
|
|
315
|
+
name: Joi.string().max(256).example('My Email Account').description('Display name for the account'),
|
|
316
|
+
email: Joi.string().empty('').email().example('user@example.com').description('Default email address of the account'),
|
|
317
|
+
|
|
318
|
+
path: accountPathSchema.example(['*']).label('AccountPath'),
|
|
319
|
+
|
|
320
|
+
subconnections: accountSchemas.subconnections,
|
|
321
|
+
|
|
322
|
+
webhooks: Joi.string()
|
|
323
|
+
.uri({
|
|
324
|
+
scheme: ['http', 'https'],
|
|
325
|
+
allowRelative: false
|
|
326
|
+
})
|
|
327
|
+
.allow('')
|
|
328
|
+
.example('https://myservice.com/imap/webhooks')
|
|
329
|
+
.description('Account-specific webhook URL'),
|
|
330
|
+
|
|
331
|
+
copy: Joi.boolean()
|
|
332
|
+
.allow(null)
|
|
333
|
+
.example(null)
|
|
334
|
+
.description('Copy submitted messages to Sent folder. Set to `null` to unset and use provider specific default.'),
|
|
335
|
+
|
|
336
|
+
logs: Joi.boolean().example(false).description('Store recent logs'),
|
|
337
|
+
|
|
338
|
+
notifyFrom: accountSchemas.notifyFrom,
|
|
339
|
+
syncFrom: accountSchemas.syncFrom,
|
|
340
|
+
|
|
341
|
+
proxy: settingsSchema.proxyUrl,
|
|
342
|
+
smtpEhloName: settingsSchema.smtpEhloName,
|
|
343
|
+
|
|
344
|
+
imap: Joi.object(imapUpdateSchema).allow(false).description('IMAP configuration').label('IMAPUpdate'),
|
|
345
|
+
smtp: Joi.object(smtpUpdateSchema).allow(false).description('SMTP configuration').label('SMTPUpdate'),
|
|
346
|
+
oauth2: Joi.object(oauth2UpdateSchema).allow(false).description('OAuth2 configuration').label('OAuth2Update'),
|
|
347
|
+
|
|
348
|
+
webhooksCustomHeaders: settingsSchema.webhooksCustomHeaders.label('AccountWebhooksCustomHeaders'),
|
|
349
|
+
|
|
350
|
+
locale: Joi.string().empty('').max(100).example('fr').description('Optional locale'),
|
|
351
|
+
tz: Joi.string().empty('').max(100).example('Europe/Tallinn').description('Optional timezone')
|
|
352
|
+
})
|
|
353
|
+
.label('UpdateAccount')
|
|
354
|
+
.example({
|
|
355
|
+
name: 'Nyan Cat',
|
|
356
|
+
email: 'nyan.cat@example.com',
|
|
357
|
+
imap: {
|
|
358
|
+
partial: true,
|
|
359
|
+
disabled: true
|
|
360
|
+
},
|
|
361
|
+
smtp: {
|
|
362
|
+
partial: true,
|
|
363
|
+
host: 'mail.example.com'
|
|
364
|
+
}
|
|
365
|
+
})
|
|
366
|
+
},
|
|
367
|
+
|
|
368
|
+
response: {
|
|
369
|
+
schema: Joi.object({
|
|
370
|
+
account: accountIdSchema.required()
|
|
371
|
+
}),
|
|
372
|
+
failAction: 'log'
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
// PUT /v1/account/{account}/reconnect - Request reconnect
|
|
378
|
+
server.route({
|
|
379
|
+
method: 'PUT',
|
|
380
|
+
path: '/v1/account/{account}/reconnect',
|
|
381
|
+
|
|
382
|
+
async handler(request) {
|
|
383
|
+
let accountObject = new Account({
|
|
384
|
+
redis,
|
|
385
|
+
account: request.params.account,
|
|
386
|
+
call,
|
|
387
|
+
secret: await getSecret(),
|
|
388
|
+
timeout: request.headers['x-ee-timeout']
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
try {
|
|
392
|
+
return { reconnect: await accountObject.requestReconnect(request.payload) };
|
|
393
|
+
} catch (err) {
|
|
394
|
+
request.logger.error({ msg: 'API request failed', err });
|
|
395
|
+
if (Boom.isBoom(err)) {
|
|
396
|
+
throw err;
|
|
397
|
+
}
|
|
398
|
+
let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
|
|
399
|
+
if (err.code) {
|
|
400
|
+
error.output.payload.code = err.code;
|
|
401
|
+
}
|
|
402
|
+
throw error;
|
|
403
|
+
}
|
|
404
|
+
},
|
|
405
|
+
options: {
|
|
406
|
+
description: 'Request reconnect',
|
|
407
|
+
notes: 'Requests connection to be reconnected',
|
|
408
|
+
tags: ['api', 'Account'],
|
|
409
|
+
|
|
410
|
+
plugins: {},
|
|
411
|
+
|
|
412
|
+
auth: {
|
|
413
|
+
strategy: 'api-token',
|
|
414
|
+
mode: 'required'
|
|
415
|
+
},
|
|
416
|
+
cors: CORS_CONFIG,
|
|
417
|
+
|
|
418
|
+
validate: {
|
|
419
|
+
options: {
|
|
420
|
+
stripUnknown: false,
|
|
421
|
+
abortEarly: false,
|
|
422
|
+
convert: true
|
|
423
|
+
},
|
|
424
|
+
failAction,
|
|
425
|
+
|
|
426
|
+
params: Joi.object({
|
|
427
|
+
account: accountIdSchema.required()
|
|
428
|
+
}),
|
|
429
|
+
|
|
430
|
+
payload: Joi.object({
|
|
431
|
+
reconnect: Joi.boolean().truthy('Y', 'true', '1').falsy('N', 'false', 0).default(false).description('Only reconnect if true')
|
|
432
|
+
}).label('RequestReconnect')
|
|
433
|
+
},
|
|
434
|
+
|
|
435
|
+
response: {
|
|
436
|
+
schema: Joi.object({
|
|
437
|
+
reconnect: Joi.boolean().truthy('Y', 'true', '1').falsy('N', 'false', 0).default(false).description('Reconnection status')
|
|
438
|
+
}).label('RequestReconnectResponse'),
|
|
439
|
+
failAction: 'log'
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
// PUT /v1/account/{account}/sync - Request syncing
|
|
445
|
+
server.route({
|
|
446
|
+
method: 'PUT',
|
|
447
|
+
path: '/v1/account/{account}/sync',
|
|
448
|
+
|
|
449
|
+
async handler(request) {
|
|
450
|
+
let accountObject = new Account({
|
|
451
|
+
redis,
|
|
452
|
+
account: request.params.account,
|
|
453
|
+
call,
|
|
454
|
+
secret: await getSecret(),
|
|
455
|
+
timeout: request.headers['x-ee-timeout']
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
try {
|
|
459
|
+
return { sync: await accountObject.requestSync(request.payload) };
|
|
460
|
+
} catch (err) {
|
|
461
|
+
request.logger.error({ msg: 'API request failed', err });
|
|
462
|
+
if (Boom.isBoom(err)) {
|
|
463
|
+
throw err;
|
|
464
|
+
}
|
|
465
|
+
let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
|
|
466
|
+
if (err.code) {
|
|
467
|
+
error.output.payload.code = err.code;
|
|
468
|
+
}
|
|
469
|
+
throw error;
|
|
470
|
+
}
|
|
471
|
+
},
|
|
472
|
+
options: {
|
|
473
|
+
description: 'Request syncing',
|
|
474
|
+
notes: 'Immediately trigger account syncing for IMAP accounts',
|
|
475
|
+
tags: ['api', 'Account'],
|
|
476
|
+
|
|
477
|
+
plugins: {},
|
|
478
|
+
|
|
479
|
+
auth: {
|
|
480
|
+
strategy: 'api-token',
|
|
481
|
+
mode: 'required'
|
|
482
|
+
},
|
|
483
|
+
cors: CORS_CONFIG,
|
|
484
|
+
|
|
485
|
+
validate: {
|
|
486
|
+
options: {
|
|
487
|
+
stripUnknown: false,
|
|
488
|
+
abortEarly: false,
|
|
489
|
+
convert: true
|
|
490
|
+
},
|
|
491
|
+
failAction,
|
|
492
|
+
|
|
493
|
+
params: Joi.object({
|
|
494
|
+
account: accountIdSchema.required()
|
|
495
|
+
}),
|
|
496
|
+
|
|
497
|
+
payload: Joi.object({
|
|
498
|
+
sync: Joi.boolean().truthy('Y', 'true', '1').falsy('N', 'false', 0).default(false).description('Only sync if true')
|
|
499
|
+
}).label('RequestSync')
|
|
500
|
+
},
|
|
501
|
+
|
|
502
|
+
response: {
|
|
503
|
+
schema: Joi.object({
|
|
504
|
+
sync: Joi.boolean().truthy('Y', 'true', '1').falsy('N', 'false', 0).default(false).description('Sync status')
|
|
505
|
+
}).label('RequestSyncResponse'),
|
|
506
|
+
failAction: 'log'
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
// DELETE /v1/account/{account} - Remove account
|
|
512
|
+
server.route({
|
|
513
|
+
method: 'DELETE',
|
|
514
|
+
path: '/v1/account/{account}',
|
|
515
|
+
|
|
516
|
+
async handler(request) {
|
|
517
|
+
let accountObject = new Account({
|
|
518
|
+
redis,
|
|
519
|
+
account: request.params.account,
|
|
520
|
+
documentsQueue,
|
|
521
|
+
call,
|
|
522
|
+
secret: await getSecret(),
|
|
523
|
+
timeout: request.headers['x-ee-timeout']
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
try {
|
|
527
|
+
return await accountObject.delete();
|
|
528
|
+
} catch (err) {
|
|
529
|
+
request.logger.error({ msg: 'API request failed', err });
|
|
530
|
+
if (Boom.isBoom(err)) {
|
|
531
|
+
throw err;
|
|
532
|
+
}
|
|
533
|
+
let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
|
|
534
|
+
if (err.code) {
|
|
535
|
+
error.output.payload.code = err.code;
|
|
536
|
+
}
|
|
537
|
+
throw error;
|
|
538
|
+
}
|
|
539
|
+
},
|
|
540
|
+
options: {
|
|
541
|
+
description: 'Remove account',
|
|
542
|
+
notes: "Stop processing and clear the account's cache",
|
|
543
|
+
|
|
544
|
+
tags: ['api', 'Account'],
|
|
545
|
+
|
|
546
|
+
plugins: {},
|
|
547
|
+
|
|
548
|
+
auth: {
|
|
549
|
+
strategy: 'api-token',
|
|
550
|
+
mode: 'required'
|
|
551
|
+
},
|
|
552
|
+
cors: CORS_CONFIG,
|
|
553
|
+
|
|
554
|
+
validate: {
|
|
555
|
+
options: {
|
|
556
|
+
stripUnknown: false,
|
|
557
|
+
abortEarly: false,
|
|
558
|
+
convert: true
|
|
559
|
+
},
|
|
560
|
+
failAction,
|
|
561
|
+
|
|
562
|
+
params: Joi.object({
|
|
563
|
+
account: accountIdSchema.required()
|
|
564
|
+
})
|
|
565
|
+
},
|
|
566
|
+
|
|
567
|
+
response: {
|
|
568
|
+
schema: Joi.object({
|
|
569
|
+
account: accountIdSchema.required(),
|
|
570
|
+
deleted: Joi.boolean().truthy('Y', 'true', '1').falsy('N', 'false', 0).default(true).description('Was the account deleted')
|
|
571
|
+
}).label('DeleteRequestResponse'),
|
|
572
|
+
failAction: 'log'
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
// PUT /v1/account/{account}/flush - Request account flush
|
|
578
|
+
server.route({
|
|
579
|
+
method: 'PUT',
|
|
580
|
+
path: '/v1/account/{account}/flush',
|
|
581
|
+
|
|
582
|
+
async handler(request, h) {
|
|
583
|
+
let accountObject = new Account({
|
|
584
|
+
redis,
|
|
585
|
+
account: request.params.account,
|
|
586
|
+
call,
|
|
587
|
+
secret: await getSecret(),
|
|
588
|
+
esClient: await h.getESClient(request.logger),
|
|
589
|
+
timeout: request.headers['x-ee-timeout']
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
try {
|
|
593
|
+
return { flush: await accountObject.flush(request.payload) };
|
|
594
|
+
} catch (err) {
|
|
595
|
+
request.logger.error({ msg: 'API request failed', err });
|
|
596
|
+
if (Boom.isBoom(err)) {
|
|
597
|
+
throw err;
|
|
598
|
+
}
|
|
599
|
+
let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
|
|
600
|
+
if (err.code) {
|
|
601
|
+
error.output.payload.code = err.code;
|
|
602
|
+
}
|
|
603
|
+
throw error;
|
|
604
|
+
}
|
|
605
|
+
},
|
|
606
|
+
options: {
|
|
607
|
+
description: 'Request account flush',
|
|
608
|
+
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.',
|
|
609
|
+
tags: ['api', 'Account'],
|
|
610
|
+
|
|
611
|
+
plugins: {},
|
|
612
|
+
|
|
613
|
+
auth: {
|
|
614
|
+
strategy: 'api-token',
|
|
615
|
+
mode: 'required'
|
|
616
|
+
},
|
|
617
|
+
cors: CORS_CONFIG,
|
|
618
|
+
|
|
619
|
+
validate: {
|
|
620
|
+
options: {
|
|
621
|
+
stripUnknown: false,
|
|
622
|
+
abortEarly: false,
|
|
623
|
+
convert: true
|
|
624
|
+
},
|
|
625
|
+
failAction,
|
|
626
|
+
|
|
627
|
+
params: Joi.object({
|
|
628
|
+
account: accountIdSchema.required()
|
|
629
|
+
}),
|
|
630
|
+
|
|
631
|
+
payload: Joi.object({
|
|
632
|
+
flush: Joi.boolean().truthy('Y', 'true', '1').falsy('N', 'false', 0).default(false).description('Only flush the account if true'),
|
|
633
|
+
notifyFrom: accountSchemas.notifyFrom.default('now'),
|
|
634
|
+
imapIndexer: accountSchemas.imapIndexer,
|
|
635
|
+
syncFrom: accountSchemas.syncFrom
|
|
636
|
+
}).label('RequestFlush')
|
|
637
|
+
},
|
|
638
|
+
|
|
639
|
+
response: {
|
|
640
|
+
schema: Joi.object({
|
|
641
|
+
flush: Joi.boolean().truthy('Y', 'true', '1').falsy('N', 'false', 0).default(false).description('Flush status')
|
|
642
|
+
}).label('RequestFlushResponse'),
|
|
643
|
+
failAction: 'log'
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
// GET /v1/accounts - List accounts
|
|
649
|
+
server.route({
|
|
650
|
+
method: 'GET',
|
|
651
|
+
path: '/v1/accounts',
|
|
652
|
+
|
|
653
|
+
async handler(request) {
|
|
654
|
+
try {
|
|
655
|
+
let accountObject = new Account({
|
|
656
|
+
redis,
|
|
657
|
+
account: request.params.account,
|
|
658
|
+
call,
|
|
659
|
+
secret: await getSecret(),
|
|
660
|
+
timeout: request.headers['x-ee-timeout']
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
return await accountObject.listAccounts(request.query.state, request.query.query, request.query.page, request.query.pageSize);
|
|
664
|
+
} catch (err) {
|
|
665
|
+
request.logger.error({ msg: 'API request failed', err });
|
|
666
|
+
if (Boom.isBoom(err)) {
|
|
667
|
+
throw err;
|
|
668
|
+
}
|
|
669
|
+
let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
|
|
670
|
+
if (err.code) {
|
|
671
|
+
error.output.payload.code = err.code;
|
|
672
|
+
}
|
|
673
|
+
throw error;
|
|
674
|
+
}
|
|
675
|
+
},
|
|
676
|
+
|
|
677
|
+
options: {
|
|
678
|
+
description: 'List accounts',
|
|
679
|
+
notes: 'Lists registered accounts',
|
|
680
|
+
tags: ['api', 'Account'],
|
|
681
|
+
|
|
682
|
+
plugins: {},
|
|
683
|
+
|
|
684
|
+
auth: {
|
|
685
|
+
strategy: 'api-token',
|
|
686
|
+
mode: 'required'
|
|
687
|
+
},
|
|
688
|
+
cors: CORS_CONFIG,
|
|
689
|
+
|
|
690
|
+
validate: {
|
|
691
|
+
options: {
|
|
692
|
+
stripUnknown: false,
|
|
693
|
+
abortEarly: false,
|
|
694
|
+
convert: true
|
|
695
|
+
},
|
|
696
|
+
failAction,
|
|
697
|
+
|
|
698
|
+
query: Joi.object({
|
|
699
|
+
page: Joi.number()
|
|
700
|
+
.integer()
|
|
701
|
+
.min(0)
|
|
702
|
+
.max(1024 * 1024)
|
|
703
|
+
.default(0)
|
|
704
|
+
.example(0)
|
|
705
|
+
.description('Page number (zero indexed, so use 0 for first page)')
|
|
706
|
+
.label('PageNumber'),
|
|
707
|
+
pageSize: Joi.number().integer().min(1).max(1000).default(20).example(20).description('How many entries per page').label('PageSize'),
|
|
708
|
+
state: Joi.string()
|
|
709
|
+
.valid('init', 'syncing', 'connecting', 'connected', 'authenticationError', 'connectError', 'unset', 'disconnected')
|
|
710
|
+
.example('connected')
|
|
711
|
+
.description('Filter accounts by state')
|
|
712
|
+
.label('AccountState'),
|
|
713
|
+
query: Joi.string().example('user@example.com').description('Filter accounts by string match').label('AccountQuery')
|
|
714
|
+
}).label('AccountsFilter')
|
|
715
|
+
},
|
|
716
|
+
|
|
717
|
+
response: {
|
|
718
|
+
schema: Joi.object({
|
|
719
|
+
total: Joi.number().integer().example(120).description('How many matching entries').label('TotalNumber'),
|
|
720
|
+
page: Joi.number().integer().example(0).description('Current page (0-based index)').label('PageNumber'),
|
|
721
|
+
pages: Joi.number().integer().example(24).description('Total page count').label('PagesNumber'),
|
|
722
|
+
|
|
723
|
+
accounts: Joi.array()
|
|
724
|
+
.items(
|
|
725
|
+
Joi.object({
|
|
726
|
+
account: accountIdSchema.required(),
|
|
727
|
+
name: Joi.string().max(256).example('My Email Account').description('Display name for the account'),
|
|
728
|
+
email: Joi.string().empty('').email().example('user@example.com').description('Default email address of the account'),
|
|
729
|
+
type: AccountTypeSchema,
|
|
730
|
+
app: Joi.string().max(256).example('AAABhaBPHscAAAAH').description('OAuth2 application ID'),
|
|
731
|
+
state: Joi.string()
|
|
732
|
+
.required()
|
|
733
|
+
.valid('init', 'syncing', 'connecting', 'connected', 'authenticationError', 'connectError', 'unset', 'disconnected')
|
|
734
|
+
.example('connected')
|
|
735
|
+
.description('Account state'),
|
|
736
|
+
webhooks: Joi.string()
|
|
737
|
+
.uri({
|
|
738
|
+
scheme: ['http', 'https'],
|
|
739
|
+
allowRelative: false
|
|
740
|
+
})
|
|
741
|
+
.example('https://myservice.com/imap/webhooks')
|
|
742
|
+
.description('Account-specific webhook URL'),
|
|
743
|
+
proxy: settingsSchema.proxyUrl,
|
|
744
|
+
smtpEhloName: settingsSchema.smtpEhloName,
|
|
745
|
+
|
|
746
|
+
counters: accountCountersSchema,
|
|
747
|
+
|
|
748
|
+
syncTime: Joi.date().iso().example('2021-02-17T13:43:18.860Z').description('Last sync time'),
|
|
749
|
+
lastError: lastErrorSchema.allow(null)
|
|
750
|
+
}).label('AccountResponseItem')
|
|
751
|
+
)
|
|
752
|
+
.label('AccountEntries')
|
|
753
|
+
}).label('AccountsFilterResponse'),
|
|
754
|
+
failAction: 'log'
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
});
|
|
758
|
+
|
|
759
|
+
// GET /v1/account/{account} - Get account info
|
|
760
|
+
server.route({
|
|
761
|
+
method: 'GET',
|
|
762
|
+
path: '/v1/account/{account}',
|
|
763
|
+
|
|
764
|
+
async handler(request) {
|
|
765
|
+
let accountObject = new Account({
|
|
766
|
+
redis,
|
|
767
|
+
account: request.params.account,
|
|
768
|
+
call,
|
|
769
|
+
secret: await getSecret(),
|
|
770
|
+
timeout: request.headers['x-ee-timeout']
|
|
771
|
+
});
|
|
772
|
+
try {
|
|
773
|
+
let accountData = await accountObject.loadAccountData();
|
|
774
|
+
|
|
775
|
+
// remove secrets
|
|
776
|
+
for (let type of ['imap', 'smtp', 'oauth2']) {
|
|
777
|
+
if (accountData[type] && accountData[type].auth) {
|
|
778
|
+
for (let key of ['pass', 'accessToken', 'refreshToken']) {
|
|
779
|
+
if (key in accountData[type].auth) {
|
|
780
|
+
accountData[type].auth[key] = '******';
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
if (accountData[type]) {
|
|
786
|
+
for (let key of ['accessToken', 'refreshToken']) {
|
|
787
|
+
if (key in accountData[type]) {
|
|
788
|
+
accountData[type][key] = '******';
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
let result = {};
|
|
795
|
+
|
|
796
|
+
for (let key of [
|
|
797
|
+
'account',
|
|
798
|
+
'name',
|
|
799
|
+
'email',
|
|
800
|
+
'copy',
|
|
801
|
+
'logs',
|
|
802
|
+
'notifyFrom',
|
|
803
|
+
'syncFrom',
|
|
804
|
+
'path',
|
|
805
|
+
'subconnections',
|
|
806
|
+
'webhooks',
|
|
807
|
+
'proxy',
|
|
808
|
+
'smtpEhloName',
|
|
809
|
+
'imapIndexer',
|
|
810
|
+
'imap',
|
|
811
|
+
'smtp',
|
|
812
|
+
'oauth2',
|
|
813
|
+
'state',
|
|
814
|
+
'smtpStatus',
|
|
815
|
+
'syncError',
|
|
816
|
+
'connections',
|
|
817
|
+
'webhooksCustomHeaders',
|
|
818
|
+
'locale',
|
|
819
|
+
'tz'
|
|
820
|
+
]) {
|
|
821
|
+
if (key in accountData) {
|
|
822
|
+
result[key] = accountData[key];
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// default false
|
|
827
|
+
for (let key of ['logs']) {
|
|
828
|
+
result[key] = !!result[key];
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
// default null
|
|
832
|
+
for (let key of ['notifyFrom', 'syncFrom', 'lastError', 'smtpStatus']) {
|
|
833
|
+
result[key] = result[key] || null;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
let oauth2App;
|
|
837
|
+
if (accountData.oauth2 && accountData.oauth2.provider) {
|
|
838
|
+
oauth2App = await oauth2Apps.get(accountData.oauth2.provider);
|
|
839
|
+
|
|
840
|
+
if (oauth2App) {
|
|
841
|
+
// Check if account is already marked as send-only
|
|
842
|
+
if (accountData.sendOnly) {
|
|
843
|
+
result.sendOnly = true;
|
|
844
|
+
} else {
|
|
845
|
+
result.type = oauth2App.provider;
|
|
846
|
+
}
|
|
847
|
+
if (oauth2App.id !== oauth2App.provider) {
|
|
848
|
+
result.app = oauth2App.id;
|
|
849
|
+
}
|
|
850
|
+
result.baseScopes = oauth2App.baseScope || 'imap';
|
|
851
|
+
} else {
|
|
852
|
+
result.type = 'oauth2';
|
|
853
|
+
}
|
|
854
|
+
} else if (accountData.oauth2 && accountData.oauth2.auth && accountData.oauth2.auth.delegatedAccount) {
|
|
855
|
+
result.type = 'delegated';
|
|
856
|
+
} else if (accountData.imap && !accountData.imap.disabled) {
|
|
857
|
+
result.type = 'imap';
|
|
858
|
+
} else {
|
|
859
|
+
result.type = 'sending';
|
|
860
|
+
result.sendOnly = true;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
if ((accountData.imap || (oauth2App && (!oauth2App.baseScopes || oauth2App.baseScopes === 'imap'))) && !result.imapIndexer) {
|
|
864
|
+
result.imapIndexer = 'full';
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
if (accountData.sync) {
|
|
868
|
+
result.syncTime = accountData.sync;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
if (accountData.state) {
|
|
872
|
+
result.lastError = accountData.state === 'connected' ? null : accountData.lastErrorState;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
if (accountData.counters) {
|
|
876
|
+
result.counters = accountData.counters;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
if (request.query.quota && !result.sendOnly) {
|
|
880
|
+
result.quota = await accountObject.getQuota();
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
return result;
|
|
884
|
+
} catch (err) {
|
|
885
|
+
request.logger.error({ msg: 'API request failed', err });
|
|
886
|
+
if (Boom.isBoom(err)) {
|
|
887
|
+
throw err;
|
|
888
|
+
}
|
|
889
|
+
let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
|
|
890
|
+
if (err.code) {
|
|
891
|
+
error.output.payload.code = err.code;
|
|
892
|
+
}
|
|
893
|
+
throw error;
|
|
894
|
+
}
|
|
895
|
+
},
|
|
896
|
+
options: {
|
|
897
|
+
description: 'Get account info',
|
|
898
|
+
notes: 'Returns stored information about the account. Passwords are not included.',
|
|
899
|
+
tags: ['api', 'Account'],
|
|
900
|
+
|
|
901
|
+
auth: {
|
|
902
|
+
strategy: 'api-token',
|
|
903
|
+
mode: 'required'
|
|
904
|
+
},
|
|
905
|
+
cors: CORS_CONFIG,
|
|
906
|
+
|
|
907
|
+
validate: {
|
|
908
|
+
options: {
|
|
909
|
+
stripUnknown: false,
|
|
910
|
+
abortEarly: false,
|
|
911
|
+
convert: true
|
|
912
|
+
},
|
|
913
|
+
failAction,
|
|
914
|
+
|
|
915
|
+
params: Joi.object({
|
|
916
|
+
account: accountIdSchema.required()
|
|
917
|
+
}),
|
|
918
|
+
|
|
919
|
+
query: Joi.object({
|
|
920
|
+
quota: Joi.boolean()
|
|
921
|
+
.truthy('Y', 'true', '1')
|
|
922
|
+
.falsy('N', 'false', 0)
|
|
923
|
+
.default(false)
|
|
924
|
+
.description('If true, then include quota information in the response')
|
|
925
|
+
.label('AccountQuota')
|
|
926
|
+
})
|
|
927
|
+
},
|
|
928
|
+
|
|
929
|
+
response: {
|
|
930
|
+
schema: Joi.object({
|
|
931
|
+
account: accountIdSchema.required(),
|
|
932
|
+
|
|
933
|
+
name: Joi.string().max(256).example('My Email Account').description('Display name for the account'),
|
|
934
|
+
email: Joi.string().empty('').email().example('user@example.com').description('Default email address of the account'),
|
|
935
|
+
|
|
936
|
+
copy: Joi.boolean().example(true).description('Copy submitted messages to Sent folder'),
|
|
937
|
+
logs: Joi.boolean().example(false).description('Store recent logs'),
|
|
938
|
+
|
|
939
|
+
notifyFrom: accountSchemas.notifyFrom,
|
|
940
|
+
syncFrom: accountSchemas.syncFrom,
|
|
941
|
+
|
|
942
|
+
path: accountPathSchema.example(['*']).label('AccountPath'),
|
|
943
|
+
|
|
944
|
+
imapIndexer: accountSchemas.imapIndexer,
|
|
945
|
+
|
|
946
|
+
subconnections: accountSchemas.subconnections,
|
|
947
|
+
|
|
948
|
+
webhooks: Joi.string()
|
|
949
|
+
.uri({
|
|
950
|
+
scheme: ['http', 'https'],
|
|
951
|
+
allowRelative: false
|
|
952
|
+
})
|
|
953
|
+
.example('https://myservice.com/imap/webhooks')
|
|
954
|
+
.description('Account-specific webhook URL'),
|
|
955
|
+
proxy: settingsSchema.proxyUrl,
|
|
956
|
+
smtpEhloName: settingsSchema.smtpEhloName,
|
|
957
|
+
|
|
958
|
+
imap: Joi.object(imapSchema).description('IMAP configuration').label('IMAPResponse'),
|
|
959
|
+
|
|
960
|
+
smtp: Joi.object(smtpSchema).description('SMTP configuration').label('SMTPResponse'),
|
|
961
|
+
|
|
962
|
+
oauth2: Joi.object(oauth2Schema).description('OAuth2 configuration').label('Oauth2Response'),
|
|
963
|
+
|
|
964
|
+
state: Joi.string()
|
|
965
|
+
.valid('init', 'syncing', 'connecting', 'connected', 'authenticationError', 'connectError', 'unset', 'disconnected')
|
|
966
|
+
.example('connected')
|
|
967
|
+
.description('Informational account state')
|
|
968
|
+
.label('AccountInfoState'),
|
|
969
|
+
|
|
970
|
+
smtpStatus: Joi.object({
|
|
971
|
+
created: Joi.date()
|
|
972
|
+
.iso()
|
|
973
|
+
.allow(null)
|
|
974
|
+
.example('2021-07-08T07:06:34.336Z')
|
|
975
|
+
.description('When was the status for SMTP connection last updated'),
|
|
976
|
+
status: Joi.string().valid('ok', 'error').description('Was the last SMTP attempt successful or not').label('SMTPStatusStatus'),
|
|
977
|
+
response: Joi.string().example('250 OK').description('SMTP response message for delivery attempt'),
|
|
978
|
+
description: Joi.string().example('Authentication failed').description('Error information'),
|
|
979
|
+
responseCode: Joi.number().integer().example(500).description('Error status code'),
|
|
980
|
+
code: Joi.string().example('EAUTH').description('Error type identifier'),
|
|
981
|
+
command: Joi.string().example('AUTH PLAIN').description('SMTP command that failed')
|
|
982
|
+
})
|
|
983
|
+
.description('Information about the last SMTP connection attempt')
|
|
984
|
+
.label('SMTPInfoStatus'),
|
|
985
|
+
|
|
986
|
+
webhooksCustomHeaders: settingsSchema.webhooksCustomHeaders.label('AccountWebhooksCustomHeaders'),
|
|
987
|
+
|
|
988
|
+
locale: Joi.string().empty('').max(100).example('fr').description('Optional locale'),
|
|
989
|
+
tz: Joi.string().empty('').max(100).example('Europe/Tallinn').description('Optional timezone'),
|
|
990
|
+
|
|
991
|
+
type: AccountTypeSchema,
|
|
992
|
+
app: Joi.string().max(256).example('AAABhaBPHscAAAAH').description('OAuth2 application ID'),
|
|
993
|
+
baseScopes: Joi.string()
|
|
994
|
+
.empty('')
|
|
995
|
+
.trim()
|
|
996
|
+
.valid(...['imap', 'api', 'pubsub'])
|
|
997
|
+
.example('imap')
|
|
998
|
+
.description('OAuth2 Base Scopes'),
|
|
999
|
+
|
|
1000
|
+
counters: accountCountersSchema,
|
|
1001
|
+
|
|
1002
|
+
quota: Joi.object({
|
|
1003
|
+
usage: Joi.number().integer().example(8547884032).description('How many bytes has the account stored in emails'),
|
|
1004
|
+
limit: Joi.number().integer().example(16106127360).description('How many bytes can the account store emails'),
|
|
1005
|
+
status: Joi.string().example('53%').description('Textual information about the usage')
|
|
1006
|
+
})
|
|
1007
|
+
.label('AccountQuota')
|
|
1008
|
+
.allow(false)
|
|
1009
|
+
.description(
|
|
1010
|
+
'Account quota information if query argument quota=true. This value will be false if the server does not provide quota information.'
|
|
1011
|
+
),
|
|
1012
|
+
|
|
1013
|
+
syncTime: Joi.date().iso().example('2021-02-17T13:43:18.860Z').description('Last sync time'),
|
|
1014
|
+
|
|
1015
|
+
lastError: lastErrorSchema.allow(null)
|
|
1016
|
+
}).label('AccountResponse'),
|
|
1017
|
+
failAction: 'log'
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
module.exports = init;
|