emailengine-app 2.67.3 → 2.68.1
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 +6 -0
- package/.github/workflows/test.yml +6 -0
- package/CHANGELOG.md +36 -0
- package/SECURITY.md +80 -0
- package/SECURITY.txt +27 -0
- package/data/google-crawlers.json +7 -1
- package/lib/account.js +24 -1
- package/lib/api-routes/account-routes.js +12 -2
- package/lib/email-client/base-client.js +26 -20
- package/lib/email-client/gmail-client.js +14 -12
- package/lib/imapproxy/imap-core/lib/imap-command.js +1 -1
- package/lib/imapproxy/imap-core/lib/imap-connection.js +7 -0
- package/lib/imapproxy/imap-core/lib/imap-server.js +1 -1
- package/lib/imapproxy/imap-server.js +92 -29
- package/lib/oauth/external-account-config.js +132 -0
- package/lib/oauth/external-account-signer.js +256 -0
- package/lib/oauth/gmail.js +113 -14
- package/lib/oauth/verify-app.js +397 -0
- package/lib/oauth2-apps.js +51 -6
- package/lib/routes-ui.js +153 -1
- package/lib/schemas.js +80 -2
- package/lib/settings.js +1 -0
- package/lib/tools.js +15 -10
- package/package.json +28 -28
- package/sbom.json +1 -1
- package/server.js +3 -3
- package/static/js/ace/ace.js +1 -1
- package/static/js/ace/ext-searchbox.js +1 -1
- package/static/js/ace/mode-handlebars.js +1 -1
- package/static/js/ace/mode-html.js +1 -1
- package/static/js/ace/mode-javascript.js +1 -1
- package/static/js/ace/mode-markdown.js +1 -1
- package/static/js/ace/worker-html.js +1 -1
- package/static/js/ace/worker-javascript.js +1 -1
- package/static/js/ace/worker-json.js +1 -1
- package/static/licenses.html +145 -115
- package/translations/messages.pot +49 -49
- package/views/config/oauth/app.hbs +224 -0
- package/views/config/oauth/edit.hbs +69 -0
- package/views/config/oauth/new.hbs +69 -0
- package/views/partials/oauth_form.hbs +99 -32
- package/workers/api.js +91 -2
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
msgid ""
|
|
2
2
|
msgstr ""
|
|
3
3
|
"Content-Type: text/plain; charset=ascii\n"
|
|
4
|
-
"POT-Creation-Date: 2026-
|
|
4
|
+
"POT-Creation-Date: 2026-06-01 09:16+0000\n"
|
|
5
5
|
|
|
6
6
|
#: views/error.hbs:4
|
|
7
|
-
#: workers/api.js:
|
|
7
|
+
#: workers/api.js:7192
|
|
8
8
|
msgid "Something went wrong"
|
|
9
9
|
msgstr ""
|
|
10
10
|
|
|
@@ -21,7 +21,7 @@ msgid "Dashboard"
|
|
|
21
21
|
msgstr ""
|
|
22
22
|
|
|
23
23
|
#: views/config/license.hbs:45
|
|
24
|
-
#: lib/routes-ui.js:
|
|
24
|
+
#: lib/routes-ui.js:2509
|
|
25
25
|
msgid "%d day"
|
|
26
26
|
msgid_plural "%d days"
|
|
27
27
|
msgstr[0] ""
|
|
@@ -104,14 +104,6 @@ msgstr ""
|
|
|
104
104
|
msgid "Enter your email address"
|
|
105
105
|
msgstr ""
|
|
106
106
|
|
|
107
|
-
#: views/accounts/register/index.hbs:2
|
|
108
|
-
msgid "Choose your email account provider"
|
|
109
|
-
msgstr ""
|
|
110
|
-
|
|
111
|
-
#: views/accounts/register/index.hbs:15
|
|
112
|
-
msgid "Standard IMAP"
|
|
113
|
-
msgstr ""
|
|
114
|
-
|
|
115
107
|
#: views/accounts/register/imap.hbs:11
|
|
116
108
|
msgid "Your name"
|
|
117
109
|
msgstr ""
|
|
@@ -134,6 +126,14 @@ msgstr ""
|
|
|
134
126
|
msgid "Continue"
|
|
135
127
|
msgstr ""
|
|
136
128
|
|
|
129
|
+
#: views/accounts/register/index.hbs:2
|
|
130
|
+
msgid "Choose your email account provider"
|
|
131
|
+
msgstr ""
|
|
132
|
+
|
|
133
|
+
#: views/accounts/register/index.hbs:15
|
|
134
|
+
msgid "Standard IMAP"
|
|
135
|
+
msgstr ""
|
|
136
|
+
|
|
137
137
|
#: views/accounts/register/imap-server.hbs:19
|
|
138
138
|
msgid "IMAP"
|
|
139
139
|
msgstr ""
|
|
@@ -249,62 +249,62 @@ msgstr ""
|
|
|
249
249
|
msgid "Request failed."
|
|
250
250
|
msgstr ""
|
|
251
251
|
|
|
252
|
-
#: lib/routes-ui.js:
|
|
252
|
+
#: lib/routes-ui.js:385
|
|
253
253
|
#: lib/ui-routes/account-routes.js:60
|
|
254
254
|
msgid "Delegated"
|
|
255
255
|
msgstr ""
|
|
256
256
|
|
|
257
|
-
#: lib/routes-ui.js:
|
|
257
|
+
#: lib/routes-ui.js:386
|
|
258
258
|
#: lib/ui-routes/account-routes.js:61
|
|
259
259
|
msgid "Using credentials from \"%s\""
|
|
260
260
|
msgstr ""
|
|
261
261
|
|
|
262
|
-
#: lib/routes-ui.js:
|
|
262
|
+
#: lib/routes-ui.js:436
|
|
263
263
|
#: lib/ui-routes/account-routes.js:111
|
|
264
264
|
msgid ""
|
|
265
265
|
"Connection timed out. This usually occurs if you are behind a firewall or "
|
|
266
266
|
"connecting to the wrong port."
|
|
267
267
|
msgstr ""
|
|
268
268
|
|
|
269
|
-
#: lib/routes-ui.js:
|
|
269
|
+
#: lib/routes-ui.js:439
|
|
270
270
|
#: lib/ui-routes/account-routes.js:114
|
|
271
271
|
msgid "The server unexpectedly closed the connection."
|
|
272
272
|
msgstr ""
|
|
273
273
|
|
|
274
|
-
#: lib/routes-ui.js:
|
|
274
|
+
#: lib/routes-ui.js:442
|
|
275
275
|
#: lib/ui-routes/account-routes.js:117
|
|
276
276
|
msgid ""
|
|
277
277
|
"The server unexpectedly closed the connection. This usually happens when "
|
|
278
278
|
"attempting to connect to a TLS port without TLS enabled."
|
|
279
279
|
msgstr ""
|
|
280
280
|
|
|
281
|
-
#: lib/routes-ui.js:
|
|
281
|
+
#: lib/routes-ui.js:447
|
|
282
282
|
#: lib/ui-routes/account-routes.js:122
|
|
283
283
|
msgid ""
|
|
284
284
|
"The server refused the connection. This typically occurs if the server is "
|
|
285
285
|
"not running, is overloaded, or you are connecting to the wrong host or port."
|
|
286
286
|
msgstr ""
|
|
287
287
|
|
|
288
|
-
#: lib/routes-ui.js:
|
|
288
|
+
#: lib/routes-ui.js:574
|
|
289
289
|
msgid "Invalid API key for OpenAI"
|
|
290
290
|
msgstr ""
|
|
291
291
|
|
|
292
|
-
#: lib/routes-ui.js:
|
|
292
|
+
#: lib/routes-ui.js:2502
|
|
293
293
|
msgid "Unknown"
|
|
294
294
|
msgstr ""
|
|
295
295
|
|
|
296
|
-
#: lib/routes-ui.js:
|
|
296
|
+
#: lib/routes-ui.js:2511
|
|
297
297
|
msgid "Indefinite"
|
|
298
298
|
msgstr ""
|
|
299
299
|
|
|
300
|
-
#: lib/routes-ui.js:
|
|
301
|
-
#: lib/routes-ui.js:
|
|
302
|
-
#: lib/routes-ui.js:
|
|
303
|
-
#: lib/routes-ui.js:
|
|
304
|
-
#: lib/routes-ui.js:
|
|
305
|
-
#: lib/routes-ui.js:
|
|
306
|
-
#: workers/api.js:
|
|
307
|
-
#: workers/api.js:
|
|
300
|
+
#: lib/routes-ui.js:5285
|
|
301
|
+
#: lib/routes-ui.js:5320
|
|
302
|
+
#: lib/routes-ui.js:5435
|
|
303
|
+
#: lib/routes-ui.js:5482
|
|
304
|
+
#: lib/routes-ui.js:5729
|
|
305
|
+
#: lib/routes-ui.js:5765
|
|
306
|
+
#: workers/api.js:2280
|
|
307
|
+
#: workers/api.js:2608
|
|
308
308
|
#: lib/ui-routes/account-routes.js:554
|
|
309
309
|
#: lib/ui-routes/account-routes.js:590
|
|
310
310
|
#: lib/ui-routes/account-routes.js:707
|
|
@@ -314,44 +314,44 @@ msgstr ""
|
|
|
314
314
|
msgid "Email Account Setup"
|
|
315
315
|
msgstr ""
|
|
316
316
|
|
|
317
|
-
#: lib/routes-ui.js:
|
|
318
|
-
#: lib/routes-ui.js:
|
|
319
|
-
#: lib/routes-ui.js:
|
|
317
|
+
#: lib/routes-ui.js:5345
|
|
318
|
+
#: lib/routes-ui.js:5378
|
|
319
|
+
#: lib/routes-ui.js:8338
|
|
320
320
|
#: lib/ui-routes/account-routes.js:615
|
|
321
321
|
#: lib/ui-routes/account-routes.js:649
|
|
322
322
|
msgid "Invalid request. Check your input and try again."
|
|
323
323
|
msgstr ""
|
|
324
324
|
|
|
325
|
-
#: lib/routes-ui.js:
|
|
326
|
-
#: lib/routes-ui.js:
|
|
325
|
+
#: lib/routes-ui.js:5538
|
|
326
|
+
#: lib/routes-ui.js:5549
|
|
327
327
|
#: lib/ui-routes/account-routes.js:811
|
|
328
328
|
#: lib/ui-routes/account-routes.js:822
|
|
329
329
|
#: lib/ui-routes/admin-entities-routes.js:2020
|
|
330
330
|
msgid "Server hostname was not found"
|
|
331
331
|
msgstr ""
|
|
332
332
|
|
|
333
|
-
#: lib/routes-ui.js:
|
|
334
|
-
#: lib/routes-ui.js:
|
|
333
|
+
#: lib/routes-ui.js:5541
|
|
334
|
+
#: lib/routes-ui.js:5552
|
|
335
335
|
#: lib/ui-routes/account-routes.js:814
|
|
336
336
|
#: lib/ui-routes/account-routes.js:825
|
|
337
337
|
#: lib/ui-routes/admin-entities-routes.js:2023
|
|
338
338
|
msgid "Invalid username or password"
|
|
339
339
|
msgstr ""
|
|
340
340
|
|
|
341
|
-
#: lib/routes-ui.js:
|
|
341
|
+
#: lib/routes-ui.js:5555
|
|
342
342
|
#: lib/ui-routes/account-routes.js:828
|
|
343
343
|
#: lib/ui-routes/admin-entities-routes.js:2026
|
|
344
344
|
msgid "Authentication credentials were not provided"
|
|
345
345
|
msgstr ""
|
|
346
346
|
|
|
347
|
-
#: lib/routes-ui.js:
|
|
347
|
+
#: lib/routes-ui.js:5558
|
|
348
348
|
#: lib/ui-routes/account-routes.js:831
|
|
349
349
|
#: lib/ui-routes/admin-entities-routes.js:2029
|
|
350
350
|
msgid "OAuth2 authentication failed"
|
|
351
351
|
msgstr ""
|
|
352
352
|
|
|
353
|
-
#: lib/routes-ui.js:
|
|
354
|
-
#: lib/routes-ui.js:
|
|
353
|
+
#: lib/routes-ui.js:5561
|
|
354
|
+
#: lib/routes-ui.js:5565
|
|
355
355
|
#: lib/ui-routes/account-routes.js:834
|
|
356
356
|
#: lib/ui-routes/account-routes.js:838
|
|
357
357
|
#: lib/ui-routes/admin-entities-routes.js:2032
|
|
@@ -359,38 +359,38 @@ msgstr ""
|
|
|
359
359
|
msgid "TLS protocol error"
|
|
360
360
|
msgstr ""
|
|
361
361
|
|
|
362
|
-
#: lib/routes-ui.js:
|
|
362
|
+
#: lib/routes-ui.js:5569
|
|
363
363
|
#: lib/ui-routes/account-routes.js:842
|
|
364
364
|
#: lib/ui-routes/admin-entities-routes.js:2040
|
|
365
365
|
msgid "Connection timed out"
|
|
366
366
|
msgstr ""
|
|
367
367
|
|
|
368
|
-
#: lib/routes-ui.js:
|
|
368
|
+
#: lib/routes-ui.js:5572
|
|
369
369
|
#: lib/ui-routes/account-routes.js:845
|
|
370
370
|
#: lib/ui-routes/admin-entities-routes.js:2043
|
|
371
371
|
msgid "Could not connect to server"
|
|
372
372
|
msgstr ""
|
|
373
373
|
|
|
374
|
-
#: lib/routes-ui.js:
|
|
374
|
+
#: lib/routes-ui.js:5575
|
|
375
375
|
#: lib/ui-routes/account-routes.js:848
|
|
376
376
|
#: lib/ui-routes/admin-entities-routes.js:2046
|
|
377
377
|
msgid "Unexpected server response"
|
|
378
378
|
msgstr ""
|
|
379
379
|
|
|
380
|
-
#: lib/routes-ui.js:
|
|
380
|
+
#: lib/routes-ui.js:8301
|
|
381
381
|
#: lib/tools.js:950
|
|
382
382
|
msgid "Invalid input"
|
|
383
383
|
msgstr ""
|
|
384
384
|
|
|
385
|
-
#: lib/routes-ui.js:
|
|
386
|
-
#: lib/routes-ui.js:
|
|
387
|
-
#: lib/routes-ui.js:
|
|
388
|
-
#: lib/routes-ui.js:
|
|
385
|
+
#: lib/routes-ui.js:8311
|
|
386
|
+
#: lib/routes-ui.js:8429
|
|
387
|
+
#: lib/routes-ui.js:8446
|
|
388
|
+
#: lib/routes-ui.js:8482
|
|
389
389
|
msgid "Subscription Management"
|
|
390
390
|
msgstr ""
|
|
391
391
|
|
|
392
|
-
#: workers/api.js:
|
|
393
|
-
#: workers/api.js:
|
|
392
|
+
#: workers/api.js:7191
|
|
393
|
+
#: workers/api.js:7308
|
|
394
394
|
msgid "Requested page not found"
|
|
395
395
|
msgstr ""
|
|
396
396
|
|
|
@@ -33,6 +33,11 @@
|
|
|
33
33
|
<i class="fas fa-user-edit fa-fw"></i> Edit app
|
|
34
34
|
</a>
|
|
35
35
|
|
|
36
|
+
<button type="button" class="btn btn-light" data-toggle="modal" data-target="#verifySetupModal"
|
|
37
|
+
title="Run live checks against the provider" id="verify-btn" data-placement="top">
|
|
38
|
+
<i class="fas fa-clipboard-check fa-fw"></i> Verify setup
|
|
39
|
+
</button>
|
|
40
|
+
|
|
36
41
|
<button type="button" class="btn btn-light" data-toggle="modal" data-target="#deleteModal"
|
|
37
42
|
title="Delete this application" id="delete-btn" data-placement="top">
|
|
38
43
|
<i class="fas fa-trash-alt fa-fw"></i> Delete app
|
|
@@ -40,6 +45,16 @@
|
|
|
40
45
|
|
|
41
46
|
</div>
|
|
42
47
|
|
|
48
|
+
{{#if canAddServiceAccount}}
|
|
49
|
+
<div class="btn-group ml-auto mb-1" role="group" aria-label="Account actions">
|
|
50
|
+
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#addServiceAccountModal"
|
|
51
|
+
title="Register an email account that authenticates through this service application"
|
|
52
|
+
id="add-service-account-btn" data-placement="top">
|
|
53
|
+
<i class="fas fa-user-plus fa-fw"></i> Add account
|
|
54
|
+
</button>
|
|
55
|
+
</div>
|
|
56
|
+
{{/if}}
|
|
57
|
+
|
|
43
58
|
</div>
|
|
44
59
|
|
|
45
60
|
{{#if app.description}}
|
|
@@ -164,6 +179,22 @@
|
|
|
164
179
|
<dd class="col-sm-9 text-monospace"><small>*****</small></dd>
|
|
165
180
|
{{/if}}
|
|
166
181
|
|
|
182
|
+
{{#if appShowAuthMethod}}
|
|
183
|
+
<dt class="col-sm-3">Authentication method</dt>
|
|
184
|
+
<dd class="col-sm-9">
|
|
185
|
+
{{#if authMethodIsExternalAccount}}
|
|
186
|
+
Workload Identity Federation
|
|
187
|
+
{{else}}
|
|
188
|
+
Service account key
|
|
189
|
+
{{/if}}
|
|
190
|
+
</dd>
|
|
191
|
+
{{/if}}
|
|
192
|
+
|
|
193
|
+
{{#if app.externalAccount}}
|
|
194
|
+
<dt class="col-sm-3">External account config</dt>
|
|
195
|
+
<dd class="col-sm-9 text-monospace"><small>***** (set)</small></dd>
|
|
196
|
+
{{/if}}
|
|
197
|
+
|
|
167
198
|
{{#if app.redirectUrl}}
|
|
168
199
|
<dt class="col-sm-3">Redirect URL</dt>
|
|
169
200
|
<dd class="col-sm-9 text-monospace"><small>{{app.redirectUrl}}</small></dd>
|
|
@@ -337,6 +368,199 @@
|
|
|
337
368
|
</div>
|
|
338
369
|
</div>
|
|
339
370
|
|
|
371
|
+
<div class="modal fade" id="verifySetupModal" tabindex="-1" aria-labelledby="verifySetupModalLabel" aria-hidden="true">
|
|
372
|
+
<div class="modal-dialog modal-lg">
|
|
373
|
+
<div class="modal-content">
|
|
374
|
+
<div class="modal-header">
|
|
375
|
+
<h5 class="modal-title" id="verifySetupModalLabel">Verify OAuth2 setup</h5>
|
|
376
|
+
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
|
377
|
+
<span aria-hidden="true">×</span>
|
|
378
|
+
</button>
|
|
379
|
+
</div>
|
|
380
|
+
<div class="modal-body">
|
|
381
|
+
<input type="hidden" id="verify-crumb" value="{{crumb}}" />
|
|
382
|
+
<p><small class="text-muted">Runs the provider authentication chain step by step and reports what passes
|
|
383
|
+
or fails.{{#if appShowAuthMethod}} Enter a mailbox address in your Workspace domain to also verify
|
|
384
|
+
domain-wide delegation and live mailbox access.{{/if}}</small></p>
|
|
385
|
+
|
|
386
|
+
<div class="form-group">
|
|
387
|
+
<div class="input-group">
|
|
388
|
+
<input type="email" class="form-control" id="verify-account"
|
|
389
|
+
placeholder="mailbox@example.com (optional)" autocomplete="off" />
|
|
390
|
+
<div class="input-group-append">
|
|
391
|
+
<button type="button" class="btn btn-primary" id="verify-run-btn">
|
|
392
|
+
<i class="fas fa-play fa-fw"></i> Run test
|
|
393
|
+
</button>
|
|
394
|
+
</div>
|
|
395
|
+
</div>
|
|
396
|
+
<small class="form-text text-muted">Leave empty to check configuration and signing only.</small>
|
|
397
|
+
</div>
|
|
398
|
+
|
|
399
|
+
<div id="verify-pending" class="d-none">
|
|
400
|
+
<p><i class="fas fa-spinner fa-spin fa-sm"></i> Running checks, please wait…</p>
|
|
401
|
+
</div>
|
|
402
|
+
<div id="verify-error" class="d-none">
|
|
403
|
+
<small class="alert alert-danger text-monospace error-message" style="display: block;"></small>
|
|
404
|
+
</div>
|
|
405
|
+
<div id="verify-verdict" class="d-none mb-3"></div>
|
|
406
|
+
<ul id="verify-steps" class="list-group"></ul>
|
|
407
|
+
</div>
|
|
408
|
+
<div class="modal-footer">
|
|
409
|
+
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
|
410
|
+
</div>
|
|
411
|
+
</div>
|
|
412
|
+
</div>
|
|
413
|
+
</div>
|
|
414
|
+
|
|
415
|
+
{{#if canAddServiceAccount}}
|
|
416
|
+
<div class="modal fade" id="addServiceAccountModal" tabindex="-1" aria-labelledby="addServiceAccountLabel"
|
|
417
|
+
aria-hidden="true">
|
|
418
|
+
<div class="modal-dialog">
|
|
419
|
+
<div class="modal-content">
|
|
420
|
+
<div class="modal-header">
|
|
421
|
+
<h5 class="modal-title" id="addServiceAccountLabel">Add an account</h5>
|
|
422
|
+
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
|
423
|
+
<span aria-hidden="true">×</span>
|
|
424
|
+
</button>
|
|
425
|
+
</div>
|
|
426
|
+
<form method="post" action="/admin/config/oauth/app/{{app.id}}/add-account">
|
|
427
|
+
<input type="hidden" name="crumb" value="{{crumb}}">
|
|
428
|
+
<div class="modal-body">
|
|
429
|
+
<p><small class="text-muted">This service application authenticates without an interactive consent
|
|
430
|
+
screen, so the account is registered directly. The mailbox must be reachable with the
|
|
431
|
+
application's service credentials.</small></p>
|
|
432
|
+
<div class="form-group">
|
|
433
|
+
<label for="service-account-name" class="col-form-label">Full name:</label>
|
|
434
|
+
<input type="text" class="form-control" id="service-account-name" name="name"
|
|
435
|
+
placeholder="e.g., John Smith">
|
|
436
|
+
</div>
|
|
437
|
+
<div class="form-group">
|
|
438
|
+
<label for="service-account-email" class="col-form-label">Email address:</label>
|
|
439
|
+
<input type="email" class="form-control" id="service-account-email" name="email"
|
|
440
|
+
placeholder="e.g., user@example.com" required>
|
|
441
|
+
</div>
|
|
442
|
+
<div class="form-group">
|
|
443
|
+
<label for="service-account-id" class="col-form-label">Account identifier (optional):</label>
|
|
444
|
+
<input type="text" class="form-control" id="service-account-id" name="account"
|
|
445
|
+
placeholder="e.g., account_123">
|
|
446
|
+
<small class="form-text text-muted">Leave blank to auto-generate. Existing accounts with this ID
|
|
447
|
+
will be updated.</small>
|
|
448
|
+
</div>
|
|
449
|
+
</div>
|
|
450
|
+
<div class="modal-footer">
|
|
451
|
+
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
|
|
452
|
+
<button type="submit" class="btn btn-primary btn-icon-split">
|
|
453
|
+
<span class="icon text-white-50">
|
|
454
|
+
<i class="fas fa-user-plus"></i>
|
|
455
|
+
</span>
|
|
456
|
+
<span class="text">Add account</span>
|
|
457
|
+
</button>
|
|
458
|
+
</div>
|
|
459
|
+
</form>
|
|
460
|
+
</div>
|
|
461
|
+
</div>
|
|
462
|
+
</div>
|
|
463
|
+
|
|
464
|
+
<script>
|
|
465
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
466
|
+
$('#addServiceAccountModal').on('shown.bs.modal', () => {
|
|
467
|
+
document.getElementById('service-account-name').focus();
|
|
468
|
+
});
|
|
469
|
+
});
|
|
470
|
+
</script>
|
|
471
|
+
{{/if}}
|
|
472
|
+
|
|
473
|
+
<script>
|
|
474
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
475
|
+
const appId = "{{app.id}}";
|
|
476
|
+
const stepsElm = document.getElementById('verify-steps');
|
|
477
|
+
const pendingElm = document.getElementById('verify-pending');
|
|
478
|
+
const errorElm = document.getElementById('verify-error');
|
|
479
|
+
const verdictElm = document.getElementById('verify-verdict');
|
|
480
|
+
const runBtn = document.getElementById('verify-run-btn');
|
|
481
|
+
const accountInput = document.getElementById('verify-account');
|
|
482
|
+
|
|
483
|
+
const ICONS = {
|
|
484
|
+
ok: '<i class="fas fa-check-circle text-success fa-fw"></i>',
|
|
485
|
+
fail: '<i class="fas fa-times-circle text-danger fa-fw"></i>',
|
|
486
|
+
skip: '<i class="fas fa-minus-circle text-muted fa-fw"></i>'
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
const esc = str => {
|
|
490
|
+
let div = document.createElement('div');
|
|
491
|
+
div.textContent = str == null ? '' : String(str);
|
|
492
|
+
return div.innerHTML;
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
const renderResult = data => {
|
|
496
|
+
stepsElm.innerHTML = '';
|
|
497
|
+
(data.steps || []).forEach(step => {
|
|
498
|
+
let li = document.createElement('li');
|
|
499
|
+
li.className = 'list-group-item';
|
|
500
|
+
let html = `${ICONS[step.status] || ''} <strong>${esc(step.label)}</strong>`;
|
|
501
|
+
if (step.message) {
|
|
502
|
+
html += `<div class="small text-muted ml-4">${esc(step.message)}</div>`;
|
|
503
|
+
}
|
|
504
|
+
if (step.hint) {
|
|
505
|
+
html += `<div class="small text-info ml-4"><i class="fas fa-lightbulb fa-fw"></i> ${esc(step.hint)}</div>`;
|
|
506
|
+
}
|
|
507
|
+
li.innerHTML = html;
|
|
508
|
+
stepsElm.appendChild(li);
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
let failed = (data.steps || []).some(s => s.status === 'fail');
|
|
512
|
+
verdictElm.className = 'mb-3 alert ' + (failed ? 'alert-danger' : 'alert-success');
|
|
513
|
+
verdictElm.textContent = failed
|
|
514
|
+
? 'Setup has problems - review the failing step(s) below.'
|
|
515
|
+
: 'Setup verified - all checks passed.';
|
|
516
|
+
verdictElm.classList.remove('d-none');
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
const runVerify = async account => {
|
|
520
|
+
errorElm.classList.add('d-none');
|
|
521
|
+
verdictElm.classList.add('d-none');
|
|
522
|
+
stepsElm.innerHTML = '';
|
|
523
|
+
pendingElm.classList.remove('d-none');
|
|
524
|
+
runBtn.disabled = true;
|
|
525
|
+
try {
|
|
526
|
+
let res = await fetch(`/admin/config/oauth/verify/${encodeURIComponent(appId)}`, {
|
|
527
|
+
method: 'post',
|
|
528
|
+
headers: { 'content-type': 'application/json' },
|
|
529
|
+
body: JSON.stringify({
|
|
530
|
+
crumb: document.getElementById('verify-crumb').value,
|
|
531
|
+
account: account || '',
|
|
532
|
+
testConnection: true
|
|
533
|
+
})
|
|
534
|
+
});
|
|
535
|
+
let data = await res.json();
|
|
536
|
+
if (!res.ok) {
|
|
537
|
+
throw new Error(data.error || `HTTP ${res.status}`);
|
|
538
|
+
}
|
|
539
|
+
renderResult(data);
|
|
540
|
+
} catch (err) {
|
|
541
|
+
errorElm.querySelector('.error-message').textContent = err.message;
|
|
542
|
+
errorElm.classList.remove('d-none');
|
|
543
|
+
} finally {
|
|
544
|
+
pendingElm.classList.add('d-none');
|
|
545
|
+
runBtn.disabled = false;
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
$('#verifySetupModal').on('shown.bs.modal', () => {
|
|
550
|
+
// Auto-run the no-mailbox checks (config + signing chain) on open.
|
|
551
|
+
runVerify(accountInput.value.trim());
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
runBtn.addEventListener('click', () => runVerify(accountInput.value.trim()));
|
|
555
|
+
accountInput.addEventListener('keydown', e => {
|
|
556
|
+
if (e.key === 'Enter') {
|
|
557
|
+
e.preventDefault();
|
|
558
|
+
runVerify(accountInput.value.trim());
|
|
559
|
+
}
|
|
560
|
+
});
|
|
561
|
+
});
|
|
562
|
+
</script>
|
|
563
|
+
|
|
340
564
|
<script>
|
|
341
565
|
document.addEventListener('DOMContentLoaded', () => {
|
|
342
566
|
// not set up by default as this element has a different data-toggle
|
|
@@ -89,6 +89,75 @@
|
|
|
89
89
|
});
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
let externalAccountFileElm = document.getElementById('externalAccountFile');
|
|
93
|
+
if (externalAccountFileElm) {
|
|
94
|
+
externalAccountFileElm.addEventListener('click', e => {
|
|
95
|
+
e.preventDefault();
|
|
96
|
+
browseFileContents('text').then(jsonStr => {
|
|
97
|
+
if (!jsonStr) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
let data;
|
|
102
|
+
try {
|
|
103
|
+
data = JSON.parse(jsonStr);
|
|
104
|
+
} catch (err) {
|
|
105
|
+
return showToast('Selected file is not JSON formatted', 'alert-triangle');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (data.type !== 'external_account') {
|
|
109
|
+
if (data.type === 'service_account') {
|
|
110
|
+
return showToast('This is a service account key file, not a Workload Identity Federation config. Switch to the "Service account key" tab to load it.', 'alert-triangle');
|
|
111
|
+
}
|
|
112
|
+
return showToast(`Selected file is not an external_account credential config (type: ${data.type || 'missing'})`, 'alert-triangle');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
let impersonationUrl = data.service_account_impersonation_url || '';
|
|
116
|
+
let emailMatch = impersonationUrl.match(/\/serviceAccounts\/([^/]+):generateAccessToken$/);
|
|
117
|
+
if (emailMatch) {
|
|
118
|
+
let derivedEmail = decodeURIComponent(emailMatch[1]);
|
|
119
|
+
let emailField = document.getElementById('serviceClientEmail');
|
|
120
|
+
if (emailField && !emailField.value) {
|
|
121
|
+
emailField.value = derivedEmail;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
document.getElementById('externalAccount').value = JSON.stringify(data, null, 2);
|
|
126
|
+
return showToast('Loaded external account configuration from file', 'check-circle');
|
|
127
|
+
}).catch(err => {
|
|
128
|
+
console.error(err);
|
|
129
|
+
return showToast('Failed to load external account file', 'alert-triangle');
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
let authMethodTabs = document.querySelectorAll('.auth-method-tab');
|
|
135
|
+
let authMethodInput = document.getElementById('authMethod');
|
|
136
|
+
if (authMethodTabs.length && authMethodInput) {
|
|
137
|
+
let updateSections = selected => {
|
|
138
|
+
document.querySelectorAll('.auth-method-section').forEach(section => {
|
|
139
|
+
let active = section.classList.contains('auth-method-section-' + selected);
|
|
140
|
+
section.classList.toggle('d-none', !active);
|
|
141
|
+
});
|
|
142
|
+
};
|
|
143
|
+
authMethodTabs.forEach(tab => {
|
|
144
|
+
tab.addEventListener('click', e => {
|
|
145
|
+
e.preventDefault();
|
|
146
|
+
if (tab.classList.contains('disabled') || tab.classList.contains('active')) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
let selected = tab.getAttribute('data-auth-method');
|
|
150
|
+
authMethodTabs.forEach(other => {
|
|
151
|
+
let isActive = other === tab;
|
|
152
|
+
other.classList.toggle('active', isActive);
|
|
153
|
+
other.setAttribute('aria-selected', isActive ? 'true' : 'false');
|
|
154
|
+
});
|
|
155
|
+
authMethodInput.value = selected;
|
|
156
|
+
updateSections(selected);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
92
161
|
});
|
|
93
162
|
|
|
94
163
|
</script>
|
|
@@ -87,6 +87,75 @@
|
|
|
87
87
|
});
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
let externalAccountFileElm = document.getElementById('externalAccountFile');
|
|
91
|
+
if (externalAccountFileElm) {
|
|
92
|
+
externalAccountFileElm.addEventListener('click', e => {
|
|
93
|
+
e.preventDefault();
|
|
94
|
+
browseFileContents('text').then(jsonStr => {
|
|
95
|
+
if (!jsonStr) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
let data;
|
|
100
|
+
try {
|
|
101
|
+
data = JSON.parse(jsonStr);
|
|
102
|
+
} catch (err) {
|
|
103
|
+
return showToast('Selected file is not JSON formatted', 'alert-triangle');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (data.type !== 'external_account') {
|
|
107
|
+
if (data.type === 'service_account') {
|
|
108
|
+
return showToast('This is a service account key file, not a Workload Identity Federation config. Switch to the "Service account key" tab to load it.', 'alert-triangle');
|
|
109
|
+
}
|
|
110
|
+
return showToast(`Selected file is not an external_account credential config (type: ${data.type || 'missing'})`, 'alert-triangle');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
let impersonationUrl = data.service_account_impersonation_url || '';
|
|
114
|
+
let emailMatch = impersonationUrl.match(/\/serviceAccounts\/([^/]+):generateAccessToken$/);
|
|
115
|
+
if (emailMatch) {
|
|
116
|
+
let derivedEmail = decodeURIComponent(emailMatch[1]);
|
|
117
|
+
let emailField = document.getElementById('serviceClientEmail');
|
|
118
|
+
if (emailField && !emailField.value) {
|
|
119
|
+
emailField.value = derivedEmail;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
document.getElementById('externalAccount').value = JSON.stringify(data, null, 2);
|
|
124
|
+
return showToast('Loaded external account configuration from file', 'check-circle');
|
|
125
|
+
}).catch(err => {
|
|
126
|
+
console.error(err);
|
|
127
|
+
return showToast('Failed to load external account file', 'alert-triangle');
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
let authMethodTabs = document.querySelectorAll('.auth-method-tab');
|
|
133
|
+
let authMethodInput = document.getElementById('authMethod');
|
|
134
|
+
if (authMethodTabs.length && authMethodInput) {
|
|
135
|
+
let updateSections = selected => {
|
|
136
|
+
document.querySelectorAll('.auth-method-section').forEach(section => {
|
|
137
|
+
let active = section.classList.contains('auth-method-section-' + selected);
|
|
138
|
+
section.classList.toggle('d-none', !active);
|
|
139
|
+
});
|
|
140
|
+
};
|
|
141
|
+
authMethodTabs.forEach(tab => {
|
|
142
|
+
tab.addEventListener('click', e => {
|
|
143
|
+
e.preventDefault();
|
|
144
|
+
if (tab.classList.contains('disabled') || tab.classList.contains('active')) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
let selected = tab.getAttribute('data-auth-method');
|
|
148
|
+
authMethodTabs.forEach(other => {
|
|
149
|
+
let isActive = other === tab;
|
|
150
|
+
other.classList.toggle('active', isActive);
|
|
151
|
+
other.setAttribute('aria-selected', isActive ? 'true' : 'false');
|
|
152
|
+
});
|
|
153
|
+
authMethodInput.value = selected;
|
|
154
|
+
updateSections(selected);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
90
159
|
});
|
|
91
160
|
|
|
92
161
|
</script>
|