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/schemas.js
CHANGED
|
@@ -4,6 +4,7 @@ const Joi = require('joi');
|
|
|
4
4
|
const config = require('@zone-eu/wild-config');
|
|
5
5
|
const { getByteSize } = require('./tools');
|
|
6
6
|
const { locales } = require('./translations');
|
|
7
|
+
const { LEGACY_KEYS, OAUTH_PROVIDERS } = require('./oauth2-apps');
|
|
7
8
|
|
|
8
9
|
const RESYNC_DELAY = 15 * 60;
|
|
9
10
|
|
|
@@ -22,13 +23,6 @@ const ADDRESS_STRATEGIES = [
|
|
|
22
23
|
{ key: 'random', title: 'Random' }
|
|
23
24
|
];
|
|
24
25
|
|
|
25
|
-
const OAUTH_PROVIDERS = {
|
|
26
|
-
gmail: 'Gmail',
|
|
27
|
-
gmailService: 'Gmail Service Accounts',
|
|
28
|
-
outlook: 'Outlook',
|
|
29
|
-
mailRu: 'Mail.ru'
|
|
30
|
-
};
|
|
31
|
-
|
|
32
26
|
const accountIdSchema = Joi.string().empty('').trim().max(256).example('user123').description('Unique identifier for the email account');
|
|
33
27
|
|
|
34
28
|
// Allowed configuration keys
|
|
@@ -450,6 +444,14 @@ const settingsSchema = {
|
|
|
450
444
|
queueKeep: Joi.number().integer().empty('').min(0).description('Number of completed and failed queue entries to retain for debugging'),
|
|
451
445
|
deliveryAttempts: Joi.number().integer().empty('').min(0).description('Maximum number of delivery attempts before marking a message as permanently failed'),
|
|
452
446
|
|
|
447
|
+
gmailSubscriptionTtl: Joi.number()
|
|
448
|
+
.integer()
|
|
449
|
+
.empty('')
|
|
450
|
+
.min(0)
|
|
451
|
+
.max(365)
|
|
452
|
+
.description('Gmail Pub/Sub subscription inactivity expiration in days. Empty for Google default (31 days), 0 for no expiration.')
|
|
453
|
+
.label('GmailSubscriptionTTL'),
|
|
454
|
+
|
|
453
455
|
/* ────────────── Templates ────────────── */
|
|
454
456
|
|
|
455
457
|
templateHeader: Joi.string()
|
|
@@ -1159,6 +1161,13 @@ const lastErrorSchema = Joi.object({
|
|
|
1159
1161
|
.label('OAuthTokenRequestError')
|
|
1160
1162
|
}).label('AccountErrorEntry');
|
|
1161
1163
|
|
|
1164
|
+
const pubSubErrorSchema = Joi.object({
|
|
1165
|
+
message: Joi.string().example('Failed to process subscription loop').description('Error message'),
|
|
1166
|
+
description: Joi.string().allow(null).example('Subscription not found').description('Error details')
|
|
1167
|
+
})
|
|
1168
|
+
.description('Pub/Sub subscription error, if any')
|
|
1169
|
+
.label('PubSubError');
|
|
1170
|
+
|
|
1162
1171
|
const templateSchemas = {
|
|
1163
1172
|
subject: Joi.string()
|
|
1164
1173
|
.allow('')
|
|
@@ -1350,6 +1359,7 @@ const googleProjectIdSchema = Joi.string()
|
|
|
1350
1359
|
.trim()
|
|
1351
1360
|
.allow('', false, null)
|
|
1352
1361
|
.max(256)
|
|
1362
|
+
.pattern(/^[a-z][a-z0-9-]{4,28}[a-z0-9]$/)
|
|
1353
1363
|
.example('project-name-425411')
|
|
1354
1364
|
.description('Google Cloud Project ID')
|
|
1355
1365
|
.label('GoogleProjectId');
|
|
@@ -1513,8 +1523,18 @@ const oauthCreateSchema = {
|
|
|
1513
1523
|
.empty('')
|
|
1514
1524
|
.max(1024)
|
|
1515
1525
|
.when('provider', {
|
|
1516
|
-
|
|
1517
|
-
|
|
1526
|
+
switch: [
|
|
1527
|
+
{
|
|
1528
|
+
is: 'outlookService',
|
|
1529
|
+
then: Joi.string().required().invalid('common', 'organizations', 'consumers').messages({
|
|
1530
|
+
'any.invalid': 'Client credentials flow requires a specific tenant ID; "{{#value}}" is not allowed'
|
|
1531
|
+
})
|
|
1532
|
+
},
|
|
1533
|
+
{
|
|
1534
|
+
is: 'outlook',
|
|
1535
|
+
then: Joi.required()
|
|
1536
|
+
}
|
|
1537
|
+
],
|
|
1518
1538
|
otherwise: Joi.optional().valid(false, null)
|
|
1519
1539
|
})
|
|
1520
1540
|
.example('common')
|
|
@@ -1525,7 +1545,7 @@ const oauthCreateSchema = {
|
|
|
1525
1545
|
.trim()
|
|
1526
1546
|
.empty('')
|
|
1527
1547
|
.when('provider', {
|
|
1528
|
-
is: 'outlook',
|
|
1548
|
+
is: Joi.valid('outlook', 'outlookService'),
|
|
1529
1549
|
then: Joi.valid('global', 'gcc-high', 'dod', 'china').default('global'),
|
|
1530
1550
|
otherwise: Joi.optional().valid(false, null)
|
|
1531
1551
|
})
|
|
@@ -1549,7 +1569,7 @@ const oauthCreateSchema = {
|
|
|
1549
1569
|
.allow('')
|
|
1550
1570
|
.uri({ scheme: ['http', 'https'], allowRelative: false })
|
|
1551
1571
|
.when('provider', {
|
|
1552
|
-
not: 'gmailService',
|
|
1572
|
+
not: Joi.valid('gmailService', 'outlookService'),
|
|
1553
1573
|
then: Joi.required(),
|
|
1554
1574
|
otherwise: Joi.optional().valid(false, null)
|
|
1555
1575
|
})
|
|
@@ -1558,6 +1578,160 @@ const oauthCreateSchema = {
|
|
|
1558
1578
|
.label('OAuth2AppRedirectUrl')
|
|
1559
1579
|
};
|
|
1560
1580
|
|
|
1581
|
+
const oauthUpdateSchema = {
|
|
1582
|
+
app: Joi.string().empty('').max(255).example('gmail').label('Provider').required(),
|
|
1583
|
+
|
|
1584
|
+
provider: Joi.string()
|
|
1585
|
+
.trim()
|
|
1586
|
+
.empty('')
|
|
1587
|
+
.max(256)
|
|
1588
|
+
.valid(...Object.keys(OAUTH_PROVIDERS))
|
|
1589
|
+
.example('gmail')
|
|
1590
|
+
.required()
|
|
1591
|
+
.description('OAuth2 provider'),
|
|
1592
|
+
|
|
1593
|
+
name: Joi.string()
|
|
1594
|
+
.trim()
|
|
1595
|
+
.empty('')
|
|
1596
|
+
.max(256)
|
|
1597
|
+
.example('My Gmail App')
|
|
1598
|
+
.description('Application name')
|
|
1599
|
+
.when('app', {
|
|
1600
|
+
not: Joi.string().valid(...LEGACY_KEYS),
|
|
1601
|
+
then: Joi.required(),
|
|
1602
|
+
otherwise: Joi.optional().valid(false, null)
|
|
1603
|
+
}),
|
|
1604
|
+
description: Joi.string().trim().allow('').max(1024).example('My cool app').description('Application description'),
|
|
1605
|
+
|
|
1606
|
+
title: Joi.string().allow('').trim().max(256).example('App title').description('Title for the application button'),
|
|
1607
|
+
|
|
1608
|
+
enabled: Joi.boolean().truthy('Y', 'true', '1', 'on').falsy('N', 'false', 0, '').default(false).description('Enable this app'),
|
|
1609
|
+
|
|
1610
|
+
clientId: Joi.string()
|
|
1611
|
+
.trim()
|
|
1612
|
+
.allow('')
|
|
1613
|
+
.max(256)
|
|
1614
|
+
.when('provider', {
|
|
1615
|
+
not: 'gmailService',
|
|
1616
|
+
then: Joi.required(),
|
|
1617
|
+
otherwise: Joi.optional().valid(false, null)
|
|
1618
|
+
})
|
|
1619
|
+
.description('OAuth2 Client ID'),
|
|
1620
|
+
|
|
1621
|
+
clientSecret: Joi.string()
|
|
1622
|
+
.trim()
|
|
1623
|
+
.empty('', false, null)
|
|
1624
|
+
.max(256)
|
|
1625
|
+
.when('provider', {
|
|
1626
|
+
not: 'gmailService',
|
|
1627
|
+
then: Joi.optional(),
|
|
1628
|
+
otherwise: Joi.forbidden()
|
|
1629
|
+
})
|
|
1630
|
+
.description('OAuth2 Client Secret'),
|
|
1631
|
+
|
|
1632
|
+
pubSubApp: Joi.string()
|
|
1633
|
+
.empty('')
|
|
1634
|
+
.base64({ paddingRequired: false, urlSafe: true })
|
|
1635
|
+
.max(512)
|
|
1636
|
+
.example('AAAAAQAACnA')
|
|
1637
|
+
.description('Cloud Pub/Sub app for Gmail API webhooks'),
|
|
1638
|
+
|
|
1639
|
+
extraScopes: Joi.string()
|
|
1640
|
+
.allow('')
|
|
1641
|
+
.trim()
|
|
1642
|
+
.max(10 * 1024)
|
|
1643
|
+
.description('OAuth2 Extra Scopes'),
|
|
1644
|
+
|
|
1645
|
+
skipScopes: Joi.string()
|
|
1646
|
+
.allow('')
|
|
1647
|
+
.trim()
|
|
1648
|
+
.max(10 * 1024)
|
|
1649
|
+
.description('OAuth2 scopes to skip from the base set'),
|
|
1650
|
+
|
|
1651
|
+
serviceClient: Joi.string()
|
|
1652
|
+
.trim()
|
|
1653
|
+
.allow('')
|
|
1654
|
+
.max(256)
|
|
1655
|
+
.when('provider', {
|
|
1656
|
+
is: 'gmailService',
|
|
1657
|
+
then: Joi.required(),
|
|
1658
|
+
otherwise: Joi.optional().valid(false, null)
|
|
1659
|
+
})
|
|
1660
|
+
.description('OAuth2 Service Client ID'),
|
|
1661
|
+
|
|
1662
|
+
googleProjectId: googleProjectIdSchema,
|
|
1663
|
+
|
|
1664
|
+
googleWorkspaceAccounts: googleWorkspaceAccountsSchema.when('provider', {
|
|
1665
|
+
is: 'gmail',
|
|
1666
|
+
then: Joi.optional().default(false)
|
|
1667
|
+
}),
|
|
1668
|
+
|
|
1669
|
+
serviceClientEmail: Joi.string()
|
|
1670
|
+
.trim()
|
|
1671
|
+
.allow('')
|
|
1672
|
+
.email()
|
|
1673
|
+
.when('provider', {
|
|
1674
|
+
is: 'gmailService',
|
|
1675
|
+
then: Joi.required(),
|
|
1676
|
+
otherwise: Joi.optional().valid(false, null)
|
|
1677
|
+
})
|
|
1678
|
+
.example('name@project-123.iam.gserviceaccount.com')
|
|
1679
|
+
.description('Service Client Email for 2-legged OAuth2 applications'),
|
|
1680
|
+
|
|
1681
|
+
serviceKey: Joi.string()
|
|
1682
|
+
.trim()
|
|
1683
|
+
.empty('', false, null)
|
|
1684
|
+
.max(100 * 1024)
|
|
1685
|
+
.when('provider', {
|
|
1686
|
+
is: 'gmailService',
|
|
1687
|
+
then: Joi.optional(),
|
|
1688
|
+
otherwise: Joi.forbidden()
|
|
1689
|
+
})
|
|
1690
|
+
.description('OAuth2 Secret Service Key'),
|
|
1691
|
+
|
|
1692
|
+
authority: Joi.string()
|
|
1693
|
+
.trim()
|
|
1694
|
+
.empty('')
|
|
1695
|
+
.max(1024)
|
|
1696
|
+
.when('provider', {
|
|
1697
|
+
switch: [
|
|
1698
|
+
{
|
|
1699
|
+
is: 'outlookService',
|
|
1700
|
+
then: Joi.string().required().invalid('common', 'organizations', 'consumers').messages({
|
|
1701
|
+
'any.invalid': 'Client credentials flow requires a specific tenant ID; "{{#value}}" is not allowed'
|
|
1702
|
+
})
|
|
1703
|
+
},
|
|
1704
|
+
{
|
|
1705
|
+
is: 'outlook',
|
|
1706
|
+
then: Joi.required()
|
|
1707
|
+
}
|
|
1708
|
+
],
|
|
1709
|
+
otherwise: Joi.optional().valid(false, null)
|
|
1710
|
+
})
|
|
1711
|
+
.example(false)
|
|
1712
|
+
.label('SupportedAccountTypes'),
|
|
1713
|
+
|
|
1714
|
+
cloud: Joi.string()
|
|
1715
|
+
.trim()
|
|
1716
|
+
.empty('')
|
|
1717
|
+
.valid('global', 'gcc-high', 'dod', 'china')
|
|
1718
|
+
.example('global')
|
|
1719
|
+
.description('Azure cloud type for Outlook OAuth2 applications')
|
|
1720
|
+
.label('AzureCloud'),
|
|
1721
|
+
|
|
1722
|
+
tenant: Joi.string().trim().empty('').max(1024).example('f8cdef31-a31e-4b4a-93e4-5f571e91255a').label('Directorytenant'),
|
|
1723
|
+
|
|
1724
|
+
redirectUrl: Joi.string()
|
|
1725
|
+
.allow('')
|
|
1726
|
+
.uri({ scheme: ['http', 'https'], allowRelative: false })
|
|
1727
|
+
.when('provider', {
|
|
1728
|
+
not: Joi.valid('gmailService', 'outlookService'),
|
|
1729
|
+
then: Joi.required(),
|
|
1730
|
+
otherwise: Joi.optional().valid(false, null)
|
|
1731
|
+
})
|
|
1732
|
+
.description('OAuth2 Callback URL')
|
|
1733
|
+
};
|
|
1734
|
+
|
|
1561
1735
|
const tokenRestrictionsSchema = Joi.object({
|
|
1562
1736
|
referrers: Joi.array()
|
|
1563
1737
|
.items(Joi.string())
|
|
@@ -1806,6 +1980,7 @@ const exportStatusSchema = Joi.object({
|
|
|
1806
1980
|
progress: exportProgressSchema,
|
|
1807
1981
|
created: Joi.date().iso().example('2024-01-15T10:30:00Z').description('When export was created'),
|
|
1808
1982
|
expiresAt: Joi.date().iso().example('2024-01-16T10:30:00Z').description('When export file expires'),
|
|
1983
|
+
truncated: Joi.boolean().example(true).description('Whether the export was truncated due to message count or size limits'),
|
|
1809
1984
|
error: Joi.string().allow(null).description('Error message if export failed')
|
|
1810
1985
|
}).label('ExportStatus');
|
|
1811
1986
|
|
|
@@ -1860,6 +2035,7 @@ module.exports = {
|
|
|
1860
2035
|
searchSchema,
|
|
1861
2036
|
messageUpdateSchema,
|
|
1862
2037
|
oauthCreateSchema,
|
|
2038
|
+
oauthUpdateSchema,
|
|
1863
2039
|
tokenRestrictionsSchema,
|
|
1864
2040
|
accountIdSchema,
|
|
1865
2041
|
ipSchema,
|
|
@@ -1880,7 +2056,8 @@ module.exports = {
|
|
|
1880
2056
|
exportStatusSchema,
|
|
1881
2057
|
exportListSchema,
|
|
1882
2058
|
exportProgressSchema,
|
|
1883
|
-
exportIdSchema
|
|
2059
|
+
exportIdSchema,
|
|
2060
|
+
pubSubErrorSchema
|
|
1884
2061
|
};
|
|
1885
2062
|
|
|
1886
2063
|
/*
|
|
@@ -22,7 +22,7 @@ const {
|
|
|
22
22
|
} = require('../tools');
|
|
23
23
|
const { Account } = require('../account');
|
|
24
24
|
const { Gateway } = require('../gateway');
|
|
25
|
-
const { oauth2Apps, oauth2ProviderData } = require('../oauth2-apps');
|
|
25
|
+
const { oauth2Apps, oauth2ProviderData, SERVICE_ACCOUNT_PROVIDERS } = require('../oauth2-apps');
|
|
26
26
|
const { autodetectImapSettings } = require('../autodetect-imap-settings');
|
|
27
27
|
const getSecret = require('../get-secret');
|
|
28
28
|
const capa = require('../capa');
|
|
@@ -538,6 +538,11 @@ function init(args) {
|
|
|
538
538
|
requestPayload.email = accountData.email;
|
|
539
539
|
}
|
|
540
540
|
|
|
541
|
+
// Service providers use client_credentials - no interactive authorization
|
|
542
|
+
if (SERVICE_ACCOUNT_PROVIDERS.has(oAuth2Client.provider)) {
|
|
543
|
+
throw Boom.badRequest('Application-only OAuth providers do not support interactive authorization');
|
|
544
|
+
}
|
|
545
|
+
|
|
541
546
|
let authorizeUrl = oAuth2Client.generateAuthUrl(requestPayload);
|
|
542
547
|
|
|
543
548
|
return h.redirect(authorizeUrl);
|
|
@@ -1352,7 +1357,7 @@ function init(args) {
|
|
|
1352
1357
|
|
|
1353
1358
|
async failAction(request, h, err) {
|
|
1354
1359
|
await request.flash({ type: 'danger', message: `Couldn't delete account. Try again.` });
|
|
1355
|
-
request.logger.error({ msg: 'Failed to delete
|
|
1360
|
+
request.logger.error({ msg: 'Failed to delete the account', err });
|
|
1356
1361
|
|
|
1357
1362
|
return h.redirect('/admin/accounts').takeover();
|
|
1358
1363
|
},
|
|
@@ -766,7 +766,7 @@ return payload;`)
|
|
|
766
766
|
|
|
767
767
|
async failAction(request, h, err) {
|
|
768
768
|
await request.flash({ type: 'danger', message: `Couldn't delete webhook. Try again.` });
|
|
769
|
-
request.logger.error({ msg: 'Failed to delete
|
|
769
|
+
request.logger.error({ msg: 'Failed to delete Webhook Route', err });
|
|
770
770
|
|
|
771
771
|
return h.redirect('/admin/webhooks').takeover();
|
|
772
772
|
},
|
|
@@ -1415,7 +1415,7 @@ return payload;`)
|
|
|
1415
1415
|
|
|
1416
1416
|
async failAction(request, h, err) {
|
|
1417
1417
|
await request.flash({ type: 'danger', message: `Couldn't delete account. Try again.` });
|
|
1418
|
-
request.logger.error({ msg: 'Failed to delete
|
|
1418
|
+
request.logger.error({ msg: 'Failed to delete the account', err });
|
|
1419
1419
|
|
|
1420
1420
|
return h.redirect('/admin/templates').takeover();
|
|
1421
1421
|
},
|
|
@@ -2122,7 +2122,7 @@ return payload;`)
|
|
|
2122
2122
|
|
|
2123
2123
|
async failAction(request, h, err) {
|
|
2124
2124
|
await request.flash({ type: 'danger', message: `Couldn't delete gateway. Try again.` });
|
|
2125
|
-
request.logger.error({ msg: 'Failed to delete
|
|
2125
|
+
request.logger.error({ msg: 'Failed to delete the gateway', err });
|
|
2126
2126
|
|
|
2127
2127
|
return h.redirect('/admin/gateways').takeover();
|
|
2128
2128
|
},
|
|
@@ -5,14 +5,22 @@ const Joi = require('joi');
|
|
|
5
5
|
|
|
6
6
|
const settings = require('../settings');
|
|
7
7
|
const { redis } = require('../db');
|
|
8
|
-
const { oauth2Apps,
|
|
8
|
+
const { oauth2Apps, OAUTH_PROVIDERS, oauth2ProviderData } = require('../oauth2-apps');
|
|
9
9
|
const getSecret = require('../get-secret');
|
|
10
10
|
const { Account } = require('../account');
|
|
11
|
-
const { oauthCreateSchema,
|
|
11
|
+
const { oauthCreateSchema, oauthUpdateSchema } = require('../schemas');
|
|
12
12
|
const consts = require('../consts');
|
|
13
13
|
|
|
14
14
|
const { DEFAULT_PAGE_SIZE } = consts;
|
|
15
15
|
|
|
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
|
+
|
|
16
24
|
const AZURE_CLOUDS = [
|
|
17
25
|
{
|
|
18
26
|
id: 'global',
|
|
@@ -39,150 +47,6 @@ const AZURE_CLOUDS = [
|
|
|
39
47
|
}
|
|
40
48
|
];
|
|
41
49
|
|
|
42
|
-
const oauthUpdateSchema = {
|
|
43
|
-
app: Joi.string().empty('').max(255).example('gmail').label('Provider').required(),
|
|
44
|
-
|
|
45
|
-
provider: Joi.string()
|
|
46
|
-
.trim()
|
|
47
|
-
.empty('')
|
|
48
|
-
.max(256)
|
|
49
|
-
.valid(...Object.keys(OAUTH_PROVIDERS))
|
|
50
|
-
.example('gmail')
|
|
51
|
-
.required()
|
|
52
|
-
.description('OAuth2 provider'),
|
|
53
|
-
|
|
54
|
-
name: Joi.string()
|
|
55
|
-
.trim()
|
|
56
|
-
.empty('')
|
|
57
|
-
.max(256)
|
|
58
|
-
.example('My Gmail App')
|
|
59
|
-
.description('Application name')
|
|
60
|
-
.when('app', {
|
|
61
|
-
not: Joi.string().valid(...LEGACY_KEYS),
|
|
62
|
-
then: Joi.required(),
|
|
63
|
-
otherwise: Joi.optional().valid(false, null)
|
|
64
|
-
}),
|
|
65
|
-
description: Joi.string().trim().allow('').max(1024).example('My cool app').description('Application description'),
|
|
66
|
-
|
|
67
|
-
title: Joi.string().allow('').trim().max(256).example('App title').description('Title for the application button'),
|
|
68
|
-
|
|
69
|
-
enabled: Joi.boolean().truthy('Y', 'true', '1', 'on').falsy('N', 'false', 0, '').default(false).description('Enable this app'),
|
|
70
|
-
|
|
71
|
-
clientId: Joi.string()
|
|
72
|
-
.trim()
|
|
73
|
-
.allow('')
|
|
74
|
-
.max(256)
|
|
75
|
-
.when('provider', {
|
|
76
|
-
not: 'gmailService',
|
|
77
|
-
then: Joi.required(),
|
|
78
|
-
otherwise: Joi.optional().valid(false, null)
|
|
79
|
-
})
|
|
80
|
-
.description('OAuth2 Client ID'),
|
|
81
|
-
|
|
82
|
-
clientSecret: Joi.string()
|
|
83
|
-
.trim()
|
|
84
|
-
.empty('', false, null)
|
|
85
|
-
.max(256)
|
|
86
|
-
.when('provider', {
|
|
87
|
-
not: 'gmailService',
|
|
88
|
-
then: Joi.optional(),
|
|
89
|
-
otherwise: Joi.forbidden()
|
|
90
|
-
})
|
|
91
|
-
.description('OAuth2 Client Secret'),
|
|
92
|
-
|
|
93
|
-
pubSubApp: Joi.string()
|
|
94
|
-
.empty('')
|
|
95
|
-
.base64({ paddingRequired: false, urlSafe: true })
|
|
96
|
-
.max(512)
|
|
97
|
-
.example('AAAAAQAACnA')
|
|
98
|
-
.description('Cloud Pub/Sub app for Gmail API webhooks'),
|
|
99
|
-
|
|
100
|
-
extraScopes: Joi.string()
|
|
101
|
-
.allow('')
|
|
102
|
-
.trim()
|
|
103
|
-
.max(10 * 1024)
|
|
104
|
-
.description('OAuth2 Extra Scopes'),
|
|
105
|
-
|
|
106
|
-
skipScopes: Joi.string()
|
|
107
|
-
.allow('')
|
|
108
|
-
.trim()
|
|
109
|
-
.max(10 * 1024)
|
|
110
|
-
.description('OAuth2 scopes to skip from the base set'),
|
|
111
|
-
|
|
112
|
-
serviceClient: Joi.string()
|
|
113
|
-
.trim()
|
|
114
|
-
.allow('')
|
|
115
|
-
.max(256)
|
|
116
|
-
.when('provider', {
|
|
117
|
-
is: 'gmailService',
|
|
118
|
-
then: Joi.required(),
|
|
119
|
-
otherwise: Joi.optional().valid(false, null)
|
|
120
|
-
})
|
|
121
|
-
.description('OAuth2 Service Client ID'),
|
|
122
|
-
|
|
123
|
-
googleProjectId: googleProjectIdSchema,
|
|
124
|
-
|
|
125
|
-
googleWorkspaceAccounts: googleWorkspaceAccountsSchema.when('provider', {
|
|
126
|
-
is: 'gmail',
|
|
127
|
-
then: Joi.optional().default(false)
|
|
128
|
-
}),
|
|
129
|
-
|
|
130
|
-
serviceClientEmail: Joi.string()
|
|
131
|
-
.trim()
|
|
132
|
-
.allow('')
|
|
133
|
-
.email()
|
|
134
|
-
.when('provider', {
|
|
135
|
-
is: 'gmailService',
|
|
136
|
-
then: Joi.required(),
|
|
137
|
-
otherwise: Joi.optional().valid(false, null)
|
|
138
|
-
})
|
|
139
|
-
.example('name@project-123.iam.gserviceaccount.com')
|
|
140
|
-
.description('Service Client Email for 2-legged OAuth2 applications'),
|
|
141
|
-
|
|
142
|
-
serviceKey: Joi.string()
|
|
143
|
-
.trim()
|
|
144
|
-
.empty('', false, null)
|
|
145
|
-
.max(100 * 1024)
|
|
146
|
-
.when('provider', {
|
|
147
|
-
is: 'gmailService',
|
|
148
|
-
then: Joi.optional(),
|
|
149
|
-
otherwise: Joi.forbidden()
|
|
150
|
-
})
|
|
151
|
-
.description('OAuth2 Secret Service Key'),
|
|
152
|
-
|
|
153
|
-
authority: Joi.string()
|
|
154
|
-
.trim()
|
|
155
|
-
.empty('')
|
|
156
|
-
.max(1024)
|
|
157
|
-
.when('provider', {
|
|
158
|
-
is: 'outlook',
|
|
159
|
-
then: Joi.required(),
|
|
160
|
-
otherwise: Joi.optional().valid(false, null)
|
|
161
|
-
})
|
|
162
|
-
.example(false)
|
|
163
|
-
.label('SupportedAccountTypes'),
|
|
164
|
-
|
|
165
|
-
cloud: Joi.string()
|
|
166
|
-
.trim()
|
|
167
|
-
.empty('')
|
|
168
|
-
.valid('global', 'gcc-high', 'dod', 'china')
|
|
169
|
-
.example('global')
|
|
170
|
-
.description('Azure cloud type for Outlook OAuth2 applications')
|
|
171
|
-
.label('AzureCloud'),
|
|
172
|
-
|
|
173
|
-
tenant: Joi.string().trim().empty('').max(1024).example('f8cdef31-a31e-4b4a-93e4-5f571e91255a').label('Directorytenant'),
|
|
174
|
-
|
|
175
|
-
redirectUrl: Joi.string()
|
|
176
|
-
.allow('')
|
|
177
|
-
.uri({ scheme: ['http', 'https'], allowRelative: false })
|
|
178
|
-
.when('provider', {
|
|
179
|
-
not: 'gmailService',
|
|
180
|
-
then: Joi.required(),
|
|
181
|
-
otherwise: Joi.optional().valid(false, null)
|
|
182
|
-
})
|
|
183
|
-
.description('OAuth2 Callback URL')
|
|
184
|
-
};
|
|
185
|
-
|
|
186
50
|
function init({ server, call }) {
|
|
187
51
|
// GET /admin/config/oauth - OAuth applications list
|
|
188
52
|
server.route({
|
|
@@ -402,6 +266,12 @@ function init({ server, call }) {
|
|
|
402
266
|
try {
|
|
403
267
|
await oauth2Apps.del(request.payload.app);
|
|
404
268
|
|
|
269
|
+
try {
|
|
270
|
+
await call({ cmd: 'googlePubSubRemove', app: request.payload.app });
|
|
271
|
+
} catch (err) {
|
|
272
|
+
request.logger.error({ msg: 'Failed to notify workers about OAuth2 app deletion', err, app: request.payload.app });
|
|
273
|
+
}
|
|
274
|
+
|
|
405
275
|
await request.flash({ type: 'info', message: `OAuth2 app deleted` });
|
|
406
276
|
|
|
407
277
|
return h.redirect('/admin/config/oauth');
|
|
@@ -421,7 +291,7 @@ function init({ server, call }) {
|
|
|
421
291
|
|
|
422
292
|
async failAction(request, h, err) {
|
|
423
293
|
await request.flash({ type: 'danger', message: `Couldn't delete OAuth2 app. Try again.` });
|
|
424
|
-
request.logger.error({ msg: 'Failed to delete
|
|
294
|
+
request.logger.error({ msg: 'Failed to delete the OAuth2 application', err });
|
|
425
295
|
|
|
426
296
|
return h.redirect('/admin/config/oauth').takeover();
|
|
427
297
|
},
|
|
@@ -442,10 +312,7 @@ function init({ server, call }) {
|
|
|
442
312
|
let providerData = oauth2ProviderData(provider);
|
|
443
313
|
|
|
444
314
|
let serviceUrl = await settings.get('serviceUrl');
|
|
445
|
-
let defaultRedirectUrl = `${serviceUrl}/oauth
|
|
446
|
-
if (provider === 'outlook') {
|
|
447
|
-
defaultRedirectUrl = defaultRedirectUrl.replace(/^http:\/\/127\.0\.0\.1\b/i, 'http://localhost');
|
|
448
|
-
}
|
|
315
|
+
let defaultRedirectUrl = normalizeOutlookRedirectUrl(`${serviceUrl}/oauth`, provider);
|
|
449
316
|
|
|
450
317
|
let pubSubApps = await oauth2Apps.list(0, 1000, { pubsub: true });
|
|
451
318
|
|
|
@@ -477,10 +344,10 @@ function init({ server, call }) {
|
|
|
477
344
|
|
|
478
345
|
values: {
|
|
479
346
|
provider,
|
|
480
|
-
redirectUrl: defaultRedirectUrl
|
|
347
|
+
...(provider !== 'outlookService' ? { redirectUrl: defaultRedirectUrl } : {})
|
|
481
348
|
},
|
|
482
349
|
|
|
483
|
-
authorityCommon:
|
|
350
|
+
authorityCommon: provider !== 'outlookService'
|
|
484
351
|
},
|
|
485
352
|
{
|
|
486
353
|
layout: 'app'
|
|
@@ -538,7 +405,7 @@ function init({ server, call }) {
|
|
|
538
405
|
throw new Error('Unexpected result');
|
|
539
406
|
}
|
|
540
407
|
|
|
541
|
-
if (oauth2App && oauth2App.pubsubUpdates && oauth2App.pubsubUpdates.
|
|
408
|
+
if (oauth2App && oauth2App.pubsubUpdates && Object.keys(oauth2App.pubsubUpdates).length > 0) {
|
|
542
409
|
await call({ cmd: 'googlePubSub', app: oauth2App.id });
|
|
543
410
|
}
|
|
544
411
|
|
|
@@ -556,10 +423,7 @@ function init({ server, call }) {
|
|
|
556
423
|
let providerData = oauth2ProviderData(provider);
|
|
557
424
|
|
|
558
425
|
let serviceUrl = await settings.get('serviceUrl');
|
|
559
|
-
let defaultRedirectUrl = `${serviceUrl}/oauth
|
|
560
|
-
if (provider === 'outlook') {
|
|
561
|
-
defaultRedirectUrl = defaultRedirectUrl.replace(/^http:\/\/127\.0\.0\.1\b/i, 'http://localhost');
|
|
562
|
-
}
|
|
426
|
+
let defaultRedirectUrl = normalizeOutlookRedirectUrl(`${serviceUrl}/oauth`, provider);
|
|
563
427
|
|
|
564
428
|
let pubSubApps = await oauth2Apps.list(0, 1000, { pubsub: true });
|
|
565
429
|
|
|
@@ -636,10 +500,7 @@ function init({ server, call }) {
|
|
|
636
500
|
let providerData = oauth2ProviderData(provider);
|
|
637
501
|
|
|
638
502
|
let serviceUrl = await settings.get('serviceUrl');
|
|
639
|
-
let defaultRedirectUrl = `${serviceUrl}/oauth
|
|
640
|
-
if (provider === 'outlook') {
|
|
641
|
-
defaultRedirectUrl = defaultRedirectUrl.replace(/^http:\/\/127\.0\.0\.1\b/i, 'http://localhost');
|
|
642
|
-
}
|
|
503
|
+
let defaultRedirectUrl = normalizeOutlookRedirectUrl(`${serviceUrl}/oauth`, provider);
|
|
643
504
|
|
|
644
505
|
let pubSubApps = await oauth2Apps.list(0, 1000, { pubsub: true });
|
|
645
506
|
|
|
@@ -708,10 +569,7 @@ function init({ server, call }) {
|
|
|
708
569
|
|
|
709
570
|
let providerData = oauth2ProviderData(appData.provider, appData.cloud);
|
|
710
571
|
let serviceUrl = await settings.get('serviceUrl');
|
|
711
|
-
let defaultRedirectUrl = `${serviceUrl}/oauth
|
|
712
|
-
if (providerData.provider === 'outlook') {
|
|
713
|
-
defaultRedirectUrl = defaultRedirectUrl.replace(/^http:\/\/127\.0\.0\.1\b/i, 'http://localhost');
|
|
714
|
-
}
|
|
572
|
+
let defaultRedirectUrl = normalizeOutlookRedirectUrl(`${serviceUrl}/oauth`, providerData.provider);
|
|
715
573
|
|
|
716
574
|
let values = Object.assign({}, appData, {
|
|
717
575
|
clientSecret: '',
|
|
@@ -824,7 +682,7 @@ function init({ server, call }) {
|
|
|
824
682
|
throw new Error('Unexpected result');
|
|
825
683
|
}
|
|
826
684
|
|
|
827
|
-
if (oauth2App && oauth2App.pubsubUpdates && oauth2App.pubsubUpdates.
|
|
685
|
+
if (oauth2App && oauth2App.pubsubUpdates && Object.keys(oauth2App.pubsubUpdates).length > 0) {
|
|
828
686
|
await call({ cmd: 'googlePubSub', app: oauth2App.id });
|
|
829
687
|
}
|
|
830
688
|
|
|
@@ -837,10 +695,7 @@ function init({ server, call }) {
|
|
|
837
695
|
let providerData = oauth2ProviderData(appData.provider, appData.cloud);
|
|
838
696
|
|
|
839
697
|
let serviceUrl = await settings.get('serviceUrl');
|
|
840
|
-
let defaultRedirectUrl = `${serviceUrl}/oauth
|
|
841
|
-
if (appData.provider === 'outlook') {
|
|
842
|
-
defaultRedirectUrl = defaultRedirectUrl.replace(/^http:\/\/127\.0\.0\.1\b/i, 'http://localhost');
|
|
843
|
-
}
|
|
698
|
+
let defaultRedirectUrl = normalizeOutlookRedirectUrl(`${serviceUrl}/oauth`, appData.provider);
|
|
844
699
|
|
|
845
700
|
let pubSubApps = await oauth2Apps.list(0, 1000, { pubsub: true });
|
|
846
701
|
|
|
@@ -926,10 +781,7 @@ function init({ server, call }) {
|
|
|
926
781
|
let providerData = oauth2ProviderData(provider);
|
|
927
782
|
|
|
928
783
|
let serviceUrl = await settings.get('serviceUrl');
|
|
929
|
-
let defaultRedirectUrl = `${serviceUrl}/oauth
|
|
930
|
-
if (provider === 'outlook') {
|
|
931
|
-
defaultRedirectUrl = defaultRedirectUrl.replace(/^http:\/\/127\.0\.0\.1\b/i, 'http://localhost');
|
|
932
|
-
}
|
|
784
|
+
let defaultRedirectUrl = normalizeOutlookRedirectUrl(`${serviceUrl}/oauth`, provider);
|
|
933
785
|
|
|
934
786
|
let pubSubApps = await oauth2Apps.list(0, 1000, { pubsub: true });
|
|
935
787
|
|