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
|
@@ -0,0 +1,631 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const Joi = require('joi');
|
|
4
|
+
const { failAction } = require('../tools');
|
|
5
|
+
const { oauth2Apps } = require('../oauth2-apps');
|
|
6
|
+
const { verifyOAuth2App } = require('../oauth/verify-app');
|
|
7
|
+
const {
|
|
8
|
+
oauthCreateSchema,
|
|
9
|
+
lastErrorSchema,
|
|
10
|
+
pubSubErrorSchema,
|
|
11
|
+
googleProjectIdSchema,
|
|
12
|
+
googleWorkspaceAccountsSchema,
|
|
13
|
+
googleTopicNameSchema,
|
|
14
|
+
googleSubscriptionNameSchema
|
|
15
|
+
} = require('../schemas');
|
|
16
|
+
const { handleError, flattenOAuthAppMeta } = require('./route-helpers');
|
|
17
|
+
|
|
18
|
+
async function init(args) {
|
|
19
|
+
const { server, call, CORS_CONFIG, OAuth2ProviderSchema } = args;
|
|
20
|
+
|
|
21
|
+
server.route({
|
|
22
|
+
method: 'GET',
|
|
23
|
+
path: '/v1/oauth2',
|
|
24
|
+
|
|
25
|
+
async handler(request) {
|
|
26
|
+
try {
|
|
27
|
+
let response = await oauth2Apps.list(request.query.page, request.query.pageSize);
|
|
28
|
+
|
|
29
|
+
for (let app of response.apps) {
|
|
30
|
+
for (let secretKey of ['clientSecret', 'serviceKey', 'accessToken', 'externalAccount']) {
|
|
31
|
+
if (app[secretKey]) {
|
|
32
|
+
app[secretKey] = '******';
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (app.extraScopes && !app.extraScopes.length) {
|
|
37
|
+
delete app.extraScopes;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (app.app) {
|
|
41
|
+
delete app.app;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
flattenOAuthAppMeta(app);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return response;
|
|
48
|
+
} catch (err) {
|
|
49
|
+
handleError(request, err);
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
options: {
|
|
54
|
+
description: 'List OAuth2 applications',
|
|
55
|
+
notes: 'Lists registered OAuth2 applications',
|
|
56
|
+
tags: ['api', 'OAuth2 Applications'],
|
|
57
|
+
|
|
58
|
+
plugins: {},
|
|
59
|
+
|
|
60
|
+
auth: {
|
|
61
|
+
strategy: 'api-token',
|
|
62
|
+
mode: 'required'
|
|
63
|
+
},
|
|
64
|
+
cors: CORS_CONFIG,
|
|
65
|
+
|
|
66
|
+
validate: {
|
|
67
|
+
options: {
|
|
68
|
+
stripUnknown: false,
|
|
69
|
+
abortEarly: false,
|
|
70
|
+
convert: true
|
|
71
|
+
},
|
|
72
|
+
failAction,
|
|
73
|
+
|
|
74
|
+
query: Joi.object({
|
|
75
|
+
page: Joi.number()
|
|
76
|
+
.integer()
|
|
77
|
+
.min(0)
|
|
78
|
+
.max(1024 * 1024)
|
|
79
|
+
.default(0)
|
|
80
|
+
.example(0)
|
|
81
|
+
.description('Page number (zero indexed, so use 0 for first page)')
|
|
82
|
+
.label('PageNumber'),
|
|
83
|
+
pageSize: Joi.number().integer().min(1).max(1000).default(20).example(20).description('How many entries per page').label('PageSize')
|
|
84
|
+
}).label('GatewaysFilter')
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
response: {
|
|
88
|
+
schema: Joi.object({
|
|
89
|
+
total: Joi.number().integer().example(120).description('How many matching entries').label('TotalNumber'),
|
|
90
|
+
page: Joi.number().integer().example(0).description('Current page (0-based index)').label('PageNumber'),
|
|
91
|
+
pages: Joi.number().integer().example(24).description('Total page count').label('PagesNumber'),
|
|
92
|
+
|
|
93
|
+
apps: Joi.array()
|
|
94
|
+
.items(
|
|
95
|
+
Joi.object({
|
|
96
|
+
id: Joi.string().max(256).required().example('AAABhaBPHscAAAAH').description('OAuth2 application ID'),
|
|
97
|
+
name: Joi.string().max(256).example('My OAuth2 App').description('Display name for the app'),
|
|
98
|
+
description: Joi.string().empty('').trim().max(1024).example('App description').description('OAuth2 application description'),
|
|
99
|
+
title: Joi.string().empty('').trim().max(256).example('App title').description('Title for the application button'),
|
|
100
|
+
provider: OAuth2ProviderSchema,
|
|
101
|
+
enabled: Joi.boolean()
|
|
102
|
+
.truthy('Y', 'true', '1', 'on')
|
|
103
|
+
.falsy('N', 'false', 0, '')
|
|
104
|
+
.example(true)
|
|
105
|
+
.description('Is the application enabled')
|
|
106
|
+
.label('AppEnabled'),
|
|
107
|
+
legacy: Joi.boolean()
|
|
108
|
+
.truthy('Y', 'true', '1', 'on')
|
|
109
|
+
.falsy('N', 'false', 0, '')
|
|
110
|
+
.example(true)
|
|
111
|
+
.description('`true` for older OAuth2 apps set via the settings endpoint'),
|
|
112
|
+
created: Joi.date().iso().example('2021-02-17T13:43:18.860Z').description('The time this entry was added').required(),
|
|
113
|
+
updated: Joi.date().iso().example('2021-02-17T13:43:18.860Z').description('The time this entry was updated'),
|
|
114
|
+
includeInListing: Joi.boolean()
|
|
115
|
+
.truthy('Y', 'true', '1', 'on')
|
|
116
|
+
.falsy('N', 'false', 0, '')
|
|
117
|
+
.example(true)
|
|
118
|
+
.description('Is the application listed in the hosted authentication form'),
|
|
119
|
+
|
|
120
|
+
clientId: Joi.string()
|
|
121
|
+
.example('4f05f488-d858-4f2c-bd12-1039062612fe')
|
|
122
|
+
.description('Client or Application ID for 3-legged OAuth2 applications')
|
|
123
|
+
.label('OAuth2AppListClientId'),
|
|
124
|
+
clientSecret: Joi.string()
|
|
125
|
+
.example('******')
|
|
126
|
+
.description('Client secret for 3-legged OAuth2 applications. Actual value is not revealed.'),
|
|
127
|
+
authority: Joi.string().example('common').description('Authorization tenant value for Outlook OAuth2 applications'),
|
|
128
|
+
redirectUrl: Joi.string()
|
|
129
|
+
.uri({
|
|
130
|
+
scheme: ['http', 'https'],
|
|
131
|
+
allowRelative: false
|
|
132
|
+
})
|
|
133
|
+
.example('https://myservice.com/oauth')
|
|
134
|
+
.description('Redirect URL for 3-legged OAuth2 applications')
|
|
135
|
+
.label('OAuth2AppListRedirectUrl'),
|
|
136
|
+
|
|
137
|
+
serviceClient: Joi.string()
|
|
138
|
+
.example('9103965568215821627203')
|
|
139
|
+
.description('Service client ID for 2-legged OAuth2 applications')
|
|
140
|
+
.label('OAuth2AppListServiceClient'),
|
|
141
|
+
|
|
142
|
+
googleProjectId: googleProjectIdSchema,
|
|
143
|
+
googleWorkspaceAccounts: googleWorkspaceAccountsSchema,
|
|
144
|
+
googleTopicName: googleTopicNameSchema,
|
|
145
|
+
googleSubscriptionName: googleSubscriptionNameSchema,
|
|
146
|
+
|
|
147
|
+
serviceClientEmail: Joi.string()
|
|
148
|
+
.email()
|
|
149
|
+
.example('name@project-123.iam.gserviceaccount.com')
|
|
150
|
+
.description('Service Client Email for 2-legged OAuth2 applications'),
|
|
151
|
+
|
|
152
|
+
serviceKey: Joi.string()
|
|
153
|
+
.example('******')
|
|
154
|
+
.description('PEM formatted service secret for 2-legged OAuth2 applications. Actual value is not revealed.'),
|
|
155
|
+
|
|
156
|
+
lastError: lastErrorSchema.allow(null),
|
|
157
|
+
pubSubError: pubSubErrorSchema.allow(null)
|
|
158
|
+
}).label('OAuth2ResponseItem')
|
|
159
|
+
)
|
|
160
|
+
.label('OAuth2Entries')
|
|
161
|
+
}).label('OAuth2FilterResponse'),
|
|
162
|
+
failAction: 'log'
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
server.route({
|
|
168
|
+
method: 'GET',
|
|
169
|
+
path: '/v1/oauth2/{app}',
|
|
170
|
+
|
|
171
|
+
async handler(request) {
|
|
172
|
+
try {
|
|
173
|
+
let app = await oauth2Apps.get(request.params.app);
|
|
174
|
+
|
|
175
|
+
// remove secrets
|
|
176
|
+
for (let secretKey of ['clientSecret', 'serviceKey', 'accessToken', 'externalAccount']) {
|
|
177
|
+
if (app[secretKey]) {
|
|
178
|
+
app[secretKey] = '******';
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (app.extraScopes && !app.extraScopes.length) {
|
|
183
|
+
delete app.extraScopes;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (app.app) {
|
|
187
|
+
delete app.app;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
flattenOAuthAppMeta(app);
|
|
191
|
+
|
|
192
|
+
return app;
|
|
193
|
+
} catch (err) {
|
|
194
|
+
handleError(request, err);
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
options: {
|
|
198
|
+
description: 'Get application info',
|
|
199
|
+
notes: 'Returns stored information about an OAuth2 application. Secrets are not included.',
|
|
200
|
+
tags: ['api', 'OAuth2 Applications'],
|
|
201
|
+
|
|
202
|
+
auth: {
|
|
203
|
+
strategy: 'api-token',
|
|
204
|
+
mode: 'required'
|
|
205
|
+
},
|
|
206
|
+
cors: CORS_CONFIG,
|
|
207
|
+
|
|
208
|
+
validate: {
|
|
209
|
+
options: {
|
|
210
|
+
stripUnknown: false,
|
|
211
|
+
abortEarly: false,
|
|
212
|
+
convert: true
|
|
213
|
+
},
|
|
214
|
+
failAction,
|
|
215
|
+
|
|
216
|
+
params: Joi.object({
|
|
217
|
+
app: Joi.string().max(256).required().example('AAABhaBPHscAAAAH').description('OAuth2 application ID')
|
|
218
|
+
})
|
|
219
|
+
},
|
|
220
|
+
|
|
221
|
+
response: {
|
|
222
|
+
schema: Joi.object({
|
|
223
|
+
id: Joi.string().max(256).required().example('AAABhaBPHscAAAAH').description('OAuth2 application ID'),
|
|
224
|
+
name: Joi.string().max(256).example('My OAuth2 App').description('Display name for the app'),
|
|
225
|
+
description: Joi.string().empty('').trim().max(1024).example('App description').description('OAuth2 application description'),
|
|
226
|
+
title: Joi.string().empty('').trim().max(256).example('App title').description('Title for the application button'),
|
|
227
|
+
provider: OAuth2ProviderSchema,
|
|
228
|
+
enabled: Joi.boolean()
|
|
229
|
+
.truthy('Y', 'true', '1', 'on')
|
|
230
|
+
.falsy('N', 'false', 0, '')
|
|
231
|
+
.example(true)
|
|
232
|
+
.description('Is the application enabled')
|
|
233
|
+
.label('AppEnabled'),
|
|
234
|
+
legacy: Joi.boolean()
|
|
235
|
+
.truthy('Y', 'true', '1', 'on')
|
|
236
|
+
.falsy('N', 'false', 0, '')
|
|
237
|
+
.example(true)
|
|
238
|
+
.description('`true` for older OAuth2 apps set via the settings endpoint'),
|
|
239
|
+
created: Joi.date().iso().example('2021-02-17T13:43:18.860Z').description('The time this entry was added').required(),
|
|
240
|
+
updated: Joi.date().iso().example('2021-02-17T13:43:18.860Z').description('The time this entry was updated'),
|
|
241
|
+
includeInListing: Joi.boolean()
|
|
242
|
+
.truthy('Y', 'true', '1', 'on')
|
|
243
|
+
.falsy('N', 'false', 0, '')
|
|
244
|
+
.example(true)
|
|
245
|
+
.description('Is the application listed in the hosted authentication form'),
|
|
246
|
+
|
|
247
|
+
clientId: Joi.string()
|
|
248
|
+
.example('4f05f488-d858-4f2c-bd12-1039062612fe')
|
|
249
|
+
.description('Client or Application ID for 3-legged OAuth2 applications')
|
|
250
|
+
.label('OAuth2AppGetClientId'),
|
|
251
|
+
clientSecret: Joi.string().example('******').description('Client secret for 3-legged OAuth2 applications. Actual value is not revealed.'),
|
|
252
|
+
authority: Joi.string().example('common').description('Authorization tenant value for Outlook OAuth2 applications'),
|
|
253
|
+
redirectUrl: Joi.string()
|
|
254
|
+
.uri({
|
|
255
|
+
scheme: ['http', 'https'],
|
|
256
|
+
allowRelative: false
|
|
257
|
+
})
|
|
258
|
+
.example('https://myservice.com/oauth')
|
|
259
|
+
.description('Redirect URL for 3-legged OAuth2 applications')
|
|
260
|
+
.label('OAuth2AppGetRedirectUrl'),
|
|
261
|
+
|
|
262
|
+
googleProjectId: googleProjectIdSchema,
|
|
263
|
+
googleWorkspaceAccounts: googleWorkspaceAccountsSchema,
|
|
264
|
+
googleTopicName: googleTopicNameSchema,
|
|
265
|
+
googleSubscriptionName: googleSubscriptionNameSchema,
|
|
266
|
+
|
|
267
|
+
serviceClientEmail: Joi.string()
|
|
268
|
+
.email()
|
|
269
|
+
.example('name@project-123.iam.gserviceaccount.com')
|
|
270
|
+
.description('Service Client Email for 2-legged OAuth2 applications'),
|
|
271
|
+
|
|
272
|
+
serviceClient: Joi.string()
|
|
273
|
+
.example('9103965568215821627203')
|
|
274
|
+
.description('Service client ID for 2-legged OAuth2 applications')
|
|
275
|
+
.label('OAuth2AppGetServiceClient'),
|
|
276
|
+
|
|
277
|
+
serviceKey: Joi.string()
|
|
278
|
+
.example('******')
|
|
279
|
+
.description('PEM formatted service secret for 2-legged OAuth2 applications. Actual value is not revealed.'),
|
|
280
|
+
|
|
281
|
+
accounts: Joi.number()
|
|
282
|
+
.integer()
|
|
283
|
+
.example(12)
|
|
284
|
+
.description('The number of accounts registered with this application. Not available for legacy apps.'),
|
|
285
|
+
|
|
286
|
+
lastError: lastErrorSchema.allow(null),
|
|
287
|
+
pubSubError: pubSubErrorSchema.allow(null)
|
|
288
|
+
}).label('ApplicationResponse'),
|
|
289
|
+
failAction: 'log'
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
server.route({
|
|
295
|
+
method: 'POST',
|
|
296
|
+
path: '/v1/oauth2',
|
|
297
|
+
|
|
298
|
+
async handler(request) {
|
|
299
|
+
try {
|
|
300
|
+
let result = await oauth2Apps.create(request.payload);
|
|
301
|
+
|
|
302
|
+
if (result && result.pubsubUpdates && Object.keys(result.pubsubUpdates).length > 0) {
|
|
303
|
+
await call({ cmd: 'googlePubSub', app: result.id });
|
|
304
|
+
delete result.pubsubUpdates;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return result;
|
|
308
|
+
} catch (err) {
|
|
309
|
+
handleError(request, err);
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
|
|
313
|
+
options: {
|
|
314
|
+
description: 'Register OAuth2 application',
|
|
315
|
+
notes: 'Registers a new OAuth2 application for a specific provider',
|
|
316
|
+
tags: ['api', 'OAuth2 Applications'],
|
|
317
|
+
|
|
318
|
+
plugins: {},
|
|
319
|
+
|
|
320
|
+
auth: {
|
|
321
|
+
strategy: 'api-token',
|
|
322
|
+
mode: 'required'
|
|
323
|
+
},
|
|
324
|
+
cors: CORS_CONFIG,
|
|
325
|
+
|
|
326
|
+
validate: {
|
|
327
|
+
options: {
|
|
328
|
+
stripUnknown: false,
|
|
329
|
+
abortEarly: false,
|
|
330
|
+
convert: true
|
|
331
|
+
},
|
|
332
|
+
failAction,
|
|
333
|
+
|
|
334
|
+
payload: Joi.object(oauthCreateSchema).tailor('api').label('CreateOAuth2App')
|
|
335
|
+
},
|
|
336
|
+
|
|
337
|
+
response: {
|
|
338
|
+
schema: Joi.object({
|
|
339
|
+
id: Joi.string().max(256).required().example('AAABhaBPHscAAAAH').description('OAuth2 application ID'),
|
|
340
|
+
created: Joi.boolean().truthy('Y', 'true', '1').falsy('N', 'false', 0).default(true).description('Was the app created')
|
|
341
|
+
}).label('CreateAppResponse'),
|
|
342
|
+
failAction: 'log'
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
server.route({
|
|
348
|
+
method: 'PUT',
|
|
349
|
+
path: '/v1/oauth2/{app}',
|
|
350
|
+
|
|
351
|
+
async handler(request) {
|
|
352
|
+
try {
|
|
353
|
+
let result = await oauth2Apps.update(request.params.app, request.payload);
|
|
354
|
+
|
|
355
|
+
if (result && result.pubsubUpdates && Object.keys(result.pubsubUpdates).length > 0) {
|
|
356
|
+
await call({ cmd: 'googlePubSub', app: result.id });
|
|
357
|
+
delete result.pubsubUpdates;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return result;
|
|
361
|
+
} catch (err) {
|
|
362
|
+
handleError(request, err);
|
|
363
|
+
}
|
|
364
|
+
},
|
|
365
|
+
options: {
|
|
366
|
+
description: 'Update OAuth2 application',
|
|
367
|
+
notes: 'Updates OAuth2 application information',
|
|
368
|
+
tags: ['api', 'OAuth2 Applications'],
|
|
369
|
+
|
|
370
|
+
plugins: {},
|
|
371
|
+
|
|
372
|
+
auth: {
|
|
373
|
+
strategy: 'api-token',
|
|
374
|
+
mode: 'required'
|
|
375
|
+
},
|
|
376
|
+
cors: CORS_CONFIG,
|
|
377
|
+
|
|
378
|
+
validate: {
|
|
379
|
+
options: {
|
|
380
|
+
stripUnknown: false,
|
|
381
|
+
abortEarly: false,
|
|
382
|
+
convert: true
|
|
383
|
+
},
|
|
384
|
+
failAction,
|
|
385
|
+
|
|
386
|
+
params: Joi.object({
|
|
387
|
+
app: Joi.string().max(256).required().example('AAABhaBPHscAAAAH').description('OAuth2 application ID')
|
|
388
|
+
}),
|
|
389
|
+
|
|
390
|
+
payload: Joi.object({
|
|
391
|
+
name: Joi.string().trim().empty('').max(256).example('My Gmail App').description('Application name'),
|
|
392
|
+
description: Joi.string().trim().allow('').max(1024).example('My cool app').description('Application description'),
|
|
393
|
+
title: Joi.string().allow('').trim().max(256).example('App title').description('Title for the application button'),
|
|
394
|
+
|
|
395
|
+
enabled: Joi.boolean().truthy('Y', 'true', '1', 'on').falsy('N', 'false', 0, '').example(true).description('Enable this app'),
|
|
396
|
+
|
|
397
|
+
clientId: Joi.string()
|
|
398
|
+
.trim()
|
|
399
|
+
.allow('', null, false)
|
|
400
|
+
.max(256)
|
|
401
|
+
.example('52422112755-3uov8bjwlrullq122rdm6l8ui25ho7qf.apps.googleusercontent.com')
|
|
402
|
+
.description('Client or Application ID for 3-legged OAuth2 applications')
|
|
403
|
+
.label('UpdateOAuth2ClientId'),
|
|
404
|
+
|
|
405
|
+
clientSecret: Joi.string()
|
|
406
|
+
.trim()
|
|
407
|
+
.empty('')
|
|
408
|
+
.max(256)
|
|
409
|
+
.example('boT7Q~dUljnfFdVuqpC11g8nGMjO8kpRAv-ZB')
|
|
410
|
+
.description('Client secret for 3-legged OAuth2 applications'),
|
|
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
|
+
.label('UpdatePubSubAppId'),
|
|
419
|
+
|
|
420
|
+
extraScopes: Joi.array()
|
|
421
|
+
.items(Joi.string().trim().max(255).example('User.Read').label('UpdateExtraScopeEntry'))
|
|
422
|
+
.description('OAuth2 Extra Scopes')
|
|
423
|
+
.label('UpdateOAuth2ExtraScopes'),
|
|
424
|
+
|
|
425
|
+
skipScopes: Joi.array()
|
|
426
|
+
.items(Joi.string().trim().max(255).example('SMTP.Send').label('UpdateSkipScopeEntry'))
|
|
427
|
+
.description('OAuth2 scopes to skip from the base set')
|
|
428
|
+
.label('UpdateOAuth2SkipScopes'),
|
|
429
|
+
|
|
430
|
+
serviceClient: Joi.string()
|
|
431
|
+
.trim()
|
|
432
|
+
.allow('', null, false)
|
|
433
|
+
.max(256)
|
|
434
|
+
.example('7103296518315821565203')
|
|
435
|
+
.description('Service client ID for 2-legged OAuth2 applications')
|
|
436
|
+
.label('UpdateServiceClient'),
|
|
437
|
+
|
|
438
|
+
googleProjectId: googleProjectIdSchema,
|
|
439
|
+
googleWorkspaceAccounts: googleWorkspaceAccountsSchema,
|
|
440
|
+
googleTopicName: googleTopicNameSchema,
|
|
441
|
+
googleSubscriptionName: googleSubscriptionNameSchema,
|
|
442
|
+
|
|
443
|
+
serviceClientEmail: Joi.string()
|
|
444
|
+
.email()
|
|
445
|
+
.example('name@project-123.iam.gserviceaccount.com')
|
|
446
|
+
.description('Service Client Email for 2-legged OAuth2 applications'),
|
|
447
|
+
|
|
448
|
+
serviceKey: Joi.string()
|
|
449
|
+
.trim()
|
|
450
|
+
.empty('')
|
|
451
|
+
.max(100 * 1024)
|
|
452
|
+
.example('-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgk...')
|
|
453
|
+
.description('PEM formatted service secret for 2-legged OAuth2 applications'),
|
|
454
|
+
|
|
455
|
+
authority: Joi.string()
|
|
456
|
+
.trim()
|
|
457
|
+
.empty('')
|
|
458
|
+
.max(1024)
|
|
459
|
+
.example('common')
|
|
460
|
+
.description('Authorization tenant value for Outlook OAuth2 applications')
|
|
461
|
+
.label('SupportedAccountTypes'),
|
|
462
|
+
|
|
463
|
+
cloud: Joi.string()
|
|
464
|
+
.trim()
|
|
465
|
+
.empty('')
|
|
466
|
+
.valid('global', 'gcc-high', 'dod', 'china')
|
|
467
|
+
.example('global')
|
|
468
|
+
.description('Azure cloud type for Outlook OAuth2 applications')
|
|
469
|
+
.label('AzureCloud'),
|
|
470
|
+
|
|
471
|
+
tenant: Joi.string().trim().empty('').max(1024).example('f8cdef31-a31e-4b4a-93e4-5f571e91255a').label('Directorytenant'),
|
|
472
|
+
|
|
473
|
+
redirectUrl: Joi.string()
|
|
474
|
+
.allow('', null, false)
|
|
475
|
+
.uri({ scheme: ['http', 'https'], allowRelative: false })
|
|
476
|
+
.example('https://myservice.com/oauth')
|
|
477
|
+
.description('Redirect URL for 3-legged OAuth2 applications')
|
|
478
|
+
.label('UpdateOAuth2RedirectUrl')
|
|
479
|
+
}).label('UpdateOAuthApp')
|
|
480
|
+
},
|
|
481
|
+
|
|
482
|
+
response: {
|
|
483
|
+
schema: Joi.object({
|
|
484
|
+
id: Joi.string().max(256).required().example('example').description('OAuth2 app ID')
|
|
485
|
+
}).label('UpdateOAuthAppResponse'),
|
|
486
|
+
failAction: 'log'
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
server.route({
|
|
492
|
+
method: 'DELETE',
|
|
493
|
+
path: '/v1/oauth2/{app}',
|
|
494
|
+
|
|
495
|
+
async handler(request) {
|
|
496
|
+
try {
|
|
497
|
+
let result = await oauth2Apps.del(request.params.app);
|
|
498
|
+
|
|
499
|
+
try {
|
|
500
|
+
await call({ cmd: 'googlePubSubRemove', app: request.params.app });
|
|
501
|
+
} catch (err) {
|
|
502
|
+
request.logger.error({ msg: 'Failed to notify workers about OAuth2 app deletion', err, app: request.params.app });
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
return result;
|
|
506
|
+
} catch (err) {
|
|
507
|
+
handleError(request, err);
|
|
508
|
+
}
|
|
509
|
+
},
|
|
510
|
+
options: {
|
|
511
|
+
description: 'Remove OAuth2 application',
|
|
512
|
+
notes: 'Delete OAuth2 application data',
|
|
513
|
+
tags: ['api', 'OAuth2 Applications'],
|
|
514
|
+
|
|
515
|
+
plugins: {},
|
|
516
|
+
|
|
517
|
+
auth: {
|
|
518
|
+
strategy: 'api-token',
|
|
519
|
+
mode: 'required'
|
|
520
|
+
},
|
|
521
|
+
cors: CORS_CONFIG,
|
|
522
|
+
|
|
523
|
+
validate: {
|
|
524
|
+
options: {
|
|
525
|
+
stripUnknown: false,
|
|
526
|
+
abortEarly: false,
|
|
527
|
+
convert: true
|
|
528
|
+
},
|
|
529
|
+
failAction,
|
|
530
|
+
|
|
531
|
+
params: Joi.object({
|
|
532
|
+
app: Joi.string().max(256).required().example('AAABhaBPHscAAAAH').description('OAuth2 application ID')
|
|
533
|
+
}).label('DeleteAppRequest')
|
|
534
|
+
},
|
|
535
|
+
|
|
536
|
+
response: {
|
|
537
|
+
schema: Joi.object({
|
|
538
|
+
id: Joi.string().max(256).required().example('AAABhaBPHscAAAAH').description('OAuth2 application ID'),
|
|
539
|
+
deleted: Joi.boolean().truthy('Y', 'true', '1').falsy('N', 'false', 0).default(true).description('Was the OAuth2 application deleted'),
|
|
540
|
+
accounts: Joi.number()
|
|
541
|
+
.integer()
|
|
542
|
+
.example(12)
|
|
543
|
+
.description('The number of accounts registered with this application. Not available for legacy apps.')
|
|
544
|
+
}).label('DeleteAppRequestResponse'),
|
|
545
|
+
failAction: 'log'
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
server.route({
|
|
551
|
+
method: 'POST',
|
|
552
|
+
path: '/v1/oauth2/{app}/verify',
|
|
553
|
+
|
|
554
|
+
async handler(request) {
|
|
555
|
+
try {
|
|
556
|
+
return await verifyOAuth2App(request.params.app, {
|
|
557
|
+
account: request.payload.account,
|
|
558
|
+
testConnection: request.payload.testConnection
|
|
559
|
+
});
|
|
560
|
+
} catch (err) {
|
|
561
|
+
handleError(request, err);
|
|
562
|
+
}
|
|
563
|
+
},
|
|
564
|
+
options: {
|
|
565
|
+
description: 'Verify OAuth2 application setup',
|
|
566
|
+
notes: 'Runs the provider authentication chain step by step and reports which steps pass or fail, with hints for fixing failures. For service-account apps an optional mailbox address enables the delegation and live mailbox checks.',
|
|
567
|
+
tags: ['api', 'OAuth2 Applications'],
|
|
568
|
+
|
|
569
|
+
plugins: {},
|
|
570
|
+
|
|
571
|
+
auth: {
|
|
572
|
+
strategy: 'api-token',
|
|
573
|
+
mode: 'required'
|
|
574
|
+
},
|
|
575
|
+
cors: CORS_CONFIG,
|
|
576
|
+
|
|
577
|
+
validate: {
|
|
578
|
+
options: {
|
|
579
|
+
stripUnknown: false,
|
|
580
|
+
abortEarly: false,
|
|
581
|
+
convert: true
|
|
582
|
+
},
|
|
583
|
+
failAction,
|
|
584
|
+
|
|
585
|
+
params: Joi.object({
|
|
586
|
+
app: Joi.string().max(256).required().example('AAABhaBPHscAAAAH').description('OAuth2 application ID')
|
|
587
|
+
}),
|
|
588
|
+
|
|
589
|
+
payload: Joi.object({
|
|
590
|
+
account: Joi.string()
|
|
591
|
+
.trim()
|
|
592
|
+
.empty('')
|
|
593
|
+
.max(256)
|
|
594
|
+
.example('user@example.com')
|
|
595
|
+
.description('Mailbox address used to verify domain-wide delegation and live mailbox access'),
|
|
596
|
+
testConnection: Joi.boolean()
|
|
597
|
+
.truthy('Y', 'true', '1', 'on')
|
|
598
|
+
.falsy('N', 'false', 0, '')
|
|
599
|
+
.default(true)
|
|
600
|
+
.description('Perform the live IMAP/API connection step when an access token is obtained')
|
|
601
|
+
}).label('VerifyOAuth2AppRequest')
|
|
602
|
+
},
|
|
603
|
+
|
|
604
|
+
response: {
|
|
605
|
+
schema: Joi.object({
|
|
606
|
+
app: Joi.string().max(256).required().example('AAABhaBPHscAAAAH').description('OAuth2 application ID'),
|
|
607
|
+
provider: Joi.string().example('gmailService').description('Provider type'),
|
|
608
|
+
authMethod: Joi.string().allow(null).example('externalAccount').description('Authentication method for service-account apps'),
|
|
609
|
+
account: Joi.string().allow(null).example('user@example.com').description('Mailbox used for the delegation/mailbox checks'),
|
|
610
|
+
ok: Joi.boolean().example(true).description('True when no verification step failed'),
|
|
611
|
+
steps: Joi.array()
|
|
612
|
+
.items(
|
|
613
|
+
Joi.object({
|
|
614
|
+
id: Joi.string().example('signJwt').description('Step identifier'),
|
|
615
|
+
label: Joi.string().example('Sign assertion (signJwt)').description('Human readable step name'),
|
|
616
|
+
status: Joi.string().valid('ok', 'fail', 'skip').example('ok').description('Step outcome'),
|
|
617
|
+
message: Joi.string().allow(null).example('Assertion signed via IAM signJwt').description('Outcome detail'),
|
|
618
|
+
hint: Joi.string()
|
|
619
|
+
.example('Grant roles/iam.serviceAccountTokenCreator to the workload principal')
|
|
620
|
+
.description('How to fix a failed step')
|
|
621
|
+
}).label('OAuth2VerifyStep')
|
|
622
|
+
)
|
|
623
|
+
.label('OAuth2VerifySteps')
|
|
624
|
+
}).label('VerifyOAuth2AppResponse'),
|
|
625
|
+
failAction: 'log'
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
module.exports = init;
|