@authrim/setup 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/README.md +303 -0
  2. package/dist/__tests__/config.test.d.ts +5 -0
  3. package/dist/__tests__/config.test.d.ts.map +1 -0
  4. package/dist/__tests__/config.test.js +115 -0
  5. package/dist/__tests__/config.test.js.map +1 -0
  6. package/dist/__tests__/keys.test.d.ts +5 -0
  7. package/dist/__tests__/keys.test.d.ts.map +1 -0
  8. package/dist/__tests__/keys.test.js +87 -0
  9. package/dist/__tests__/keys.test.js.map +1 -0
  10. package/dist/__tests__/naming.test.d.ts +5 -0
  11. package/dist/__tests__/naming.test.d.ts.map +1 -0
  12. package/dist/__tests__/naming.test.js +84 -0
  13. package/dist/__tests__/naming.test.js.map +1 -0
  14. package/dist/cli/commands/config.d.ts +13 -0
  15. package/dist/cli/commands/config.d.ts.map +1 -0
  16. package/dist/cli/commands/config.js +231 -0
  17. package/dist/cli/commands/config.js.map +1 -0
  18. package/dist/cli/commands/deploy.d.ts +21 -0
  19. package/dist/cli/commands/deploy.d.ts.map +1 -0
  20. package/dist/cli/commands/deploy.js +304 -0
  21. package/dist/cli/commands/deploy.js.map +1 -0
  22. package/dist/cli/commands/init.d.ts +14 -0
  23. package/dist/cli/commands/init.d.ts.map +1 -0
  24. package/dist/cli/commands/init.js +1248 -0
  25. package/dist/cli/commands/init.js.map +1 -0
  26. package/dist/core/admin.d.ts +64 -0
  27. package/dist/core/admin.d.ts.map +1 -0
  28. package/dist/core/admin.js +247 -0
  29. package/dist/core/admin.js.map +1 -0
  30. package/dist/core/cloudflare.d.ts +157 -0
  31. package/dist/core/cloudflare.d.ts.map +1 -0
  32. package/dist/core/cloudflare.js +452 -0
  33. package/dist/core/cloudflare.js.map +1 -0
  34. package/dist/core/config.d.ts +891 -0
  35. package/dist/core/config.d.ts.map +1 -0
  36. package/dist/core/config.js +208 -0
  37. package/dist/core/config.js.map +1 -0
  38. package/dist/core/deploy.d.ts +81 -0
  39. package/dist/core/deploy.d.ts.map +1 -0
  40. package/dist/core/deploy.js +389 -0
  41. package/dist/core/deploy.js.map +1 -0
  42. package/dist/core/keys.d.ts +111 -0
  43. package/dist/core/keys.d.ts.map +1 -0
  44. package/dist/core/keys.js +287 -0
  45. package/dist/core/keys.js.map +1 -0
  46. package/dist/core/lock.d.ts +220 -0
  47. package/dist/core/lock.d.ts.map +1 -0
  48. package/dist/core/lock.js +230 -0
  49. package/dist/core/lock.js.map +1 -0
  50. package/dist/core/naming.d.ts +151 -0
  51. package/dist/core/naming.d.ts.map +1 -0
  52. package/dist/core/naming.js +209 -0
  53. package/dist/core/naming.js.map +1 -0
  54. package/dist/core/source.d.ts +68 -0
  55. package/dist/core/source.d.ts.map +1 -0
  56. package/dist/core/source.js +285 -0
  57. package/dist/core/source.js.map +1 -0
  58. package/dist/core/wrangler.d.ts +87 -0
  59. package/dist/core/wrangler.d.ts.map +1 -0
  60. package/dist/core/wrangler.js +398 -0
  61. package/dist/core/wrangler.js.map +1 -0
  62. package/dist/index.d.ts +11 -0
  63. package/dist/index.d.ts.map +1 -0
  64. package/dist/index.js +117 -0
  65. package/dist/index.js.map +1 -0
  66. package/dist/web/api.d.ts +21 -0
  67. package/dist/web/api.d.ts.map +1 -0
  68. package/dist/web/api.js +423 -0
  69. package/dist/web/api.js.map +1 -0
  70. package/dist/web/server.d.ts +12 -0
  71. package/dist/web/server.d.ts.map +1 -0
  72. package/dist/web/server.js +112 -0
  73. package/dist/web/server.js.map +1 -0
  74. package/dist/web/ui.d.ts +7 -0
  75. package/dist/web/ui.d.ts.map +1 -0
  76. package/dist/web/ui.js +765 -0
  77. package/dist/web/ui.js.map +1 -0
  78. package/package.json +61 -0
@@ -0,0 +1,1248 @@
1
+ /**
2
+ * Init Command - Setup wizard for Authrim
3
+ *
4
+ * Provides both CLI and Web UI modes for setting up Authrim.
5
+ */
6
+ import { input, select, confirm, password } from '@inquirer/prompts';
7
+ import chalk from 'chalk';
8
+ import ora from 'ora';
9
+ import { readFile, writeFile } from 'node:fs/promises';
10
+ import { existsSync } from 'node:fs';
11
+ import { join, resolve } from 'node:path';
12
+ import { createDefaultConfig, parseConfig } from '../../core/config.js';
13
+ import { generateAllSecrets, saveKeysToDirectory, generateKeyId } from '../../core/keys.js';
14
+ import { generateWranglerConfig, toToml } from '../../core/wrangler.js';
15
+ import { CORE_WORKER_COMPONENTS, } from '../../core/naming.js';
16
+ import { isWranglerInstalled, checkAuth, provisionResources, toResourceIds, getAccountId, } from '../../core/cloudflare.js';
17
+ import { createLockFile, saveLockFile, loadLockFile, } from '../../core/lock.js';
18
+ import { downloadSource, verifySourceStructure, } from '../../core/source.js';
19
+ // =============================================================================
20
+ // Banner
21
+ // =============================================================================
22
+ function printBanner() {
23
+ console.log('');
24
+ console.log(chalk.blue('╔═══════════════════════════════════════════════════════════╗'));
25
+ console.log(chalk.blue('║') +
26
+ chalk.bold.white(' 🔐 Authrim Setup v0.1.0 ') +
27
+ chalk.blue('║'));
28
+ console.log(chalk.blue('║') +
29
+ chalk.gray(' OIDC Provider on Cloudflare Workers ') +
30
+ chalk.blue('║'));
31
+ console.log(chalk.blue('╚═══════════════════════════════════════════════════════════╝'));
32
+ console.log('');
33
+ }
34
+ // =============================================================================
35
+ // Source Directory Detection
36
+ // =============================================================================
37
+ /**
38
+ * Check if we're in a valid Authrim source directory
39
+ */
40
+ function isAuthrimSourceDir(dir = '.') {
41
+ const requiredPaths = [
42
+ 'packages/ar-auth',
43
+ 'packages/ar-token',
44
+ 'packages/ar-lib-core',
45
+ 'package.json',
46
+ ];
47
+ for (const path of requiredPaths) {
48
+ if (!existsSync(join(dir, path))) {
49
+ return false;
50
+ }
51
+ }
52
+ return true;
53
+ }
54
+ /**
55
+ * Ensure Authrim source is available, downloading if necessary
56
+ */
57
+ async function ensureAuthrimSource(options) {
58
+ const currentDir = resolve('.');
59
+ // Check if we're already in an Authrim source directory
60
+ if (isAuthrimSourceDir(currentDir)) {
61
+ return currentDir;
62
+ }
63
+ // Check if --keep path exists and is valid
64
+ if (options.keep && isAuthrimSourceDir(options.keep)) {
65
+ return resolve(options.keep);
66
+ }
67
+ // Need to download source
68
+ console.log('');
69
+ console.log(chalk.yellow('⚠️ Authrim source code not found'));
70
+ console.log('');
71
+ const targetDir = options.keep || './authrim';
72
+ const shouldDownload = await confirm({
73
+ message: `Download source code to ${targetDir}?`,
74
+ default: true,
75
+ });
76
+ if (!shouldDownload) {
77
+ console.log(chalk.gray('\nCancelled.'));
78
+ console.log(chalk.gray('To clone manually:'));
79
+ console.log(chalk.cyan(' git clone https://github.com/sgrastar/authrim'));
80
+ console.log('');
81
+ process.exit(0);
82
+ }
83
+ // Download source
84
+ const spinner = ora('Downloading source code...').start();
85
+ try {
86
+ const result = await downloadSource({
87
+ targetDir,
88
+ onProgress: (msg) => {
89
+ spinner.text = msg;
90
+ },
91
+ });
92
+ spinner.succeed(`Source code downloaded (${result.gitRef})`);
93
+ // Verify structure
94
+ const verification = await verifySourceStructure(targetDir);
95
+ if (!verification.valid) {
96
+ console.log(chalk.yellow('\n⚠️ Source structure verification warnings:'));
97
+ for (const error of verification.errors) {
98
+ console.log(chalk.yellow(` • ${error}`));
99
+ }
100
+ }
101
+ return resolve(targetDir);
102
+ }
103
+ catch (error) {
104
+ spinner.fail('Download failed');
105
+ console.error(chalk.red(`\nError: ${error}`));
106
+ process.exit(1);
107
+ }
108
+ }
109
+ // =============================================================================
110
+ // Main Command
111
+ // =============================================================================
112
+ export async function initCommand(options) {
113
+ printBanner();
114
+ // Load existing config if provided
115
+ if (options.config) {
116
+ await handleExistingConfig(options.config);
117
+ return;
118
+ }
119
+ // If --cli flag is provided, skip the startup menu
120
+ if (options.cli) {
121
+ const sourceDir = await ensureAuthrimSource(options);
122
+ process.chdir(sourceDir);
123
+ await runCliSetup(options);
124
+ return;
125
+ }
126
+ // Show startup menu
127
+ console.log(chalk.gray('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
128
+ console.log('');
129
+ console.log(' Set up Authrim OIDC Provider on Cloudflare Workers.');
130
+ console.log('');
131
+ console.log(chalk.gray('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
132
+ console.log('');
133
+ const startupChoice = await select({
134
+ message: 'Choose setup method',
135
+ choices: [
136
+ {
137
+ value: 'webui',
138
+ name: '🌐 Web UI (Recommended)',
139
+ description: 'Interactive setup in your browser',
140
+ },
141
+ {
142
+ value: 'cli',
143
+ name: '⌨️ CLI Mode',
144
+ description: 'Interactive setup in terminal',
145
+ },
146
+ {
147
+ value: 'cancel',
148
+ name: '❌ Cancel',
149
+ description: 'Exit setup',
150
+ },
151
+ ],
152
+ });
153
+ if (startupChoice === 'cancel') {
154
+ console.log('');
155
+ console.log(chalk.gray('Setup cancelled.'));
156
+ console.log('');
157
+ console.log(chalk.gray('To resume later:'));
158
+ console.log(chalk.cyan(' npx @authrim/setup'));
159
+ console.log('');
160
+ return;
161
+ }
162
+ // Ensure source is available
163
+ const sourceDir = await ensureAuthrimSource(options);
164
+ process.chdir(sourceDir);
165
+ if (startupChoice === 'cli') {
166
+ await runCliSetup(options);
167
+ }
168
+ else {
169
+ // Start Web UI
170
+ console.log('');
171
+ console.log(chalk.cyan('🌐 Starting Web UI...'));
172
+ console.log('');
173
+ const { startWebServer } = await import('../../web/server.js');
174
+ await startWebServer({ openBrowser: true });
175
+ }
176
+ }
177
+ // =============================================================================
178
+ // CLI Setup Flow
179
+ // =============================================================================
180
+ async function runCliSetup(options) {
181
+ // Step 1: Choose setup mode
182
+ const setupMode = await select({
183
+ message: 'Choose setup mode',
184
+ choices: [
185
+ {
186
+ value: 'quick',
187
+ name: '⚡ Quick Setup (5 minutes)',
188
+ description: 'Deploy Authrim with minimal configuration',
189
+ },
190
+ {
191
+ value: 'normal',
192
+ name: '🔧 Custom Setup',
193
+ description: 'Configure all options step by step',
194
+ },
195
+ ],
196
+ });
197
+ if (setupMode === 'quick') {
198
+ await runQuickSetup(options);
199
+ }
200
+ else {
201
+ await runNormalSetup(options);
202
+ }
203
+ }
204
+ // =============================================================================
205
+ // Quick Setup
206
+ // =============================================================================
207
+ async function runQuickSetup(options) {
208
+ console.log('');
209
+ console.log(chalk.blue('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
210
+ console.log(chalk.bold('⚡ Quick Setup'));
211
+ console.log(chalk.blue('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
212
+ console.log('');
213
+ // Step 1: Environment prefix
214
+ const envPrefix = await select({
215
+ message: 'Select environment',
216
+ choices: [
217
+ { value: 'prod', name: 'prod (Production)' },
218
+ { value: 'staging', name: 'staging (Staging)' },
219
+ { value: 'dev', name: 'dev (Development)' },
220
+ ],
221
+ default: options.env || 'prod',
222
+ });
223
+ // Step 2: Cloudflare API Token
224
+ const cfApiToken = await password({
225
+ message: 'Enter Cloudflare API Token',
226
+ mask: '*',
227
+ validate: (value) => {
228
+ if (!value || value.length < 10) {
229
+ return 'Please enter a valid API Token';
230
+ }
231
+ return true;
232
+ },
233
+ });
234
+ // Step 3: Domain configuration
235
+ const useCustomDomain = await confirm({
236
+ message: 'Configure custom domain?',
237
+ default: false,
238
+ });
239
+ let apiDomain = null;
240
+ let loginUiDomain = null;
241
+ let adminUiDomain = null;
242
+ if (useCustomDomain) {
243
+ apiDomain = await input({
244
+ message: 'API (issuer) domain',
245
+ validate: (value) => {
246
+ if (!value)
247
+ return true; // Allow empty for workers.dev fallback
248
+ if (!/^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/.test(value)) {
249
+ return 'Please enter a valid domain (e.g., auth.example.com)';
250
+ }
251
+ return true;
252
+ },
253
+ });
254
+ loginUiDomain = await input({
255
+ message: 'Login UI domain (Enter to skip)',
256
+ default: '',
257
+ });
258
+ adminUiDomain = await input({
259
+ message: 'Admin UI domain (Enter to skip)',
260
+ default: '',
261
+ });
262
+ }
263
+ // Create configuration
264
+ const config = createDefaultConfig(envPrefix);
265
+ config.urls = {
266
+ api: {
267
+ custom: apiDomain || null,
268
+ auto: `https://${envPrefix}-ar-router.workers.dev`, // Placeholder
269
+ },
270
+ loginUi: {
271
+ custom: loginUiDomain || null,
272
+ auto: `https://${envPrefix}-ar-ui.pages.dev`,
273
+ },
274
+ adminUi: {
275
+ custom: adminUiDomain || null,
276
+ auto: `https://${envPrefix}-ar-ui.pages.dev/admin`,
277
+ },
278
+ };
279
+ // Show summary
280
+ console.log('');
281
+ console.log(chalk.blue('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
282
+ console.log(chalk.bold('📋 Configuration Summary'));
283
+ console.log(chalk.blue('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
284
+ console.log('');
285
+ console.log(` Environment: ${chalk.cyan(envPrefix)}`);
286
+ console.log(` API URL: ${chalk.cyan(config.urls.api.custom || config.urls.api.auto)}`);
287
+ console.log(` Login UI: ${chalk.cyan(config.urls.loginUi.custom || config.urls.loginUi.auto)}`);
288
+ console.log(` Admin UI: ${chalk.cyan(config.urls.adminUi.custom || config.urls.adminUi.auto)}`);
289
+ console.log('');
290
+ const proceed = await confirm({
291
+ message: 'Start setup with this configuration?',
292
+ default: true,
293
+ });
294
+ if (!proceed) {
295
+ console.log(chalk.yellow('Setup cancelled.'));
296
+ return;
297
+ }
298
+ // Run setup
299
+ await executeSetup(config, cfApiToken, options.keep);
300
+ }
301
+ // =============================================================================
302
+ // Normal Setup
303
+ // =============================================================================
304
+ async function runNormalSetup(options) {
305
+ console.log('');
306
+ console.log(chalk.blue('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
307
+ console.log(chalk.bold('🔧 Custom Setup'));
308
+ console.log(chalk.blue('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
309
+ console.log('');
310
+ // Step 1: Environment prefix
311
+ const envPrefix = await input({
312
+ message: 'Enter environment prefix',
313
+ default: options.env || 'prod',
314
+ validate: (value) => {
315
+ if (!/^[a-z][a-z0-9-]*$/.test(value)) {
316
+ return 'Only lowercase alphanumeric and hyphens allowed (e.g., prod, staging, dev)';
317
+ }
318
+ return true;
319
+ },
320
+ });
321
+ // Step 2: Cloudflare API Token
322
+ const cfApiToken = await password({
323
+ message: 'Enter Cloudflare API Token',
324
+ mask: '*',
325
+ validate: (value) => {
326
+ if (!value || value.length < 10) {
327
+ return 'Please enter a valid API Token';
328
+ }
329
+ return true;
330
+ },
331
+ });
332
+ // Step 3: Profile selection
333
+ const profile = await select({
334
+ message: 'Select OIDC profile',
335
+ choices: [
336
+ {
337
+ value: 'basic-op',
338
+ name: 'Basic OP (Standard OIDC Provider)',
339
+ description: 'Standard OIDC features',
340
+ },
341
+ {
342
+ value: 'fapi-rw',
343
+ name: 'FAPI Read-Write (Financial Grade)',
344
+ description: 'FAPI 1.0 Read-Write Security Profile compliant',
345
+ },
346
+ {
347
+ value: 'fapi2-security',
348
+ name: 'FAPI 2.0 Security Profile',
349
+ description: 'FAPI 2.0 Security Profile compliant (highest security)',
350
+ },
351
+ ],
352
+ default: 'basic-op',
353
+ });
354
+ // Step 4: Domain configuration
355
+ const useCustomDomain = await confirm({
356
+ message: 'Configure custom domain?',
357
+ default: false,
358
+ });
359
+ let apiDomain = null;
360
+ let loginUiDomain = null;
361
+ let adminUiDomain = null;
362
+ if (useCustomDomain) {
363
+ apiDomain = await input({
364
+ message: 'API (issuer) domain',
365
+ validate: (value) => {
366
+ if (!value)
367
+ return true;
368
+ if (!/^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/.test(value)) {
369
+ return 'Please enter a valid domain';
370
+ }
371
+ return true;
372
+ },
373
+ });
374
+ loginUiDomain = await input({
375
+ message: 'Login UI domain (Enter to skip)',
376
+ default: '',
377
+ });
378
+ adminUiDomain = await input({
379
+ message: 'Admin UI domain (Enter to skip)',
380
+ default: '',
381
+ });
382
+ }
383
+ // Step 5: Optional components
384
+ console.log('');
385
+ console.log(chalk.blue('━━━ Optional Components ━━━'));
386
+ console.log('');
387
+ const enableSaml = await confirm({
388
+ message: 'Enable SAML support?',
389
+ default: false,
390
+ });
391
+ const enableVc = await confirm({
392
+ message: 'Enable Verifiable Credentials?',
393
+ default: false,
394
+ });
395
+ const enableBridge = await confirm({
396
+ message: 'Enable External IdP Bridge?',
397
+ default: false,
398
+ });
399
+ const enablePolicy = await confirm({
400
+ message: 'Enable ReBAC Policy service?',
401
+ default: false,
402
+ });
403
+ // Step 6: Feature flags
404
+ console.log('');
405
+ console.log(chalk.blue('━━━ Feature Flags ━━━'));
406
+ console.log('');
407
+ const enableQueue = await confirm({
408
+ message: 'Enable Cloudflare Queues? (for audit logs)',
409
+ default: false,
410
+ });
411
+ const enableR2 = await confirm({
412
+ message: 'Enable Cloudflare R2? (for avatars)',
413
+ default: false,
414
+ });
415
+ const emailProvider = await select({
416
+ message: 'Select email provider',
417
+ choices: [
418
+ { value: 'none', name: 'None (email disabled)' },
419
+ { value: 'resend', name: 'Resend' },
420
+ { value: 'sendgrid', name: 'SendGrid' },
421
+ { value: 'ses', name: 'AWS SES' },
422
+ ],
423
+ default: 'none',
424
+ });
425
+ // Step 7: Advanced OIDC settings
426
+ const configureOidc = await confirm({
427
+ message: 'Configure OIDC settings? (token TTL, etc.)',
428
+ default: false,
429
+ });
430
+ let accessTokenTtl = 3600; // 1 hour
431
+ let refreshTokenTtl = 604800; // 7 days
432
+ let authCodeTtl = 600; // 10 minutes
433
+ let pkceRequired = true;
434
+ if (configureOidc) {
435
+ console.log('');
436
+ console.log(chalk.blue('━━━ OIDC Settings ━━━'));
437
+ console.log('');
438
+ const accessTokenTtlStr = await input({
439
+ message: 'Access Token TTL (sec)',
440
+ default: '3600',
441
+ validate: (value) => {
442
+ const num = parseInt(value, 10);
443
+ if (isNaN(num) || num <= 0)
444
+ return 'Please enter a positive integer';
445
+ return true;
446
+ },
447
+ });
448
+ accessTokenTtl = parseInt(accessTokenTtlStr, 10);
449
+ const refreshTokenTtlStr = await input({
450
+ message: 'Refresh Token TTL (sec)',
451
+ default: '604800',
452
+ validate: (value) => {
453
+ const num = parseInt(value, 10);
454
+ if (isNaN(num) || num <= 0)
455
+ return 'Please enter a positive integer';
456
+ return true;
457
+ },
458
+ });
459
+ refreshTokenTtl = parseInt(refreshTokenTtlStr, 10);
460
+ const authCodeTtlStr = await input({
461
+ message: 'Authorization Code TTL (sec)',
462
+ default: '600',
463
+ validate: (value) => {
464
+ const num = parseInt(value, 10);
465
+ if (isNaN(num) || num <= 0)
466
+ return 'Please enter a positive integer';
467
+ return true;
468
+ },
469
+ });
470
+ authCodeTtl = parseInt(authCodeTtlStr, 10);
471
+ pkceRequired = await confirm({
472
+ message: 'Require PKCE?',
473
+ default: true,
474
+ });
475
+ }
476
+ // Step 8: Sharding settings
477
+ const configureSharding = await confirm({
478
+ message: 'Configure sharding? (for high-load environments)',
479
+ default: false,
480
+ });
481
+ let authCodeShards = 64;
482
+ let refreshTokenShards = 8;
483
+ if (configureSharding) {
484
+ console.log('');
485
+ console.log(chalk.blue('━━━ Sharding Settings ━━━'));
486
+ console.log(chalk.gray(' Note: Power of 2 recommended for shard count (8, 16, 32, 64, 128)'));
487
+ console.log('');
488
+ const authCodeShardsStr = await input({
489
+ message: 'Auth Code shard count',
490
+ default: '64',
491
+ validate: (value) => {
492
+ const num = parseInt(value, 10);
493
+ if (isNaN(num) || num <= 0)
494
+ return 'Please enter a positive integer';
495
+ return true;
496
+ },
497
+ });
498
+ authCodeShards = parseInt(authCodeShardsStr, 10);
499
+ const refreshTokenShardsStr = await input({
500
+ message: 'Refresh Token shard count',
501
+ default: '8',
502
+ validate: (value) => {
503
+ const num = parseInt(value, 10);
504
+ if (isNaN(num) || num <= 0)
505
+ return 'Please enter a positive integer';
506
+ return true;
507
+ },
508
+ });
509
+ refreshTokenShards = parseInt(refreshTokenShardsStr, 10);
510
+ }
511
+ // Create configuration
512
+ const config = createDefaultConfig(envPrefix);
513
+ config.profile = profile;
514
+ config.components = {
515
+ ...config.components,
516
+ saml: enableSaml,
517
+ async: enableQueue, // async is tied to queue
518
+ vc: enableVc,
519
+ bridge: enableBridge,
520
+ policy: enablePolicy,
521
+ };
522
+ config.urls = {
523
+ api: {
524
+ custom: apiDomain || null,
525
+ auto: `https://${envPrefix}-ar-router.workers.dev`,
526
+ },
527
+ loginUi: {
528
+ custom: loginUiDomain || null,
529
+ auto: `https://${envPrefix}-ar-ui.pages.dev`,
530
+ },
531
+ adminUi: {
532
+ custom: adminUiDomain || null,
533
+ auto: `https://${envPrefix}-ar-ui.pages.dev/admin`,
534
+ },
535
+ };
536
+ config.oidc = {
537
+ ...config.oidc,
538
+ accessTokenTtl,
539
+ refreshTokenTtl,
540
+ authCodeTtl,
541
+ pkceRequired,
542
+ };
543
+ config.sharding = {
544
+ authCodeShards,
545
+ refreshTokenShards,
546
+ };
547
+ config.features = {
548
+ queue: { enabled: enableQueue },
549
+ r2: { enabled: enableR2 },
550
+ email: { provider: emailProvider },
551
+ };
552
+ // Show summary
553
+ console.log('');
554
+ console.log(chalk.blue('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
555
+ console.log(chalk.bold('📋 Configuration Summary'));
556
+ console.log(chalk.blue('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
557
+ console.log('');
558
+ console.log(chalk.bold('Basic Settings:'));
559
+ console.log(` Environment: ${chalk.cyan(envPrefix)}`);
560
+ console.log(` Profile: ${chalk.cyan(profile)}`);
561
+ console.log('');
562
+ console.log(chalk.bold('URL Settings:'));
563
+ console.log(` API URL: ${chalk.cyan(config.urls.api.custom || config.urls.api.auto)}`);
564
+ console.log(` Login UI: ${chalk.cyan(config.urls.loginUi.custom || config.urls.loginUi.auto)}`);
565
+ console.log(` Admin UI: ${chalk.cyan(config.urls.adminUi.custom || config.urls.adminUi.auto)}`);
566
+ console.log('');
567
+ console.log(chalk.bold('Components:'));
568
+ console.log(` SAML: ${enableSaml ? chalk.green('Enabled') : chalk.gray('Disabled')}`);
569
+ console.log(` VC: ${enableVc ? chalk.green('Enabled') : chalk.gray('Disabled')}`);
570
+ console.log(` Bridge: ${enableBridge ? chalk.green('Enabled') : chalk.gray('Disabled')}`);
571
+ console.log(` Policy: ${enablePolicy ? chalk.green('Enabled') : chalk.gray('Disabled')}`);
572
+ console.log('');
573
+ console.log(chalk.bold('Feature Flags:'));
574
+ console.log(` Queue: ${enableQueue ? chalk.green('Enabled') : chalk.gray('Disabled')}`);
575
+ console.log(` R2: ${enableR2 ? chalk.green('Enabled') : chalk.gray('Disabled')}`);
576
+ console.log(` Email: ${chalk.cyan(emailProvider)}`);
577
+ console.log('');
578
+ console.log(chalk.bold('OIDC Settings:'));
579
+ console.log(` Access TTL: ${chalk.cyan(accessTokenTtl + 'sec')}`);
580
+ console.log(` Refresh TTL: ${chalk.cyan(refreshTokenTtl + 'sec')}`);
581
+ console.log(` Auth Code TTL: ${chalk.cyan(authCodeTtl + 'sec')}`);
582
+ console.log(` PKCE Required: ${pkceRequired ? chalk.green('Yes') : chalk.yellow('No')}`);
583
+ console.log('');
584
+ console.log(chalk.bold('Sharding:'));
585
+ console.log(` Auth Code: ${chalk.cyan(authCodeShards)} shards`);
586
+ console.log(` Refresh Token: ${chalk.cyan(refreshTokenShards)} shards`);
587
+ console.log('');
588
+ const proceed = await confirm({
589
+ message: 'Start setup with this configuration?',
590
+ default: true,
591
+ });
592
+ if (!proceed) {
593
+ console.log(chalk.yellow('Setup cancelled.'));
594
+ return;
595
+ }
596
+ await executeSetup(config, cfApiToken, options.keep);
597
+ }
598
+ // =============================================================================
599
+ // Execute Setup
600
+ // =============================================================================
601
+ async function executeSetup(config, cfApiToken, keepPath) {
602
+ const outputDir = keepPath || '.';
603
+ const env = config.environment.prefix;
604
+ let secrets = null;
605
+ console.log('');
606
+ console.log(chalk.blue('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
607
+ console.log(chalk.bold('🚀 Running Setup...'));
608
+ console.log(chalk.blue('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
609
+ console.log('');
610
+ // Step 0: Check wrangler and auth
611
+ const wranglerCheck = ora('Checking wrangler status...').start();
612
+ try {
613
+ const installed = await isWranglerInstalled();
614
+ if (!installed) {
615
+ wranglerCheck.fail('wrangler is not installed');
616
+ console.log(chalk.yellow(' npm install -g wrangler to install'));
617
+ return;
618
+ }
619
+ const auth = await checkAuth();
620
+ if (!auth.isLoggedIn) {
621
+ wranglerCheck.fail('Not logged in to Cloudflare');
622
+ console.log(chalk.yellow(' wrangler login to install'));
623
+ return;
624
+ }
625
+ wranglerCheck.succeed(`Connected to Cloudflare (${auth.email || 'authenticated'})`);
626
+ // Get account ID and update auto URLs
627
+ const accountId = await getAccountId();
628
+ if (accountId) {
629
+ config.cloudflare = { accountId };
630
+ }
631
+ }
632
+ catch (error) {
633
+ wranglerCheck.fail('Failed to check wrangler');
634
+ console.error(error);
635
+ return;
636
+ }
637
+ // Step 1: Generate keys
638
+ const keysSpinner = ora('Generating cryptographic keys...').start();
639
+ try {
640
+ const keyId = generateKeyId(env);
641
+ secrets = generateAllSecrets(keyId);
642
+ const keysDir = join(outputDir, '.keys');
643
+ await saveKeysToDirectory(secrets, keysDir);
644
+ config.keys = {
645
+ keyId: secrets.keyPair.keyId,
646
+ publicKeyJwk: secrets.keyPair.publicKeyJwk,
647
+ secretsPath: './.keys/',
648
+ includeSecrets: false,
649
+ };
650
+ keysSpinner.succeed(`Keys generated (${keysDir})`);
651
+ }
652
+ catch (error) {
653
+ keysSpinner.fail('Failed to generate keys');
654
+ throw error;
655
+ }
656
+ // Step 2: Provision Cloudflare resources
657
+ console.log('');
658
+ console.log(chalk.blue('⏳ Creating Cloudflare resources...'));
659
+ console.log('');
660
+ let provisionedResources;
661
+ try {
662
+ provisionedResources = await provisionResources({
663
+ env,
664
+ createD1: true,
665
+ createKV: true,
666
+ createQueues: config.features.queue?.enabled,
667
+ createR2: config.features.r2?.enabled,
668
+ onProgress: (msg) => console.log(` ${msg}`),
669
+ });
670
+ }
671
+ catch (error) {
672
+ console.log(chalk.red(' ✗ Failed to create resources'));
673
+ console.error(error);
674
+ // Ask if user wants to continue without provisioning
675
+ const continueWithoutProvisioning = await confirm({
676
+ message: 'Continue without provisioning? (you will need to create resources manually)',
677
+ default: false,
678
+ });
679
+ if (!continueWithoutProvisioning) {
680
+ return;
681
+ }
682
+ // Create empty resources
683
+ provisionedResources = { d1: [], kv: [], queues: [], r2: [] };
684
+ }
685
+ // Step 3: Create lock file
686
+ const lockSpinner = ora('authrim-lock.json generating...').start();
687
+ try {
688
+ const lockFile = createLockFile(env, provisionedResources);
689
+ const lockPath = join(outputDir, 'authrim-lock.json');
690
+ await saveLockFile(lockFile, lockPath);
691
+ lockSpinner.succeed(`authrim-lock.json saved (${lockPath})`);
692
+ }
693
+ catch (error) {
694
+ lockSpinner.fail('authrim-lock.json save failed');
695
+ console.error(error);
696
+ }
697
+ // Step 4: Save configuration
698
+ const configSpinner = ora('Saving configuration...').start();
699
+ try {
700
+ const configPath = join(outputDir, 'authrim-config.json');
701
+ config.updatedAt = new Date().toISOString();
702
+ await writeFile(configPath, JSON.stringify(config, null, 2), 'utf-8');
703
+ configSpinner.succeed(`Configuration saved (${configPath})`);
704
+ }
705
+ catch (error) {
706
+ configSpinner.fail('Configuration save failed');
707
+ throw error;
708
+ }
709
+ // Step 5: Generate wrangler.toml files (if keeping source or in existing repo)
710
+ const resourceIds = toResourceIds(provisionedResources);
711
+ const packagesDir = join(outputDir, 'packages');
712
+ if (existsSync(packagesDir)) {
713
+ const wranglerSpinner = ora('Generating wrangler.toml files...').start();
714
+ try {
715
+ for (const component of CORE_WORKER_COMPONENTS) {
716
+ const componentDir = join(packagesDir, component);
717
+ if (!existsSync(componentDir)) {
718
+ continue; // Skip if component directory doesn't exist
719
+ }
720
+ const wranglerConfig = generateWranglerConfig(component, config, resourceIds);
721
+ const tomlContent = toToml(wranglerConfig);
722
+ const tomlPath = join(componentDir, `wrangler.${env}.toml`);
723
+ await writeFile(tomlPath, tomlContent, 'utf-8');
724
+ }
725
+ wranglerSpinner.succeed('wrangler.toml files generated');
726
+ }
727
+ catch (error) {
728
+ wranglerSpinner.fail('wrangler.toml generation failed');
729
+ console.error(error);
730
+ }
731
+ }
732
+ // Summary
733
+ console.log('');
734
+ console.log(chalk.green('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
735
+ console.log(chalk.bold.green('🎉 Setup Complete!'));
736
+ console.log(chalk.green('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
737
+ console.log('');
738
+ // Show provisioned resources
739
+ if (provisionedResources.d1.length > 0 || provisionedResources.kv.length > 0) {
740
+ console.log(chalk.bold('📦 Created Resources:'));
741
+ console.log('');
742
+ if (provisionedResources.d1.length > 0) {
743
+ console.log(' D1 Databases:');
744
+ for (const db of provisionedResources.d1) {
745
+ console.log(` ✓ ${db.name} (${db.id.slice(0, 8)}...)`);
746
+ }
747
+ }
748
+ if (provisionedResources.kv.length > 0) {
749
+ console.log(' KV Namespaces:');
750
+ for (const kv of provisionedResources.kv) {
751
+ console.log(` ✓ ${kv.name} (${kv.id.slice(0, 8)}...)`);
752
+ }
753
+ }
754
+ console.log('');
755
+ }
756
+ console.log(chalk.bold('📁 Generated Files:'));
757
+ console.log(` - ${join(outputDir, 'authrim-config.json')}`);
758
+ console.log(` - ${join(outputDir, 'authrim-lock.json')}`);
759
+ console.log(` - ${join(outputDir, '.keys/')} ${chalk.gray('(private keys - add to .gitignore)')}`);
760
+ console.log('');
761
+ // Show URLs
762
+ console.log(chalk.bold('🌐 Endpoints:'));
763
+ const apiUrl = config.urls?.api?.custom || config.urls?.api?.auto || '';
764
+ const loginUrl = config.urls?.loginUi?.custom || config.urls?.loginUi?.auto || '';
765
+ const adminUrl = config.urls?.adminUi?.custom || config.urls?.adminUi?.auto || '';
766
+ console.log(` OIDC Provider: ${chalk.cyan(apiUrl)}`);
767
+ console.log(` Login UI: ${chalk.cyan(loginUrl)}`);
768
+ console.log(` Admin UI: ${chalk.cyan(adminUrl)}`);
769
+ console.log('');
770
+ // Next steps
771
+ console.log(chalk.bold('📋 Next Steps:'));
772
+ console.log('');
773
+ console.log(` 1. Upload secrets to Cloudflare:`);
774
+ console.log(chalk.cyan(` npx @authrim/setup secrets --env=${env}`));
775
+ console.log('');
776
+ console.log(` 2. Deploy Workers:`);
777
+ console.log(chalk.cyan(` pnpm deploy --env=${env}`));
778
+ console.log('');
779
+ }
780
+ // =============================================================================
781
+ // Handle Existing Config
782
+ // =============================================================================
783
+ async function handleExistingConfig(configPath) {
784
+ const spinner = ora(`Loading configuration: ${configPath}`).start();
785
+ try {
786
+ const content = await readFile(configPath, 'utf-8');
787
+ const config = parseConfig(JSON.parse(content));
788
+ spinner.succeed('Configuration loaded');
789
+ console.log('');
790
+ console.log(chalk.blue('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
791
+ console.log(chalk.bold('📋 Configuration Summary'));
792
+ console.log(chalk.blue('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
793
+ console.log('');
794
+ console.log(` Environment: ${chalk.cyan(config.environment.prefix)}`);
795
+ console.log(` Profile: ${chalk.cyan(config.profile)}`);
796
+ console.log(` Version: ${chalk.cyan(config.version)}`);
797
+ if (config.urls?.api) {
798
+ const apiUrl = config.urls.api.custom || config.urls.api.auto;
799
+ console.log(` API URL: ${chalk.cyan(apiUrl || 'Not configured')}`);
800
+ }
801
+ console.log('');
802
+ const action = await select({
803
+ message: 'Select action',
804
+ choices: [
805
+ { value: 'deploy', name: '🚀 Redeploy' },
806
+ { value: 'edit', name: '✏️ Edit config' },
807
+ { value: 'show', name: '📋 Show config' },
808
+ { value: 'cancel', name: '❌ Cancel' },
809
+ ],
810
+ });
811
+ switch (action) {
812
+ case 'deploy':
813
+ await handleRedeploy(config, configPath);
814
+ break;
815
+ case 'edit':
816
+ await handleEditConfig(config, configPath);
817
+ break;
818
+ case 'show':
819
+ console.log('');
820
+ console.log(JSON.stringify(config, null, 2));
821
+ break;
822
+ case 'cancel':
823
+ console.log(chalk.yellow('Cancelled.'));
824
+ break;
825
+ }
826
+ }
827
+ catch (error) {
828
+ spinner.fail('Failed to load configuration');
829
+ console.error(error);
830
+ }
831
+ }
832
+ // =============================================================================
833
+ // Redeploy from Existing Config
834
+ // =============================================================================
835
+ async function handleRedeploy(config, configPath) {
836
+ const env = config.environment.prefix;
837
+ const lockPath = configPath.replace('authrim-config.json', 'authrim-lock.json');
838
+ console.log('');
839
+ console.log(chalk.blue('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
840
+ console.log(chalk.bold('🚀 Redeploy'));
841
+ console.log(chalk.blue('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
842
+ console.log('');
843
+ // Check prerequisites
844
+ const wranglerCheck = ora('Checking wrangler status...').start();
845
+ try {
846
+ const installed = await isWranglerInstalled();
847
+ if (!installed) {
848
+ wranglerCheck.fail('wrangler is not installed');
849
+ console.log(chalk.yellow(' npm install -g wrangler to install'));
850
+ return;
851
+ }
852
+ const auth = await checkAuth();
853
+ if (!auth.isLoggedIn) {
854
+ wranglerCheck.fail('Not logged in to Cloudflare');
855
+ console.log(chalk.yellow(' wrangler login to install'));
856
+ return;
857
+ }
858
+ wranglerCheck.succeed(`Connected to Cloudflare (${auth.email || 'authenticated'})`);
859
+ }
860
+ catch (error) {
861
+ wranglerCheck.fail('Failed to check wrangler');
862
+ console.error(error);
863
+ return;
864
+ }
865
+ // Load lock file
866
+ const lock = await loadLockFile(lockPath);
867
+ const hasLock = lock !== null;
868
+ if (!hasLock) {
869
+ console.log(chalk.yellow('\n⚠️ authrim-lock.json not found'));
870
+ const createResources = await confirm({
871
+ message: 'Create new Cloudflare resources?',
872
+ default: true,
873
+ });
874
+ if (!createResources) {
875
+ console.log(chalk.yellow('Cancelled.'));
876
+ return;
877
+ }
878
+ // Provision new resources
879
+ console.log('');
880
+ console.log(chalk.blue('⏳ Creating Cloudflare resources...'));
881
+ console.log('');
882
+ try {
883
+ const provisionedResources = await provisionResources({
884
+ env,
885
+ createD1: true,
886
+ createKV: true,
887
+ createQueues: config.features.queue?.enabled,
888
+ createR2: config.features.r2?.enabled,
889
+ onProgress: (msg) => console.log(` ${msg}`),
890
+ });
891
+ // Create and save lock file
892
+ const newLock = createLockFile(env, provisionedResources);
893
+ await saveLockFile(newLock, lockPath);
894
+ console.log(chalk.green(`\n✓ authrim-lock.json saved`));
895
+ }
896
+ catch (error) {
897
+ console.log(chalk.red(' ✗ Failed to create resources'));
898
+ console.error(error);
899
+ return;
900
+ }
901
+ }
902
+ else {
903
+ // Show existing resources summary
904
+ console.log(chalk.bold('\n📦 Existing Resources:'));
905
+ console.log(` D1 Databases: ${chalk.cyan(Object.keys(lock.d1).length)}`);
906
+ console.log(` KV Namespaces: ${chalk.cyan(Object.keys(lock.kv).length)}`);
907
+ if (lock.workers) {
908
+ const deployedCount = Object.values(lock.workers).filter((w) => w.deployedAt).length;
909
+ console.log(` Workers: ${chalk.cyan(deployedCount)} deployed`);
910
+ }
911
+ }
912
+ // Determine components to deploy
913
+ const enabledComponents = ['ar-lib-core', 'ar-discovery'];
914
+ enabledComponents.push('ar-auth', 'ar-token', 'ar-userinfo', 'ar-management');
915
+ if (config.components.saml)
916
+ enabledComponents.push('ar-saml');
917
+ if (config.components.async)
918
+ enabledComponents.push('ar-async');
919
+ if (config.components.vc)
920
+ enabledComponents.push('ar-vc');
921
+ if (config.components.bridge)
922
+ enabledComponents.push('ar-bridge');
923
+ if (config.components.policy)
924
+ enabledComponents.push('ar-policy');
925
+ enabledComponents.push('ar-router');
926
+ console.log(chalk.bold('\n📋 Components to Deploy:'));
927
+ for (const comp of enabledComponents) {
928
+ console.log(chalk.cyan(` • ${comp}`));
929
+ }
930
+ console.log('');
931
+ // Confirm deployment
932
+ const proceed = await confirm({
933
+ message: 'Start deployment?',
934
+ default: true,
935
+ });
936
+ if (!proceed) {
937
+ console.log(chalk.yellow('Cancelled.'));
938
+ return;
939
+ }
940
+ // Run deploy using the deploy command
941
+ console.log('');
942
+ const { deployCommand } = await import('./deploy.js');
943
+ await deployCommand({
944
+ config: configPath,
945
+ env,
946
+ yes: true,
947
+ });
948
+ }
949
+ // =============================================================================
950
+ // Edit Existing Config
951
+ // =============================================================================
952
+ async function handleEditConfig(config, configPath) {
953
+ console.log('');
954
+ console.log(chalk.blue('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
955
+ console.log(chalk.bold('✏️ Edit Configuration'));
956
+ console.log(chalk.blue('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
957
+ console.log('');
958
+ const editSection = await select({
959
+ message: 'Select section to edit',
960
+ choices: [
961
+ { value: 'urls', name: '🌐 URL Settings' },
962
+ { value: 'components', name: '📦 Components' },
963
+ { value: 'profile', name: '🔐 OIDCProfile' },
964
+ { value: 'oidc', name: '⚙️ OIDC Settings (TTL, etc.)' },
965
+ { value: 'features', name: '🎛️ Feature Flags' },
966
+ { value: 'sharding', name: '⚡ Sharding Settings' },
967
+ { value: 'cancel', name: '❌ Cancel' },
968
+ ],
969
+ });
970
+ if (editSection === 'cancel') {
971
+ console.log(chalk.yellow('Cancelled.'));
972
+ return;
973
+ }
974
+ let configModified = false;
975
+ switch (editSection) {
976
+ case 'urls':
977
+ configModified = await editUrls(config);
978
+ break;
979
+ case 'components':
980
+ configModified = await editComponents(config);
981
+ break;
982
+ case 'profile':
983
+ configModified = await editProfile(config);
984
+ break;
985
+ case 'oidc':
986
+ configModified = await editOidcSettings(config);
987
+ break;
988
+ case 'features':
989
+ configModified = await editFeatures(config);
990
+ break;
991
+ case 'sharding':
992
+ configModified = await editSharding(config);
993
+ break;
994
+ }
995
+ if (configModified) {
996
+ config.updatedAt = new Date().toISOString();
997
+ const saveChanges = await confirm({
998
+ message: 'Save changes?',
999
+ default: true,
1000
+ });
1001
+ if (saveChanges) {
1002
+ await writeFile(configPath, JSON.stringify(config, null, 2), 'utf-8');
1003
+ console.log(chalk.green(`\n✓ Configuration saved: ${configPath}`));
1004
+ const redeploy = await confirm({
1005
+ message: 'Redeploy to apply changes?',
1006
+ default: false,
1007
+ });
1008
+ if (redeploy) {
1009
+ await handleRedeploy(config, configPath);
1010
+ }
1011
+ }
1012
+ else {
1013
+ console.log(chalk.yellow('Changes were not saved.'));
1014
+ }
1015
+ }
1016
+ }
1017
+ // =============================================================================
1018
+ // Edit URL Configuration
1019
+ // =============================================================================
1020
+ async function editUrls(config) {
1021
+ const env = config.environment.prefix;
1022
+ // Ensure urls object exists
1023
+ if (!config.urls) {
1024
+ config.urls = {
1025
+ api: { custom: null, auto: `https://${env}-ar-router.workers.dev` },
1026
+ loginUi: { custom: null, auto: `https://${env}-ar-ui.pages.dev` },
1027
+ adminUi: { custom: null, auto: `https://${env}-ar-ui.pages.dev/admin` },
1028
+ };
1029
+ }
1030
+ console.log(chalk.bold('\nCurrent URL Settings:'));
1031
+ console.log(` API: ${chalk.cyan(config.urls.api?.custom || config.urls.api?.auto || 'Not set')}`);
1032
+ console.log(` Login UI: ${chalk.cyan(config.urls.loginUi?.custom || config.urls.loginUi?.auto || 'Not set')}`);
1033
+ console.log(` Admin UI: ${chalk.cyan(config.urls.adminUi?.custom || config.urls.adminUi?.auto || 'Not set')}`);
1034
+ console.log('');
1035
+ const apiDomain = await input({
1036
+ message: 'API (issuer) domain (leave empty for workers.dev)',
1037
+ default: config.urls.api?.custom || '',
1038
+ validate: (value) => {
1039
+ if (!value)
1040
+ return true;
1041
+ if (!/^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/.test(value)) {
1042
+ return 'Please enter a valid domain';
1043
+ }
1044
+ return true;
1045
+ },
1046
+ });
1047
+ const loginUiDomain = await input({
1048
+ message: 'Login UI domain (leave empty for pages.dev)',
1049
+ default: config.urls.loginUi?.custom || '',
1050
+ });
1051
+ const adminUiDomain = await input({
1052
+ message: 'Admin UI domain (leave empty for pages.dev)',
1053
+ default: config.urls.adminUi?.custom || '',
1054
+ });
1055
+ config.urls.api = {
1056
+ custom: apiDomain || null,
1057
+ auto: config.urls.api?.auto || `https://${env}-ar-router.workers.dev`,
1058
+ };
1059
+ config.urls.loginUi = {
1060
+ custom: loginUiDomain || null,
1061
+ auto: config.urls.loginUi?.auto || `https://${env}-ar-ui.pages.dev`,
1062
+ };
1063
+ config.urls.adminUi = {
1064
+ custom: adminUiDomain || null,
1065
+ auto: config.urls.adminUi?.auto || `https://${env}-ar-ui.pages.dev/admin`,
1066
+ };
1067
+ return true;
1068
+ }
1069
+ // =============================================================================
1070
+ // Edit Components
1071
+ // =============================================================================
1072
+ async function editComponents(config) {
1073
+ console.log(chalk.bold('\nCurrent Component Settings:'));
1074
+ console.log(` SAML: ${config.components.saml ? chalk.green('Enabled') : chalk.gray('Disabled')}`);
1075
+ console.log(` Async: ${config.components.async ? chalk.green('Enabled') : chalk.gray('Disabled')}`);
1076
+ console.log(` VC: ${config.components.vc ? chalk.green('Enabled') : chalk.gray('Disabled')}`);
1077
+ console.log(` Bridge: ${config.components.bridge ? chalk.green('Enabled') : chalk.gray('Disabled')}`);
1078
+ console.log(` Policy: ${config.components.policy ? chalk.green('Enabled') : chalk.gray('Disabled')}`);
1079
+ console.log('');
1080
+ config.components.saml = await confirm({
1081
+ message: 'Enable SAML support?',
1082
+ default: config.components.saml,
1083
+ });
1084
+ config.components.async = await confirm({
1085
+ message: 'Enable async processing (Queue)?',
1086
+ default: config.components.async,
1087
+ });
1088
+ config.components.vc = await confirm({
1089
+ message: 'Enable Verifiable Credentials?',
1090
+ default: config.components.vc,
1091
+ });
1092
+ config.components.bridge = await confirm({
1093
+ message: 'Enable External IdP Bridge?',
1094
+ default: config.components.bridge,
1095
+ });
1096
+ config.components.policy = await confirm({
1097
+ message: 'Enable ReBAC Policy service?',
1098
+ default: config.components.policy,
1099
+ });
1100
+ return true;
1101
+ }
1102
+ // =============================================================================
1103
+ // Edit Profile
1104
+ // =============================================================================
1105
+ async function editProfile(config) {
1106
+ console.log(`\nCurrent profile: ${chalk.cyan(config.profile)}`);
1107
+ console.log('');
1108
+ const profile = await select({
1109
+ message: 'Select OIDC profile',
1110
+ choices: [
1111
+ {
1112
+ value: 'basic-op',
1113
+ name: 'Basic OP (Standard OIDC Provider)',
1114
+ description: 'Standard OIDC features',
1115
+ },
1116
+ {
1117
+ value: 'fapi-rw',
1118
+ name: 'FAPI Read-Write (Financial Grade)',
1119
+ description: 'FAPI 1.0 Read-Write Security Profile compliant',
1120
+ },
1121
+ {
1122
+ value: 'fapi2-security',
1123
+ name: 'FAPI 2.0 Security Profile',
1124
+ description: 'FAPI 2.0 Security Profile compliant (highest security)',
1125
+ },
1126
+ ],
1127
+ default: config.profile,
1128
+ });
1129
+ config.profile = profile;
1130
+ return true;
1131
+ }
1132
+ // =============================================================================
1133
+ // Edit OIDC Settings
1134
+ // =============================================================================
1135
+ async function editOidcSettings(config) {
1136
+ console.log(chalk.bold('\nCurrent OIDC Settings:'));
1137
+ console.log(` Access Token TTL: ${chalk.cyan(config.oidc.accessTokenTtl)}sec`);
1138
+ console.log(` Refresh Token TTL: ${chalk.cyan(config.oidc.refreshTokenTtl)}sec`);
1139
+ console.log(` Auth Code TTL: ${chalk.cyan(config.oidc.authCodeTtl)}sec`);
1140
+ console.log(` PKCE Required: ${config.oidc.pkceRequired ? chalk.green('Yes') : chalk.yellow('No')}`);
1141
+ console.log('');
1142
+ const accessTokenTtl = await input({
1143
+ message: 'Access Token TTL (sec)',
1144
+ default: String(config.oidc.accessTokenTtl),
1145
+ validate: (value) => {
1146
+ const num = parseInt(value, 10);
1147
+ if (isNaN(num) || num <= 0)
1148
+ return 'Please enter a positive integer';
1149
+ return true;
1150
+ },
1151
+ });
1152
+ const refreshTokenTtl = await input({
1153
+ message: 'Refresh Token TTL (sec)',
1154
+ default: String(config.oidc.refreshTokenTtl),
1155
+ validate: (value) => {
1156
+ const num = parseInt(value, 10);
1157
+ if (isNaN(num) || num <= 0)
1158
+ return 'Please enter a positive integer';
1159
+ return true;
1160
+ },
1161
+ });
1162
+ const authCodeTtl = await input({
1163
+ message: 'Authorization Code TTL (sec)',
1164
+ default: String(config.oidc.authCodeTtl),
1165
+ validate: (value) => {
1166
+ const num = parseInt(value, 10);
1167
+ if (isNaN(num) || num <= 0)
1168
+ return 'Please enter a positive integer';
1169
+ return true;
1170
+ },
1171
+ });
1172
+ const pkceRequired = await confirm({
1173
+ message: 'Require PKCE?',
1174
+ default: config.oidc.pkceRequired,
1175
+ });
1176
+ config.oidc.accessTokenTtl = parseInt(accessTokenTtl, 10);
1177
+ config.oidc.refreshTokenTtl = parseInt(refreshTokenTtl, 10);
1178
+ config.oidc.authCodeTtl = parseInt(authCodeTtl, 10);
1179
+ config.oidc.pkceRequired = pkceRequired;
1180
+ return true;
1181
+ }
1182
+ // =============================================================================
1183
+ // Edit Features
1184
+ // =============================================================================
1185
+ async function editFeatures(config) {
1186
+ console.log(chalk.bold('\nCurrent Feature Flags:'));
1187
+ console.log(` Queue: ${config.features.queue?.enabled ? chalk.green('Enabled') : chalk.gray('Disabled')}`);
1188
+ console.log(` R2: ${config.features.r2?.enabled ? chalk.green('Enabled') : chalk.gray('Disabled')}`);
1189
+ console.log(` Email: ${chalk.cyan(config.features.email?.provider || 'none')}`);
1190
+ console.log('');
1191
+ const queueEnabled = await confirm({
1192
+ message: 'Enable Cloudflare Queues? (for audit logs)',
1193
+ default: config.features.queue?.enabled || false,
1194
+ });
1195
+ const r2Enabled = await confirm({
1196
+ message: 'Enable Cloudflare R2? (for avatars)',
1197
+ default: config.features.r2?.enabled || false,
1198
+ });
1199
+ const emailProvider = await select({
1200
+ message: 'Select email provider',
1201
+ choices: [
1202
+ { value: 'none', name: 'None (email disabled)' },
1203
+ { value: 'resend', name: 'Resend' },
1204
+ { value: 'sendgrid', name: 'SendGrid' },
1205
+ { value: 'ses', name: 'AWS SES' },
1206
+ ],
1207
+ default: config.features.email?.provider || 'none',
1208
+ });
1209
+ config.features.queue = { enabled: queueEnabled };
1210
+ config.features.r2 = { enabled: r2Enabled };
1211
+ config.features.email = { provider: emailProvider };
1212
+ return true;
1213
+ }
1214
+ // =============================================================================
1215
+ // Edit Sharding
1216
+ // =============================================================================
1217
+ async function editSharding(config) {
1218
+ console.log(chalk.bold('\nCurrent Sharding Settings:'));
1219
+ console.log(` Auth Code Shards: ${chalk.cyan(config.sharding.authCodeShards)}`);
1220
+ console.log(` Refresh Token Shards: ${chalk.cyan(config.sharding.refreshTokenShards)}`);
1221
+ console.log('');
1222
+ console.log(chalk.gray(' Note: Power of 2 recommended for shard count (8, 16, 32, 64, 128)'));
1223
+ console.log('');
1224
+ const authCodeShards = await input({
1225
+ message: 'Auth Code shard count',
1226
+ default: String(config.sharding.authCodeShards),
1227
+ validate: (value) => {
1228
+ const num = parseInt(value, 10);
1229
+ if (isNaN(num) || num <= 0)
1230
+ return 'Please enter a positive integer';
1231
+ return true;
1232
+ },
1233
+ });
1234
+ const refreshTokenShards = await input({
1235
+ message: 'Refresh Token shard count',
1236
+ default: String(config.sharding.refreshTokenShards),
1237
+ validate: (value) => {
1238
+ const num = parseInt(value, 10);
1239
+ if (isNaN(num) || num <= 0)
1240
+ return 'Please enter a positive integer';
1241
+ return true;
1242
+ },
1243
+ });
1244
+ config.sharding.authCodeShards = parseInt(authCodeShards, 10);
1245
+ config.sharding.refreshTokenShards = parseInt(refreshTokenShards, 10);
1246
+ return true;
1247
+ }
1248
+ //# sourceMappingURL=init.js.map