@authrim/setup 0.1.49 → 0.1.51

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/web/ui.js CHANGED
@@ -746,6 +746,94 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
746
746
  color: var(--text-muted);
747
747
  font-size: 0.8rem;
748
748
  }
749
+
750
+ /* Database configuration styles */
751
+ .database-config-grid {
752
+ display: grid;
753
+ grid-template-columns: 1fr 1fr;
754
+ gap: 1.5rem;
755
+ margin-bottom: 1.5rem;
756
+ }
757
+
758
+ .database-card {
759
+ background: var(--bg);
760
+ border: 1px solid var(--border);
761
+ border-radius: 0.5rem;
762
+ padding: 1.25rem;
763
+ }
764
+
765
+ .database-card h3 {
766
+ margin: 0 0 1rem 0;
767
+ font-size: 1.1rem;
768
+ }
769
+
770
+ .db-description {
771
+ font-size: 0.875rem;
772
+ color: var(--text-muted);
773
+ margin-bottom: 1rem;
774
+ }
775
+
776
+ .db-description p {
777
+ margin: 0 0 0.5rem 0;
778
+ }
779
+
780
+ .db-description ul {
781
+ margin: 0.5rem 0;
782
+ padding-left: 1.25rem;
783
+ }
784
+
785
+ .db-description li {
786
+ margin-bottom: 0.25rem;
787
+ }
788
+
789
+ .db-hint {
790
+ font-style: italic;
791
+ margin-top: 0.75rem;
792
+ padding: 0.5rem;
793
+ background: #f0f9ff;
794
+ border-radius: 4px;
795
+ }
796
+
797
+ .region-selection h4 {
798
+ margin: 0 0 0.75rem 0;
799
+ font-size: 0.95rem;
800
+ font-weight: 600;
801
+ }
802
+
803
+ .radio-group {
804
+ display: flex;
805
+ flex-direction: column;
806
+ gap: 0.5rem;
807
+ }
808
+
809
+ .radio-item {
810
+ display: flex;
811
+ align-items: center;
812
+ gap: 0.5rem;
813
+ cursor: pointer;
814
+ padding: 0.25rem 0;
815
+ }
816
+
817
+ .radio-item input[type="radio"] {
818
+ margin: 0;
819
+ width: 16px;
820
+ height: 16px;
821
+ }
822
+
823
+ .radio-separator {
824
+ font-size: 0.75rem;
825
+ color: var(--text-muted);
826
+ margin: 0.5rem 0 0.25rem 0;
827
+ font-weight: 500;
828
+ text-transform: uppercase;
829
+ letter-spacing: 0.05em;
830
+ }
831
+
832
+ @media (max-width: 768px) {
833
+ .database-config-grid {
834
+ grid-template-columns: 1fr;
835
+ }
836
+ }
749
837
  </style>
750
838
  </head>
751
839
  <body>
@@ -772,6 +860,14 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
772
860
  <div class="step step-pending" id="step-3">3</div>
773
861
  <div class="step-connector"></div>
774
862
  <div class="step step-pending" id="step-4">4</div>
863
+ <div class="step-connector"></div>
864
+ <div class="step step-pending" id="step-5">5</div>
865
+ <div class="step-connector"></div>
866
+ <div class="step step-pending" id="step-6">6</div>
867
+ <div class="step-connector"></div>
868
+ <div class="step step-pending" id="step-7">7</div>
869
+ <div class="step-connector"></div>
870
+ <div class="step step-pending" id="step-8">8</div>
775
871
  </div>
776
872
 
777
873
  <!-- Step 1: Prerequisites -->
@@ -1035,7 +1131,206 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
1035
1131
  </div>
1036
1132
  </div>
1037
1133
 
1038
- <!-- Step 3: Provisioning -->
1134
+ <!-- Step 3: Database Configuration -->
1135
+ <div id="section-database" class="card hidden">
1136
+ <h2 class="card-title">🗄️ Database Configuration</h2>
1137
+
1138
+ <div class="warning-box" style="background: #fef3c7; border-left: 4px solid #f59e0b; padding: 0.75rem 1rem; margin-bottom: 1.5rem; border-radius: 0.375rem;">
1139
+ ⚠️ Database region cannot be changed after creation.
1140
+ </div>
1141
+
1142
+ <div class="database-config-grid">
1143
+ <!-- Core Database -->
1144
+ <div class="database-card">
1145
+ <h3>🗄️ Core Database</h3>
1146
+ <div class="db-description">
1147
+ <p>Stores application data including:</p>
1148
+ <ul>
1149
+ <li>OAuth clients and their configurations</li>
1150
+ <li>Authorization codes and access tokens</li>
1151
+ <li>User sessions and login state</li>
1152
+ <li>Tenant settings and configurations</li>
1153
+ <li>Audit logs and security events</li>
1154
+ </ul>
1155
+ <p class="db-hint">This database handles all authentication flows and should be placed close to your primary user base.</p>
1156
+ </div>
1157
+
1158
+ <div class="region-selection">
1159
+ <h4>Region</h4>
1160
+ <div class="radio-group">
1161
+ <label class="radio-item">
1162
+ <input type="radio" name="db-core-location" value="auto" checked>
1163
+ <span>Automatic (nearest to you)</span>
1164
+ </label>
1165
+ <div class="radio-separator">Location Hints</div>
1166
+ <label class="radio-item">
1167
+ <input type="radio" name="db-core-location" value="wnam">
1168
+ <span>North America (West)</span>
1169
+ </label>
1170
+ <label class="radio-item">
1171
+ <input type="radio" name="db-core-location" value="enam">
1172
+ <span>North America (East)</span>
1173
+ </label>
1174
+ <label class="radio-item">
1175
+ <input type="radio" name="db-core-location" value="weur">
1176
+ <span>Europe (West)</span>
1177
+ </label>
1178
+ <label class="radio-item">
1179
+ <input type="radio" name="db-core-location" value="eeur">
1180
+ <span>Europe (East)</span>
1181
+ </label>
1182
+ <label class="radio-item">
1183
+ <input type="radio" name="db-core-location" value="apac">
1184
+ <span>Asia Pacific</span>
1185
+ </label>
1186
+ <label class="radio-item">
1187
+ <input type="radio" name="db-core-location" value="oc">
1188
+ <span>Oceania</span>
1189
+ </label>
1190
+ <div class="radio-separator">Jurisdiction (Compliance)</div>
1191
+ <label class="radio-item">
1192
+ <input type="radio" name="db-core-location" value="eu">
1193
+ <span>EU Jurisdiction (GDPR compliance)</span>
1194
+ </label>
1195
+ </div>
1196
+ </div>
1197
+ </div>
1198
+
1199
+ <!-- PII Database -->
1200
+ <div class="database-card">
1201
+ <h3>🔒 PII Database</h3>
1202
+ <div class="db-description">
1203
+ <p>Stores personal user data including:</p>
1204
+ <ul>
1205
+ <li>User profiles (name, email, phone)</li>
1206
+ <li>Passkey/WebAuthn credentials</li>
1207
+ <li>User preferences and settings</li>
1208
+ <li>Any custom user attributes</li>
1209
+ </ul>
1210
+ <p class="db-hint">This database contains personal data. Consider placing it in a region that complies with your data protection requirements.</p>
1211
+ </div>
1212
+
1213
+ <div class="region-selection">
1214
+ <h4>Region</h4>
1215
+ <div class="radio-group">
1216
+ <label class="radio-item">
1217
+ <input type="radio" name="db-pii-location" value="auto" checked>
1218
+ <span>Automatic (nearest to you)</span>
1219
+ </label>
1220
+ <div class="radio-separator">Location Hints</div>
1221
+ <label class="radio-item">
1222
+ <input type="radio" name="db-pii-location" value="wnam">
1223
+ <span>North America (West)</span>
1224
+ </label>
1225
+ <label class="radio-item">
1226
+ <input type="radio" name="db-pii-location" value="enam">
1227
+ <span>North America (East)</span>
1228
+ </label>
1229
+ <label class="radio-item">
1230
+ <input type="radio" name="db-pii-location" value="weur">
1231
+ <span>Europe (West)</span>
1232
+ </label>
1233
+ <label class="radio-item">
1234
+ <input type="radio" name="db-pii-location" value="eeur">
1235
+ <span>Europe (East)</span>
1236
+ </label>
1237
+ <label class="radio-item">
1238
+ <input type="radio" name="db-pii-location" value="apac">
1239
+ <span>Asia Pacific</span>
1240
+ </label>
1241
+ <label class="radio-item">
1242
+ <input type="radio" name="db-pii-location" value="oc">
1243
+ <span>Oceania</span>
1244
+ </label>
1245
+ <div class="radio-separator">Jurisdiction (Compliance)</div>
1246
+ <label class="radio-item">
1247
+ <input type="radio" name="db-pii-location" value="eu">
1248
+ <span>EU Jurisdiction (GDPR compliance)</span>
1249
+ </label>
1250
+ </div>
1251
+ </div>
1252
+ </div>
1253
+ </div>
1254
+
1255
+ <div class="button-group">
1256
+ <button class="btn-secondary" id="btn-back-database">Back</button>
1257
+ <button class="btn-primary" id="btn-continue-database">Continue</button>
1258
+ </div>
1259
+ </div>
1260
+
1261
+ <!-- Step 4: Email Provider Configuration -->
1262
+ <div id="section-email" class="card hidden">
1263
+ <h2 class="card-title">📧 Email Provider</h2>
1264
+
1265
+ <p style="margin-bottom: 1rem; color: var(--text-muted);">
1266
+ Configure email sending for magic links and verification codes.
1267
+ You can configure this later if you prefer.
1268
+ </p>
1269
+
1270
+ <div class="radio-group" style="margin-bottom: 1.5rem;">
1271
+ <label class="radio-item" style="padding: 0.75rem; border: 1px solid var(--border); border-radius: 8px;">
1272
+ <input type="radio" name="email-setup-choice" value="later" checked>
1273
+ <span style="display: flex; flex-direction: column; gap: 0.25rem;">
1274
+ <strong>Configure later</strong>
1275
+ <small style="color: var(--text-muted);">Skip for now. Magic links will return URLs instead of sending emails.</small>
1276
+ </span>
1277
+ </label>
1278
+ <label class="radio-item" style="padding: 0.75rem; border: 1px solid var(--border); border-radius: 8px; margin-top: 0.5rem;">
1279
+ <input type="radio" name="email-setup-choice" value="configure">
1280
+ <span style="display: flex; flex-direction: column; gap: 0.25rem;">
1281
+ <strong>Configure Resend</strong>
1282
+ <small style="color: var(--text-muted);">Set up email sending with Resend (recommended for production).</small>
1283
+ </span>
1284
+ </label>
1285
+ </div>
1286
+
1287
+ <!-- Resend Configuration Form (hidden by default) -->
1288
+ <div id="resend-config-form" class="hidden" style="background: var(--bg); border: 1px solid var(--border); border-radius: 8px; padding: 1.25rem;">
1289
+ <h3 style="margin: 0 0 1rem 0; font-size: 1rem;">🔑 Resend Configuration</h3>
1290
+
1291
+ <div class="alert alert-info" style="margin-bottom: 1rem;">
1292
+ <strong>📋 Before you begin:</strong>
1293
+ <ol style="margin: 0.5rem 0 0 1rem; padding: 0;">
1294
+ <li>Create a Resend account at <a href="https://resend.com" target="_blank" style="color: var(--primary);">resend.com</a></li>
1295
+ <li>Add and verify your domain at <a href="https://resend.com/domains" target="_blank" style="color: var(--primary);">Domains Dashboard</a></li>
1296
+ <li>Create an API key at <a href="https://resend.com/api-keys" target="_blank" style="color: var(--primary);">API Keys</a></li>
1297
+ </ol>
1298
+ </div>
1299
+
1300
+ <div class="form-group">
1301
+ <label for="resend-api-key">Resend API Key</label>
1302
+ <input type="password" id="resend-api-key" placeholder="re_xxxxxxxxxx" autocomplete="off">
1303
+ <small style="color: var(--text-muted);">Your API key starts with "re_"</small>
1304
+ </div>
1305
+
1306
+ <div class="form-group">
1307
+ <label for="email-from-address">From Email Address</label>
1308
+ <input type="email" id="email-from-address" placeholder="noreply@yourdomain.com" autocomplete="off">
1309
+ <small style="color: var(--text-muted);">Must be from a verified domain in your Resend account</small>
1310
+ </div>
1311
+
1312
+ <div class="form-group">
1313
+ <label for="email-from-name">From Display Name (optional)</label>
1314
+ <input type="text" id="email-from-name" placeholder="Authrim" autocomplete="off">
1315
+ <small style="color: var(--text-muted);">Displayed as the sender name in email clients</small>
1316
+ </div>
1317
+
1318
+ <div class="alert alert-warning" style="margin-top: 1rem;">
1319
+ <strong>⚠️ Domain Verification Required</strong>
1320
+ <p style="margin: 0.25rem 0 0 0; font-size: 0.875rem;">
1321
+ Before your domain is verified, emails can only be sent from <code>onboarding@resend.dev</code> (for testing).
1322
+ <a href="https://resend.com/docs/dashboard/domains/introduction" target="_blank" style="color: var(--primary);">Learn more about domain verification →</a>
1323
+ </p>
1324
+ </div>
1325
+ </div>
1326
+
1327
+ <div class="button-group">
1328
+ <button class="btn-secondary" id="btn-back-email">Back</button>
1329
+ <button class="btn-primary" id="btn-continue-email">Continue</button>
1330
+ </div>
1331
+ </div>
1332
+
1333
+ <!-- Step 5: Provisioning -->
1039
1334
  <div id="section-provision" class="card hidden">
1040
1335
  <h2 class="card-title">
1041
1336
  Resource Provisioning
@@ -1307,6 +1602,10 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
1307
1602
  2: document.getElementById('step-2'),
1308
1603
  3: document.getElementById('step-3'),
1309
1604
  4: document.getElementById('step-4'),
1605
+ 5: document.getElementById('step-5'),
1606
+ 6: document.getElementById('step-6'),
1607
+ 7: document.getElementById('step-7'),
1608
+ 8: document.getElementById('step-8'),
1310
1609
  };
1311
1610
 
1312
1611
  const sections = {
@@ -1315,6 +1614,8 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
1315
1614
  mode: document.getElementById('section-mode'),
1316
1615
  loadConfig: document.getElementById('section-load-config'),
1317
1616
  config: document.getElementById('section-config'),
1617
+ database: document.getElementById('section-database'),
1618
+ email: document.getElementById('section-email'),
1318
1619
  provision: document.getElementById('section-provision'),
1319
1620
  deploy: document.getElementById('section-deploy'),
1320
1621
  complete: document.getElementById('section-complete'),
@@ -1346,7 +1647,7 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
1346
1647
  // Step navigation
1347
1648
  function setStep(step) {
1348
1649
  currentStep = step;
1349
- for (let i = 1; i <= 4; i++) {
1650
+ for (let i = 1; i <= 7; i++) {
1350
1651
  const el = steps[i];
1351
1652
  el.className = 'step ' + (i < step ? 'step-complete' : i === step ? 'step-active' : 'step-pending');
1352
1653
  }
@@ -1577,28 +1878,80 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
1577
1878
  document.getElementById('btn-load-config').addEventListener('click', async () => {
1578
1879
  if (!loadedConfig) return;
1579
1880
 
1580
- // Use loaded config
1881
+ // Support both new format (v1.0.0) and old format (v0.1.x)
1882
+ const isNewFormat = loadedConfig.version === '1.0.0' || loadedConfig.environment?.prefix;
1883
+
1884
+ // Extract values (with fallback for old format)
1885
+ const env = isNewFormat
1886
+ ? loadedConfig.environment?.prefix
1887
+ : loadedConfig.env || 'prod';
1888
+
1889
+ const apiDomain = isNewFormat
1890
+ ? loadedConfig.urls?.api?.custom
1891
+ : loadedConfig.apiDomain;
1892
+
1893
+ const loginUiDomain = isNewFormat
1894
+ ? loadedConfig.urls?.loginUi?.custom
1895
+ : loadedConfig.loginUiDomain;
1896
+
1897
+ const adminUiDomain = isNewFormat
1898
+ ? loadedConfig.urls?.adminUi?.custom
1899
+ : loadedConfig.adminUiDomain;
1900
+
1901
+ const tenant = loadedConfig.tenant || {
1902
+ name: 'default',
1903
+ displayName: 'Default Tenant',
1904
+ multiTenant: false,
1905
+ };
1906
+
1907
+ const components = loadedConfig.components || {
1908
+ api: true,
1909
+ loginUi: true,
1910
+ adminUi: true,
1911
+ saml: false,
1912
+ async: false,
1913
+ vc: false,
1914
+ bridge: true,
1915
+ policy: true,
1916
+ };
1917
+
1918
+ // Build internal config
1581
1919
  config = {
1582
- env: loadedConfig.environment?.prefix || 'prod',
1583
- apiDomain: loadedConfig.urls?.api?.custom || null,
1584
- loginUiDomain: loadedConfig.urls?.loginUi?.custom || null,
1585
- adminUiDomain: loadedConfig.urls?.adminUi?.custom || null,
1586
- components: loadedConfig.components || {
1587
- api: true,
1588
- loginUi: true,
1589
- adminUi: true,
1590
- saml: false,
1591
- async: false,
1592
- vc: false,
1593
- },
1920
+ env,
1921
+ apiDomain: apiDomain || null,
1922
+ loginUiDomain: loginUiDomain || null,
1923
+ adminUiDomain: adminUiDomain || null,
1924
+ tenant,
1925
+ components,
1594
1926
  };
1595
1927
 
1596
1928
  // Set form values
1597
1929
  document.getElementById('env').value = config.env;
1598
- document.getElementById('api-domain').value = config.apiDomain || '';
1930
+ document.getElementById('base-domain').value = config.tenant?.baseDomain || config.apiDomain || '';
1599
1931
  document.getElementById('login-domain').value = config.loginUiDomain || '';
1600
1932
  document.getElementById('admin-domain').value = config.adminUiDomain || '';
1601
- // Trigger env input to update default labels
1933
+ document.getElementById('tenant-name').value = config.tenant?.name || 'default';
1934
+ document.getElementById('tenant-display').value = config.tenant?.displayName || 'Default Tenant';
1935
+ document.getElementById('naked-domain').checked = config.tenant?.nakedDomain || false;
1936
+
1937
+ // Set component checkboxes
1938
+ if (document.getElementById('comp-login-ui')) {
1939
+ document.getElementById('comp-login-ui').checked = components.loginUi !== false;
1940
+ }
1941
+ if (document.getElementById('comp-admin-ui')) {
1942
+ document.getElementById('comp-admin-ui').checked = components.adminUi !== false;
1943
+ }
1944
+ if (document.getElementById('comp-saml')) {
1945
+ document.getElementById('comp-saml').checked = components.saml === true;
1946
+ }
1947
+ if (document.getElementById('comp-async')) {
1948
+ document.getElementById('comp-async').checked = components.async === true;
1949
+ }
1950
+ if (document.getElementById('comp-vc')) {
1951
+ document.getElementById('comp-vc').checked = components.vc === true;
1952
+ }
1953
+
1954
+ // Trigger env input to update preview/default labels
1602
1955
  document.getElementById('env').dispatchEvent(new Event('input'));
1603
1956
 
1604
1957
  // Skip to provisioning if resources already exist
@@ -1873,15 +2226,145 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
1873
2226
  updateResourcePreview(env);
1874
2227
  updateProvisionButtons();
1875
2228
 
1876
- setStep(3);
1877
- showSection('provision');
2229
+ // Go to database configuration step
2230
+ setStep(4);
2231
+ showSection('database');
1878
2232
  });
1879
2233
 
1880
2234
  document.getElementById('btn-back-config').addEventListener('click', () => {
1881
- setStep(2);
2235
+ // Go back to email configuration (previous step in the flow)
2236
+ setStep(5);
2237
+ showSection('email');
2238
+ });
2239
+
2240
+ // Database configuration handlers
2241
+ document.getElementById('btn-back-database').addEventListener('click', () => {
2242
+ setStep(3);
1882
2243
  showSection('config');
1883
2244
  });
1884
2245
 
2246
+ document.getElementById('btn-continue-database').addEventListener('click', () => {
2247
+ // Get selected values
2248
+ const coreLocation = document.querySelector('input[name="db-core-location"]:checked').value;
2249
+ const piiLocation = document.querySelector('input[name="db-pii-location"]:checked').value;
2250
+
2251
+ // Parse location vs jurisdiction
2252
+ function parseDbLocation(value) {
2253
+ if (value === 'eu') {
2254
+ return { location: 'auto', jurisdiction: 'eu' };
2255
+ }
2256
+ return { location: value, jurisdiction: 'none' };
2257
+ }
2258
+
2259
+ // Add database config to config object
2260
+ config.database = {
2261
+ core: parseDbLocation(coreLocation),
2262
+ pii: parseDbLocation(piiLocation),
2263
+ };
2264
+
2265
+ // Proceed to email configuration
2266
+ setStep(5);
2267
+ showSection('email');
2268
+ });
2269
+
2270
+ // Email configuration handlers
2271
+ // Toggle resend config form visibility
2272
+ document.querySelectorAll('input[name="email-setup-choice"]').forEach(radio => {
2273
+ radio.addEventListener('change', () => {
2274
+ const resendForm = document.getElementById('resend-config-form');
2275
+ const choice = document.querySelector('input[name="email-setup-choice"]:checked').value;
2276
+ if (choice === 'configure') {
2277
+ resendForm.classList.remove('hidden');
2278
+ } else {
2279
+ resendForm.classList.add('hidden');
2280
+ }
2281
+ });
2282
+ });
2283
+
2284
+ document.getElementById('btn-back-email').addEventListener('click', () => {
2285
+ setStep(4);
2286
+ showSection('database');
2287
+ });
2288
+
2289
+ document.getElementById('btn-continue-email').addEventListener('click', async () => {
2290
+ const choice = document.querySelector('input[name="email-setup-choice"]:checked').value;
2291
+ const btn = document.getElementById('btn-continue-email');
2292
+
2293
+ if (choice === 'configure') {
2294
+ // Validate and store email configuration
2295
+ const apiKey = document.getElementById('resend-api-key').value.trim();
2296
+ const fromAddress = document.getElementById('email-from-address').value.trim();
2297
+ const fromName = document.getElementById('email-from-name').value.trim();
2298
+
2299
+ // Validate API key format
2300
+ if (!apiKey) {
2301
+ alert('Please enter your Resend API key');
2302
+ return;
2303
+ }
2304
+ if (!apiKey.startsWith('re_')) {
2305
+ if (!confirm('API key does not start with "re_". This may not be a valid Resend API key. Continue anyway?')) {
2306
+ return;
2307
+ }
2308
+ }
2309
+
2310
+ // Validate email address
2311
+ if (!fromAddress) {
2312
+ alert('Please enter a From email address');
2313
+ return;
2314
+ }
2315
+ if (!fromAddress.includes('@')) {
2316
+ alert('Please enter a valid email address');
2317
+ return;
2318
+ }
2319
+
2320
+ // Save email configuration to server
2321
+ btn.disabled = true;
2322
+ btn.textContent = 'Saving...';
2323
+
2324
+ try {
2325
+ const result = await api('/email/configure', {
2326
+ method: 'POST',
2327
+ body: {
2328
+ env: config.env,
2329
+ provider: 'resend',
2330
+ apiKey: apiKey,
2331
+ fromAddress: fromAddress,
2332
+ fromName: fromName || undefined,
2333
+ },
2334
+ });
2335
+
2336
+ if (!result.success) {
2337
+ throw new Error(result.error || 'Failed to save email configuration');
2338
+ }
2339
+
2340
+ // Store email configuration (without apiKey for config file)
2341
+ config.email = {
2342
+ provider: 'resend',
2343
+ fromAddress: fromAddress,
2344
+ fromName: fromName || undefined,
2345
+ configured: true,
2346
+ };
2347
+ } catch (error) {
2348
+ alert('Failed to save email configuration: ' + error.message);
2349
+ btn.disabled = false;
2350
+ btn.textContent = 'Continue';
2351
+ return;
2352
+ }
2353
+
2354
+ btn.disabled = false;
2355
+ btn.textContent = 'Continue';
2356
+ } else {
2357
+ // Configure later - no email provider
2358
+ config.email = {
2359
+ provider: 'none',
2360
+ };
2361
+ }
2362
+
2363
+ // Proceed to provision
2364
+ setStep(6);
2365
+ showSection('provision');
2366
+ });
2367
+
1885
2368
  // Provision
1886
2369
  document.getElementById('btn-provision').addEventListener('click', async () => {
1887
2370
  const btn = document.getElementById('btn-provision');
@@ -1953,7 +2436,7 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
1953
2436
 
1954
2437
  const result = await api('/provision', {
1955
2438
  method: 'POST',
1956
- body: { env: config.env },
2439
+ body: { env: config.env, databaseConfig: config.database },
1957
2440
  });
1958
2441
 
1959
2442
  // Stop polling
@@ -1999,12 +2482,12 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
1999
2482
 
2000
2483
  // Continue to Deploy button
2001
2484
  document.getElementById('btn-goto-deploy').addEventListener('click', () => {
2002
- setStep(4);
2485
+ setStep(7);
2003
2486
  showSection('deploy');
2004
2487
  });
2005
2488
 
2006
2489
  document.getElementById('btn-back-provision').addEventListener('click', () => {
2007
- setStep(3);
2490
+ setStep(6);
2008
2491
  // Update buttons based on provisioning status
2009
2492
  updateProvisionButtons();
2010
2493
  // Show resource preview if not completed
@@ -2211,6 +2694,7 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
2211
2694
  urlsEl.appendChild(debugDiv);
2212
2695
  }
2213
2696
 
2697
+ setStep(8);
2214
2698
  showSection('complete');
2215
2699
  }
2216
2700
 
@@ -2293,19 +2777,97 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
2293
2777
  }
2294
2778
  }
2295
2779
 
2296
- // Save configuration to file
2780
+ // Save configuration to file (AuthrimConfigSchema format)
2297
2781
  function saveConfigToFile() {
2298
- if (!config) {
2782
+ if (!config || !config.env) {
2299
2783
  alert('No configuration to save');
2300
2784
  return;
2301
2785
  }
2302
2786
 
2787
+ const now = new Date().toISOString();
2788
+ const env = config.env;
2789
+
2790
+ // Calculate auto-generated URLs
2791
+ const workersDomain = env + '-ar-router.workers.dev';
2792
+ const pagesDomain = env + '-ar-ui.pages.dev';
2793
+
2794
+ // Build issuer URL based on tenant settings
2795
+ let issuerAutoUrl = 'https://' + workersDomain;
2796
+ if (config.tenant && config.tenant.baseDomain) {
2797
+ if (config.tenant.nakedDomain) {
2798
+ issuerAutoUrl = 'https://' + config.tenant.baseDomain;
2799
+ } else {
2800
+ const tenantName = config.tenant.name || 'default';
2801
+ issuerAutoUrl = 'https://' + tenantName + '.' + config.tenant.baseDomain;
2802
+ }
2803
+ }
2804
+
2805
+ // Build config in AuthrimConfigSchema format
2303
2806
  const configToSave = {
2304
- ...config,
2305
- savedAt: new Date().toISOString(),
2306
- version: '0.1.36',
2807
+ version: '1.0.0',
2808
+ createdAt: now,
2809
+ updatedAt: now,
2810
+ environment: {
2811
+ prefix: env,
2812
+ },
2813
+ urls: {
2814
+ api: {
2815
+ custom: config.apiDomain || null,
2816
+ auto: config.apiDomain ? issuerAutoUrl : 'https://' + workersDomain,
2817
+ },
2818
+ loginUi: {
2819
+ custom: config.loginUiDomain || null,
2820
+ auto: 'https://' + pagesDomain,
2821
+ },
2822
+ adminUi: {
2823
+ custom: config.adminUiDomain || null,
2824
+ auto: 'https://' + pagesDomain + '/admin',
2825
+ },
2826
+ },
2827
+ tenant: {
2828
+ name: config.tenant?.name || 'default',
2829
+ displayName: config.tenant?.displayName || 'Default Tenant',
2830
+ multiTenant: config.tenant?.multiTenant || false,
2831
+ baseDomain: config.tenant?.baseDomain || undefined,
2832
+ },
2833
+ components: config.components || {
2834
+ api: true,
2835
+ loginUi: true,
2836
+ adminUi: true,
2837
+ saml: false,
2838
+ async: false,
2839
+ vc: false,
2840
+ bridge: true,
2841
+ policy: true,
2842
+ },
2843
+ keys: {
2844
+ secretsPath: './.keys/',
2845
+ },
2846
+ database: config.database || {
2847
+ core: { location: 'auto', jurisdiction: 'none' },
2848
+ pii: { location: 'auto', jurisdiction: 'none' },
2849
+ },
2850
+ features: {
2851
+ email: {
2852
+ provider: config.email?.provider || 'none',
2853
+ fromAddress: config.email?.fromAddress || undefined,
2854
+ fromName: config.email?.fromName || undefined,
2855
+ configured: config.email?.provider === 'resend' && config.email?.apiKey ? true : false,
2856
+ },
2857
+ },
2307
2858
  };
2308
2859
 
2860
+ // Remove undefined values for cleaner output
2861
+ if (!configToSave.tenant.baseDomain) {
2862
+ delete configToSave.tenant.baseDomain;
2863
+ }
2864
+ if (!configToSave.features.email.fromAddress) {
2865
+ delete configToSave.features.email.fromAddress;
2866
+ }
2867
+ if (!configToSave.features.email.fromName) {
2868
+ delete configToSave.features.email.fromName;
2869
+ }
2870
+
2309
2871
  const blob = new Blob([JSON.stringify(configToSave, null, 2)], { type: 'application/json' });
2310
2872
  const url = URL.createObjectURL(blob);
2311
2873
  const a = document.createElement('a');