emailengine-app 2.63.4 → 2.65.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/test.yml +4 -0
- package/CHANGELOG.md +70 -0
- package/copy-static-files.sh +1 -1
- package/data/google-crawlers.json +1 -1
- package/eslint.config.js +2 -0
- package/lib/account.js +13 -9
- package/lib/api-routes/account-routes.js +7 -1
- package/lib/consts.js +17 -1
- package/lib/email-client/gmail/gmail-api.js +1 -12
- package/lib/email-client/imap-client.js +5 -3
- package/lib/email-client/outlook/graph-api.js +9 -15
- package/lib/email-client/outlook-client.js +406 -177
- package/lib/export.js +17 -0
- package/lib/imapproxy/imap-server.js +3 -2
- package/lib/oauth/gmail.js +12 -1
- package/lib/oauth/outlook.js +99 -1
- package/lib/oauth/pubsub/google.js +253 -85
- package/lib/oauth2-apps.js +620 -389
- package/lib/outbox.js +1 -1
- package/lib/routes-ui.js +193 -238
- package/lib/schemas.js +189 -12
- package/lib/ui-routes/account-routes.js +7 -2
- package/lib/ui-routes/admin-entities-routes.js +3 -3
- package/lib/ui-routes/oauth-routes.js +27 -175
- package/package.json +21 -21
- package/sbom.json +1 -1
- package/server.js +54 -22
- package/static/licenses.html +30 -90
- package/translations/de.mo +0 -0
- package/translations/de.po +54 -42
- package/translations/en.mo +0 -0
- package/translations/en.po +55 -43
- package/translations/et.mo +0 -0
- package/translations/et.po +54 -42
- package/translations/fr.mo +0 -0
- package/translations/fr.po +54 -42
- package/translations/ja.mo +0 -0
- package/translations/ja.po +54 -42
- package/translations/messages.pot +93 -71
- package/translations/nl.mo +0 -0
- package/translations/nl.po +54 -42
- package/translations/pl.mo +0 -0
- package/translations/pl.po +54 -42
- package/views/config/oauth/app.hbs +12 -0
- package/views/config/oauth/edit.hbs +2 -0
- package/views/config/oauth/index.hbs +4 -1
- package/views/config/oauth/new.hbs +2 -0
- package/views/config/oauth/subscriptions.hbs +175 -0
- package/views/error.hbs +4 -4
- package/views/partials/oauth_form.hbs +179 -4
- package/views/partials/oauth_tabs.hbs +8 -0
- package/views/partials/scope_info.hbs +10 -0
- package/workers/api.js +174 -96
- package/workers/documents.js +1 -0
- package/workers/export.js +6 -2
- package/workers/imap.js +33 -49
- package/workers/smtp.js +1 -0
- package/workers/submit.js +1 -0
- package/workers/webhooks.js +42 -30
package/lib/outbox.js
CHANGED
|
@@ -11,7 +11,7 @@ async function list(options) {
|
|
|
11
11
|
|
|
12
12
|
let jobCounts = await submitQueue.getJobCounts();
|
|
13
13
|
|
|
14
|
-
let jobStates = ['delayed', 'paused', 'wait', 'active'];
|
|
14
|
+
let jobStates = ['delayed', 'paused', 'wait', 'active', 'failed'];
|
|
15
15
|
|
|
16
16
|
let totalJobs = jobStates.map(state => Number(jobCounts[state]) || 0).reduce((previousValue, currentValue) => previousValue + currentValue);
|
|
17
17
|
|
package/lib/routes-ui.js
CHANGED
|
@@ -37,7 +37,7 @@ const { Account } = require('./account');
|
|
|
37
37
|
const { Gateway } = require('./gateway');
|
|
38
38
|
const { redis, submitQueue, notifyQueue, documentsQueue } = require('./db');
|
|
39
39
|
const psl = require('psl');
|
|
40
|
-
const { oauth2Apps,
|
|
40
|
+
const { oauth2Apps, OAUTH_PROVIDERS, oauth2ProviderData, SERVICE_ACCOUNT_PROVIDERS } = require('./oauth2-apps');
|
|
41
41
|
const { autodetectImapSettings } = require('./autodetect-imap-settings');
|
|
42
42
|
const getSecret = require('./get-secret');
|
|
43
43
|
const os = require('os');
|
|
@@ -45,10 +45,9 @@ const {
|
|
|
45
45
|
ADDRESS_STRATEGIES,
|
|
46
46
|
settingsSchema,
|
|
47
47
|
oauthCreateSchema,
|
|
48
|
+
oauthUpdateSchema,
|
|
48
49
|
accountIdSchema,
|
|
49
50
|
defaultAccountTypeSchema,
|
|
50
|
-
googleProjectIdSchema,
|
|
51
|
-
googleWorkspaceAccountsSchema,
|
|
52
51
|
exportIdSchema
|
|
53
52
|
} = require('./schemas');
|
|
54
53
|
const fs = require('fs');
|
|
@@ -58,7 +57,6 @@ const { Client: ElasticSearch } = require('@elastic/elasticsearch');
|
|
|
58
57
|
const { llmPreProcess } = require('./llm-pre-process');
|
|
59
58
|
const { locales } = require('./translations');
|
|
60
59
|
const capa = require('./capa');
|
|
61
|
-
const exampleWebhookPayloads = require('./payload-examples-webhooks.json');
|
|
62
60
|
const exampleDocumentsPayloads = require('./payload-examples-documents.json');
|
|
63
61
|
const { defaultMappings } = require('./es');
|
|
64
62
|
const { getESClient } = require('../lib/document-store');
|
|
@@ -358,150 +356,6 @@ const OKTA_OAUTH2_CLIENT_ID = readEnvValue('OKTA_OAUTH2_CLIENT_ID');
|
|
|
358
356
|
const OKTA_OAUTH2_CLIENT_SECRET = readEnvValue('OKTA_OAUTH2_CLIENT_SECRET');
|
|
359
357
|
const USE_OKTA_AUTH = !!(OKTA_OAUTH2_ISSUER && OKTA_OAUTH2_CLIENT_ID && OKTA_OAUTH2_CLIENT_SECRET);
|
|
360
358
|
|
|
361
|
-
const oauthUpdateSchema = {
|
|
362
|
-
app: Joi.string().empty('').max(255).example('gmail').label('Provider').required(),
|
|
363
|
-
|
|
364
|
-
provider: Joi.string()
|
|
365
|
-
.trim()
|
|
366
|
-
.empty('')
|
|
367
|
-
.max(256)
|
|
368
|
-
.valid(...Object.keys(OAUTH_PROVIDERS))
|
|
369
|
-
.example('gmail')
|
|
370
|
-
.required()
|
|
371
|
-
.description('OAuth2 provider'),
|
|
372
|
-
|
|
373
|
-
name: Joi.string()
|
|
374
|
-
.trim()
|
|
375
|
-
.empty('')
|
|
376
|
-
.max(256)
|
|
377
|
-
.example('My Gmail App')
|
|
378
|
-
.description('Application name')
|
|
379
|
-
.when('app', {
|
|
380
|
-
not: Joi.string().valid(...LEGACY_KEYS),
|
|
381
|
-
then: Joi.required(),
|
|
382
|
-
otherwise: Joi.optional().valid(false, null)
|
|
383
|
-
}),
|
|
384
|
-
description: Joi.string().trim().allow('').max(1024).example('My cool app').description('Application description'),
|
|
385
|
-
|
|
386
|
-
title: Joi.string().allow('').trim().max(256).example('App title').description('Title for the application button'),
|
|
387
|
-
|
|
388
|
-
enabled: Joi.boolean().truthy('Y', 'true', '1', 'on').falsy('N', 'false', 0, '').default(false).description('Enable this app'),
|
|
389
|
-
|
|
390
|
-
clientId: Joi.string()
|
|
391
|
-
.trim()
|
|
392
|
-
.allow('')
|
|
393
|
-
.max(256)
|
|
394
|
-
.when('provider', {
|
|
395
|
-
not: 'gmailService',
|
|
396
|
-
then: Joi.required(),
|
|
397
|
-
otherwise: Joi.optional().valid(false, null)
|
|
398
|
-
})
|
|
399
|
-
.description('OAuth2 Client ID'),
|
|
400
|
-
|
|
401
|
-
clientSecret: Joi.string()
|
|
402
|
-
.trim()
|
|
403
|
-
.empty('', false, null)
|
|
404
|
-
.max(256)
|
|
405
|
-
.when('provider', {
|
|
406
|
-
not: 'gmailService',
|
|
407
|
-
then: Joi.optional(),
|
|
408
|
-
otherwise: Joi.forbidden()
|
|
409
|
-
})
|
|
410
|
-
.description('OAuth2 Client Secret'),
|
|
411
|
-
|
|
412
|
-
pubSubApp: Joi.string()
|
|
413
|
-
.empty('')
|
|
414
|
-
.base64({ paddingRequired: false, urlSafe: true })
|
|
415
|
-
.max(512)
|
|
416
|
-
.example('AAAAAQAACnA')
|
|
417
|
-
.description('Cloud Pub/Sub app for Gmail API webhooks'),
|
|
418
|
-
|
|
419
|
-
extraScopes: Joi.string()
|
|
420
|
-
.allow('')
|
|
421
|
-
.trim()
|
|
422
|
-
.max(10 * 1024)
|
|
423
|
-
.description('OAuth2 Extra Scopes'),
|
|
424
|
-
|
|
425
|
-
skipScopes: Joi.string()
|
|
426
|
-
.allow('')
|
|
427
|
-
.trim()
|
|
428
|
-
.max(10 * 1024)
|
|
429
|
-
.description('OAuth2 scopes to skip from the base set'),
|
|
430
|
-
|
|
431
|
-
serviceClient: Joi.string()
|
|
432
|
-
.trim()
|
|
433
|
-
.allow('')
|
|
434
|
-
.max(256)
|
|
435
|
-
.when('provider', {
|
|
436
|
-
is: 'gmailService',
|
|
437
|
-
then: Joi.required(),
|
|
438
|
-
otherwise: Joi.optional().valid(false, null)
|
|
439
|
-
})
|
|
440
|
-
.description('OAuth2 Service Client ID'),
|
|
441
|
-
|
|
442
|
-
googleProjectId: googleProjectIdSchema,
|
|
443
|
-
|
|
444
|
-
googleWorkspaceAccounts: googleWorkspaceAccountsSchema.when('provider', {
|
|
445
|
-
is: 'gmail',
|
|
446
|
-
then: Joi.optional().default(false)
|
|
447
|
-
}),
|
|
448
|
-
|
|
449
|
-
serviceClientEmail: Joi.string()
|
|
450
|
-
.trim()
|
|
451
|
-
.allow('')
|
|
452
|
-
.email()
|
|
453
|
-
.when('provider', {
|
|
454
|
-
is: 'gmailService',
|
|
455
|
-
then: Joi.required(),
|
|
456
|
-
otherwise: Joi.optional().valid(false, null)
|
|
457
|
-
})
|
|
458
|
-
.example('name@project-123.iam.gserviceaccount.com')
|
|
459
|
-
.description('Service Client Email for 2-legged OAuth2 applications'),
|
|
460
|
-
|
|
461
|
-
serviceKey: Joi.string()
|
|
462
|
-
.trim()
|
|
463
|
-
.empty('', false, null)
|
|
464
|
-
.max(100 * 1024)
|
|
465
|
-
.when('provider', {
|
|
466
|
-
is: 'gmailService',
|
|
467
|
-
then: Joi.optional(),
|
|
468
|
-
otherwise: Joi.forbidden()
|
|
469
|
-
})
|
|
470
|
-
.description('OAuth2 Secret Service Key'),
|
|
471
|
-
|
|
472
|
-
authority: Joi.string()
|
|
473
|
-
.trim()
|
|
474
|
-
.empty('')
|
|
475
|
-
.max(1024)
|
|
476
|
-
.when('provider', {
|
|
477
|
-
is: 'outlook',
|
|
478
|
-
then: Joi.required(),
|
|
479
|
-
otherwise: Joi.optional().valid(false, null)
|
|
480
|
-
})
|
|
481
|
-
.example(false)
|
|
482
|
-
.label('SupportedAccountTypes'),
|
|
483
|
-
|
|
484
|
-
cloud: Joi.string()
|
|
485
|
-
.trim()
|
|
486
|
-
.empty('')
|
|
487
|
-
.valid('global', 'gcc-high', 'dod', 'china')
|
|
488
|
-
.example('global')
|
|
489
|
-
.description('Azure cloud type for Outlook OAuth2 applications')
|
|
490
|
-
.label('AzureCloud'),
|
|
491
|
-
|
|
492
|
-
tenant: Joi.string().trim().empty('').max(1024).example('f8cdef31-a31e-4b4a-93e4-5f571e91255a').label('Directorytenant'),
|
|
493
|
-
|
|
494
|
-
redirectUrl: Joi.string()
|
|
495
|
-
.allow('')
|
|
496
|
-
.uri({ scheme: ['http', 'https'], allowRelative: false })
|
|
497
|
-
.when('provider', {
|
|
498
|
-
not: 'gmailService',
|
|
499
|
-
then: Joi.required(),
|
|
500
|
-
otherwise: Joi.optional().valid(false, null)
|
|
501
|
-
})
|
|
502
|
-
.description('OAuth2 Callback URL')
|
|
503
|
-
};
|
|
504
|
-
|
|
505
359
|
function formatAccountData(account, gt) {
|
|
506
360
|
account.type = {};
|
|
507
361
|
|
|
@@ -719,34 +573,6 @@ async function getOpenAiError(gt) {
|
|
|
719
573
|
return openAiError;
|
|
720
574
|
}
|
|
721
575
|
|
|
722
|
-
async function getExampleWebhookPayloads() {
|
|
723
|
-
let serviceUrl = await settings.get('serviceUrl');
|
|
724
|
-
let date = new Date().toISOString();
|
|
725
|
-
|
|
726
|
-
let examplePayloads = structuredClone(exampleWebhookPayloads);
|
|
727
|
-
|
|
728
|
-
examplePayloads.forEach(payload => {
|
|
729
|
-
if (payload && payload.content) {
|
|
730
|
-
if (typeof payload.content.serviceUrl === 'string') {
|
|
731
|
-
payload.content.serviceUrl = serviceUrl;
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
if (typeof payload.content.date === 'string') {
|
|
735
|
-
payload.content.date = date;
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
if (payload.content.data && typeof payload.content.data.date === 'string') {
|
|
739
|
-
payload.content.data.date = date;
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
if (payload.content.data && typeof payload.content.data.created === 'string') {
|
|
743
|
-
payload.content.data.created = date;
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
});
|
|
747
|
-
return examplePayloads;
|
|
748
|
-
}
|
|
749
|
-
|
|
750
576
|
async function getExampleDocumentsPayloads() {
|
|
751
577
|
let date = new Date().toISOString();
|
|
752
578
|
|
|
@@ -886,6 +712,21 @@ function applyRoutes(server, call) {
|
|
|
886
712
|
throw error;
|
|
887
713
|
}
|
|
888
714
|
|
|
715
|
+
/**
|
|
716
|
+
* Fetch the list of Pub/Sub apps and mark the one matching selectedId as selected.
|
|
717
|
+
* Returns the apps array ready for template rendering.
|
|
718
|
+
*/
|
|
719
|
+
async function getPubSubAppsForSelect(selectedId) {
|
|
720
|
+
let result = await oauth2Apps.list(0, 1000, { pubsub: true });
|
|
721
|
+
let apps = (result && result.apps) || [];
|
|
722
|
+
for (let app of apps) {
|
|
723
|
+
if (app.id === selectedId) {
|
|
724
|
+
app.selected = true;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
return apps;
|
|
728
|
+
}
|
|
729
|
+
|
|
889
730
|
// List exports for account
|
|
890
731
|
server.route({
|
|
891
732
|
method: 'GET',
|
|
@@ -2568,6 +2409,7 @@ return true;`
|
|
|
2568
2409
|
pageTitle: 'OAuth2',
|
|
2569
2410
|
menuConfig: true,
|
|
2570
2411
|
menuConfigOauth: true,
|
|
2412
|
+
activeApplications: true,
|
|
2571
2413
|
|
|
2572
2414
|
newLink: newLink.pathname + newLink.search,
|
|
2573
2415
|
|
|
@@ -2614,6 +2456,160 @@ return true;`
|
|
|
2614
2456
|
}
|
|
2615
2457
|
});
|
|
2616
2458
|
|
|
2459
|
+
// GET /admin/config/oauth/subscriptions - Gmail Pub/Sub subscriptions list
|
|
2460
|
+
server.route({
|
|
2461
|
+
method: 'GET',
|
|
2462
|
+
path: '/admin/config/oauth/subscriptions',
|
|
2463
|
+
async handler(request, h) {
|
|
2464
|
+
try {
|
|
2465
|
+
let data = await oauth2Apps.list(request.query.page - 1, request.query.pageSize, { pubsub: true });
|
|
2466
|
+
|
|
2467
|
+
let gmailSubscriptionTtl = await settings.get('gmailSubscriptionTtl');
|
|
2468
|
+
|
|
2469
|
+
// Compute human-readable expiration for each app
|
|
2470
|
+
// meta.subscriptionExpiration is:
|
|
2471
|
+
// undefined - no data yet (app predates this feature or ensurePubsub hasn't run)
|
|
2472
|
+
// null - indefinite (no TTL set, ensurePubsub confirmed this)
|
|
2473
|
+
// "Ns" - TTL in seconds (e.g. "2678400s" for 31 days)
|
|
2474
|
+
let gt = request.app.gt;
|
|
2475
|
+
for (let app of data.apps) {
|
|
2476
|
+
if (!app.pubSubSubscription) {
|
|
2477
|
+
app.expirationLabel = '';
|
|
2478
|
+
continue;
|
|
2479
|
+
}
|
|
2480
|
+
|
|
2481
|
+
let meta = app.meta || {};
|
|
2482
|
+
if (!('subscriptionExpiration' in meta)) {
|
|
2483
|
+
app.expirationLabel = gt.gettext('Unknown');
|
|
2484
|
+
continue;
|
|
2485
|
+
}
|
|
2486
|
+
|
|
2487
|
+
let seconds = parseInt(meta.subscriptionExpiration, 10);
|
|
2488
|
+
if (seconds > 0) {
|
|
2489
|
+
let days = Math.round(seconds / 86400);
|
|
2490
|
+
app.expirationLabel = util.format(gt.ngettext('%d day', '%d days', days), days);
|
|
2491
|
+
} else {
|
|
2492
|
+
app.expirationLabel = gt.gettext('Indefinite');
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
2495
|
+
|
|
2496
|
+
let nextPage = false;
|
|
2497
|
+
let prevPage = false;
|
|
2498
|
+
|
|
2499
|
+
let getPagingUrl = page => {
|
|
2500
|
+
let url = new URL(`admin/config/oauth/subscriptions`, 'http://localhost');
|
|
2501
|
+
|
|
2502
|
+
if (page) {
|
|
2503
|
+
url.searchParams.append('page', page);
|
|
2504
|
+
}
|
|
2505
|
+
|
|
2506
|
+
if (request.query.pageSize !== DEFAULT_PAGE_SIZE) {
|
|
2507
|
+
url.searchParams.append('pageSize', request.query.pageSize);
|
|
2508
|
+
}
|
|
2509
|
+
|
|
2510
|
+
return url.pathname + url.search;
|
|
2511
|
+
};
|
|
2512
|
+
|
|
2513
|
+
if (data.pages > data.page + 1) {
|
|
2514
|
+
nextPage = getPagingUrl(data.page + 2);
|
|
2515
|
+
}
|
|
2516
|
+
|
|
2517
|
+
if (data.page > 0) {
|
|
2518
|
+
prevPage = getPagingUrl(data.page);
|
|
2519
|
+
}
|
|
2520
|
+
|
|
2521
|
+
return h.view(
|
|
2522
|
+
'config/oauth/subscriptions',
|
|
2523
|
+
{
|
|
2524
|
+
pageTitle: 'OAuth2',
|
|
2525
|
+
menuConfig: true,
|
|
2526
|
+
menuConfigOauth: true,
|
|
2527
|
+
activeSubscriptions: true,
|
|
2528
|
+
|
|
2529
|
+
showPaging: data.pages > 1,
|
|
2530
|
+
nextPage,
|
|
2531
|
+
prevPage,
|
|
2532
|
+
firstPage: data.page === 0,
|
|
2533
|
+
pageLinks: new Array(data.pages || 1).fill(0).map((z, i) => ({
|
|
2534
|
+
url: getPagingUrl(i + 1),
|
|
2535
|
+
title: i + 1,
|
|
2536
|
+
active: i === data.page
|
|
2537
|
+
})),
|
|
2538
|
+
|
|
2539
|
+
apps: data.apps,
|
|
2540
|
+
|
|
2541
|
+
values: {
|
|
2542
|
+
gmailSubscriptionTtl: typeof gmailSubscriptionTtl === 'number' ? gmailSubscriptionTtl : ''
|
|
2543
|
+
}
|
|
2544
|
+
},
|
|
2545
|
+
{
|
|
2546
|
+
layout: 'app'
|
|
2547
|
+
}
|
|
2548
|
+
);
|
|
2549
|
+
} catch (err) {
|
|
2550
|
+
request.logger.error({ msg: 'Failed to load subscriptions page', err });
|
|
2551
|
+
throwAsBoom(err);
|
|
2552
|
+
}
|
|
2553
|
+
},
|
|
2554
|
+
|
|
2555
|
+
options: {
|
|
2556
|
+
validate: {
|
|
2557
|
+
options: {
|
|
2558
|
+
stripUnknown: true,
|
|
2559
|
+
abortEarly: false,
|
|
2560
|
+
convert: true
|
|
2561
|
+
},
|
|
2562
|
+
|
|
2563
|
+
async failAction(request, h /*, err*/) {
|
|
2564
|
+
return h.redirect('/admin/config/oauth/subscriptions').takeover();
|
|
2565
|
+
},
|
|
2566
|
+
|
|
2567
|
+
query: Joi.object({
|
|
2568
|
+
page: Joi.number().integer().min(1).max(1000000).default(1),
|
|
2569
|
+
pageSize: Joi.number().integer().min(1).max(250).default(DEFAULT_PAGE_SIZE)
|
|
2570
|
+
})
|
|
2571
|
+
}
|
|
2572
|
+
}
|
|
2573
|
+
});
|
|
2574
|
+
|
|
2575
|
+
server.route({
|
|
2576
|
+
method: 'POST',
|
|
2577
|
+
path: '/admin/config/oauth/subscriptions',
|
|
2578
|
+
async handler(request, h) {
|
|
2579
|
+
try {
|
|
2580
|
+
// Joi .empty('').allow(null) ensures this is either null or a number
|
|
2581
|
+
let ttl = request.payload.gmailSubscriptionTtl != null ? request.payload.gmailSubscriptionTtl : null;
|
|
2582
|
+
await settings.set('gmailSubscriptionTtl', ttl);
|
|
2583
|
+
|
|
2584
|
+
await request.flash({ type: 'info', message: 'Configuration updated' });
|
|
2585
|
+
return h.redirect('/admin/config/oauth/subscriptions');
|
|
2586
|
+
} catch (err) {
|
|
2587
|
+
await request.flash({ type: 'danger', message: 'Failed to save settings' });
|
|
2588
|
+
request.logger.error({ msg: 'Failed to save subscription settings', err });
|
|
2589
|
+
return h.redirect('/admin/config/oauth/subscriptions');
|
|
2590
|
+
}
|
|
2591
|
+
},
|
|
2592
|
+
options: {
|
|
2593
|
+
validate: {
|
|
2594
|
+
options: {
|
|
2595
|
+
stripUnknown: true,
|
|
2596
|
+
abortEarly: false,
|
|
2597
|
+
convert: true
|
|
2598
|
+
},
|
|
2599
|
+
|
|
2600
|
+
async failAction(request, h /*, err*/) {
|
|
2601
|
+
await request.flash({ type: 'danger', message: 'Invalid setting value' });
|
|
2602
|
+
return h.redirect('/admin/config/oauth/subscriptions').takeover();
|
|
2603
|
+
},
|
|
2604
|
+
|
|
2605
|
+
payload: Joi.object({
|
|
2606
|
+
gmailSubscriptionTtl: Joi.number().integer().empty('').allow(null).min(0).max(365),
|
|
2607
|
+
crumb: Joi.string().max(256)
|
|
2608
|
+
})
|
|
2609
|
+
}
|
|
2610
|
+
}
|
|
2611
|
+
});
|
|
2612
|
+
|
|
2617
2613
|
server.route({
|
|
2618
2614
|
method: 'GET',
|
|
2619
2615
|
path: '/admin/config/oauth/app/{app}',
|
|
@@ -2724,6 +2720,12 @@ return true;`
|
|
|
2724
2720
|
try {
|
|
2725
2721
|
await oauth2Apps.del(request.payload.app);
|
|
2726
2722
|
|
|
2723
|
+
try {
|
|
2724
|
+
await call({ cmd: 'googlePubSubRemove', app: request.payload.app });
|
|
2725
|
+
} catch (err) {
|
|
2726
|
+
request.logger.error({ msg: 'Failed to notify workers about OAuth2 app deletion', err, app: request.payload.app });
|
|
2727
|
+
}
|
|
2728
|
+
|
|
2727
2729
|
await request.flash({ type: 'info', message: `OAuth2 app deleted` });
|
|
2728
2730
|
|
|
2729
2731
|
return h.redirect('/admin/config/oauth');
|
|
@@ -2743,7 +2745,7 @@ return true;`
|
|
|
2743
2745
|
|
|
2744
2746
|
async failAction(request, h, err) {
|
|
2745
2747
|
await request.flash({ type: 'danger', message: `Couldn't delete OAuth2 app. Try again.` });
|
|
2746
|
-
request.logger.error({ msg: 'Failed to delete
|
|
2748
|
+
request.logger.error({ msg: 'Failed to delete the OAuth2 application', err });
|
|
2747
2749
|
|
|
2748
2750
|
return h.redirect('/admin/config/oauth').takeover();
|
|
2749
2751
|
},
|
|
@@ -2768,8 +2770,6 @@ return true;`
|
|
|
2768
2770
|
defaultRedirectUrl = defaultRedirectUrl.replace(/^http:\/\/127\.0\.0\.1\b/i, 'http://localhost');
|
|
2769
2771
|
}
|
|
2770
2772
|
|
|
2771
|
-
let pubSubApps = await oauth2Apps.list(0, 1000, { pubsub: true });
|
|
2772
|
-
|
|
2773
2773
|
return h.view(
|
|
2774
2774
|
'config/oauth/new',
|
|
2775
2775
|
{
|
|
@@ -2787,7 +2787,7 @@ return true;`
|
|
|
2787
2787
|
baseScopesApi: false,
|
|
2788
2788
|
baseScopesPubsub: false,
|
|
2789
2789
|
|
|
2790
|
-
pubSubApps:
|
|
2790
|
+
pubSubApps: await getPubSubAppsForSelect(null),
|
|
2791
2791
|
|
|
2792
2792
|
azureClouds: structuredClone(AZURE_CLOUDS).map(entry => {
|
|
2793
2793
|
if (entry.id === 'global') {
|
|
@@ -2858,7 +2858,7 @@ return true;`
|
|
|
2858
2858
|
throw new Error('Unexpected result');
|
|
2859
2859
|
}
|
|
2860
2860
|
|
|
2861
|
-
if (oauth2App && oauth2App.pubsubUpdates && oauth2App.pubsubUpdates.
|
|
2861
|
+
if (oauth2App && oauth2App.pubsubUpdates && Object.keys(oauth2App.pubsubUpdates).length > 0) {
|
|
2862
2862
|
await call({ cmd: 'googlePubSub', app: oauth2App.id });
|
|
2863
2863
|
}
|
|
2864
2864
|
|
|
@@ -2881,8 +2881,6 @@ return true;`
|
|
|
2881
2881
|
defaultRedirectUrl = defaultRedirectUrl.replace(/^http:\/\/127\.0\.0\.1\b/i, 'http://localhost');
|
|
2882
2882
|
}
|
|
2883
2883
|
|
|
2884
|
-
let pubSubApps = await oauth2Apps.list(0, 1000, { pubsub: true });
|
|
2885
|
-
|
|
2886
2884
|
return h.view(
|
|
2887
2885
|
'config/oauth/new',
|
|
2888
2886
|
{
|
|
@@ -2896,15 +2894,7 @@ return true;`
|
|
|
2896
2894
|
providerData,
|
|
2897
2895
|
defaultRedirectUrl,
|
|
2898
2896
|
|
|
2899
|
-
pubSubApps:
|
|
2900
|
-
pubSubApps &&
|
|
2901
|
-
pubSubApps.apps &&
|
|
2902
|
-
pubSubApps.apps.map(app => {
|
|
2903
|
-
if (app.id === request.payload.pubSubApp) {
|
|
2904
|
-
app.selected = true;
|
|
2905
|
-
}
|
|
2906
|
-
return app;
|
|
2907
|
-
}),
|
|
2897
|
+
pubSubApps: await getPubSubAppsForSelect(request.payload.pubSubApp),
|
|
2908
2898
|
|
|
2909
2899
|
baseScopesApi: baseScopes === 'api',
|
|
2910
2900
|
baseScopesImap: baseScopes === 'imap' || !baseScopes,
|
|
@@ -2961,8 +2951,6 @@ return true;`
|
|
|
2961
2951
|
defaultRedirectUrl = defaultRedirectUrl.replace(/^http:\/\/127\.0\.0\.1\b/i, 'http://localhost');
|
|
2962
2952
|
}
|
|
2963
2953
|
|
|
2964
|
-
let pubSubApps = await oauth2Apps.list(0, 1000, { pubsub: true });
|
|
2965
|
-
|
|
2966
2954
|
return h
|
|
2967
2955
|
.view(
|
|
2968
2956
|
'config/oauth/new',
|
|
@@ -2977,15 +2965,7 @@ return true;`
|
|
|
2977
2965
|
providerData,
|
|
2978
2966
|
defaultRedirectUrl,
|
|
2979
2967
|
|
|
2980
|
-
pubSubApps:
|
|
2981
|
-
pubSubApps &&
|
|
2982
|
-
pubSubApps.apps &&
|
|
2983
|
-
pubSubApps.apps.map(app => {
|
|
2984
|
-
if (app.id === request.payload.pubSubApp) {
|
|
2985
|
-
app.selected = true;
|
|
2986
|
-
}
|
|
2987
|
-
return app;
|
|
2988
|
-
}),
|
|
2968
|
+
pubSubApps: await getPubSubAppsForSelect(request.payload.pubSubApp),
|
|
2989
2969
|
|
|
2990
2970
|
baseScopesApi: baseScopes === 'api',
|
|
2991
2971
|
baseScopesImap: baseScopes === 'imap' || !baseScopes,
|
|
@@ -3041,8 +3021,6 @@ return true;`
|
|
|
3041
3021
|
tenant: appData.authority && !['common', 'organizations', 'consumers'].includes(appData.authority) ? appData.authority : ''
|
|
3042
3022
|
});
|
|
3043
3023
|
|
|
3044
|
-
let pubSubApps = await oauth2Apps.list(0, 1000, { pubsub: true });
|
|
3045
|
-
|
|
3046
3024
|
return h.view(
|
|
3047
3025
|
'config/oauth/edit',
|
|
3048
3026
|
{
|
|
@@ -3059,15 +3037,7 @@ return true;`
|
|
|
3059
3037
|
hasClientSecret: !!appData.clientSecret,
|
|
3060
3038
|
hasServiceKey: !!appData.serviceKey,
|
|
3061
3039
|
|
|
3062
|
-
pubSubApps:
|
|
3063
|
-
pubSubApps &&
|
|
3064
|
-
pubSubApps.apps &&
|
|
3065
|
-
pubSubApps.apps.map(app => {
|
|
3066
|
-
if (app.id === values.pubSubApp) {
|
|
3067
|
-
app.selected = true;
|
|
3068
|
-
}
|
|
3069
|
-
return app;
|
|
3070
|
-
}),
|
|
3040
|
+
pubSubApps: await getPubSubAppsForSelect(values.pubSubApp),
|
|
3071
3041
|
|
|
3072
3042
|
values,
|
|
3073
3043
|
|
|
@@ -3142,7 +3112,7 @@ return true;`
|
|
|
3142
3112
|
throw new Error('Unexpected result');
|
|
3143
3113
|
}
|
|
3144
3114
|
|
|
3145
|
-
if (oauth2App && oauth2App.pubsubUpdates && oauth2App.pubsubUpdates.
|
|
3115
|
+
if (oauth2App && oauth2App.pubsubUpdates && Object.keys(oauth2App.pubsubUpdates).length > 0) {
|
|
3146
3116
|
await call({ cmd: 'googlePubSub', app: oauth2App.id });
|
|
3147
3117
|
}
|
|
3148
3118
|
|
|
@@ -3160,8 +3130,6 @@ return true;`
|
|
|
3160
3130
|
defaultRedirectUrl = defaultRedirectUrl.replace(/^http:\/\/127\.0\.0\.1\b/i, 'http://localhost');
|
|
3161
3131
|
}
|
|
3162
3132
|
|
|
3163
|
-
let pubSubApps = await oauth2Apps.list(0, 1000, { pubsub: true });
|
|
3164
|
-
|
|
3165
3133
|
return h.view(
|
|
3166
3134
|
'config/oauth/edit',
|
|
3167
3135
|
{
|
|
@@ -3177,15 +3145,7 @@ return true;`
|
|
|
3177
3145
|
hasClientSecret: !!appData.clientSecret,
|
|
3178
3146
|
hasServiceKey: !!appData.serviceKey,
|
|
3179
3147
|
|
|
3180
|
-
pubSubApps:
|
|
3181
|
-
pubSubApps &&
|
|
3182
|
-
pubSubApps.apps &&
|
|
3183
|
-
pubSubApps.apps.map(app => {
|
|
3184
|
-
if (app.id === request.payload.pubSubApp) {
|
|
3185
|
-
app.selected = true;
|
|
3186
|
-
}
|
|
3187
|
-
return app;
|
|
3188
|
-
}),
|
|
3148
|
+
pubSubApps: await getPubSubAppsForSelect(request.payload.pubSubApp),
|
|
3189
3149
|
|
|
3190
3150
|
baseScopesApi: request.payload.baseScopes === 'api',
|
|
3191
3151
|
baseScopesImap: request.payload.baseScopes === 'imap' || !request.payload.baseScopes,
|
|
@@ -3249,8 +3209,6 @@ return true;`
|
|
|
3249
3209
|
defaultRedirectUrl = defaultRedirectUrl.replace(/^http:\/\/127\.0\.0\.1\b/i, 'http://localhost');
|
|
3250
3210
|
}
|
|
3251
3211
|
|
|
3252
|
-
let pubSubApps = await oauth2Apps.list(0, 1000, { pubsub: true });
|
|
3253
|
-
|
|
3254
3212
|
return h
|
|
3255
3213
|
.view(
|
|
3256
3214
|
'config/oauth/edit',
|
|
@@ -3268,15 +3226,7 @@ return true;`
|
|
|
3268
3226
|
hasClientSecret: !!appData.clientSecret,
|
|
3269
3227
|
hasServiceKey: !!appData.serviceKey,
|
|
3270
3228
|
|
|
3271
|
-
pubSubApps:
|
|
3272
|
-
pubSubApps &&
|
|
3273
|
-
pubSubApps.apps &&
|
|
3274
|
-
pubSubApps.apps.map(app => {
|
|
3275
|
-
if (app.id === request.payload.pubSubApp) {
|
|
3276
|
-
app.selected = true;
|
|
3277
|
-
}
|
|
3278
|
-
return app;
|
|
3279
|
-
}),
|
|
3229
|
+
pubSubApps: await getPubSubAppsForSelect(request.payload.pubSubApp),
|
|
3280
3230
|
|
|
3281
3231
|
baseScopesApi: request.payload.baseScopes === 'api',
|
|
3282
3232
|
baseScopesImap: request.payload.baseScopes === 'imap' || !request.payload.baseScopes,
|
|
@@ -4666,6 +4616,11 @@ ${Buffer.from(data.content, 'base64url').toString('base64')}
|
|
|
4666
4616
|
requestPayload.email = accountData.email;
|
|
4667
4617
|
}
|
|
4668
4618
|
|
|
4619
|
+
// Service providers use client_credentials - no interactive authorization
|
|
4620
|
+
if (SERVICE_ACCOUNT_PROVIDERS.has(oAuth2Client.provider)) {
|
|
4621
|
+
throw Boom.badRequest('Application-only OAuth providers do not support interactive authorization');
|
|
4622
|
+
}
|
|
4623
|
+
|
|
4669
4624
|
let authorizeUrl = oAuth2Client.generateAuthUrl(requestPayload);
|
|
4670
4625
|
|
|
4671
4626
|
return h.redirect(authorizeUrl);
|
|
@@ -5473,7 +5428,7 @@ ${Buffer.from(data.content, 'base64url').toString('base64')}
|
|
|
5473
5428
|
|
|
5474
5429
|
async failAction(request, h, err) {
|
|
5475
5430
|
await request.flash({ type: 'danger', message: `Couldn't delete account. Try again.` });
|
|
5476
|
-
request.logger.error({ msg: 'Failed to delete
|
|
5431
|
+
request.logger.error({ msg: 'Failed to delete the account', err });
|
|
5477
5432
|
|
|
5478
5433
|
return h.redirect('/admin/accounts').takeover();
|
|
5479
5434
|
},
|