@authrim/setup 0.1.141 → 0.1.142
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/dist/__tests__/keys.test.js.map +1 -1
- package/dist/__tests__/migrate.test.js +4 -4
- package/dist/__tests__/migrate.test.js.map +1 -1
- package/dist/__tests__/paths.test.js.map +1 -1
- package/dist/cli/commands/deploy.d.ts.map +1 -1
- package/dist/cli/commands/deploy.js +57 -63
- package/dist/cli/commands/deploy.js.map +1 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +231 -171
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/core/admin.d.ts.map +1 -1
- package/dist/core/admin.js +13 -3
- package/dist/core/admin.js.map +1 -1
- package/dist/core/cloudflare.d.ts +38 -1
- package/dist/core/cloudflare.d.ts.map +1 -1
- package/dist/core/cloudflare.js +729 -115
- package/dist/core/cloudflare.js.map +1 -1
- package/dist/core/config.d.ts +136 -28
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +58 -11
- package/dist/core/config.js.map +1 -1
- package/dist/core/deploy.d.ts +18 -0
- package/dist/core/deploy.d.ts.map +1 -1
- package/dist/core/deploy.js +126 -25
- package/dist/core/deploy.js.map +1 -1
- package/dist/core/keys.d.ts.map +1 -1
- package/dist/core/keys.js +2 -0
- package/dist/core/keys.js.map +1 -1
- package/dist/core/login-ui-client.d.ts.map +1 -1
- package/dist/core/login-ui-client.js +43 -7
- package/dist/core/login-ui-client.js.map +1 -1
- package/dist/core/paths.d.ts.map +1 -1
- package/dist/core/paths.js +5 -5
- package/dist/core/paths.js.map +1 -1
- package/dist/core/tenant-mode.d.ts +4 -0
- package/dist/core/tenant-mode.d.ts.map +1 -0
- package/dist/core/tenant-mode.js +17 -0
- package/dist/core/tenant-mode.js.map +1 -0
- package/dist/core/ui-deployment.d.ts +21 -0
- package/dist/core/ui-deployment.d.ts.map +1 -0
- package/dist/core/ui-deployment.js +90 -0
- package/dist/core/ui-deployment.js.map +1 -0
- package/dist/core/ui-env.d.ts +17 -0
- package/dist/core/ui-env.d.ts.map +1 -1
- package/dist/core/ui-env.js +16 -0
- package/dist/core/ui-env.js.map +1 -1
- package/dist/core/url-config.d.ts +16 -0
- package/dist/core/url-config.d.ts.map +1 -0
- package/dist/core/url-config.js +46 -0
- package/dist/core/url-config.js.map +1 -0
- package/dist/core/wrangler.d.ts +50 -1
- package/dist/core/wrangler.d.ts.map +1 -1
- package/dist/core/wrangler.js +169 -55
- package/dist/core/wrangler.js.map +1 -1
- package/dist/i18n/locales/de.d.ts.map +1 -1
- package/dist/i18n/locales/de.js +37 -0
- package/dist/i18n/locales/de.js.map +1 -1
- package/dist/i18n/locales/en.d.ts.map +1 -1
- package/dist/i18n/locales/en.js +37 -0
- package/dist/i18n/locales/en.js.map +1 -1
- package/dist/i18n/locales/es.d.ts.map +1 -1
- package/dist/i18n/locales/es.js +37 -0
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.d.ts.map +1 -1
- package/dist/i18n/locales/fr.js +37 -0
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/id.d.ts.map +1 -1
- package/dist/i18n/locales/id.js +37 -0
- package/dist/i18n/locales/id.js.map +1 -1
- package/dist/i18n/locales/ja.d.ts.map +1 -1
- package/dist/i18n/locales/ja.js +37 -0
- package/dist/i18n/locales/ja.js.map +1 -1
- package/dist/i18n/locales/ko.d.ts.map +1 -1
- package/dist/i18n/locales/ko.js +37 -0
- package/dist/i18n/locales/ko.js.map +1 -1
- package/dist/i18n/locales/pt.d.ts.map +1 -1
- package/dist/i18n/locales/pt.js +37 -0
- package/dist/i18n/locales/pt.js.map +1 -1
- package/dist/i18n/locales/ru.d.ts.map +1 -1
- package/dist/i18n/locales/ru.js +37 -0
- package/dist/i18n/locales/ru.js.map +1 -1
- package/dist/i18n/locales/zh-CN.d.ts.map +1 -1
- package/dist/i18n/locales/zh-CN.js +37 -0
- package/dist/i18n/locales/zh-CN.js.map +1 -1
- package/dist/i18n/locales/zh-TW.d.ts.map +1 -1
- package/dist/i18n/locales/zh-TW.js +37 -0
- package/dist/i18n/locales/zh-TW.js.map +1 -1
- package/dist/i18n/types.d.ts +8 -0
- package/dist/i18n/types.d.ts.map +1 -1
- package/dist/index.js +38 -29
- package/dist/index.js.map +1 -1
- package/dist/web/api.d.ts.map +1 -1
- package/dist/web/api.js +207 -95
- package/dist/web/api.js.map +1 -1
- package/dist/web/ui.d.ts.map +1 -1
- package/dist/web/ui.js +506 -109
- package/dist/web/ui.js.map +1 -1
- package/migrations/000_fresh_schema.sql +227 -9
- package/migrations/admin/006_admin_setup_tokens.sql +91 -91
- package/migrations/admin/007_admin_role_inheritance.sql +32 -0
- package/migrations/admin/008_admin_rebac_definitions.sql +117 -0
- package/migrations/admin/009_optimize_admin_audit_indexes.sql +15 -0
- package/package.json +5 -5
package/dist/web/ui.js
CHANGED
|
@@ -2034,6 +2034,7 @@ export function getHtmlTemplate(sessionToken, manageOnly, locale = 'en', transla
|
|
|
2034
2034
|
<label for="env"><span data-i18n="web.form.envName">Environment Name</span> <span style="color: var(--error);">*</span></label>
|
|
2035
2035
|
<input type="text" id="env" placeholder="e.g., prod, staging, dev" data-i18n-placeholder="web.form.envNamePlaceholder" required>
|
|
2036
2036
|
<small style="color: var(--text-muted)" data-i18n="web.form.envNameHint">Lowercase letters, numbers, and hyphens only</small>
|
|
2037
|
+
<span id="env-error" style="display: none; color: var(--error); font-size: 0.85rem;" data-i18n="web.form.envNameError">Only lowercase letters, numbers, and hyphens are allowed (must start with a letter)</span>
|
|
2037
2038
|
</div>
|
|
2038
2039
|
|
|
2039
2040
|
<!-- 3. Domain Configuration -->
|
|
@@ -2045,6 +2046,16 @@ export function getHtmlTemplate(sessionToken, manageOnly, locale = 'en', transla
|
|
|
2045
2046
|
<label for="base-domain" data-i18n="web.form.baseDomain">Base Domain (API Domain)</label>
|
|
2046
2047
|
<input type="text" id="base-domain" placeholder="oidc.example.com" data-i18n-placeholder="web.form.baseDomainPlaceholder">
|
|
2047
2048
|
<small style="color: var(--text-muted)" data-i18n="web.form.baseDomainHint">Custom domain for Authrim. Leave empty to use workers.dev</small>
|
|
2049
|
+
<div id="domain-check-row" style="display: none; margin-top: 0.5rem; align-items: center; gap: 0.5rem;">
|
|
2050
|
+
<button type="button" id="check-domain-btn" class="btn btn-secondary" style="padding: 0.3rem 0.75rem; font-size: 0.85rem;" data-i18n="domain.checkZoneButton">
|
|
2051
|
+
Check Zone
|
|
2052
|
+
</button>
|
|
2053
|
+
<span id="domain-check-status" style="font-size: 0.85rem;"></span>
|
|
2054
|
+
</div>
|
|
2055
|
+
<label class="checkbox-item" id="custom-domain-binding-row" style="display: none; align-items: center; gap: 0.5rem; margin-top: 0.5rem;">
|
|
2056
|
+
<input type="checkbox" id="custom-domain-binding" checked>
|
|
2057
|
+
<span data-i18n="domain.configureBinding">Configure custom domain binding for Workers</span>
|
|
2058
|
+
</label>
|
|
2048
2059
|
<label class="checkbox-item" id="naked-domain-label" style="display: flex; align-items: center; gap: 0.5rem; margin-top: 0.5rem;">
|
|
2049
2060
|
<input type="checkbox" id="naked-domain">
|
|
2050
2061
|
<span data-i18n="web.form.nakedDomain">Exclude tenant name from URL</span>
|
|
@@ -2061,8 +2072,8 @@ export function getHtmlTemplate(sessionToken, manageOnly, locale = 'en', transla
|
|
|
2061
2072
|
<div id="tenant-fields">
|
|
2062
2073
|
<div class="form-group" style="margin-bottom: 0.5rem;">
|
|
2063
2074
|
<label for="tenant-name" data-i18n="web.form.tenantId">Default Tenant ID</label>
|
|
2064
|
-
<input type="text" id="tenant-name" placeholder="default" value="default" data-i18n-placeholder="web.form.tenantIdPlaceholder">
|
|
2065
|
-
<small style="color: var(--text-muted)" data-i18n="web.form.tenantIdHint">First tenant identifier (lowercase, no spaces)
|
|
2075
|
+
<input type="text" id="tenant-name" placeholder="default" value="default" disabled readonly data-i18n-placeholder="web.form.tenantIdPlaceholder">
|
|
2076
|
+
<small style="color: var(--text-muted)" data-i18n="web.form.tenantIdHint">First tenant identifier (lowercase, no spaces). Leave empty to use "default".</small>
|
|
2066
2077
|
<small id="tenant-workers-note" style="color: #6b7280; display: none;" data-i18n="web.form.tenantIdWorkerNote">
|
|
2067
2078
|
(Tenant ID is used internally. URL subdomain requires custom domain.)
|
|
2068
2079
|
</small>
|
|
@@ -2071,9 +2082,25 @@ export function getHtmlTemplate(sessionToken, manageOnly, locale = 'en', transla
|
|
|
2071
2082
|
|
|
2072
2083
|
<div class="form-group" style="margin-bottom: 0;">
|
|
2073
2084
|
<label for="tenant-display" data-i18n="web.form.tenantDisplay">Tenant Display Name</label>
|
|
2074
|
-
<input type="text" id="tenant-display" placeholder="My Company" value="
|
|
2085
|
+
<input type="text" id="tenant-display" placeholder="My Company" value="" data-i18n-placeholder="web.form.tenantDisplayPlaceholder">
|
|
2075
2086
|
<small style="color: var(--text-muted)" data-i18n="web.form.tenantDisplayHint">Name shown on login page and consent screen</small>
|
|
2076
2087
|
</div>
|
|
2088
|
+
|
|
2089
|
+
<!-- Primary Tenant (for naked domain) -->
|
|
2090
|
+
<div class="form-group" id="primary-tenant-row" style="margin-bottom: 0.75rem;">
|
|
2091
|
+
<label for="primary-tenant">Primary Tenant (for naked domain)</label>
|
|
2092
|
+
<input type="text" id="primary-tenant" placeholder="Leave empty to use default tenant">
|
|
2093
|
+
<small style="color: var(--text-muted)">Tenant ID to use when accessing the naked domain (e.g., example.com). Leave empty to use the default tenant above.</small>
|
|
2094
|
+
</div>
|
|
2095
|
+
|
|
2096
|
+
<div class="form-group" style="margin-bottom: 0;">
|
|
2097
|
+
<label for="user-id-format" data-i18n="web.form.userIdFormat">User ID Format</label>
|
|
2098
|
+
<select id="user-id-format">
|
|
2099
|
+
<option value="nanoid" selected data-i18n="web.form.userIdNanoid">NanoID (recommended)</option>
|
|
2100
|
+
<option value="uuid" data-i18n="web.form.userIdUuid">UUID v4</option>
|
|
2101
|
+
</select>
|
|
2102
|
+
<small style="color: var(--text-muted)" data-i18n="web.form.userIdFormatHint">Format for generating user IDs. Cannot be changed after users are created.</small>
|
|
2103
|
+
</div>
|
|
2077
2104
|
</div>
|
|
2078
2105
|
|
|
2079
2106
|
<!-- 3.2 UI Domains -->
|
|
@@ -2539,6 +2566,15 @@ export function getHtmlTemplate(sessionToken, manageOnly, locale = 'en', transla
|
|
|
2539
2566
|
</div>
|
|
2540
2567
|
</div>
|
|
2541
2568
|
|
|
2569
|
+
<div class="resource-section" style="margin-bottom: 1.5rem;">
|
|
2570
|
+
<div class="resource-section-title">
|
|
2571
|
+
🔗 URLs
|
|
2572
|
+
</div>
|
|
2573
|
+
<div id="detail-url-list" class="resource-list">
|
|
2574
|
+
<div style="color: var(--text-muted); padding: 0.75rem 0;" data-i18n="web.status.loading">Loading...</div>
|
|
2575
|
+
</div>
|
|
2576
|
+
</div>
|
|
2577
|
+
|
|
2542
2578
|
<div id="detail-resources">
|
|
2543
2579
|
<!-- Workers -->
|
|
2544
2580
|
<div class="resource-section">
|
|
@@ -3073,7 +3109,7 @@ export function getHtmlTemplate(sessionToken, manageOnly, locale = 'en', transla
|
|
|
3073
3109
|
return div;
|
|
3074
3110
|
}
|
|
3075
3111
|
|
|
3076
|
-
function createUrlItem(label,
|
|
3112
|
+
function createUrlItem(label, text, href) {
|
|
3077
3113
|
const div = document.createElement('div');
|
|
3078
3114
|
div.className = 'url-item';
|
|
3079
3115
|
|
|
@@ -3081,14 +3117,23 @@ export function getHtmlTemplate(sessionToken, manageOnly, locale = 'en', transla
|
|
|
3081
3117
|
labelSpan.className = 'url-label';
|
|
3082
3118
|
labelSpan.textContent = label;
|
|
3083
3119
|
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3120
|
+
let valueEl;
|
|
3121
|
+
if (href) {
|
|
3122
|
+
const link = document.createElement('a');
|
|
3123
|
+
link.href = href;
|
|
3124
|
+
link.target = '_blank';
|
|
3125
|
+
link.className = 'url-value';
|
|
3126
|
+
link.textContent = text;
|
|
3127
|
+
valueEl = link;
|
|
3128
|
+
} else {
|
|
3129
|
+
const span = document.createElement('span');
|
|
3130
|
+
span.className = 'url-value';
|
|
3131
|
+
span.textContent = text;
|
|
3132
|
+
valueEl = span;
|
|
3133
|
+
}
|
|
3089
3134
|
|
|
3090
3135
|
div.appendChild(labelSpan);
|
|
3091
|
-
div.appendChild(
|
|
3136
|
+
div.appendChild(valueEl);
|
|
3092
3137
|
return div;
|
|
3093
3138
|
}
|
|
3094
3139
|
|
|
@@ -3376,14 +3421,26 @@ export function getHtmlTemplate(sessionToken, manageOnly, locale = 'en', transla
|
|
|
3376
3421
|
components,
|
|
3377
3422
|
};
|
|
3378
3423
|
|
|
3424
|
+
// Helper to remove https:// prefix for display in input fields
|
|
3425
|
+
const stripProtocol = (url) => {
|
|
3426
|
+
if (!url) return '';
|
|
3427
|
+
return url.replace(/^https?:[/][/]/, '');
|
|
3428
|
+
};
|
|
3429
|
+
|
|
3379
3430
|
// Set form values
|
|
3380
3431
|
document.getElementById('env').value = config.env;
|
|
3381
|
-
document.getElementById('base-domain').value = config.tenant?.baseDomain || config.apiDomain
|
|
3382
|
-
document.getElementById('login-domain').value = config.loginUiDomain
|
|
3383
|
-
document.getElementById('admin-domain').value = config.adminUiDomain
|
|
3432
|
+
document.getElementById('base-domain').value = stripProtocol(config.tenant?.baseDomain || config.apiDomain);
|
|
3433
|
+
document.getElementById('login-domain').value = stripProtocol(config.loginUiDomain);
|
|
3434
|
+
document.getElementById('admin-domain').value = stripProtocol(config.adminUiDomain);
|
|
3384
3435
|
document.getElementById('tenant-name').value = config.tenant?.name || 'default';
|
|
3385
3436
|
document.getElementById('tenant-display').value = config.tenant?.displayName || 'Default Tenant';
|
|
3386
3437
|
document.getElementById('naked-domain').checked = config.tenant?.nakedDomain || false;
|
|
3438
|
+
if (document.getElementById('user-id-format')) {
|
|
3439
|
+
document.getElementById('user-id-format').value = config.tenant?.userIdFormat || 'nanoid';
|
|
3440
|
+
}
|
|
3441
|
+
if (document.getElementById('primary-tenant')) {
|
|
3442
|
+
document.getElementById('primary-tenant').value = config.tenant?.primaryTenant || '';
|
|
3443
|
+
}
|
|
3387
3444
|
|
|
3388
3445
|
// Set component checkboxes
|
|
3389
3446
|
if (document.getElementById('comp-login-ui')) {
|
|
@@ -3402,6 +3459,14 @@ export function getHtmlTemplate(sessionToken, manageOnly, locale = 'en', transla
|
|
|
3402
3459
|
document.getElementById('comp-vc').checked = components.vc === true;
|
|
3403
3460
|
}
|
|
3404
3461
|
|
|
3462
|
+
// Restore domain check UI if custom domain is set
|
|
3463
|
+
const loadedBaseDomain = document.getElementById('base-domain').value.trim();
|
|
3464
|
+
if (loadedBaseDomain && /^[a-z0-9][a-z0-9.-]*\\.[a-z]{2,}$/i.test(loadedBaseDomain)) {
|
|
3465
|
+
document.getElementById('domain-check-row').style.display = 'flex';
|
|
3466
|
+
// Auto-trigger zone check for loaded domain
|
|
3467
|
+
setTimeout(() => document.getElementById('check-domain-btn').click(), 300);
|
|
3468
|
+
}
|
|
3469
|
+
|
|
3405
3470
|
// Trigger env input to update preview/default labels
|
|
3406
3471
|
document.getElementById('env').dispatchEvent(new Event('input'));
|
|
3407
3472
|
|
|
@@ -3420,7 +3485,7 @@ export function getHtmlTemplate(sessionToken, manageOnly, locale = 'en', transla
|
|
|
3420
3485
|
const env = document.getElementById('env').value.trim().toLowerCase().replace(/[^a-z0-9-]/g, '') || '{env}';
|
|
3421
3486
|
const baseDomain = document.getElementById('base-domain').value.trim();
|
|
3422
3487
|
const nakedDomain = document.getElementById('naked-domain').checked;
|
|
3423
|
-
const tenantName = document.getElementById('tenant-name').value.trim()
|
|
3488
|
+
const tenantName = document.getElementById('tenant-name').value.trim();
|
|
3424
3489
|
const loginDomain = document.getElementById('login-domain').value.trim();
|
|
3425
3490
|
const adminDomain = document.getElementById('admin-domain').value.trim();
|
|
3426
3491
|
|
|
@@ -3457,7 +3522,9 @@ export function getHtmlTemplate(sessionToken, manageOnly, locale = 'en', transla
|
|
|
3457
3522
|
if (nakedDomain) {
|
|
3458
3523
|
document.getElementById('preview-issuer').textContent = 'https://' + baseDomain;
|
|
3459
3524
|
} else {
|
|
3460
|
-
|
|
3525
|
+
// Multi-tenant: show placeholder or actual tenant name
|
|
3526
|
+
const tenantDisplay = tenantName || '{tenant}';
|
|
3527
|
+
document.getElementById('preview-issuer').textContent = 'https://' + tenantDisplay + '.' + baseDomain;
|
|
3461
3528
|
}
|
|
3462
3529
|
} else {
|
|
3463
3530
|
// Workers.dev - no tenant prefix (wildcard subdomains not supported)
|
|
@@ -3504,6 +3571,32 @@ export function getHtmlTemplate(sessionToken, manageOnly, locale = 'en', transla
|
|
|
3504
3571
|
}
|
|
3505
3572
|
});
|
|
3506
3573
|
|
|
3574
|
+
// Validate environment name on blur
|
|
3575
|
+
const envInput = document.getElementById('env');
|
|
3576
|
+
const envError = document.getElementById('env-error');
|
|
3577
|
+
function validateEnvName(value) {
|
|
3578
|
+
return /^[a-z][a-z0-9-]*$/.test(value);
|
|
3579
|
+
}
|
|
3580
|
+
envInput.addEventListener('blur', () => {
|
|
3581
|
+
const value = envInput.value.trim();
|
|
3582
|
+
if (value && !validateEnvName(value)) {
|
|
3583
|
+
envInput.style.borderColor = 'var(--error)';
|
|
3584
|
+
if (envError) envError.style.display = 'block';
|
|
3585
|
+
} else {
|
|
3586
|
+
envInput.style.borderColor = '';
|
|
3587
|
+
if (envError) envError.style.display = 'none';
|
|
3588
|
+
}
|
|
3589
|
+
});
|
|
3590
|
+
envInput.addEventListener('input', () => {
|
|
3591
|
+
if (envInput.style.borderColor) {
|
|
3592
|
+
const value = envInput.value.trim();
|
|
3593
|
+
if (!value || validateEnvName(value)) {
|
|
3594
|
+
envInput.style.borderColor = '';
|
|
3595
|
+
if (envError) envError.style.display = 'none';
|
|
3596
|
+
}
|
|
3597
|
+
}
|
|
3598
|
+
});
|
|
3599
|
+
|
|
3507
3600
|
// Update UI based on base domain presence
|
|
3508
3601
|
function updateBaseDomainUI() {
|
|
3509
3602
|
const baseDomain = document.getElementById('base-domain').value.trim();
|
|
@@ -3513,6 +3606,9 @@ export function getHtmlTemplate(sessionToken, manageOnly, locale = 'en', transla
|
|
|
3513
3606
|
const workersDevNote = document.getElementById('workers-dev-note');
|
|
3514
3607
|
const tenantWorkersNote = document.getElementById('tenant-workers-note');
|
|
3515
3608
|
const tenantFields = document.getElementById('tenant-fields');
|
|
3609
|
+
const tenantNameInput = document.getElementById('tenant-name');
|
|
3610
|
+
const primaryTenantRow = document.getElementById('primary-tenant-row');
|
|
3611
|
+
const primaryTenantInput = document.getElementById('primary-tenant');
|
|
3516
3612
|
|
|
3517
3613
|
if (baseDomain) {
|
|
3518
3614
|
// Custom domain - enable tenant subdomain options
|
|
@@ -3521,10 +3617,13 @@ export function getHtmlTemplate(sessionToken, manageOnly, locale = 'en', transla
|
|
|
3521
3617
|
nakedDomainHint.style.display = 'block';
|
|
3522
3618
|
workersDevNote.style.display = 'none';
|
|
3523
3619
|
tenantWorkersNote.style.display = 'none';
|
|
3620
|
+
tenantNameInput.disabled = false;
|
|
3621
|
+
tenantNameInput.readOnly = false;
|
|
3524
3622
|
// Show tenant fields if naked domain is not checked
|
|
3525
3623
|
if (!nakedDomainCheckbox.checked) {
|
|
3526
3624
|
tenantFields.style.display = 'block';
|
|
3527
3625
|
}
|
|
3626
|
+
primaryTenantRow.style.display = nakedDomainCheckbox.checked ? 'block' : 'none';
|
|
3528
3627
|
} else {
|
|
3529
3628
|
// Workers.dev - tenant subdomains not supported
|
|
3530
3629
|
nakedDomainCheckbox.disabled = true;
|
|
@@ -3533,7 +3632,12 @@ export function getHtmlTemplate(sessionToken, manageOnly, locale = 'en', transla
|
|
|
3533
3632
|
nakedDomainHint.style.display = 'none';
|
|
3534
3633
|
workersDevNote.style.display = 'block';
|
|
3535
3634
|
tenantWorkersNote.style.display = 'block';
|
|
3536
|
-
tenantFields.style.display = 'block';
|
|
3635
|
+
tenantFields.style.display = 'block';
|
|
3636
|
+
tenantNameInput.value = 'default';
|
|
3637
|
+
tenantNameInput.disabled = true;
|
|
3638
|
+
tenantNameInput.readOnly = true;
|
|
3639
|
+
primaryTenantInput.value = '';
|
|
3640
|
+
primaryTenantRow.style.display = 'none';
|
|
3537
3641
|
}
|
|
3538
3642
|
}
|
|
3539
3643
|
|
|
@@ -3541,6 +3645,68 @@ export function getHtmlTemplate(sessionToken, manageOnly, locale = 'en', transla
|
|
|
3541
3645
|
document.getElementById('base-domain').addEventListener('input', () => {
|
|
3542
3646
|
updateBaseDomainUI();
|
|
3543
3647
|
updatePreview();
|
|
3648
|
+
// Show/hide domain check row
|
|
3649
|
+
const domainCheckRow = document.getElementById('domain-check-row');
|
|
3650
|
+
const baseDomain = document.getElementById('base-domain').value.trim();
|
|
3651
|
+
if (baseDomain && /^[a-z0-9][a-z0-9.-]*\\.[a-z]{2,}$/i.test(baseDomain)) {
|
|
3652
|
+
domainCheckRow.style.display = 'flex';
|
|
3653
|
+
} else {
|
|
3654
|
+
domainCheckRow.style.display = 'none';
|
|
3655
|
+
document.getElementById('domain-check-status').textContent = '';
|
|
3656
|
+
document.getElementById('custom-domain-binding-row').style.display = 'none';
|
|
3657
|
+
}
|
|
3658
|
+
});
|
|
3659
|
+
|
|
3660
|
+
// Check Domain button handler
|
|
3661
|
+
let domainZoneId = null;
|
|
3662
|
+
document.getElementById('check-domain-btn').addEventListener('click', async () => {
|
|
3663
|
+
const domain = document.getElementById('base-domain').value.trim();
|
|
3664
|
+
if (!domain) return;
|
|
3665
|
+
|
|
3666
|
+
const statusEl = document.getElementById('domain-check-status');
|
|
3667
|
+
const bindingRow = document.getElementById('custom-domain-binding-row');
|
|
3668
|
+
statusEl.textContent = t('domain.checkingZone', { domain });
|
|
3669
|
+
statusEl.style.color = 'var(--text-muted)';
|
|
3670
|
+
domainZoneId = null;
|
|
3671
|
+
|
|
3672
|
+
try {
|
|
3673
|
+
const result = await api('/cloudflare/check-zone', {
|
|
3674
|
+
method: 'POST',
|
|
3675
|
+
body: { domain },
|
|
3676
|
+
});
|
|
3677
|
+
|
|
3678
|
+
if (result.found) {
|
|
3679
|
+
statusEl.textContent = '✓ ' + t('domain.zoneFound', { zone: result.zone.name, status: result.zone.status });
|
|
3680
|
+
statusEl.style.color = 'var(--success, #22c55e)';
|
|
3681
|
+
bindingRow.style.display = 'flex';
|
|
3682
|
+
domainZoneId = result.zone.id;
|
|
3683
|
+
} else {
|
|
3684
|
+
const errorMsg = result.error
|
|
3685
|
+
? '⚠ ' + t('domain.zoneCheckFailed') + ': ' + result.error
|
|
3686
|
+
: '⚠ ' + t('domain.zoneNotFound', { zone: domain });
|
|
3687
|
+
statusEl.textContent = errorMsg;
|
|
3688
|
+
statusEl.style.color = 'var(--warning, #d97706)';
|
|
3689
|
+
bindingRow.style.display = 'none';
|
|
3690
|
+
domainZoneId = null;
|
|
3691
|
+
}
|
|
3692
|
+
} catch (e) {
|
|
3693
|
+
statusEl.textContent = '⚠ ' + t('domain.zoneCheckFailed');
|
|
3694
|
+
statusEl.style.color = 'var(--warning, #d97706)';
|
|
3695
|
+
bindingRow.style.display = 'none';
|
|
3696
|
+
domainZoneId = null;
|
|
3697
|
+
}
|
|
3698
|
+
});
|
|
3699
|
+
|
|
3700
|
+
// Auto-check domain on blur (debounced)
|
|
3701
|
+
let domainCheckTimer;
|
|
3702
|
+
document.getElementById('base-domain').addEventListener('blur', () => {
|
|
3703
|
+
clearTimeout(domainCheckTimer);
|
|
3704
|
+
domainCheckTimer = setTimeout(() => {
|
|
3705
|
+
const domain = document.getElementById('base-domain').value.trim();
|
|
3706
|
+
if (domain && /^[a-z0-9][a-z0-9.-]*\\.[a-z]{2,}$/i.test(domain)) {
|
|
3707
|
+
document.getElementById('check-domain-btn').click();
|
|
3708
|
+
}
|
|
3709
|
+
}, 500);
|
|
3544
3710
|
});
|
|
3545
3711
|
|
|
3546
3712
|
// Initial UI state
|
|
@@ -3549,12 +3715,15 @@ export function getHtmlTemplate(sessionToken, manageOnly, locale = 'en', transla
|
|
|
3549
3715
|
// Naked domain toggle - show/hide tenant name field and update placeholder
|
|
3550
3716
|
document.getElementById('naked-domain').addEventListener('change', (e) => {
|
|
3551
3717
|
const tenantFields = document.getElementById('tenant-fields');
|
|
3718
|
+
const primaryTenantRow = document.getElementById('primary-tenant-row');
|
|
3552
3719
|
const baseDomainInput = document.getElementById('base-domain');
|
|
3553
3720
|
if (e.target.checked) {
|
|
3554
3721
|
tenantFields.style.display = 'none';
|
|
3722
|
+
primaryTenantRow.style.display = 'block';
|
|
3555
3723
|
baseDomainInput.placeholder = 'example.com';
|
|
3556
3724
|
} else {
|
|
3557
3725
|
tenantFields.style.display = 'block';
|
|
3726
|
+
primaryTenantRow.style.display = 'none';
|
|
3558
3727
|
baseDomainInput.placeholder = 'oidc.example.com';
|
|
3559
3728
|
}
|
|
3560
3729
|
updatePreview();
|
|
@@ -3597,11 +3766,15 @@ export function getHtmlTemplate(sessionToken, manageOnly, locale = 'en', transla
|
|
|
3597
3766
|
|
|
3598
3767
|
document.getElementById('btn-configure').addEventListener('click', async () => {
|
|
3599
3768
|
// Get and validate environment name
|
|
3600
|
-
|
|
3601
|
-
if (!
|
|
3602
|
-
|
|
3769
|
+
const envRaw = document.getElementById('env').value.trim();
|
|
3770
|
+
if (!envRaw || !validateEnvName(envRaw)) {
|
|
3771
|
+
document.getElementById('env').style.borderColor = 'var(--error)';
|
|
3772
|
+
const errEl = document.getElementById('env-error');
|
|
3773
|
+
if (errEl) errEl.style.display = 'block';
|
|
3774
|
+
document.getElementById('env').focus();
|
|
3603
3775
|
return;
|
|
3604
3776
|
}
|
|
3777
|
+
const env = envRaw;
|
|
3605
3778
|
|
|
3606
3779
|
// Check if environment already exists
|
|
3607
3780
|
const configureBtn = document.getElementById('btn-configure');
|
|
@@ -3627,26 +3800,32 @@ export function getHtmlTemplate(sessionToken, manageOnly, locale = 'en', transla
|
|
|
3627
3800
|
}
|
|
3628
3801
|
|
|
3629
3802
|
const baseDomain = document.getElementById('base-domain').value.trim();
|
|
3630
|
-
const
|
|
3631
|
-
const
|
|
3803
|
+
const hasCustomApiDomain = !!baseDomain;
|
|
3804
|
+
const nakedDomain = hasCustomApiDomain && document.getElementById('naked-domain').checked;
|
|
3805
|
+
const tenantName = hasCustomApiDomain
|
|
3806
|
+
? (document.getElementById('tenant-name').value.trim() || 'default')
|
|
3807
|
+
: 'default';
|
|
3632
3808
|
const tenantDisplayName = document.getElementById('tenant-display').value.trim() || 'Default Tenant';
|
|
3809
|
+
const userIdFormat = document.getElementById('user-id-format').value || 'nanoid';
|
|
3810
|
+
const primaryTenant = hasCustomApiDomain && nakedDomain
|
|
3811
|
+
? (document.getElementById('primary-tenant').value.trim() || undefined)
|
|
3812
|
+
: undefined;
|
|
3633
3813
|
const loginDomain = document.getElementById('login-domain').value.trim();
|
|
3634
3814
|
const adminDomain = document.getElementById('admin-domain').value.trim();
|
|
3635
3815
|
|
|
3636
|
-
// API domain = base domain or null (workers.dev fallback)
|
|
3637
|
-
const apiDomain = baseDomain || null;
|
|
3638
|
-
|
|
3639
3816
|
config = {
|
|
3640
3817
|
env,
|
|
3641
|
-
apiDomain,
|
|
3818
|
+
apiDomain: baseDomain || null,
|
|
3642
3819
|
loginUiDomain: loginDomain || null,
|
|
3643
3820
|
adminUiDomain: adminDomain || null,
|
|
3644
3821
|
tenant: {
|
|
3645
|
-
name:
|
|
3822
|
+
name: tenantName,
|
|
3646
3823
|
displayName: tenantDisplayName,
|
|
3647
|
-
multiTenant:
|
|
3648
|
-
baseDomain: baseDomain
|
|
3649
|
-
nakedDomain:
|
|
3824
|
+
multiTenant: hasCustomApiDomain,
|
|
3825
|
+
baseDomain: hasCustomApiDomain ? baseDomain : undefined,
|
|
3826
|
+
nakedDomain: nakedDomain,
|
|
3827
|
+
userIdFormat: userIdFormat,
|
|
3828
|
+
primaryTenant: primaryTenant,
|
|
3650
3829
|
},
|
|
3651
3830
|
components: {
|
|
3652
3831
|
api: true,
|
|
@@ -3661,15 +3840,18 @@ export function getHtmlTemplate(sessionToken, manageOnly, locale = 'en', transla
|
|
|
3661
3840
|
};
|
|
3662
3841
|
|
|
3663
3842
|
// Create default config with component settings
|
|
3843
|
+
const customDomainBinding = document.getElementById('custom-domain-binding')?.checked ?? false;
|
|
3664
3844
|
await api('/config/default', {
|
|
3665
3845
|
method: 'POST',
|
|
3666
3846
|
body: {
|
|
3667
3847
|
env,
|
|
3668
|
-
apiDomain,
|
|
3848
|
+
apiDomain: config.apiDomain,
|
|
3669
3849
|
loginUiDomain: loginDomain,
|
|
3670
3850
|
adminUiDomain: adminDomain,
|
|
3671
3851
|
tenant: config.tenant,
|
|
3672
3852
|
components: config.components,
|
|
3853
|
+
zoneId: domainZoneId || null,
|
|
3854
|
+
customDomainBinding: config.apiDomain ? customDomainBinding : false,
|
|
3673
3855
|
},
|
|
3674
3856
|
});
|
|
3675
3857
|
|
|
@@ -3956,7 +4138,7 @@ export function getHtmlTemplate(sessionToken, manageOnly, locale = 'en', transla
|
|
|
3956
4138
|
|
|
3957
4139
|
const result = await api('/provision', {
|
|
3958
4140
|
method: 'POST',
|
|
3959
|
-
body: { env: config.env, databaseConfig: config.database },
|
|
4141
|
+
body: { env: config.env, databaseConfig: config.database, createR2: true },
|
|
3960
4142
|
});
|
|
3961
4143
|
|
|
3962
4144
|
// Stop polling
|
|
@@ -3969,6 +4151,10 @@ export function getHtmlTemplate(sessionToken, manageOnly, locale = 'en', transla
|
|
|
3969
4151
|
// Final progress update
|
|
3970
4152
|
updateProgressUI('provision', totalResources, totalResources, '✅ Provisioning complete!');
|
|
3971
4153
|
output.textContent += '\\n✅ Provisioning complete!\\n';
|
|
4154
|
+
if (result.savedPaths) {
|
|
4155
|
+
output.textContent += '📁 Config: ' + result.savedPaths.config + '\\n';
|
|
4156
|
+
output.textContent += '📁 Lock: ' + result.savedPaths.lock + '\\n';
|
|
4157
|
+
}
|
|
3972
4158
|
scrollToBottom(log);
|
|
3973
4159
|
status.textContent = t('web.status.complete');
|
|
3974
4160
|
status.className = 'status-badge status-success';
|
|
@@ -4128,11 +4314,14 @@ export function getHtmlTemplate(sessionToken, manageOnly, locale = 'en', transla
|
|
|
4128
4314
|
apiUrl = 'https://' + workersDomain;
|
|
4129
4315
|
}
|
|
4130
4316
|
// Login UI URL for setup page (setup page is in Login UI, not API)
|
|
4317
|
+
const loginUiEnabled = config.components?.loginUi !== false;
|
|
4131
4318
|
const loginPagesDomain = config.env + '-ar-login-ui.pages.dev';
|
|
4132
|
-
const loginUiUrl =
|
|
4319
|
+
const loginUiUrl = loginUiEnabled
|
|
4320
|
+
? (config.loginUiDomain ? 'https://' + config.loginUiDomain : 'https://' + loginPagesDomain)
|
|
4321
|
+
: null;
|
|
4133
4322
|
|
|
4134
4323
|
output.textContent += ' API URL: ' + apiUrl + '\\n';
|
|
4135
|
-
output.textContent += ' Login UI URL: ' + loginUiUrl + '\\n';
|
|
4324
|
+
output.textContent += ' Login UI URL: ' + (loginUiUrl || 'Not deployed') + '\\n';
|
|
4136
4325
|
output.textContent += ' Keys Dir: .authrim-keys/' + config.env + '/\\n';
|
|
4137
4326
|
scrollToBottom(log);
|
|
4138
4327
|
|
|
@@ -4215,19 +4404,32 @@ export function getHtmlTemplate(sessionToken, manageOnly, locale = 'en', transla
|
|
|
4215
4404
|
// Workers.dev - no tenant prefix (wildcard subdomains not supported)
|
|
4216
4405
|
apiUrl = 'https://' + workersDomain;
|
|
4217
4406
|
}
|
|
4218
|
-
const
|
|
4219
|
-
const
|
|
4407
|
+
const loginUiEnabled = config.components?.loginUi !== false;
|
|
4408
|
+
const loginUrl = loginUiEnabled
|
|
4409
|
+
? (config.loginUiDomain ? 'https://' + config.loginUiDomain : 'https://' + loginPagesDomain)
|
|
4410
|
+
: null;
|
|
4411
|
+
const adminUrl = (config.adminUiDomain ? 'https://' + config.adminUiDomain : 'https://' + adminPagesDomain) + '/admin';
|
|
4220
4412
|
|
|
4221
4413
|
// Clear and rebuild URLs section safely
|
|
4222
4414
|
urlsEl.textContent = '';
|
|
4223
4415
|
|
|
4224
4416
|
// API URL with OIDC Discovery link
|
|
4225
|
-
urlsEl.appendChild(createUrlItem('API (Issuer):', apiUrl));
|
|
4417
|
+
urlsEl.appendChild(createUrlItem('API (Issuer):', apiUrl, apiUrl));
|
|
4226
4418
|
const discoveryUrl = apiUrl + '/.well-known/openid-configuration';
|
|
4227
|
-
urlsEl.appendChild(createUrlItem('Discovery:', discoveryUrl));
|
|
4228
|
-
|
|
4229
|
-
urlsEl.appendChild(createUrlItem('Login UI:', loginUrl));
|
|
4230
|
-
urlsEl.appendChild(createUrlItem('Admin UI:', adminUrl));
|
|
4419
|
+
urlsEl.appendChild(createUrlItem('Discovery:', discoveryUrl, discoveryUrl));
|
|
4420
|
+
|
|
4421
|
+
urlsEl.appendChild(createUrlItem('Login UI:', loginUrl || t('web.status.notDeployed'), loginUrl || undefined));
|
|
4422
|
+
urlsEl.appendChild(createUrlItem('Admin UI:', adminUrl, adminUrl));
|
|
4423
|
+
|
|
4424
|
+
// Show custom domain propagation note when any custom domain is set
|
|
4425
|
+
if (config.apiDomain || config.loginUiDomain || config.adminUiDomain) {
|
|
4426
|
+
const domainNoteDiv = document.createElement('div');
|
|
4427
|
+
domainNoteDiv.className = 'hint-box';
|
|
4428
|
+
domainNoteDiv.style.marginTop = '0.75rem';
|
|
4429
|
+
domainNoteDiv.setAttribute('data-i18n', 'web.complete.customDomainNote');
|
|
4430
|
+
domainNoteDiv.textContent = t('web.complete.customDomainNote');
|
|
4431
|
+
urlsEl.appendChild(domainNoteDiv);
|
|
4432
|
+
}
|
|
4231
4433
|
|
|
4232
4434
|
// Create Admin Setup section (separate, prominent box)
|
|
4233
4435
|
const adminSetupSection = document.createElement('div');
|
|
@@ -4242,50 +4444,123 @@ export function getHtmlTemplate(sessionToken, manageOnly, locale = 'en', transla
|
|
|
4242
4444
|
const day = expiresDate.getDate();
|
|
4243
4445
|
const hours = expiresDate.getHours().toString().padStart(2, '0');
|
|
4244
4446
|
const minutes = expiresDate.getMinutes().toString().padStart(2, '0');
|
|
4245
|
-
expiresText =
|
|
4447
|
+
expiresText = \`\${month}/\${day} \${hours}:\${minutes}\`;
|
|
4246
4448
|
}
|
|
4247
4449
|
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4450
|
+
// Header row
|
|
4451
|
+
const headerDiv = document.createElement('div');
|
|
4452
|
+
headerDiv.style.cssText = 'display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.75rem;';
|
|
4453
|
+
const iconSpan = document.createElement('span');
|
|
4454
|
+
iconSpan.style.fontSize = '1.5rem';
|
|
4455
|
+
iconSpan.textContent = '🔐';
|
|
4456
|
+
const titleH4 = document.createElement('h4');
|
|
4457
|
+
titleH4.style.cssText = 'margin: 0; font-size: 1.1rem; font-weight: 600; color: var(--primary);';
|
|
4458
|
+
titleH4.textContent = t('web.complete.adminAccountTitle');
|
|
4459
|
+
titleH4.setAttribute('data-i18n', 'web.complete.adminAccountTitle');
|
|
4460
|
+
const importantBadge = document.createElement('span');
|
|
4461
|
+
importantBadge.style.cssText = 'background: var(--warning); color: white; font-size: 0.7rem; padding: 0.2rem 0.5rem; border-radius: 4px; font-weight: 600;';
|
|
4462
|
+
importantBadge.textContent = t('web.complete.adminAccountImportant');
|
|
4463
|
+
importantBadge.setAttribute('data-i18n', 'web.complete.adminAccountImportant');
|
|
4464
|
+
headerDiv.appendChild(iconSpan);
|
|
4465
|
+
headerDiv.appendChild(titleH4);
|
|
4466
|
+
headerDiv.appendChild(importantBadge);
|
|
4467
|
+
|
|
4468
|
+
// Description
|
|
4469
|
+
const descP = document.createElement('p');
|
|
4470
|
+
descP.style.cssText = 'margin: 0 0 0.75rem; font-size: 0.9rem; color: var(--text-muted);';
|
|
4471
|
+
descP.textContent = t('web.complete.adminAccountDesc');
|
|
4472
|
+
descP.setAttribute('data-i18n', 'web.complete.adminAccountDesc');
|
|
4473
|
+
|
|
4474
|
+
// URL input + copy button row
|
|
4475
|
+
const inputRow = document.createElement('div');
|
|
4476
|
+
inputRow.style.cssText = 'display: flex; gap: 0.5rem; align-items: center;';
|
|
4477
|
+
const urlInput = document.createElement('input');
|
|
4478
|
+
urlInput.type = 'text';
|
|
4479
|
+
urlInput.value = result.setupUrl;
|
|
4480
|
+
urlInput.readOnly = true;
|
|
4481
|
+
urlInput.style.cssText = 'flex: 1; min-width: 200px; padding: 0.625rem 0.75rem; border: 1px solid var(--border); border-radius: 8px; font-family: var(--font-mono); font-size: 0.8rem; background: var(--card-bg); color: var(--text);';
|
|
4482
|
+
const copyBtn = document.createElement('button');
|
|
4483
|
+
copyBtn.className = 'btn-secondary';
|
|
4484
|
+
copyBtn.style.whiteSpace = 'nowrap';
|
|
4485
|
+
copyBtn.textContent = t('web.complete.copy');
|
|
4486
|
+
copyBtn.setAttribute('data-i18n', 'web.complete.copy');
|
|
4487
|
+
const setupUrlForCopy = result.setupUrl;
|
|
4488
|
+
copyBtn.addEventListener('click', () => {
|
|
4489
|
+
navigator.clipboard.writeText(setupUrlForCopy);
|
|
4490
|
+
copyBtn.removeAttribute('data-i18n'); // prevent updateAllTranslations from overwriting "Copied"
|
|
4491
|
+
copyBtn.textContent = t('web.complete.copied');
|
|
4492
|
+
setTimeout(() => {
|
|
4493
|
+
copyBtn.textContent = t('web.complete.copy');
|
|
4494
|
+
copyBtn.setAttribute('data-i18n', 'web.complete.copy');
|
|
4495
|
+
}, 2000);
|
|
4496
|
+
});
|
|
4497
|
+
inputRow.appendChild(urlInput);
|
|
4498
|
+
inputRow.appendChild(copyBtn);
|
|
4499
|
+
|
|
4500
|
+
// Open Setup button
|
|
4501
|
+
const openDiv = document.createElement('div');
|
|
4502
|
+
openDiv.style.cssText = 'text-align: center; margin-top: 1rem;';
|
|
4503
|
+
const openLink = document.createElement('a');
|
|
4504
|
+
openLink.href = result.setupUrl;
|
|
4505
|
+
openLink.target = '_blank';
|
|
4506
|
+
openLink.className = 'btn-primary';
|
|
4507
|
+
openLink.textContent = t('web.complete.openSetup');
|
|
4508
|
+
openLink.setAttribute('data-i18n', 'web.complete.openSetup');
|
|
4509
|
+
openDiv.appendChild(openLink);
|
|
4510
|
+
|
|
4511
|
+
// Warning hint (uses innerHTML for <strong> tags — not updated on language change)
|
|
4512
|
+
const hintDiv = document.createElement('div');
|
|
4513
|
+
hintDiv.className = 'hint-box';
|
|
4514
|
+
hintDiv.style.marginTop = '0.75rem';
|
|
4515
|
+
const warningTemplate = t('web.complete.urlWarning', { date: expiresText });
|
|
4516
|
+
// warningTemplate may contain <strong> tags — parse safely
|
|
4517
|
+
const warningPrefix = document.createTextNode('⚠️ ');
|
|
4518
|
+
hintDiv.appendChild(warningPrefix);
|
|
4519
|
+
const warningSpan = document.createElement('span');
|
|
4520
|
+
warningSpan.innerHTML = warningTemplate; // safe: value from our own translation strings only
|
|
4521
|
+
hintDiv.appendChild(warningSpan);
|
|
4522
|
+
|
|
4523
|
+
adminSetupSection.appendChild(headerDiv);
|
|
4524
|
+
adminSetupSection.appendChild(descP);
|
|
4525
|
+
adminSetupSection.appendChild(inputRow);
|
|
4526
|
+
adminSetupSection.appendChild(openDiv);
|
|
4527
|
+
adminSetupSection.appendChild(hintDiv);
|
|
4268
4528
|
} else {
|
|
4269
|
-
//
|
|
4270
|
-
|
|
4529
|
+
// Header row
|
|
4530
|
+
const headerDiv = document.createElement('div');
|
|
4531
|
+
headerDiv.style.cssText = 'display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.5rem;';
|
|
4532
|
+
const iconSpan = document.createElement('span');
|
|
4533
|
+
iconSpan.style.fontSize = '1.5rem';
|
|
4534
|
+
iconSpan.textContent = '🔐';
|
|
4535
|
+
const titleH4 = document.createElement('h4');
|
|
4536
|
+
titleH4.style.cssText = 'margin: 0; font-size: 1.1rem; font-weight: 600; color: var(--text-muted);';
|
|
4537
|
+
titleH4.textContent = t('web.complete.adminAccountTitle');
|
|
4538
|
+
titleH4.setAttribute('data-i18n', 'web.complete.adminAccountTitle');
|
|
4539
|
+
headerDiv.appendChild(iconSpan);
|
|
4540
|
+
headerDiv.appendChild(titleH4);
|
|
4541
|
+
|
|
4542
|
+
const descP = document.createElement('p');
|
|
4543
|
+
descP.style.cssText = 'margin: 0; font-size: 0.9rem; color: var(--text-muted);';
|
|
4544
|
+
descP.textContent = t('web.complete.adminSetupUnavailable');
|
|
4545
|
+
descP.setAttribute('data-i18n', 'web.complete.adminSetupUnavailable');
|
|
4546
|
+
|
|
4547
|
+
adminSetupSection.appendChild(headerDiv);
|
|
4548
|
+
adminSetupSection.appendChild(descP);
|
|
4549
|
+
|
|
4271
4550
|
if (result && result.adminSetupDebug) {
|
|
4272
4551
|
const debug = result.adminSetupDebug;
|
|
4552
|
+
const debugP = document.createElement('p');
|
|
4553
|
+
debugP.style.cssText = 'margin: 0.5rem 0 0; font-size: 0.85rem;';
|
|
4273
4554
|
if (debug.alreadyCompleted) {
|
|
4274
|
-
|
|
4555
|
+
debugP.style.color = 'var(--text-muted)';
|
|
4556
|
+
debugP.textContent = t('web.complete.adminSetupUnavailable');
|
|
4557
|
+
debugP.setAttribute('data-i18n', 'web.complete.adminSetupUnavailable');
|
|
4275
4558
|
} else if (debug.error) {
|
|
4276
|
-
|
|
4559
|
+
debugP.style.color = 'var(--error)';
|
|
4560
|
+
debugP.textContent = 'Error: ' + debug.error;
|
|
4277
4561
|
}
|
|
4562
|
+
if (debugP.textContent) adminSetupSection.appendChild(debugP);
|
|
4278
4563
|
}
|
|
4279
|
-
adminSetupSection.innerHTML = \`
|
|
4280
|
-
<div style="display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.5rem;">
|
|
4281
|
-
<span style="font-size: 1.5rem;">🔐</span>
|
|
4282
|
-
<h4 style="margin: 0; font-size: 1.1rem; font-weight: 600; color: var(--text-muted);">Admin Account Setup</h4>
|
|
4283
|
-
</div>
|
|
4284
|
-
<p style="margin: 0; font-size: 0.9rem; color: var(--text-muted);">
|
|
4285
|
-
Setup URL not available. You can configure admin access from the Admin UI later.
|
|
4286
|
-
</p>
|
|
4287
|
-
\${debugInfo}
|
|
4288
|
-
\`;
|
|
4289
4564
|
}
|
|
4290
4565
|
urlsEl.parentNode.insertBefore(adminSetupSection, urlsEl.nextSibling);
|
|
4291
4566
|
|
|
@@ -4421,22 +4696,13 @@ export function getHtmlTemplate(sessionToken, manageOnly, locale = 'en', transla
|
|
|
4421
4696
|
const now = new Date().toISOString();
|
|
4422
4697
|
const env = config.env;
|
|
4423
4698
|
|
|
4424
|
-
// Calculate auto-generated URLs
|
|
4425
|
-
const workersDomain =
|
|
4699
|
+
// Calculate auto-generated URLs (use workersSubdomain if available for full-form URL)
|
|
4700
|
+
const workersDomain = workersSubdomain
|
|
4701
|
+
? env + '-ar-router.' + workersSubdomain + '.workers.dev'
|
|
4702
|
+
: env + '-ar-router.workers.dev';
|
|
4426
4703
|
const loginPagesDomain = env + '-ar-login-ui.pages.dev';
|
|
4427
4704
|
const adminPagesDomain = env + '-ar-admin-ui.pages.dev';
|
|
4428
4705
|
|
|
4429
|
-
// Build issuer URL based on tenant settings
|
|
4430
|
-
let issuerAutoUrl = 'https://' + workersDomain;
|
|
4431
|
-
if (config.tenant && config.tenant.baseDomain) {
|
|
4432
|
-
if (config.tenant.nakedDomain) {
|
|
4433
|
-
issuerAutoUrl = 'https://' + config.tenant.baseDomain;
|
|
4434
|
-
} else {
|
|
4435
|
-
const tenantName = config.tenant.name || 'default';
|
|
4436
|
-
issuerAutoUrl = 'https://' + tenantName + '.' + config.tenant.baseDomain;
|
|
4437
|
-
}
|
|
4438
|
-
}
|
|
4439
|
-
|
|
4440
4706
|
// Build config in AuthrimConfigSchema format
|
|
4441
4707
|
const configToSave = {
|
|
4442
4708
|
version: '1.0.0',
|
|
@@ -4448,7 +4714,9 @@ export function getHtmlTemplate(sessionToken, manageOnly, locale = 'en', transla
|
|
|
4448
4714
|
urls: {
|
|
4449
4715
|
api: {
|
|
4450
4716
|
custom: config.apiDomain || null,
|
|
4451
|
-
auto
|
|
4717
|
+
// api.auto must always be the workers.dev URL (used for proxy backend and CORS).
|
|
4718
|
+
// The custom domain (issuer URL) belongs in api.custom, not api.auto.
|
|
4719
|
+
auto: 'https://' + workersDomain,
|
|
4452
4720
|
},
|
|
4453
4721
|
loginUi: {
|
|
4454
4722
|
custom: config.loginUiDomain || null,
|
|
@@ -4464,6 +4732,9 @@ export function getHtmlTemplate(sessionToken, manageOnly, locale = 'en', transla
|
|
|
4464
4732
|
displayName: config.tenant?.displayName || 'Default Tenant',
|
|
4465
4733
|
multiTenant: config.tenant?.multiTenant || false,
|
|
4466
4734
|
baseDomain: config.tenant?.baseDomain || undefined,
|
|
4735
|
+
nakedDomain: config.tenant?.nakedDomain ?? false,
|
|
4736
|
+
userIdFormat: config.tenant?.userIdFormat || 'nanoid',
|
|
4737
|
+
primaryTenant: config.tenant?.primaryTenant || undefined,
|
|
4467
4738
|
},
|
|
4468
4739
|
components: config.components || {
|
|
4469
4740
|
api: true,
|
|
@@ -4497,6 +4768,9 @@ export function getHtmlTemplate(sessionToken, manageOnly, locale = 'en', transla
|
|
|
4497
4768
|
if (!configToSave.tenant.baseDomain) {
|
|
4498
4769
|
delete configToSave.tenant.baseDomain;
|
|
4499
4770
|
}
|
|
4771
|
+
if (!configToSave.tenant.primaryTenant) {
|
|
4772
|
+
delete configToSave.tenant.primaryTenant;
|
|
4773
|
+
}
|
|
4500
4774
|
if (!configToSave.features.email.fromAddress) {
|
|
4501
4775
|
delete configToSave.features.email.fromAddress;
|
|
4502
4776
|
}
|
|
@@ -4691,6 +4965,7 @@ export function getHtmlTemplate(sessionToken, manageOnly, locale = 'en', transla
|
|
|
4691
4965
|
selectedEnvForDetail = env;
|
|
4692
4966
|
|
|
4693
4967
|
document.getElementById('detail-env-name').textContent = env.env;
|
|
4968
|
+
renderEnvDetailUrls(env);
|
|
4694
4969
|
|
|
4695
4970
|
// Render resource lists with loading state
|
|
4696
4971
|
renderResourceList('detail-workers-list', 'detail-workers-count', env.workers, 'name', 'worker');
|
|
@@ -4737,6 +5012,118 @@ export function getHtmlTemplate(sessionToken, manageOnly, locale = 'en', transla
|
|
|
4737
5012
|
loadResourceDetails(env);
|
|
4738
5013
|
}
|
|
4739
5014
|
|
|
5015
|
+
function stripTrailingSlash(url) {
|
|
5016
|
+
const value = String(url || '');
|
|
5017
|
+
return value.endsWith('/') ? value.slice(0, -1) : value;
|
|
5018
|
+
}
|
|
5019
|
+
|
|
5020
|
+
function createEnvDetailUrlRow(label, url, description) {
|
|
5021
|
+
const row = document.createElement('div');
|
|
5022
|
+
row.style.cssText = 'display: flex; flex-direction: column; gap: 0.35rem; padding: 0.875rem 1rem; background: var(--bg); border: 1px solid var(--border); border-radius: 8px;';
|
|
5023
|
+
|
|
5024
|
+
const top = document.createElement('div');
|
|
5025
|
+
top.style.cssText = 'display: flex; justify-content: space-between; gap: 1rem; align-items: center; flex-wrap: wrap;';
|
|
5026
|
+
|
|
5027
|
+
const labelEl = document.createElement('div');
|
|
5028
|
+
labelEl.style.cssText = 'font-weight: 600;';
|
|
5029
|
+
labelEl.textContent = label;
|
|
5030
|
+
top.appendChild(labelEl);
|
|
5031
|
+
|
|
5032
|
+
const open = document.createElement('a');
|
|
5033
|
+
open.href = url;
|
|
5034
|
+
open.target = '_blank';
|
|
5035
|
+
open.rel = 'noopener noreferrer';
|
|
5036
|
+
open.className = 'btn-secondary';
|
|
5037
|
+
open.style.cssText = 'padding: 0.35rem 0.75rem; font-size: 0.8rem; white-space: nowrap;';
|
|
5038
|
+
open.textContent = 'Open';
|
|
5039
|
+
top.appendChild(open);
|
|
5040
|
+
|
|
5041
|
+
const urlEl = document.createElement('a');
|
|
5042
|
+
urlEl.href = url;
|
|
5043
|
+
urlEl.target = '_blank';
|
|
5044
|
+
urlEl.rel = 'noopener noreferrer';
|
|
5045
|
+
urlEl.style.cssText = 'font-family: var(--font-mono); font-size: 0.85rem; color: var(--primary); word-break: break-all;';
|
|
5046
|
+
urlEl.textContent = url;
|
|
5047
|
+
|
|
5048
|
+
row.appendChild(top);
|
|
5049
|
+
row.appendChild(urlEl);
|
|
5050
|
+
|
|
5051
|
+
if (description) {
|
|
5052
|
+
const descEl = document.createElement('div');
|
|
5053
|
+
descEl.style.cssText = 'font-size: 0.8rem; color: var(--text-muted);';
|
|
5054
|
+
descEl.textContent = description;
|
|
5055
|
+
row.appendChild(descEl);
|
|
5056
|
+
}
|
|
5057
|
+
|
|
5058
|
+
return row;
|
|
5059
|
+
}
|
|
5060
|
+
|
|
5061
|
+
function buildEnvDetailUrls(envName, config) {
|
|
5062
|
+
const workersDomain = workersSubdomain
|
|
5063
|
+
? envName + '-ar-router.' + workersSubdomain + '.workers.dev'
|
|
5064
|
+
: envName + '-ar-router.workers.dev';
|
|
5065
|
+
const fallbackIssuer = 'https://' + workersDomain;
|
|
5066
|
+
|
|
5067
|
+
const tenant = config?.tenant || {};
|
|
5068
|
+
const tenantName = tenant.name || 'default';
|
|
5069
|
+
const baseDomain = tenant.baseDomain;
|
|
5070
|
+
const nakedDomain = tenant.nakedDomain === true;
|
|
5071
|
+
|
|
5072
|
+
let issuerUrl = fallbackIssuer;
|
|
5073
|
+
if (baseDomain) {
|
|
5074
|
+
issuerUrl = nakedDomain
|
|
5075
|
+
? 'https://' + baseDomain
|
|
5076
|
+
: 'https://' + tenantName + '.' + baseDomain;
|
|
5077
|
+
} else if (config?.urls?.api?.custom) {
|
|
5078
|
+
issuerUrl = stripTrailingSlash(config.urls.api.custom);
|
|
5079
|
+
}
|
|
5080
|
+
|
|
5081
|
+
const loginBaseUrl = stripTrailingSlash(
|
|
5082
|
+
config?.urls?.loginUi?.custom || 'https://' + envName + '-ar-login-ui.pages.dev'
|
|
5083
|
+
);
|
|
5084
|
+
const adminBaseUrl = stripTrailingSlash(
|
|
5085
|
+
config?.urls?.adminUi?.custom || 'https://' + envName + '-ar-admin-ui.pages.dev'
|
|
5086
|
+
);
|
|
5087
|
+
|
|
5088
|
+
return [
|
|
5089
|
+
{
|
|
5090
|
+
label: 'Issuer',
|
|
5091
|
+
url: issuerUrl,
|
|
5092
|
+
description: 'Canonical OIDC issuer URL',
|
|
5093
|
+
},
|
|
5094
|
+
{
|
|
5095
|
+
label: 'Login UI',
|
|
5096
|
+
url: loginBaseUrl + '/login',
|
|
5097
|
+
description: 'Login screen entry point',
|
|
5098
|
+
},
|
|
5099
|
+
{
|
|
5100
|
+
label: 'Admin UI',
|
|
5101
|
+
url: adminBaseUrl + '/admin/info',
|
|
5102
|
+
description: 'Admin console entry point',
|
|
5103
|
+
},
|
|
5104
|
+
];
|
|
5105
|
+
}
|
|
5106
|
+
|
|
5107
|
+
async function renderEnvDetailUrls(env) {
|
|
5108
|
+
const listEl = document.getElementById('detail-url-list');
|
|
5109
|
+
listEl.textContent = '';
|
|
5110
|
+
|
|
5111
|
+
let config = null;
|
|
5112
|
+
try {
|
|
5113
|
+
const configResponse = await api('/config?env=' + encodeURIComponent(env.env));
|
|
5114
|
+
if (configResponse.exists && configResponse.config) {
|
|
5115
|
+
config = configResponse.config;
|
|
5116
|
+
}
|
|
5117
|
+
} catch (error) {
|
|
5118
|
+
console.warn('Failed to load config for env detail URLs:', error);
|
|
5119
|
+
}
|
|
5120
|
+
|
|
5121
|
+
const urls = buildEnvDetailUrls(env.env, config);
|
|
5122
|
+
for (const item of urls) {
|
|
5123
|
+
listEl.appendChild(createEnvDetailUrlRow(item.label, item.url, item.description));
|
|
5124
|
+
}
|
|
5125
|
+
}
|
|
5126
|
+
|
|
4740
5127
|
// ===========================================
|
|
4741
5128
|
// Worker Update Functions
|
|
4742
5129
|
// ===========================================
|
|
@@ -5311,28 +5698,37 @@ export function getHtmlTemplate(sessionToken, manageOnly, locale = 'en', transla
|
|
|
5311
5698
|
return;
|
|
5312
5699
|
}
|
|
5313
5700
|
|
|
5314
|
-
//
|
|
5315
|
-
const router = selectedEnvForDetail.workers.find(w =>
|
|
5316
|
-
w.name.toLowerCase().includes('router')
|
|
5317
|
-
);
|
|
5318
|
-
|
|
5701
|
+
// Determine base URL: prefer custom domain from config, fallback to workers.dev
|
|
5319
5702
|
let baseUrl = '';
|
|
5320
|
-
|
|
5321
|
-
|
|
5322
|
-
|
|
5323
|
-
|
|
5324
|
-
|
|
5325
|
-
|
|
5326
|
-
// Fallback without subdomain (shouldn't happen in practice)
|
|
5327
|
-
baseUrl = 'https://' + router.name + '.workers.dev';
|
|
5703
|
+
|
|
5704
|
+
// Try to load config to get custom API domain
|
|
5705
|
+
try {
|
|
5706
|
+
const configResponse = await api('/config?env=' + encodeURIComponent(selectedEnvForDetail.env));
|
|
5707
|
+
if (configResponse.exists && configResponse.config) {
|
|
5708
|
+
baseUrl = configResponse.config.urls?.api?.custom || configResponse.config.urls?.api?.auto || '';
|
|
5328
5709
|
}
|
|
5329
|
-
}
|
|
5330
|
-
//
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
|
|
5334
|
-
|
|
5335
|
-
|
|
5710
|
+
} catch (e) {
|
|
5711
|
+
// Config not available, will fallback to workers.dev
|
|
5712
|
+
}
|
|
5713
|
+
|
|
5714
|
+
// Fallback to workers.dev URL if no config URL found
|
|
5715
|
+
if (!baseUrl) {
|
|
5716
|
+
const router = selectedEnvForDetail.workers.find(w =>
|
|
5717
|
+
w.name.toLowerCase().includes('router')
|
|
5718
|
+
);
|
|
5719
|
+
if (router && router.name) {
|
|
5720
|
+
if (workersSubdomain) {
|
|
5721
|
+
baseUrl = 'https://' + router.name + '.' + workersSubdomain + '.workers.dev';
|
|
5722
|
+
} else {
|
|
5723
|
+
baseUrl = 'https://' + router.name + '.workers.dev';
|
|
5724
|
+
}
|
|
5725
|
+
} else {
|
|
5726
|
+
baseUrl = prompt('Enter the base URL for the router (e.g., https://myenv-ar-router.subdomain.workers.dev):');
|
|
5727
|
+
if (!baseUrl) {
|
|
5728
|
+
btn.disabled = false;
|
|
5729
|
+
btn.textContent = '🔐 Start Admin Account Setup with Passkey';
|
|
5730
|
+
return;
|
|
5731
|
+
}
|
|
5336
5732
|
}
|
|
5337
5733
|
}
|
|
5338
5734
|
|
|
@@ -5342,6 +5738,7 @@ export function getHtmlTemplate(sessionToken, manageOnly, locale = 'en', transla
|
|
|
5342
5738
|
body: JSON.stringify({
|
|
5343
5739
|
kvNamespaceId: configKv.id,
|
|
5344
5740
|
baseUrl: baseUrl,
|
|
5741
|
+
env: selectedEnvForDetail.env,
|
|
5345
5742
|
}),
|
|
5346
5743
|
});
|
|
5347
5744
|
|