emailengine-app 2.68.1 → 2.69.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/.github/workflows/deploy.yml +2 -0
  2. package/.github/workflows/release.yaml +4 -0
  3. package/CHANGELOG.md +40 -0
  4. package/config/default.toml +2 -0
  5. package/data/google-crawlers.json +7 -1
  6. package/lib/account.js +62 -25
  7. package/lib/api-routes/account-routes.js +493 -75
  8. package/lib/api-routes/blocklist-routes.js +337 -0
  9. package/lib/api-routes/delivery-test-routes.js +321 -0
  10. package/lib/api-routes/export-routes.js +1 -12
  11. package/lib/api-routes/gateway-routes.js +376 -0
  12. package/lib/api-routes/license-routes.js +142 -0
  13. package/lib/api-routes/mailbox-routes.js +318 -0
  14. package/lib/api-routes/message-routes.js +21 -129
  15. package/lib/api-routes/oauth2-app-routes.js +631 -0
  16. package/lib/api-routes/outbox-routes.js +173 -0
  17. package/lib/api-routes/pubsub-routes.js +98 -0
  18. package/lib/api-routes/route-helpers.js +45 -0
  19. package/lib/api-routes/settings-routes.js +331 -0
  20. package/lib/api-routes/stats-routes.js +77 -0
  21. package/lib/api-routes/submit-routes.js +472 -0
  22. package/lib/api-routes/template-routes.js +7 -55
  23. package/lib/api-routes/token-routes.js +297 -0
  24. package/lib/api-routes/webhook-route-routes.js +152 -0
  25. package/lib/email-client/gmail-client.js +14 -0
  26. package/lib/email-client/imap/mailbox.js +34 -11
  27. package/lib/email-client/imap/subconnection.js +20 -12
  28. package/lib/email-client/imap/sync-operations.js +130 -2
  29. package/lib/email-client/imap-client.js +116 -58
  30. package/lib/email-client/outlook-client.js +85 -13
  31. package/lib/export.js +60 -19
  32. package/lib/imapproxy/imap-core/lib/commands/starttls.js +18 -0
  33. package/lib/imapproxy/imap-core/lib/imap-command.js +6 -1
  34. package/lib/imapproxy/imap-core/lib/imap-connection.js +106 -23
  35. package/lib/imapproxy/imap-core/lib/imap-server.js +24 -0
  36. package/lib/imapproxy/imap-core/lib/imap-stream.js +26 -0
  37. package/lib/message-port-stream.js +113 -16
  38. package/lib/reject-worker-calls.js +42 -0
  39. package/lib/routes-ui.js +37 -8778
  40. package/lib/schemas.js +26 -1
  41. package/lib/tools.js +68 -0
  42. package/lib/ui-routes/account-routes.js +40 -210
  43. package/lib/ui-routes/admin-config-routes.js +913 -487
  44. package/lib/ui-routes/admin-entities-routes.js +1 -0
  45. package/lib/ui-routes/auth-routes.js +1339 -0
  46. package/lib/ui-routes/dashboard-routes.js +188 -0
  47. package/lib/ui-routes/document-store-routes.js +800 -0
  48. package/lib/ui-routes/export-routes.js +217 -0
  49. package/lib/ui-routes/internals-routes.js +354 -0
  50. package/lib/ui-routes/network-config-routes.js +759 -0
  51. package/lib/ui-routes/{oauth-routes.js → oauth-config-routes.js} +371 -91
  52. package/lib/ui-routes/route-helpers.js +316 -0
  53. package/lib/ui-routes/smtp-test-routes.js +236 -0
  54. package/lib/ui-routes/unsubscribe-routes.js +234 -0
  55. package/lib/webhook-request.js +36 -0
  56. package/package.json +8 -8
  57. package/sbom.json +1 -1
  58. package/server.js +214 -16
  59. package/static/licenses.html +12 -12
  60. package/translations/messages.pot +129 -149
  61. package/views/dashboard.hbs +7 -26
  62. package/views/internals/index.hbs +15 -0
  63. package/views/tokens/index.hbs +9 -0
  64. package/workers/api.js +198 -4401
  65. package/workers/export.js +87 -54
  66. package/workers/imap.js +29 -13
  67. package/workers/submit.js +20 -11
  68. package/workers/webhooks.js +6 -20
@@ -0,0 +1,318 @@
1
+ 'use strict';
2
+
3
+ const Boom = require('@hapi/boom');
4
+ const Joi = require('joi');
5
+ const { redis } = require('../db');
6
+ const { Account } = require('../account');
7
+ const getSecret = require('../get-secret');
8
+ const { failAction } = require('../tools');
9
+ const { handleError } = require('./route-helpers');
10
+ const { accountIdSchema, mailboxesSchema } = require('../schemas');
11
+
12
+ async function init(args) {
13
+ const { server, call, CORS_CONFIG, FLAG_SORT_ORDER } = args;
14
+
15
+ server.route({
16
+ method: 'GET',
17
+ path: '/v1/account/{account}/mailboxes',
18
+
19
+ async handler(request) {
20
+ let accountObject = new Account({
21
+ redis,
22
+ account: request.params.account,
23
+ call,
24
+ secret: await getSecret(),
25
+ timeout: request.headers['x-ee-timeout']
26
+ });
27
+
28
+ try {
29
+ let mailboxes = await accountObject.getMailboxListing(request.query);
30
+
31
+ if (mailboxes && Array.isArray(mailboxes)) {
32
+ mailboxes = mailboxes.sort((a, b) => {
33
+ if (a.specialUse && !b.specialUse) {
34
+ return -1;
35
+ }
36
+ if (!a.specialUse && b.specialUse) {
37
+ return 1;
38
+ }
39
+ if (a.specialUse && b.specialUse) {
40
+ return FLAG_SORT_ORDER.indexOf(a.specialUse) - FLAG_SORT_ORDER.indexOf(b.specialUse);
41
+ }
42
+
43
+ return a.path.localeCompare(b.path);
44
+ });
45
+ }
46
+
47
+ return { mailboxes };
48
+ } catch (err) {
49
+ handleError(request, err);
50
+ }
51
+ },
52
+
53
+ options: {
54
+ description: 'List mailboxes',
55
+ notes: 'Lists all available mailboxes',
56
+ tags: ['api', 'Mailbox'],
57
+
58
+ auth: {
59
+ strategy: 'api-token',
60
+ mode: 'required'
61
+ },
62
+ cors: CORS_CONFIG,
63
+
64
+ validate: {
65
+ options: {
66
+ stripUnknown: false,
67
+ abortEarly: false,
68
+ convert: true
69
+ },
70
+ failAction,
71
+
72
+ params: Joi.object({
73
+ account: accountIdSchema.required()
74
+ }),
75
+
76
+ query: Joi.object({
77
+ counters: Joi.boolean()
78
+ .truthy('Y', 'true', '1')
79
+ .falsy('N', 'false', 0)
80
+ .default(false)
81
+ .description('If true, then includes message counters in the response')
82
+ .label('MailboxCounters')
83
+ }).label('MailboxListQuery')
84
+ },
85
+
86
+ response: {
87
+ schema: Joi.object({
88
+ mailboxes: mailboxesSchema
89
+ }).label('MailboxesFilterResponse'),
90
+ failAction: 'log'
91
+ }
92
+ }
93
+ });
94
+
95
+ server.route({
96
+ method: 'POST',
97
+ path: '/v1/account/{account}/mailbox',
98
+
99
+ async handler(request) {
100
+ let accountObject = new Account({
101
+ redis,
102
+ account: request.params.account,
103
+ call,
104
+ secret: await getSecret(),
105
+ timeout: request.headers['x-ee-timeout']
106
+ });
107
+
108
+ try {
109
+ return await accountObject.createMailbox(request.payload.path);
110
+ } catch (err) {
111
+ request.logger.error({ msg: 'API request failed', err });
112
+ if (Boom.isBoom(err)) {
113
+ throw err;
114
+ }
115
+ let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
116
+ if (err.code) {
117
+ error.output.payload.code = err.code;
118
+ }
119
+ if (err.info) {
120
+ error.output.payload.details = err.info;
121
+ }
122
+ throw error;
123
+ }
124
+ },
125
+
126
+ options: {
127
+ description: 'Create mailbox',
128
+ notes: 'Create new mailbox folder',
129
+ tags: ['api', 'Mailbox'],
130
+
131
+ plugins: {},
132
+
133
+ auth: {
134
+ strategy: 'api-token',
135
+ mode: 'required'
136
+ },
137
+ cors: CORS_CONFIG,
138
+
139
+ validate: {
140
+ options: {
141
+ stripUnknown: false,
142
+ abortEarly: false,
143
+ convert: true
144
+ },
145
+ failAction,
146
+
147
+ params: Joi.object({
148
+ account: accountIdSchema.required()
149
+ }),
150
+
151
+ payload: Joi.object({
152
+ path: Joi.array()
153
+ .items(Joi.string().max(256))
154
+ .single()
155
+ .example(['Parent folder', 'Subfolder'])
156
+ .description('Mailbox path as an array or a string. If account is namespaced then namespace prefix is added by default.')
157
+ .label('MailboxPath')
158
+ }).label('CreateMailbox')
159
+ },
160
+
161
+ response: {
162
+ schema: Joi.object({
163
+ path: Joi.string().required().example('Kalender/S&APw-nnip&AOQ-evad').description('Full path to mailbox').label('MailboxPath'),
164
+ mailboxId: Joi.string().example('1439876283476').description('Mailbox ID (if server has support)').label('MailboxId'),
165
+ created: Joi.boolean().example(true).description('Was the mailbox created')
166
+ }).label('CreateMailboxResponse'),
167
+ failAction: 'log'
168
+ }
169
+ }
170
+ });
171
+
172
+ server.route({
173
+ method: 'PUT',
174
+ path: '/v1/account/{account}/mailbox',
175
+
176
+ async handler(request) {
177
+ let accountObject = new Account({
178
+ redis,
179
+ account: request.params.account,
180
+ call,
181
+ secret: await getSecret(),
182
+ timeout: request.headers['x-ee-timeout']
183
+ });
184
+
185
+ try {
186
+ return await accountObject.modifyMailbox(request.payload.path, request.payload.newPath, request.payload.subscribed);
187
+ } catch (err) {
188
+ request.logger.error({ msg: 'API request failed', err });
189
+ if (Boom.isBoom(err)) {
190
+ throw err;
191
+ }
192
+ let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
193
+ if (err.code) {
194
+ error.output.payload.code = err.code;
195
+ }
196
+ if (err.info) {
197
+ error.output.payload.details = err.info;
198
+ }
199
+ throw error;
200
+ }
201
+ },
202
+
203
+ options: {
204
+ description: 'Modify mailbox',
205
+ notes: 'Modify an existing mailbox folder (rename or change subscription status)',
206
+ tags: ['api', 'Mailbox'],
207
+
208
+ plugins: {},
209
+
210
+ auth: {
211
+ strategy: 'api-token',
212
+ mode: 'required'
213
+ },
214
+ cors: CORS_CONFIG,
215
+
216
+ validate: {
217
+ options: {
218
+ stripUnknown: false,
219
+ abortEarly: false,
220
+ convert: true
221
+ },
222
+ failAction,
223
+
224
+ params: Joi.object({
225
+ account: accountIdSchema.required()
226
+ }),
227
+
228
+ payload: Joi.object({
229
+ path: Joi.string().required().example('Folder Name').description('Mailbox folder path to modify').label('ExistingMailboxPath'),
230
+ newPath: Joi.array()
231
+ .items(Joi.string().max(256))
232
+ .single()
233
+ .example(['Parent folder', 'Subfolder'])
234
+ .description('New mailbox path as an array or a string. If account is namespaced then namespace prefix is added by default. Optional.')
235
+ .label('TargetMailboxPath'),
236
+ subscribed: Joi.boolean()
237
+ .example(true)
238
+ .description('Change mailbox subscription status. Only applies to IMAP accounts, ignored for Gmail and Outlook.')
239
+ .label('SubscriptionStatus')
240
+ })
241
+ .or('newPath', 'subscribed')
242
+ .label('ModifyMailbox')
243
+ },
244
+
245
+ response: {
246
+ schema: Joi.object({
247
+ path: Joi.string().required().example('Mail').description('Mailbox folder path').label('ExistingMailboxPath'),
248
+ newPath: Joi.string().example('Kalender/S&APw-nnip&AOQ-evad').description('Full path to mailbox if renamed').label('NewMailboxPath'),
249
+ renamed: Joi.boolean().example(true).description('Was the mailbox renamed'),
250
+ subscribed: Joi.boolean().example(true).description('Subscription status after modification')
251
+ }).label('ModifyMailboxResponse'),
252
+ failAction: 'log'
253
+ }
254
+ }
255
+ });
256
+
257
+ server.route({
258
+ method: 'DELETE',
259
+ path: '/v1/account/{account}/mailbox',
260
+
261
+ async handler(request) {
262
+ let accountObject = new Account({
263
+ redis,
264
+ account: request.params.account,
265
+ call,
266
+ secret: await getSecret(),
267
+ timeout: request.headers['x-ee-timeout']
268
+ });
269
+
270
+ try {
271
+ return await accountObject.deleteMailbox(request.query.path);
272
+ } catch (err) {
273
+ handleError(request, err);
274
+ }
275
+ },
276
+
277
+ options: {
278
+ description: 'Delete mailbox',
279
+ notes: 'Delete existing mailbox folder',
280
+ tags: ['api', 'Mailbox'],
281
+
282
+ plugins: {},
283
+
284
+ auth: {
285
+ strategy: 'api-token',
286
+ mode: 'required'
287
+ },
288
+ cors: CORS_CONFIG,
289
+
290
+ validate: {
291
+ options: {
292
+ stripUnknown: false,
293
+ abortEarly: false,
294
+ convert: true
295
+ },
296
+ failAction,
297
+
298
+ params: Joi.object({
299
+ account: accountIdSchema.required()
300
+ }),
301
+
302
+ query: Joi.object({
303
+ path: Joi.string().required().example('My Outdated Mail').description('Mailbox folder path to delete').label('MailboxPath')
304
+ }).label('DeleteMailbox')
305
+ },
306
+
307
+ response: {
308
+ schema: Joi.object({
309
+ path: Joi.string().required().example('Kalender/S&APw-nnip&AOQ-evad').description('Full path to mailbox').label('MailboxPath'),
310
+ deleted: Joi.boolean().example(true).description('Was the mailbox deleted')
311
+ }).label('DeleteMailboxResponse'),
312
+ failAction: 'log'
313
+ }
314
+ }
315
+ });
316
+ }
317
+
318
+ module.exports = init;
@@ -4,9 +4,9 @@ const { redis } = require('../db');
4
4
  const { Account } = require('../account');
5
5
  const getSecret = require('../get-secret');
6
6
  const settings = require('../settings');
7
- const Boom = require('@hapi/boom');
8
7
  const Joi = require('joi');
9
8
  const { failAction } = require('../tools');
9
+ const { handleError } = require('./route-helpers');
10
10
 
11
11
  const {
12
12
  accountIdSchema,
@@ -44,15 +44,7 @@ async function init(args) {
44
44
  const response = await accountObject.getRawMessage(request.params.message);
45
45
  return h.response(response);
46
46
  } catch (err) {
47
- request.logger.error({ msg: 'API request failed', err });
48
- if (Boom.isBoom(err)) {
49
- throw err;
50
- }
51
- let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
52
- if (err.code) {
53
- error.output.payload.code = err.code;
54
- }
55
- throw error;
47
+ handleError(request, err);
56
48
  }
57
49
  },
58
50
  options: {
@@ -106,15 +98,7 @@ async function init(args) {
106
98
  try {
107
99
  return await accountObject.getMessage(request.params.message, request.query);
108
100
  } catch (err) {
109
- request.logger.error({ msg: 'API request failed', err });
110
- if (Boom.isBoom(err)) {
111
- throw err;
112
- }
113
- let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
114
- if (err.code) {
115
- error.output.payload.code = err.code;
116
- }
117
- throw error;
101
+ handleError(request, err);
118
102
  }
119
103
  },
120
104
  options: {
@@ -213,15 +197,7 @@ async function init(args) {
213
197
  try {
214
198
  return await accountObject.uploadMessage(request.payload);
215
199
  } catch (err) {
216
- request.logger.error({ msg: 'API request failed', err });
217
- if (Boom.isBoom(err)) {
218
- throw err;
219
- }
220
- let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
221
- if (err.code) {
222
- error.output.payload.code = err.code;
223
- }
224
- throw error;
200
+ handleError(request, err);
225
201
  }
226
202
  },
227
203
  options: {
@@ -390,15 +366,7 @@ async function init(args) {
390
366
  try {
391
367
  return await accountObject.updateMessage(request.params.message, request.payload);
392
368
  } catch (err) {
393
- request.logger.error({ msg: 'API request failed', err });
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;
369
+ handleError(request, err);
402
370
  }
403
371
  },
404
372
  options: {
@@ -464,15 +432,7 @@ async function init(args) {
464
432
  try {
465
433
  return await accountObject.updateMessages(request.query.path, request.payload.search, request.payload.update);
466
434
  } catch (err) {
467
- request.logger.error({ msg: 'API request failed', err });
468
- if (Boom.isBoom(err)) {
469
- throw err;
470
- }
471
- let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
472
- if (err.code) {
473
- error.output.payload.code = err.code;
474
- }
475
- throw error;
435
+ handleError(request, err);
476
436
  }
477
437
  },
478
438
  options: {
@@ -548,15 +508,7 @@ async function init(args) {
548
508
  }
549
509
  return await accountObject.moveMessage(request.params.message, { path: request.payload.path }, { source: sourceOption });
550
510
  } catch (err) {
551
- request.logger.error({ msg: 'API request failed', err });
552
- if (Boom.isBoom(err)) {
553
- throw err;
554
- }
555
- let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
556
- if (err.code) {
557
- error.output.payload.code = err.code;
558
- }
559
- throw error;
511
+ handleError(request, err);
560
512
  }
561
513
  },
562
514
  options: {
@@ -626,15 +578,7 @@ async function init(args) {
626
578
  try {
627
579
  return await accountObject.moveMessages(request.query.path, request.payload.search, { path: request.payload.path });
628
580
  } catch (err) {
629
- request.logger.error({ msg: 'API request failed', err });
630
- if (Boom.isBoom(err)) {
631
- throw err;
632
- }
633
- let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
634
- if (err.code) {
635
- error.output.payload.code = err.code;
636
- }
637
- throw error;
581
+ handleError(request, err);
638
582
  }
639
583
  },
640
584
  options: {
@@ -709,15 +653,7 @@ async function init(args) {
709
653
  try {
710
654
  return await accountObject.deleteMessage(request.params.message, request.query.force);
711
655
  } catch (err) {
712
- request.logger.error({ msg: 'API request failed', err });
713
- if (Boom.isBoom(err)) {
714
- throw err;
715
- }
716
- let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
717
- if (err.code) {
718
- error.output.payload.code = err.code;
719
- }
720
- throw error;
656
+ handleError(request, err);
721
657
  }
722
658
  },
723
659
  options: {
@@ -787,15 +723,7 @@ async function init(args) {
787
723
  try {
788
724
  return await accountObject.deleteMessages(request.query.path, request.payload.search, request.query.force);
789
725
  } catch (err) {
790
- request.logger.error({ msg: 'API request failed', err });
791
- if (Boom.isBoom(err)) {
792
- throw err;
793
- }
794
- let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
795
- if (err.code) {
796
- error.output.payload.code = err.code;
797
- }
798
- throw error;
726
+ handleError(request, err);
799
727
  }
800
728
  },
801
729
  options: {
@@ -881,15 +809,7 @@ async function init(args) {
881
809
  try {
882
810
  return await accountObject.listMessages(request.query);
883
811
  } catch (err) {
884
- request.logger.error({ msg: 'API request failed', err });
885
- if (Boom.isBoom(err)) {
886
- throw err;
887
- }
888
- let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
889
- if (err.code) {
890
- error.output.payload.code = err.code;
891
- }
892
- throw error;
812
+ handleError(request, err);
893
813
  }
894
814
  },
895
815
  options: {
@@ -988,15 +908,7 @@ async function init(args) {
988
908
  try {
989
909
  return await accountObject.searchMessages(Object.assign(request.query, request.payload));
990
910
  } catch (err) {
991
- request.logger.error({ msg: 'API request failed', err });
992
- if (Boom.isBoom(err)) {
993
- throw err;
994
- }
995
- let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
996
- if (err.code) {
997
- error.output.payload.code = err.code;
998
- }
999
- throw error;
911
+ handleError(request, err);
1000
912
  }
1001
913
  },
1002
914
  options: {
@@ -1059,7 +971,7 @@ async function init(args) {
1059
971
  .truthy('Y', 'true', '1')
1060
972
  .falsy('N', 'false', 0)
1061
973
  .description(
1062
- 'MS Graph only. If enabled, uses the $search parameter for MS Graph search queries instead of $filter. This allows searching the "to", "cc", "bcc", "larger", "smaller", "body", "before", "sentBefore", "since", and the "sentSince" fields. Note that $search returns up to 1,000 results, does not indicate the total number of matching results or pages, and returns results sorted by relevance rather than date.'
974
+ 'MS Graph only. If enabled, uses the $search parameter for MS Graph search queries instead of $filter. This allows searching the "to", "cc", "bcc", "larger", "smaller", "body", "before", "sentBefore", "since", and the "sentSince" fields. Note that $search returns up to 1,000 results, does not indicate the total number of matching results or pages, and returns results sorted by relevance rather than date. The "labels" filter is not available in this mode - leave this option disabled to filter by category.'
1063
975
  )
1064
976
  .label('useOutlookSearch')
1065
977
  .optional(),
@@ -1102,7 +1014,11 @@ async function init(args) {
1102
1014
  header: {
1103
1015
  'Message-ID': '<12345@example.com>'
1104
1016
  },
1105
- gmailRaw: 'has:attachment in:unread'
1017
+ gmailRaw: 'has:attachment in:unread',
1018
+ labels: {
1019
+ has: ['Important'],
1020
+ not: ['Horizon']
1021
+ }
1106
1022
  }
1107
1023
  })
1108
1024
  },
@@ -1152,15 +1068,7 @@ async function init(args) {
1152
1068
  try {
1153
1069
  return await accountObject.searchMessages(Object.assign({ documentStore: true }, request.query, request.payload), { unified: true });
1154
1070
  } catch (err) {
1155
- request.logger.error({ msg: 'API request failed', err });
1156
- if (Boom.isBoom(err)) {
1157
- throw err;
1158
- }
1159
- let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
1160
- if (err.code) {
1161
- error.output.payload.code = err.code;
1162
- }
1163
- throw error;
1071
+ handleError(request, err);
1164
1072
  }
1165
1073
  },
1166
1074
  options: {
@@ -1243,15 +1151,7 @@ async function init(args) {
1243
1151
  try {
1244
1152
  return await accountObject.getText(request.params.text, request.query);
1245
1153
  } catch (err) {
1246
- request.logger.error({ msg: 'API request failed', err });
1247
- if (Boom.isBoom(err)) {
1248
- throw err;
1249
- }
1250
- let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
1251
- if (err.code) {
1252
- error.output.payload.code = err.code;
1253
- }
1254
- throw error;
1154
+ handleError(request, err);
1255
1155
  }
1256
1156
  },
1257
1157
  options: {
@@ -1329,15 +1229,7 @@ async function init(args) {
1329
1229
  try {
1330
1230
  return await accountObject.getAttachment(request.params.attachment);
1331
1231
  } catch (err) {
1332
- request.logger.error({ msg: 'API request failed', err });
1333
- if (Boom.isBoom(err)) {
1334
- throw err;
1335
- }
1336
- let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
1337
- if (err.code) {
1338
- error.output.payload.code = err.code;
1339
- }
1340
- throw error;
1232
+ handleError(request, err);
1341
1233
  }
1342
1234
  },
1343
1235
  options: {