@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.
- package/README.md +303 -0
- package/dist/__tests__/config.test.d.ts +5 -0
- package/dist/__tests__/config.test.d.ts.map +1 -0
- package/dist/__tests__/config.test.js +115 -0
- package/dist/__tests__/config.test.js.map +1 -0
- package/dist/__tests__/keys.test.d.ts +5 -0
- package/dist/__tests__/keys.test.d.ts.map +1 -0
- package/dist/__tests__/keys.test.js +87 -0
- package/dist/__tests__/keys.test.js.map +1 -0
- package/dist/__tests__/naming.test.d.ts +5 -0
- package/dist/__tests__/naming.test.d.ts.map +1 -0
- package/dist/__tests__/naming.test.js +84 -0
- package/dist/__tests__/naming.test.js.map +1 -0
- package/dist/cli/commands/config.d.ts +13 -0
- package/dist/cli/commands/config.d.ts.map +1 -0
- package/dist/cli/commands/config.js +231 -0
- package/dist/cli/commands/config.js.map +1 -0
- package/dist/cli/commands/deploy.d.ts +21 -0
- package/dist/cli/commands/deploy.d.ts.map +1 -0
- package/dist/cli/commands/deploy.js +304 -0
- package/dist/cli/commands/deploy.js.map +1 -0
- package/dist/cli/commands/init.d.ts +14 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +1248 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/core/admin.d.ts +64 -0
- package/dist/core/admin.d.ts.map +1 -0
- package/dist/core/admin.js +247 -0
- package/dist/core/admin.js.map +1 -0
- package/dist/core/cloudflare.d.ts +157 -0
- package/dist/core/cloudflare.d.ts.map +1 -0
- package/dist/core/cloudflare.js +452 -0
- package/dist/core/cloudflare.js.map +1 -0
- package/dist/core/config.d.ts +891 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +208 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/deploy.d.ts +81 -0
- package/dist/core/deploy.d.ts.map +1 -0
- package/dist/core/deploy.js +389 -0
- package/dist/core/deploy.js.map +1 -0
- package/dist/core/keys.d.ts +111 -0
- package/dist/core/keys.d.ts.map +1 -0
- package/dist/core/keys.js +287 -0
- package/dist/core/keys.js.map +1 -0
- package/dist/core/lock.d.ts +220 -0
- package/dist/core/lock.d.ts.map +1 -0
- package/dist/core/lock.js +230 -0
- package/dist/core/lock.js.map +1 -0
- package/dist/core/naming.d.ts +151 -0
- package/dist/core/naming.d.ts.map +1 -0
- package/dist/core/naming.js +209 -0
- package/dist/core/naming.js.map +1 -0
- package/dist/core/source.d.ts +68 -0
- package/dist/core/source.d.ts.map +1 -0
- package/dist/core/source.js +285 -0
- package/dist/core/source.js.map +1 -0
- package/dist/core/wrangler.d.ts +87 -0
- package/dist/core/wrangler.d.ts.map +1 -0
- package/dist/core/wrangler.js +398 -0
- package/dist/core/wrangler.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +117 -0
- package/dist/index.js.map +1 -0
- package/dist/web/api.d.ts +21 -0
- package/dist/web/api.d.ts.map +1 -0
- package/dist/web/api.js +423 -0
- package/dist/web/api.js.map +1 -0
- package/dist/web/server.d.ts +12 -0
- package/dist/web/server.d.ts.map +1 -0
- package/dist/web/server.js +112 -0
- package/dist/web/server.js.map +1 -0
- package/dist/web/ui.d.ts +7 -0
- package/dist/web/ui.d.ts.map +1 -0
- package/dist/web/ui.js +765 -0
- package/dist/web/ui.js.map +1 -0
- 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
|