emailengine-app 2.68.0 → 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 (74) hide show
  1. package/.github/codeql/codeql-config.yml +16 -0
  2. package/.github/workflows/codeql.yml +102 -0
  3. package/.github/workflows/deploy.yml +8 -0
  4. package/.github/workflows/release.yaml +4 -0
  5. package/.github/workflows/test.yml +3 -0
  6. package/CHANGELOG.md +49 -0
  7. package/SECURITY.md +80 -0
  8. package/SECURITY.txt +27 -0
  9. package/config/default.toml +2 -0
  10. package/data/google-crawlers.json +13 -1
  11. package/lib/account.js +62 -25
  12. package/lib/api-routes/account-routes.js +493 -75
  13. package/lib/api-routes/blocklist-routes.js +337 -0
  14. package/lib/api-routes/delivery-test-routes.js +321 -0
  15. package/lib/api-routes/export-routes.js +1 -12
  16. package/lib/api-routes/gateway-routes.js +376 -0
  17. package/lib/api-routes/license-routes.js +142 -0
  18. package/lib/api-routes/mailbox-routes.js +318 -0
  19. package/lib/api-routes/message-routes.js +21 -129
  20. package/lib/api-routes/oauth2-app-routes.js +631 -0
  21. package/lib/api-routes/outbox-routes.js +173 -0
  22. package/lib/api-routes/pubsub-routes.js +98 -0
  23. package/lib/api-routes/route-helpers.js +45 -0
  24. package/lib/api-routes/settings-routes.js +331 -0
  25. package/lib/api-routes/stats-routes.js +77 -0
  26. package/lib/api-routes/submit-routes.js +472 -0
  27. package/lib/api-routes/template-routes.js +7 -55
  28. package/lib/api-routes/token-routes.js +297 -0
  29. package/lib/api-routes/webhook-route-routes.js +152 -0
  30. package/lib/email-client/gmail-client.js +14 -0
  31. package/lib/email-client/imap/mailbox.js +34 -11
  32. package/lib/email-client/imap/subconnection.js +20 -12
  33. package/lib/email-client/imap/sync-operations.js +130 -2
  34. package/lib/email-client/imap-client.js +116 -58
  35. package/lib/email-client/outlook-client.js +85 -13
  36. package/lib/export.js +60 -19
  37. package/lib/imapproxy/imap-core/lib/commands/starttls.js +18 -0
  38. package/lib/imapproxy/imap-core/lib/imap-command.js +7 -2
  39. package/lib/imapproxy/imap-core/lib/imap-connection.js +113 -23
  40. package/lib/imapproxy/imap-core/lib/imap-server.js +25 -1
  41. package/lib/imapproxy/imap-core/lib/imap-stream.js +26 -0
  42. package/lib/imapproxy/imap-server.js +92 -29
  43. package/lib/message-port-stream.js +113 -16
  44. package/lib/reject-worker-calls.js +42 -0
  45. package/lib/routes-ui.js +37 -8778
  46. package/lib/schemas.js +26 -1
  47. package/lib/tools.js +73 -0
  48. package/lib/ui-routes/account-routes.js +40 -210
  49. package/lib/ui-routes/admin-config-routes.js +913 -487
  50. package/lib/ui-routes/admin-entities-routes.js +1 -0
  51. package/lib/ui-routes/auth-routes.js +1339 -0
  52. package/lib/ui-routes/dashboard-routes.js +188 -0
  53. package/lib/ui-routes/document-store-routes.js +800 -0
  54. package/lib/ui-routes/export-routes.js +217 -0
  55. package/lib/ui-routes/internals-routes.js +354 -0
  56. package/lib/ui-routes/network-config-routes.js +759 -0
  57. package/lib/ui-routes/{oauth-routes.js → oauth-config-routes.js} +371 -91
  58. package/lib/ui-routes/route-helpers.js +316 -0
  59. package/lib/ui-routes/smtp-test-routes.js +236 -0
  60. package/lib/ui-routes/unsubscribe-routes.js +234 -0
  61. package/lib/webhook-request.js +36 -0
  62. package/package.json +17 -17
  63. package/sbom.json +1 -1
  64. package/server.js +217 -19
  65. package/static/licenses.html +52 -182
  66. package/translations/messages.pot +131 -151
  67. package/views/dashboard.hbs +7 -26
  68. package/views/internals/index.hbs +15 -0
  69. package/views/tokens/index.hbs +9 -0
  70. package/workers/api.js +198 -4401
  71. package/workers/export.js +87 -54
  72. package/workers/imap.js +29 -13
  73. package/workers/submit.js +20 -11
  74. package/workers/webhooks.js +6 -20
@@ -1,26 +1,29 @@
1
1
  'use strict';
2
2
 
3
- const Boom = require('@hapi/boom');
3
+ // NB! This file is processed by the gettext parser (npm run gettext) and can not use newer syntax like ?.
4
+
5
+ // Admin UI routes for OAuth2 application config (/admin/config/oauth*): listing apps,
6
+ // per-app view/edit/delete, creating apps, adding accounts, provider subscriptions, and
7
+ // app verification. Extracted verbatim from lib/routes-ui.js. AZURE_CLOUDS and the
8
+ // authMethodContext/getPubSubAppsForSelect render helpers move with the routes (only the
9
+ // OAuth app config pages use them).
10
+
4
11
  const Joi = require('joi');
12
+ const util = require('util');
13
+ const Boom = require('@hapi/boom');
5
14
 
6
15
  const settings = require('../settings');
16
+ const consts = require('../consts');
7
17
  const { redis } = require('../db');
8
- const { oauth2Apps, OAUTH_PROVIDERS, SERVICE_ACCOUNT_PROVIDERS, oauth2ProviderData } = require('../oauth2-apps');
9
18
  const getSecret = require('../get-secret');
10
19
  const { Account } = require('../account');
11
- const { oauthCreateSchema, oauthUpdateSchema } = require('../schemas');
12
- const consts = require('../consts');
20
+ const { oauth2Apps, OAUTH_PROVIDERS, oauth2ProviderData, SERVICE_ACCOUNT_PROVIDERS } = require('../oauth2-apps');
21
+ const { verifyOAuth2App } = require('../oauth/verify-app');
22
+ const { oauthCreateSchema, oauthUpdateSchema, accountIdSchema } = require('../schemas');
23
+ const { throwAsBoom } = require('./route-helpers');
13
24
 
14
25
  const { DEFAULT_PAGE_SIZE } = consts;
15
26
 
16
- // Microsoft Entra requires 'localhost' rather than '127.0.0.1' in redirect URIs
17
- function normalizeOutlookRedirectUrl(redirectUrl, provider) {
18
- if (provider === 'outlook') {
19
- return redirectUrl.replace(/^http:\/\/127\.0\.0\.1\b/i, 'http://localhost');
20
- }
21
- return redirectUrl;
22
- }
23
-
24
27
  const AZURE_CLOUDS = [
25
28
  {
26
29
  id: 'global',
@@ -47,8 +50,35 @@ const AZURE_CLOUDS = [
47
50
  }
48
51
  ];
49
52
 
50
- function init({ server, call }) {
51
- // GET /admin/config/oauth - OAuth applications list
53
+ function init(args) {
54
+ const { server, call } = args;
55
+
56
+ // Render-context booleans for the gmailService authMethod tab selector.
57
+ // When `locked` is set the selector is shown but cannot be switched - the
58
+ // authentication method is fixed once an app has been created.
59
+ function authMethodContext(authMethod, locked) {
60
+ return {
61
+ authMethodIsServiceKey: !authMethod || authMethod === 'serviceKey',
62
+ authMethodIsExternalAccount: authMethod === 'externalAccount',
63
+ authMethodLocked: !!locked
64
+ };
65
+ }
66
+
67
+ /**
68
+ * Fetch the list of Pub/Sub apps and mark the one matching selectedId as selected.
69
+ * Returns the apps array ready for template rendering.
70
+ */
71
+ async function getPubSubAppsForSelect(selectedId) {
72
+ let result = await oauth2Apps.list(0, 1000, { pubsub: true });
73
+ let apps = (result && result.apps) || [];
74
+ for (let app of apps) {
75
+ if (app.id === selectedId) {
76
+ app.selected = true;
77
+ }
78
+ }
79
+ return apps;
80
+ }
81
+
52
82
  server.route({
53
83
  method: 'GET',
54
84
  path: '/admin/config/oauth',
@@ -108,11 +138,12 @@ function init({ server, call }) {
108
138
  pageTitle: 'OAuth2',
109
139
  menuConfig: true,
110
140
  menuConfigOauth: true,
141
+ activeApplications: true,
111
142
 
112
143
  newLink: newLink.pathname + newLink.search,
113
144
 
114
145
  searchTarget: '/admin/config/oauth',
115
- searchPlaceholder: 'Search for OAuth2 applications...',
146
+ searchPlaceholder: 'Search for OAuth2 applications',
116
147
  query: request.query.query,
117
148
 
118
149
  showPaging: data.pages > 1,
@@ -154,7 +185,160 @@ function init({ server, call }) {
154
185
  }
155
186
  });
156
187
 
157
- // GET /admin/config/oauth/app/{app} - View OAuth application details
188
+ // GET /admin/config/oauth/subscriptions - Gmail Pub/Sub subscriptions list
189
+ server.route({
190
+ method: 'GET',
191
+ path: '/admin/config/oauth/subscriptions',
192
+ async handler(request, h) {
193
+ try {
194
+ let data = await oauth2Apps.list(request.query.page - 1, request.query.pageSize, { pubsub: true });
195
+
196
+ let gmailSubscriptionTtl = await settings.get('gmailSubscriptionTtl');
197
+
198
+ // Compute human-readable expiration for each app
199
+ // meta.subscriptionExpiration is:
200
+ // undefined - no data yet (app predates this feature or ensurePubsub hasn't run)
201
+ // null - indefinite (no TTL set, ensurePubsub confirmed this)
202
+ // "Ns" - TTL in seconds (e.g. "2678400s" for 31 days)
203
+ let gt = request.app.gt;
204
+ for (let app of data.apps) {
205
+ if (!app.pubSubSubscription) {
206
+ app.expirationLabel = '';
207
+ continue;
208
+ }
209
+
210
+ let meta = app.meta || {};
211
+ if (!('subscriptionExpiration' in meta)) {
212
+ app.expirationLabel = gt.gettext('Unknown');
213
+ continue;
214
+ }
215
+
216
+ let seconds = parseInt(meta.subscriptionExpiration, 10);
217
+ if (seconds > 0) {
218
+ let days = Math.round(seconds / 86400);
219
+ app.expirationLabel = util.format(gt.ngettext('%d day', '%d days', days), days);
220
+ } else {
221
+ app.expirationLabel = gt.gettext('Indefinite');
222
+ }
223
+ }
224
+
225
+ let nextPage = false;
226
+ let prevPage = false;
227
+
228
+ let getPagingUrl = page => {
229
+ let url = new URL(`admin/config/oauth/subscriptions`, 'http://localhost');
230
+
231
+ if (page) {
232
+ url.searchParams.append('page', page);
233
+ }
234
+
235
+ if (request.query.pageSize !== DEFAULT_PAGE_SIZE) {
236
+ url.searchParams.append('pageSize', request.query.pageSize);
237
+ }
238
+
239
+ return url.pathname + url.search;
240
+ };
241
+
242
+ if (data.pages > data.page + 1) {
243
+ nextPage = getPagingUrl(data.page + 2);
244
+ }
245
+
246
+ if (data.page > 0) {
247
+ prevPage = getPagingUrl(data.page);
248
+ }
249
+
250
+ return h.view(
251
+ 'config/oauth/subscriptions',
252
+ {
253
+ pageTitle: 'OAuth2',
254
+ menuConfig: true,
255
+ menuConfigOauth: true,
256
+ activeSubscriptions: true,
257
+
258
+ showPaging: data.pages > 1,
259
+ nextPage,
260
+ prevPage,
261
+ firstPage: data.page === 0,
262
+ pageLinks: new Array(data.pages || 1).fill(0).map((z, i) => ({
263
+ url: getPagingUrl(i + 1),
264
+ title: i + 1,
265
+ active: i === data.page
266
+ })),
267
+
268
+ apps: data.apps,
269
+
270
+ values: {
271
+ gmailSubscriptionTtl: typeof gmailSubscriptionTtl === 'number' ? gmailSubscriptionTtl : ''
272
+ }
273
+ },
274
+ {
275
+ layout: 'app'
276
+ }
277
+ );
278
+ } catch (err) {
279
+ request.logger.error({ msg: 'Failed to load subscriptions page', err });
280
+ throwAsBoom(err);
281
+ }
282
+ },
283
+
284
+ options: {
285
+ validate: {
286
+ options: {
287
+ stripUnknown: true,
288
+ abortEarly: false,
289
+ convert: true
290
+ },
291
+
292
+ async failAction(request, h /*, err*/) {
293
+ return h.redirect('/admin/config/oauth/subscriptions').takeover();
294
+ },
295
+
296
+ query: Joi.object({
297
+ page: Joi.number().integer().min(1).max(1000000).default(1),
298
+ pageSize: Joi.number().integer().min(1).max(250).default(DEFAULT_PAGE_SIZE)
299
+ })
300
+ }
301
+ }
302
+ });
303
+
304
+ server.route({
305
+ method: 'POST',
306
+ path: '/admin/config/oauth/subscriptions',
307
+ async handler(request, h) {
308
+ try {
309
+ // Joi .empty('').allow(null) ensures this is either null or a number
310
+ let ttl = request.payload.gmailSubscriptionTtl != null ? request.payload.gmailSubscriptionTtl : null;
311
+ await settings.set('gmailSubscriptionTtl', ttl);
312
+
313
+ await request.flash({ type: 'info', message: 'Configuration updated' });
314
+ return h.redirect('/admin/config/oauth/subscriptions');
315
+ } catch (err) {
316
+ await request.flash({ type: 'danger', message: 'Failed to save settings' });
317
+ request.logger.error({ msg: 'Failed to save subscription settings', err });
318
+ return h.redirect('/admin/config/oauth/subscriptions');
319
+ }
320
+ },
321
+ options: {
322
+ validate: {
323
+ options: {
324
+ stripUnknown: true,
325
+ abortEarly: false,
326
+ convert: true
327
+ },
328
+
329
+ async failAction(request, h /*, err*/) {
330
+ await request.flash({ type: 'danger', message: 'Invalid setting value' });
331
+ return h.redirect('/admin/config/oauth/subscriptions').takeover();
332
+ },
333
+
334
+ payload: Joi.object({
335
+ gmailSubscriptionTtl: Joi.number().integer().empty('').allow(null).min(0).max(365),
336
+ crumb: Joi.string().max(256)
337
+ })
338
+ }
339
+ }
340
+ });
341
+
158
342
  server.route({
159
343
  method: 'GET',
160
344
  path: '/admin/config/oauth/app/{app}',
@@ -213,8 +397,10 @@ function init({ server, call }) {
213
397
  app.cloudData = AZURE_CLOUDS.find(entry => entry.id === app.cloud);
214
398
  }
215
399
 
216
- let isServiceAccount = SERVICE_ACCOUNT_PROVIDERS.has(app.provider);
217
- let serviceUrl = (await settings.get('serviceUrl')) || '';
400
+ // Service account apps scoped for email access (IMAP/SMTP or Gmail/Graph API) can register
401
+ // accounts directly without an interactive consent flow. Pub/Sub-scoped service apps are for
402
+ // webhook notifications only, so they must not offer the direct add-account shortcut.
403
+ let canAddServiceAccount = SERVICE_ACCOUNT_PROVIDERS.has(app.provider) && app.enabled && app.baseScopes !== 'pubsub';
218
404
 
219
405
  return h.view(
220
406
  'config/oauth/app',
@@ -231,10 +417,13 @@ function init({ server, call }) {
231
417
  baseScopesImap: app.baseScopes === 'imap' || !app.baseScopes,
232
418
  baseScopesPubsub: app.baseScopes === 'pubsub',
233
419
 
420
+ appShowAuthMethod: app.provider === 'gmailService',
421
+ authMethodIsExternalAccount: app.authMethod === 'externalAccount',
422
+
423
+ canAddServiceAccount,
424
+
234
425
  disabledScopes,
235
426
  isSendOnlyGmail,
236
- isServiceAccount,
237
- serviceUrl,
238
427
 
239
428
  providerData
240
429
  },
@@ -263,7 +452,6 @@ function init({ server, call }) {
263
452
  }
264
453
  });
265
454
 
266
- // POST /admin/config/oauth/delete - Delete OAuth application
267
455
  server.route({
268
456
  method: 'POST',
269
457
  path: '/admin/config/oauth/delete',
@@ -308,7 +496,119 @@ function init({ server, call }) {
308
496
  }
309
497
  });
310
498
 
311
- // GET /admin/config/oauth/new - New OAuth application form
499
+ server.route({
500
+ method: 'POST',
501
+ path: '/admin/config/oauth/app/{app}/add-account',
502
+ async handler(request, h) {
503
+ const appId = request.params.app;
504
+ try {
505
+ const app = await oauth2Apps.get(appId);
506
+ if (!app) {
507
+ let error = Boom.boomify(new Error('Application was not found.'), { statusCode: 404 });
508
+ throw error;
509
+ }
510
+
511
+ // Direct account registration is only valid for email-scoped service account apps. Interactive
512
+ // providers must use the hosted consent flow, and Pub/Sub-scoped apps grant no mailbox access.
513
+ if (!SERVICE_ACCOUNT_PROVIDERS.has(app.provider) || !app.enabled || app.baseScopes === 'pubsub') {
514
+ let error = Boom.boomify(new Error('This application can not register accounts directly.'), { statusCode: 400 });
515
+ throw error;
516
+ }
517
+
518
+ let accountData = {
519
+ account: request.payload.account || null,
520
+ email: request.payload.email,
521
+ oauth2: {
522
+ provider: app.id,
523
+ auth: {
524
+ user: request.payload.email
525
+ }
526
+ }
527
+ };
528
+
529
+ if (request.payload.name) {
530
+ accountData.name = request.payload.name;
531
+ }
532
+
533
+ const accountObject = new Account({ redis, call, secret: await getSecret() });
534
+ const result = await accountObject.create(accountData);
535
+
536
+ await request.flash({ type: 'info', message: `Account ${result.state === 'existing' ? 'updated' : 'added'}` });
537
+
538
+ return h.redirect(`/admin/accounts/${result.account}`);
539
+ } catch (err) {
540
+ request.logger.error({ msg: 'Failed to register service account', err, app: appId, remoteAddress: request.app.ip });
541
+ await request.flash({ type: 'danger', message: `Failed to add account${err.message ? `: ${err.message}` : ''}` });
542
+ return h.redirect(`/admin/config/oauth/app/${appId}`);
543
+ }
544
+ },
545
+ options: {
546
+ validate: {
547
+ options: {
548
+ stripUnknown: true,
549
+ abortEarly: false,
550
+ convert: true
551
+ },
552
+
553
+ async failAction(request, h, err) {
554
+ request.logger.error({ msg: 'Failed to register service account', err, app: request.params.app });
555
+ await request.flash({ type: 'danger', message: `Failed to add account. Provide a valid email address.` });
556
+ return h.redirect(`/admin/config/oauth/app/${request.params.app}`).takeover();
557
+ },
558
+
559
+ params: Joi.object({
560
+ app: Joi.string().empty('').max(255).example('gmail').label('Provider').required()
561
+ }),
562
+
563
+ payload: Joi.object({
564
+ account: accountIdSchema.default(null),
565
+ name: Joi.string().empty('').max(256).example('John Smith').description('Account Name'),
566
+ email: Joi.string().email().required().example('user@example.com').label('Email').description('Mailbox email address')
567
+ })
568
+ }
569
+ }
570
+ });
571
+
572
+ server.route({
573
+ method: 'POST',
574
+ path: '/admin/config/oauth/verify/{app}',
575
+ async handler(request, h) {
576
+ try {
577
+ return await verifyOAuth2App(request.params.app, {
578
+ account: request.payload.account,
579
+ testConnection: request.payload.testConnection
580
+ });
581
+ } catch (err) {
582
+ request.logger.error({ msg: 'Failed to verify OAuth2 application', err, app: request.params.app });
583
+ return h.response({ error: err.message, code: err.code || null }).code(err.statusCode || 500);
584
+ }
585
+ },
586
+ options: {
587
+ validate: {
588
+ options: {
589
+ stripUnknown: true,
590
+ abortEarly: false,
591
+ convert: true
592
+ },
593
+
594
+ async failAction(request, h, err) {
595
+ request.logger.error({ msg: 'Invalid verify request', err, app: request.params.app });
596
+ return h.response({ error: 'Invalid request' }).code(400).takeover();
597
+ },
598
+
599
+ params: Joi.object({
600
+ app: Joi.string().empty('').max(255).required().label('Provider')
601
+ }),
602
+
603
+ payload: Joi.object({
604
+ crumb: Joi.string().optional(),
605
+ account: Joi.string().trim().empty('').max(256).optional(),
606
+ testConnection: Joi.boolean().truthy('Y', 'true', '1', 'on').falsy('N', 'false', 0, '').default(true)
607
+ })
608
+ }
609
+ }
610
+ });
611
+
312
612
  server.route({
313
613
  method: 'GET',
314
614
  path: '/admin/config/oauth/new',
@@ -317,9 +617,10 @@ function init({ server, call }) {
317
617
  let providerData = oauth2ProviderData(provider);
318
618
 
319
619
  let serviceUrl = await settings.get('serviceUrl');
320
- let defaultRedirectUrl = normalizeOutlookRedirectUrl(`${serviceUrl}/oauth`, provider);
321
-
322
- let pubSubApps = await oauth2Apps.list(0, 1000, { pubsub: true });
620
+ let defaultRedirectUrl = `${serviceUrl}/oauth`;
621
+ if (provider === 'outlook') {
622
+ defaultRedirectUrl = defaultRedirectUrl.replace(/^http:\/\/127\.0\.0\.1\b/i, 'http://localhost');
623
+ }
323
624
 
324
625
  return h.view(
325
626
  'config/oauth/new',
@@ -338,7 +639,9 @@ function init({ server, call }) {
338
639
  baseScopesApi: false,
339
640
  baseScopesPubsub: false,
340
641
 
341
- pubSubApps: pubSubApps && pubSubApps.apps,
642
+ ...authMethodContext('serviceKey'),
643
+
644
+ pubSubApps: await getPubSubAppsForSelect(null),
342
645
 
343
646
  azureClouds: structuredClone(AZURE_CLOUDS).map(entry => {
344
647
  if (entry.id === 'global') {
@@ -349,10 +652,11 @@ function init({ server, call }) {
349
652
 
350
653
  values: {
351
654
  provider,
352
- ...(provider !== 'outlookService' ? { redirectUrl: defaultRedirectUrl } : {})
655
+ redirectUrl: defaultRedirectUrl,
656
+ authMethod: 'serviceKey'
353
657
  },
354
658
 
355
- authorityCommon: provider !== 'outlookService'
659
+ authorityCommon: true
356
660
  },
357
661
  {
358
662
  layout: 'app'
@@ -383,7 +687,6 @@ function init({ server, call }) {
383
687
  }
384
688
  });
385
689
 
386
- // POST /admin/config/oauth/new - Create OAuth application
387
690
  server.route({
388
691
  method: 'POST',
389
692
  path: '/admin/config/oauth/new',
@@ -428,9 +731,10 @@ function init({ server, call }) {
428
731
  let providerData = oauth2ProviderData(provider);
429
732
 
430
733
  let serviceUrl = await settings.get('serviceUrl');
431
- let defaultRedirectUrl = normalizeOutlookRedirectUrl(`${serviceUrl}/oauth`, provider);
432
-
433
- let pubSubApps = await oauth2Apps.list(0, 1000, { pubsub: true });
734
+ let defaultRedirectUrl = `${serviceUrl}/oauth`;
735
+ if (provider === 'outlook') {
736
+ defaultRedirectUrl = defaultRedirectUrl.replace(/^http:\/\/127\.0\.0\.1\b/i, 'http://localhost');
737
+ }
434
738
 
435
739
  return h.view(
436
740
  'config/oauth/new',
@@ -445,20 +749,14 @@ function init({ server, call }) {
445
749
  providerData,
446
750
  defaultRedirectUrl,
447
751
 
448
- pubSubApps:
449
- pubSubApps &&
450
- pubSubApps.apps &&
451
- pubSubApps.apps.map(app => {
452
- if (app.id === request.payload.pubSubApp) {
453
- app.selected = true;
454
- }
455
- return app;
456
- }),
752
+ pubSubApps: await getPubSubAppsForSelect(request.payload.pubSubApp),
457
753
 
458
754
  baseScopesApi: baseScopes === 'api',
459
755
  baseScopesImap: baseScopes === 'imap' || !baseScopes,
460
756
  baseScopesPubsub: baseScopes === 'pubsub',
461
757
 
758
+ ...authMethodContext(request.payload.authMethod),
759
+
462
760
  azureClouds: structuredClone(AZURE_CLOUDS).map(entry => {
463
761
  entry.selected = request.payload.cloud === entry.id;
464
762
  return entry;
@@ -505,9 +803,10 @@ function init({ server, call }) {
505
803
  let providerData = oauth2ProviderData(provider);
506
804
 
507
805
  let serviceUrl = await settings.get('serviceUrl');
508
- let defaultRedirectUrl = normalizeOutlookRedirectUrl(`${serviceUrl}/oauth`, provider);
509
-
510
- let pubSubApps = await oauth2Apps.list(0, 1000, { pubsub: true });
806
+ let defaultRedirectUrl = `${serviceUrl}/oauth`;
807
+ if (provider === 'outlook') {
808
+ defaultRedirectUrl = defaultRedirectUrl.replace(/^http:\/\/127\.0\.0\.1\b/i, 'http://localhost');
809
+ }
511
810
 
512
811
  return h
513
812
  .view(
@@ -523,20 +822,14 @@ function init({ server, call }) {
523
822
  providerData,
524
823
  defaultRedirectUrl,
525
824
 
526
- pubSubApps:
527
- pubSubApps &&
528
- pubSubApps.apps &&
529
- pubSubApps.apps.map(app => {
530
- if (app.id === request.payload.pubSubApp) {
531
- app.selected = true;
532
- }
533
- return app;
534
- }),
825
+ pubSubApps: await getPubSubAppsForSelect(request.payload.pubSubApp),
535
826
 
536
827
  baseScopesApi: baseScopes === 'api',
537
828
  baseScopesImap: baseScopes === 'imap' || !baseScopes,
538
829
  baseScopesPubsub: baseScopes === 'pubsub',
539
830
 
831
+ ...authMethodContext(request.payload.authMethod),
832
+
540
833
  azureClouds: structuredClone(AZURE_CLOUDS).map(entry => {
541
834
  entry.selected = request.payload.cloud === entry.id;
542
835
  return entry;
@@ -561,7 +854,6 @@ function init({ server, call }) {
561
854
  }
562
855
  });
563
856
 
564
- // GET /admin/config/oauth/edit/{app} - Edit OAuth application form
565
857
  server.route({
566
858
  method: 'GET',
567
859
  path: '/admin/config/oauth/edit/{app}',
@@ -574,19 +866,21 @@ function init({ server, call }) {
574
866
 
575
867
  let providerData = oauth2ProviderData(appData.provider, appData.cloud);
576
868
  let serviceUrl = await settings.get('serviceUrl');
577
- let defaultRedirectUrl = normalizeOutlookRedirectUrl(`${serviceUrl}/oauth`, providerData.provider);
869
+ let defaultRedirectUrl = `${serviceUrl}/oauth`;
870
+ if (providerData.provider === 'outlook') {
871
+ defaultRedirectUrl = defaultRedirectUrl.replace(/^http:\/\/127\.0\.0\.1\b/i, 'http://localhost');
872
+ }
578
873
 
579
874
  let values = Object.assign({}, appData, {
580
875
  clientSecret: '',
581
876
  serviceKey: '',
877
+ externalAccount: '',
582
878
  extraScopes: [].concat(appData.extraScopes || []).join('\n'),
583
879
  skipScopes: [].concat(appData.skipScopes || []).join('\n'),
584
880
 
585
881
  tenant: appData.authority && !['common', 'organizations', 'consumers'].includes(appData.authority) ? appData.authority : ''
586
882
  });
587
883
 
588
- let pubSubApps = await oauth2Apps.list(0, 1000, { pubsub: true });
589
-
590
884
  return h.view(
591
885
  'config/oauth/edit',
592
886
  {
@@ -602,16 +896,11 @@ function init({ server, call }) {
602
896
 
603
897
  hasClientSecret: !!appData.clientSecret,
604
898
  hasServiceKey: !!appData.serviceKey,
899
+ hasExternalAccount: !!appData.externalAccount,
605
900
 
606
- pubSubApps:
607
- pubSubApps &&
608
- pubSubApps.apps &&
609
- pubSubApps.apps.map(app => {
610
- if (app.id === values.pubSubApp) {
611
- app.selected = true;
612
- }
613
- return app;
614
- }),
901
+ ...authMethodContext(appData.authMethod, !!appData.serviceKey || !!appData.externalAccount),
902
+
903
+ pubSubApps: await getPubSubAppsForSelect(values.pubSubApp),
615
904
 
616
905
  values,
617
906
 
@@ -654,7 +943,6 @@ function init({ server, call }) {
654
943
  }
655
944
  });
656
945
 
657
- // POST /admin/config/oauth/edit - Update OAuth application
658
946
  server.route({
659
947
  method: 'POST',
660
948
  path: '/admin/config/oauth/edit',
@@ -700,9 +988,10 @@ function init({ server, call }) {
700
988
  let providerData = oauth2ProviderData(appData.provider, appData.cloud);
701
989
 
702
990
  let serviceUrl = await settings.get('serviceUrl');
703
- let defaultRedirectUrl = normalizeOutlookRedirectUrl(`${serviceUrl}/oauth`, appData.provider);
704
-
705
- let pubSubApps = await oauth2Apps.list(0, 1000, { pubsub: true });
991
+ let defaultRedirectUrl = `${serviceUrl}/oauth`;
992
+ if (appData.provider === 'outlook') {
993
+ defaultRedirectUrl = defaultRedirectUrl.replace(/^http:\/\/127\.0\.0\.1\b/i, 'http://localhost');
994
+ }
706
995
 
707
996
  return h.view(
708
997
  'config/oauth/edit',
@@ -718,16 +1007,11 @@ function init({ server, call }) {
718
1007
 
719
1008
  hasClientSecret: !!appData.clientSecret,
720
1009
  hasServiceKey: !!appData.serviceKey,
1010
+ hasExternalAccount: !!appData.externalAccount,
1011
+
1012
+ ...authMethodContext(appData.authMethod, !!appData.serviceKey || !!appData.externalAccount),
721
1013
 
722
- pubSubApps:
723
- pubSubApps &&
724
- pubSubApps.apps &&
725
- pubSubApps.apps.map(app => {
726
- if (app.id === request.payload.pubSubApp) {
727
- app.selected = true;
728
- }
729
- return app;
730
- }),
1014
+ pubSubApps: await getPubSubAppsForSelect(request.payload.pubSubApp),
731
1015
 
732
1016
  baseScopesApi: request.payload.baseScopes === 'api',
733
1017
  baseScopesImap: request.payload.baseScopes === 'imap' || !request.payload.baseScopes,
@@ -786,9 +1070,10 @@ function init({ server, call }) {
786
1070
  let providerData = oauth2ProviderData(provider);
787
1071
 
788
1072
  let serviceUrl = await settings.get('serviceUrl');
789
- let defaultRedirectUrl = normalizeOutlookRedirectUrl(`${serviceUrl}/oauth`, provider);
790
-
791
- let pubSubApps = await oauth2Apps.list(0, 1000, { pubsub: true });
1073
+ let defaultRedirectUrl = `${serviceUrl}/oauth`;
1074
+ if (provider === 'outlook') {
1075
+ defaultRedirectUrl = defaultRedirectUrl.replace(/^http:\/\/127\.0\.0\.1\b/i, 'http://localhost');
1076
+ }
792
1077
 
793
1078
  return h
794
1079
  .view(
@@ -806,16 +1091,11 @@ function init({ server, call }) {
806
1091
 
807
1092
  hasClientSecret: !!appData.clientSecret,
808
1093
  hasServiceKey: !!appData.serviceKey,
1094
+ hasExternalAccount: !!appData.externalAccount,
1095
+
1096
+ ...authMethodContext(request.payload.authMethod),
809
1097
 
810
- pubSubApps:
811
- pubSubApps &&
812
- pubSubApps.apps &&
813
- pubSubApps.apps.map(app => {
814
- if (app.id === request.payload.pubSubApp) {
815
- app.selected = true;
816
- }
817
- return app;
818
- }),
1098
+ pubSubApps: await getPubSubAppsForSelect(request.payload.pubSubApp),
819
1099
 
820
1100
  baseScopesApi: request.payload.baseScopes === 'api',
821
1101
  baseScopesImap: request.payload.baseScopes === 'imap' || !request.payload.baseScopes,