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.
- package/.github/codeql/codeql-config.yml +16 -0
- package/.github/workflows/codeql.yml +102 -0
- package/.github/workflows/deploy.yml +8 -0
- package/.github/workflows/release.yaml +4 -0
- package/.github/workflows/test.yml +3 -0
- package/CHANGELOG.md +49 -0
- package/SECURITY.md +80 -0
- package/SECURITY.txt +27 -0
- package/config/default.toml +2 -0
- package/data/google-crawlers.json +13 -1
- package/lib/account.js +62 -25
- package/lib/api-routes/account-routes.js +493 -75
- package/lib/api-routes/blocklist-routes.js +337 -0
- package/lib/api-routes/delivery-test-routes.js +321 -0
- package/lib/api-routes/export-routes.js +1 -12
- package/lib/api-routes/gateway-routes.js +376 -0
- package/lib/api-routes/license-routes.js +142 -0
- package/lib/api-routes/mailbox-routes.js +318 -0
- package/lib/api-routes/message-routes.js +21 -129
- package/lib/api-routes/oauth2-app-routes.js +631 -0
- package/lib/api-routes/outbox-routes.js +173 -0
- package/lib/api-routes/pubsub-routes.js +98 -0
- package/lib/api-routes/route-helpers.js +45 -0
- package/lib/api-routes/settings-routes.js +331 -0
- package/lib/api-routes/stats-routes.js +77 -0
- package/lib/api-routes/submit-routes.js +472 -0
- package/lib/api-routes/template-routes.js +7 -55
- package/lib/api-routes/token-routes.js +297 -0
- package/lib/api-routes/webhook-route-routes.js +152 -0
- package/lib/email-client/gmail-client.js +14 -0
- package/lib/email-client/imap/mailbox.js +34 -11
- package/lib/email-client/imap/subconnection.js +20 -12
- package/lib/email-client/imap/sync-operations.js +130 -2
- package/lib/email-client/imap-client.js +116 -58
- package/lib/email-client/outlook-client.js +85 -13
- package/lib/export.js +60 -19
- package/lib/imapproxy/imap-core/lib/commands/starttls.js +18 -0
- package/lib/imapproxy/imap-core/lib/imap-command.js +7 -2
- package/lib/imapproxy/imap-core/lib/imap-connection.js +113 -23
- package/lib/imapproxy/imap-core/lib/imap-server.js +25 -1
- package/lib/imapproxy/imap-core/lib/imap-stream.js +26 -0
- package/lib/imapproxy/imap-server.js +92 -29
- package/lib/message-port-stream.js +113 -16
- package/lib/reject-worker-calls.js +42 -0
- package/lib/routes-ui.js +37 -8778
- package/lib/schemas.js +26 -1
- package/lib/tools.js +73 -0
- package/lib/ui-routes/account-routes.js +40 -210
- package/lib/ui-routes/admin-config-routes.js +913 -487
- 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} +371 -91
- package/lib/ui-routes/route-helpers.js +316 -0
- package/lib/ui-routes/smtp-test-routes.js +236 -0
- package/lib/ui-routes/unsubscribe-routes.js +234 -0
- package/lib/webhook-request.js +36 -0
- package/package.json +17 -17
- package/sbom.json +1 -1
- package/server.js +217 -19
- package/static/licenses.html +52 -182
- package/translations/messages.pot +131 -151
- package/views/dashboard.hbs +7 -26
- package/views/internals/index.hbs +15 -0
- package/views/tokens/index.hbs +9 -0
- package/workers/api.js +198 -4401
- package/workers/export.js +87 -54
- package/workers/imap.js +29 -13
- package/workers/submit.js +20 -11
- package/workers/webhooks.js +6 -20
|
@@ -1,26 +1,29 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
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 {
|
|
12
|
-
const
|
|
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(
|
|
51
|
-
|
|
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/
|
|
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
|
-
|
|
217
|
-
|
|
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
|
-
|
|
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 =
|
|
321
|
-
|
|
322
|
-
|
|
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
|
-
|
|
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
|
-
|
|
655
|
+
redirectUrl: defaultRedirectUrl,
|
|
656
|
+
authMethod: 'serviceKey'
|
|
353
657
|
},
|
|
354
658
|
|
|
355
|
-
authorityCommon:
|
|
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 =
|
|
432
|
-
|
|
433
|
-
|
|
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 =
|
|
509
|
-
|
|
510
|
-
|
|
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 =
|
|
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
|
-
|
|
607
|
-
|
|
608
|
-
|
|
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 =
|
|
704
|
-
|
|
705
|
-
|
|
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 =
|
|
790
|
-
|
|
791
|
-
|
|
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,
|