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
|
@@ -4,11 +4,14 @@ const { redis } = require('../db');
|
|
|
4
4
|
const { Account } = require('../account');
|
|
5
5
|
const getSecret = require('../get-secret');
|
|
6
6
|
const { templates } = require('../templates');
|
|
7
|
-
const Boom = require('@hapi/boom');
|
|
8
7
|
const Joi = require('joi');
|
|
9
8
|
const { failAction } = require('../tools');
|
|
9
|
+
const { handleError, throwNotFound } = require('./route-helpers');
|
|
10
10
|
|
|
11
|
-
const { templateSchemas, accountIdSchema } = require('../schemas');
|
|
11
|
+
const { templateSchemas, accountIdSchema, errorResponses } = require('../schemas');
|
|
12
|
+
|
|
13
|
+
// Template owner field shared by all template response schemas
|
|
14
|
+
const templateAccountIdSchema = accountIdSchema.required().allow(null).description('Account ID. Null for public templates');
|
|
12
15
|
|
|
13
16
|
async function init(args) {
|
|
14
17
|
const { server, call, CORS_CONFIG } = args;
|
|
@@ -43,15 +46,7 @@ async function init(args) {
|
|
|
43
46
|
request.payload.content
|
|
44
47
|
);
|
|
45
48
|
} catch (err) {
|
|
46
|
-
|
|
47
|
-
if (Boom.isBoom(err)) {
|
|
48
|
-
throw err;
|
|
49
|
-
}
|
|
50
|
-
let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
|
|
51
|
-
if (err.code) {
|
|
52
|
-
error.output.payload.code = err.code;
|
|
53
|
-
}
|
|
54
|
-
throw error;
|
|
49
|
+
handleError(request, err);
|
|
55
50
|
}
|
|
56
51
|
},
|
|
57
52
|
|
|
@@ -60,7 +55,11 @@ async function init(args) {
|
|
|
60
55
|
notes: 'Create a new stored template. Templates can be used when sending emails as the content of the message.',
|
|
61
56
|
tags: ['api', 'Templates'],
|
|
62
57
|
|
|
63
|
-
plugins: {
|
|
58
|
+
plugins: {
|
|
59
|
+
'hapi-swagger': {
|
|
60
|
+
responses: errorResponses(400, 401, 403, 404, 429, 500)
|
|
61
|
+
}
|
|
62
|
+
},
|
|
64
63
|
|
|
65
64
|
auth: {
|
|
66
65
|
strategy: 'api-token',
|
|
@@ -112,7 +111,7 @@ async function init(args) {
|
|
|
112
111
|
response: {
|
|
113
112
|
schema: Joi.object({
|
|
114
113
|
created: Joi.boolean().description('Was the template created or not'),
|
|
115
|
-
account:
|
|
114
|
+
account: templateAccountIdSchema,
|
|
116
115
|
id: Joi.string().max(256).required().example('example').description('Template ID')
|
|
117
116
|
}).label('CreateTemplateResponse'),
|
|
118
117
|
failAction: 'log'
|
|
@@ -135,15 +134,7 @@ async function init(args) {
|
|
|
135
134
|
|
|
136
135
|
return await templates.update(request.params.template, meta, request.payload.content);
|
|
137
136
|
} catch (err) {
|
|
138
|
-
|
|
139
|
-
if (Boom.isBoom(err)) {
|
|
140
|
-
throw err;
|
|
141
|
-
}
|
|
142
|
-
let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
|
|
143
|
-
if (err.code) {
|
|
144
|
-
error.output.payload.code = err.code;
|
|
145
|
-
}
|
|
146
|
-
throw error;
|
|
137
|
+
handleError(request, err);
|
|
147
138
|
}
|
|
148
139
|
},
|
|
149
140
|
|
|
@@ -152,7 +143,11 @@ async function init(args) {
|
|
|
152
143
|
notes: 'Update a stored template.',
|
|
153
144
|
tags: ['api', 'Templates'],
|
|
154
145
|
|
|
155
|
-
plugins: {
|
|
146
|
+
plugins: {
|
|
147
|
+
'hapi-swagger': {
|
|
148
|
+
responses: errorResponses(400, 401, 403, 404, 429, 500)
|
|
149
|
+
}
|
|
150
|
+
},
|
|
156
151
|
|
|
157
152
|
auth: {
|
|
158
153
|
strategy: 'api-token',
|
|
@@ -198,7 +193,7 @@ async function init(args) {
|
|
|
198
193
|
response: {
|
|
199
194
|
schema: Joi.object({
|
|
200
195
|
updated: Joi.boolean().description('Was the template updated or not'),
|
|
201
|
-
account:
|
|
196
|
+
account: templateAccountIdSchema,
|
|
202
197
|
id: Joi.string().max(256).required().example('example').description('Template ID')
|
|
203
198
|
}).label('UpdateTemplateResponse'),
|
|
204
199
|
failAction: 'log'
|
|
@@ -220,15 +215,7 @@ async function init(args) {
|
|
|
220
215
|
|
|
221
216
|
return await templates.list(request.query.account, request.query.page, request.query.pageSize);
|
|
222
217
|
} catch (err) {
|
|
223
|
-
|
|
224
|
-
if (Boom.isBoom(err)) {
|
|
225
|
-
throw err;
|
|
226
|
-
}
|
|
227
|
-
let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
|
|
228
|
-
if (err.code) {
|
|
229
|
-
error.output.payload.code = err.code;
|
|
230
|
-
}
|
|
231
|
-
throw error;
|
|
218
|
+
handleError(request, err);
|
|
232
219
|
}
|
|
233
220
|
},
|
|
234
221
|
|
|
@@ -237,7 +224,11 @@ async function init(args) {
|
|
|
237
224
|
notes: 'Lists stored templates for the account',
|
|
238
225
|
tags: ['api', 'Templates'],
|
|
239
226
|
|
|
240
|
-
plugins: {
|
|
227
|
+
plugins: {
|
|
228
|
+
'hapi-swagger': {
|
|
229
|
+
responses: errorResponses(400, 401, 403, 404, 429, 500)
|
|
230
|
+
}
|
|
231
|
+
},
|
|
241
232
|
|
|
242
233
|
auth: {
|
|
243
234
|
strategy: 'api-token',
|
|
@@ -270,7 +261,7 @@ async function init(args) {
|
|
|
270
261
|
|
|
271
262
|
response: {
|
|
272
263
|
schema: Joi.object({
|
|
273
|
-
account:
|
|
264
|
+
account: templateAccountIdSchema,
|
|
274
265
|
total: Joi.number().integer().example(120).description('How many matching entries').label('TotalNumber'),
|
|
275
266
|
page: Joi.number().integer().example(0).description('Current page (0-based index)').label('PageNumber'),
|
|
276
267
|
pages: Joi.number().integer().example(24).description('Total page count').label('PagesNumber'),
|
|
@@ -308,17 +299,13 @@ async function init(args) {
|
|
|
308
299
|
|
|
309
300
|
async handler(request) {
|
|
310
301
|
try {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
if (Boom.isBoom(err)) {
|
|
315
|
-
throw err;
|
|
302
|
+
let templateData = await templates.get(request.params.template);
|
|
303
|
+
if (!templateData) {
|
|
304
|
+
throwNotFound();
|
|
316
305
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
}
|
|
321
|
-
throw error;
|
|
306
|
+
return templateData;
|
|
307
|
+
} catch (err) {
|
|
308
|
+
handleError(request, err);
|
|
322
309
|
}
|
|
323
310
|
},
|
|
324
311
|
|
|
@@ -327,7 +314,11 @@ async function init(args) {
|
|
|
327
314
|
notes: 'Retrieve template content and information',
|
|
328
315
|
tags: ['api', 'Templates'],
|
|
329
316
|
|
|
330
|
-
plugins: {
|
|
317
|
+
plugins: {
|
|
318
|
+
'hapi-swagger': {
|
|
319
|
+
responses: errorResponses(400, 401, 403, 404, 429, 500)
|
|
320
|
+
}
|
|
321
|
+
},
|
|
331
322
|
|
|
332
323
|
auth: {
|
|
333
324
|
strategy: 'api-token',
|
|
@@ -349,7 +340,7 @@ async function init(args) {
|
|
|
349
340
|
|
|
350
341
|
response: {
|
|
351
342
|
schema: Joi.object({
|
|
352
|
-
account:
|
|
343
|
+
account: templateAccountIdSchema,
|
|
353
344
|
id: Joi.string().max(256).required().example('AAABgS-UcAYAAAABAA').description('Template ID'),
|
|
354
345
|
name: Joi.string().max(256).example('Transaction receipt').description('Name of the template').label('TemplateName').required(),
|
|
355
346
|
description: Joi.string()
|
|
@@ -369,12 +360,7 @@ async function init(args) {
|
|
|
369
360
|
subject: templateSchemas.subject,
|
|
370
361
|
text: templateSchemas.text,
|
|
371
362
|
html: templateSchemas.html,
|
|
372
|
-
previewText: templateSchemas.previewText
|
|
373
|
-
format: Joi.string()
|
|
374
|
-
.valid('html', 'markdown')
|
|
375
|
-
.default('html')
|
|
376
|
-
.description('Markup language for HTML ("html" or "markdown")')
|
|
377
|
-
.label('TemplateContentFormat')
|
|
363
|
+
previewText: templateSchemas.previewText
|
|
378
364
|
}).label('RequestTemplateContent')
|
|
379
365
|
}).label('AccountTemplateResponse'),
|
|
380
366
|
failAction: 'log'
|
|
@@ -390,15 +376,7 @@ async function init(args) {
|
|
|
390
376
|
try {
|
|
391
377
|
return await templates.del(request.params.template);
|
|
392
378
|
} catch (err) {
|
|
393
|
-
|
|
394
|
-
if (Boom.isBoom(err)) {
|
|
395
|
-
throw err;
|
|
396
|
-
}
|
|
397
|
-
let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
|
|
398
|
-
if (err.code) {
|
|
399
|
-
error.output.payload.code = err.code;
|
|
400
|
-
}
|
|
401
|
-
throw error;
|
|
379
|
+
handleError(request, err);
|
|
402
380
|
}
|
|
403
381
|
},
|
|
404
382
|
options: {
|
|
@@ -406,7 +384,11 @@ async function init(args) {
|
|
|
406
384
|
notes: 'Delete a stored template',
|
|
407
385
|
tags: ['api', 'Templates'],
|
|
408
386
|
|
|
409
|
-
plugins: {
|
|
387
|
+
plugins: {
|
|
388
|
+
'hapi-swagger': {
|
|
389
|
+
responses: errorResponses(400, 401, 403, 429, 500)
|
|
390
|
+
}
|
|
391
|
+
},
|
|
410
392
|
|
|
411
393
|
auth: {
|
|
412
394
|
strategy: 'api-token',
|
|
@@ -430,7 +412,7 @@ async function init(args) {
|
|
|
430
412
|
response: {
|
|
431
413
|
schema: Joi.object({
|
|
432
414
|
deleted: Joi.boolean().truthy('Y', 'true', '1').falsy('N', 'false', 0).default(true).description('Was the template deleted'),
|
|
433
|
-
account:
|
|
415
|
+
account: templateAccountIdSchema,
|
|
434
416
|
id: Joi.string().max(256).required().example('AAABgS-UcAYAAAABAA').description('Template ID')
|
|
435
417
|
}).label('DeleteTemplateResponse'),
|
|
436
418
|
failAction: 'log'
|
|
@@ -446,20 +428,19 @@ async function init(args) {
|
|
|
446
428
|
let accountObject = new Account({ redis, account: request.params.account, call, secret: await getSecret() });
|
|
447
429
|
|
|
448
430
|
try {
|
|
431
|
+
if (!request.query.force) {
|
|
432
|
+
let err = new Error('Set the force=true query parameter to flush account templates');
|
|
433
|
+
err.code = 'ForceRequired';
|
|
434
|
+
err.statusCode = 400;
|
|
435
|
+
throw err;
|
|
436
|
+
}
|
|
437
|
+
|
|
449
438
|
// throws if account does not exist
|
|
450
439
|
await accountObject.loadAccountData();
|
|
451
440
|
|
|
452
441
|
return await templates.flush(request.params.account);
|
|
453
442
|
} catch (err) {
|
|
454
|
-
|
|
455
|
-
if (Boom.isBoom(err)) {
|
|
456
|
-
throw err;
|
|
457
|
-
}
|
|
458
|
-
let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
|
|
459
|
-
if (err.code) {
|
|
460
|
-
error.output.payload.code = err.code;
|
|
461
|
-
}
|
|
462
|
-
throw error;
|
|
443
|
+
handleError(request, err);
|
|
463
444
|
}
|
|
464
445
|
},
|
|
465
446
|
options: {
|
|
@@ -467,7 +448,11 @@ async function init(args) {
|
|
|
467
448
|
notes: 'Delete all stored templates for an account',
|
|
468
449
|
tags: ['api', 'Templates'],
|
|
469
450
|
|
|
470
|
-
plugins: {
|
|
451
|
+
plugins: {
|
|
452
|
+
'hapi-swagger': {
|
|
453
|
+
responses: errorResponses(400, 401, 403, 404, 429, 500)
|
|
454
|
+
}
|
|
455
|
+
},
|
|
471
456
|
|
|
472
457
|
auth: {
|
|
473
458
|
strategy: 'api-token',
|
|
@@ -499,7 +484,7 @@ async function init(args) {
|
|
|
499
484
|
|
|
500
485
|
response: {
|
|
501
486
|
schema: Joi.object({
|
|
502
|
-
|
|
487
|
+
flushed: Joi.boolean().truthy('Y', 'true', '1').falsy('N', 'false', 0).default(true).description('Were the templates flushed'),
|
|
503
488
|
account: accountIdSchema.required()
|
|
504
489
|
}).label('FlushTemplatesResponse'),
|
|
505
490
|
failAction: 'log'
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const Joi = require('joi');
|
|
4
|
+
const { redis } = require('../db');
|
|
5
|
+
const { Account } = require('../account');
|
|
6
|
+
const getSecret = require('../get-secret');
|
|
7
|
+
const tokens = require('../tokens');
|
|
8
|
+
const { failAction } = require('../tools');
|
|
9
|
+
const { handleError } = require('./route-helpers');
|
|
10
|
+
const { accountIdSchema, tokenRestrictionsSchema, ipSchema, tokenIdSchema, errorResponses } = require('../schemas');
|
|
11
|
+
|
|
12
|
+
const tokenAccessSchema = Joi.object({
|
|
13
|
+
time: Joi.date().iso().allow(null).example('2021-02-17T13:43:18.860Z').description('Last time this token was used. Null if the token has never been used'),
|
|
14
|
+
ip: ipSchema.allow(null).description('IP address of the last request that used this token. Null if the address was not available')
|
|
15
|
+
})
|
|
16
|
+
.unknown()
|
|
17
|
+
.description('Token usage information')
|
|
18
|
+
.label('TokenAccess');
|
|
19
|
+
|
|
20
|
+
const tokenScopesSchema = Joi.array()
|
|
21
|
+
.items(Joi.string().example('api').label('TokenScopeEntry'))
|
|
22
|
+
.description('Scopes this token is valid for')
|
|
23
|
+
.label('TokenScopes');
|
|
24
|
+
|
|
25
|
+
const tokenMetadataSchema = Joi.string()
|
|
26
|
+
.empty('')
|
|
27
|
+
.max(1024 * 1024)
|
|
28
|
+
.custom((value, helpers) => {
|
|
29
|
+
try {
|
|
30
|
+
// check if parsing fails
|
|
31
|
+
JSON.parse(value);
|
|
32
|
+
return value;
|
|
33
|
+
} catch (err) {
|
|
34
|
+
return helpers.message('Metadata must be a valid JSON string');
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
.example('{"example": "value"}')
|
|
38
|
+
.description('Related metadata in JSON format')
|
|
39
|
+
.label('JsonMetaData');
|
|
40
|
+
|
|
41
|
+
// Both token listings (root and account) return the same item shape from tokens.list(),
|
|
42
|
+
// the account listing just adds the account ID
|
|
43
|
+
const tokenListItemFields = {
|
|
44
|
+
description: Joi.string().empty('').trim().max(1024).example('Token description').description('Token description'),
|
|
45
|
+
metadata: tokenMetadataSchema,
|
|
46
|
+
ip: ipSchema.description('IP address of the requester').label('TokenIP'),
|
|
47
|
+
remoteAddress: ipSchema.description('IP address of the client that created the token').label('TokenRemoteAddress'),
|
|
48
|
+
scopes: tokenScopesSchema,
|
|
49
|
+
restrictions: tokenRestrictionsSchema,
|
|
50
|
+
created: Joi.date().iso().example('2021-02-17T13:43:18.860Z').description('The time this token was created'),
|
|
51
|
+
access: tokenAccessSchema,
|
|
52
|
+
id: tokenIdSchema
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
async function init(args) {
|
|
56
|
+
const { server, call, CORS_CONFIG } = args;
|
|
57
|
+
|
|
58
|
+
server.route({
|
|
59
|
+
method: 'POST',
|
|
60
|
+
path: '/v1/token',
|
|
61
|
+
|
|
62
|
+
async handler(request) {
|
|
63
|
+
let accountObject = new Account({
|
|
64
|
+
redis,
|
|
65
|
+
account: request.payload.account,
|
|
66
|
+
call,
|
|
67
|
+
secret: await getSecret(),
|
|
68
|
+
timeout: request.headers['x-ee-timeout']
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
// throws if account does not exist
|
|
73
|
+
await accountObject.loadAccountData();
|
|
74
|
+
|
|
75
|
+
let token = await tokens.provision(Object.assign({}, request.payload, { remoteAddress: request.app.ip }));
|
|
76
|
+
|
|
77
|
+
return { token };
|
|
78
|
+
} catch (err) {
|
|
79
|
+
handleError(request, err);
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
options: {
|
|
84
|
+
description: 'Provision an access token',
|
|
85
|
+
notes: 'Provisions a new access token for an account',
|
|
86
|
+
tags: ['api', 'Access Tokens'],
|
|
87
|
+
|
|
88
|
+
plugins: {
|
|
89
|
+
'hapi-swagger': {
|
|
90
|
+
responses: errorResponses(400, 401, 403, 404, 429, 500)
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
auth: {
|
|
95
|
+
strategy: 'api-token',
|
|
96
|
+
mode: 'required'
|
|
97
|
+
},
|
|
98
|
+
cors: CORS_CONFIG,
|
|
99
|
+
|
|
100
|
+
validate: {
|
|
101
|
+
options: {
|
|
102
|
+
stripUnknown: false,
|
|
103
|
+
abortEarly: false,
|
|
104
|
+
convert: true
|
|
105
|
+
},
|
|
106
|
+
failAction,
|
|
107
|
+
|
|
108
|
+
payload: Joi.object({
|
|
109
|
+
account: accountIdSchema.required(),
|
|
110
|
+
|
|
111
|
+
description: Joi.string().empty('').trim().max(1024).required().example('Token description').description('Token description'),
|
|
112
|
+
|
|
113
|
+
scopes: Joi.array()
|
|
114
|
+
.items(Joi.string().valid('api', 'smtp', 'imap-proxy').label('TokenScope'))
|
|
115
|
+
.single()
|
|
116
|
+
.default(['api'])
|
|
117
|
+
.required()
|
|
118
|
+
.description(
|
|
119
|
+
'Token permission scopes: "api" for REST API access, "smtp" for SMTP submission, "imap-proxy" for IMAP proxy authentication'
|
|
120
|
+
)
|
|
121
|
+
.label('Scopes'),
|
|
122
|
+
|
|
123
|
+
metadata: tokenMetadataSchema,
|
|
124
|
+
|
|
125
|
+
restrictions: tokenRestrictionsSchema,
|
|
126
|
+
|
|
127
|
+
ip: ipSchema.description('IP address of the requester').label('TokenIP')
|
|
128
|
+
}).label('CreateToken')
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
response: {
|
|
132
|
+
schema: Joi.object({
|
|
133
|
+
token: Joi.string().length(64).hex().required().example('123456').description('Access token')
|
|
134
|
+
}).label('CreateTokenResponse'),
|
|
135
|
+
failAction: 'log'
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
server.route({
|
|
141
|
+
method: 'DELETE',
|
|
142
|
+
path: '/v1/token/{token}',
|
|
143
|
+
|
|
144
|
+
async handler(request) {
|
|
145
|
+
try {
|
|
146
|
+
return { deleted: await tokens.delete(request.params.token, { remoteAddress: request.app.ip }) };
|
|
147
|
+
} catch (err) {
|
|
148
|
+
handleError(request, err);
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
options: {
|
|
152
|
+
description: 'Remove a token',
|
|
153
|
+
notes: 'Delete an access token',
|
|
154
|
+
tags: ['api', 'Access Tokens'],
|
|
155
|
+
|
|
156
|
+
plugins: {
|
|
157
|
+
'hapi-swagger': {
|
|
158
|
+
responses: errorResponses(400, 401, 403, 429, 500)
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
auth: {
|
|
163
|
+
strategy: 'api-token',
|
|
164
|
+
mode: 'required'
|
|
165
|
+
},
|
|
166
|
+
cors: CORS_CONFIG,
|
|
167
|
+
|
|
168
|
+
validate: {
|
|
169
|
+
options: {
|
|
170
|
+
stripUnknown: false,
|
|
171
|
+
abortEarly: false,
|
|
172
|
+
convert: true
|
|
173
|
+
},
|
|
174
|
+
failAction,
|
|
175
|
+
|
|
176
|
+
params: Joi.object({
|
|
177
|
+
token: Joi.string().length(64).hex().required().example('123456').description('Access token')
|
|
178
|
+
}).label('DeleteTokenRequest')
|
|
179
|
+
},
|
|
180
|
+
|
|
181
|
+
response: {
|
|
182
|
+
schema: Joi.object({
|
|
183
|
+
deleted: Joi.boolean().truthy('Y', 'true', '1').falsy('N', 'false', 0).default(true).description('Was the token deleted')
|
|
184
|
+
}).label('DeleteTokenRequestResponse'),
|
|
185
|
+
failAction: 'log'
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
server.route({
|
|
191
|
+
method: 'GET',
|
|
192
|
+
path: '/v1/tokens',
|
|
193
|
+
|
|
194
|
+
async handler(request) {
|
|
195
|
+
try {
|
|
196
|
+
// TODO: allow paging
|
|
197
|
+
return { tokens: (await tokens.list(null, 0, 1000)).tokens };
|
|
198
|
+
} catch (err) {
|
|
199
|
+
handleError(request, err);
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
options: {
|
|
204
|
+
description: 'List root tokens',
|
|
205
|
+
notes: 'Lists access tokens registered for root access',
|
|
206
|
+
tags: ['api', 'Access Tokens'],
|
|
207
|
+
|
|
208
|
+
plugins: {
|
|
209
|
+
'hapi-swagger': {
|
|
210
|
+
responses: errorResponses(401, 403, 429, 500)
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
|
|
214
|
+
auth: {
|
|
215
|
+
strategy: 'api-token',
|
|
216
|
+
mode: 'required'
|
|
217
|
+
},
|
|
218
|
+
cors: CORS_CONFIG,
|
|
219
|
+
|
|
220
|
+
validate: {
|
|
221
|
+
options: {
|
|
222
|
+
stripUnknown: false,
|
|
223
|
+
abortEarly: false,
|
|
224
|
+
convert: true
|
|
225
|
+
},
|
|
226
|
+
failAction
|
|
227
|
+
},
|
|
228
|
+
|
|
229
|
+
response: {
|
|
230
|
+
schema: Joi.object({
|
|
231
|
+
tokens: Joi.array().items(Joi.object(tokenListItemFields).label('RootTokensItem')).label('RootTokensEntries')
|
|
232
|
+
}).label('RootTokensResponse'),
|
|
233
|
+
failAction: 'log'
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
server.route({
|
|
239
|
+
method: 'GET',
|
|
240
|
+
path: '/v1/tokens/account/{account}',
|
|
241
|
+
|
|
242
|
+
async handler(request) {
|
|
243
|
+
try {
|
|
244
|
+
// TODO: allow paging
|
|
245
|
+
return { tokens: (await tokens.list(request.params.account, 0, 1000)).tokens };
|
|
246
|
+
} catch (err) {
|
|
247
|
+
handleError(request, err);
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
|
|
251
|
+
options: {
|
|
252
|
+
description: 'List account tokens',
|
|
253
|
+
notes: 'Lists access tokens registered for an account',
|
|
254
|
+
tags: ['api', 'Access Tokens'],
|
|
255
|
+
|
|
256
|
+
plugins: {
|
|
257
|
+
'hapi-swagger': {
|
|
258
|
+
responses: errorResponses(400, 401, 403, 429, 500)
|
|
259
|
+
}
|
|
260
|
+
},
|
|
261
|
+
|
|
262
|
+
auth: {
|
|
263
|
+
strategy: 'api-token',
|
|
264
|
+
mode: 'required'
|
|
265
|
+
},
|
|
266
|
+
cors: CORS_CONFIG,
|
|
267
|
+
|
|
268
|
+
validate: {
|
|
269
|
+
options: {
|
|
270
|
+
stripUnknown: false,
|
|
271
|
+
abortEarly: false,
|
|
272
|
+
convert: true
|
|
273
|
+
},
|
|
274
|
+
failAction,
|
|
275
|
+
params: Joi.object({
|
|
276
|
+
account: accountIdSchema.required()
|
|
277
|
+
})
|
|
278
|
+
},
|
|
279
|
+
|
|
280
|
+
response: {
|
|
281
|
+
schema: Joi.object({
|
|
282
|
+
tokens: Joi.array()
|
|
283
|
+
.items(
|
|
284
|
+
Joi.object({
|
|
285
|
+
account: accountIdSchema.required(),
|
|
286
|
+
...tokenListItemFields
|
|
287
|
+
}).label('AccountTokensItem')
|
|
288
|
+
)
|
|
289
|
+
.label('AccountTokensEntries')
|
|
290
|
+
}).label('AccountsTokensResponse'),
|
|
291
|
+
failAction: 'log'
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
module.exports = init;
|