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