@authrim/setup 0.1.49 → 0.1.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -719,8 +719,112 @@ async function runQuickSetup(options) {
719
719
  default: '',
720
720
  });
721
721
  }
722
+ // Database Configuration
723
+ console.log('');
724
+ console.log(chalk.blue('━━━ Database Configuration ━━━'));
725
+ console.log(chalk.yellow('⚠️ Database region cannot be changed after creation.'));
726
+ console.log('');
727
+ const locationChoices = [
728
+ { name: 'Automatic (nearest to you)', value: 'auto' },
729
+ { name: '── Location Hints ──', value: '__separator1__', disabled: true },
730
+ { name: 'North America (West)', value: 'wnam' },
731
+ { name: 'North America (East)', value: 'enam' },
732
+ { name: 'Europe (West)', value: 'weur' },
733
+ { name: 'Europe (East)', value: 'eeur' },
734
+ { name: 'Asia Pacific', value: 'apac' },
735
+ { name: 'Oceania', value: 'oc' },
736
+ { name: '── Jurisdiction (Compliance) ──', value: '__separator2__', disabled: true },
737
+ { name: 'EU Jurisdiction (GDPR compliance)', value: 'eu' },
738
+ ];
739
+ console.log(chalk.gray(' Core DB: Stores OAuth clients, tokens, sessions, audit logs'));
740
+ const coreDbLocation = await select({
741
+ message: 'Core Database region',
742
+ choices: locationChoices,
743
+ default: 'auto',
744
+ });
745
+ console.log('');
746
+ console.log(chalk.gray(' PII DB: Stores user profiles, credentials, personal data'));
747
+ const piiDbLocation = await select({
748
+ message: 'PII Database region',
749
+ choices: locationChoices,
750
+ default: 'auto',
751
+ });
752
+ // Parse location vs jurisdiction
753
+ function parseDbLocation(value) {
754
+ if (value === 'eu') {
755
+ return { location: 'auto', jurisdiction: 'eu' };
756
+ }
757
+ return {
758
+ location: value,
759
+ jurisdiction: 'none',
760
+ };
761
+ }
762
+ // Email Provider Configuration
763
+ console.log('');
764
+ console.log(chalk.blue('━━━ Email Provider ━━━'));
765
+ console.log(chalk.gray('Configure email sending for magic links and verification codes.'));
766
+ console.log('');
767
+ const configureEmail = await confirm({
768
+ message: 'Configure email provider now?',
769
+ default: false,
770
+ });
771
+ let emailConfig = { provider: 'none' };
772
+ if (configureEmail) {
773
+ console.log('');
774
+ console.log(chalk.gray('Resend is the supported email provider.'));
775
+ console.log(chalk.gray('Get your API key at: https://resend.com/api-keys'));
776
+ console.log(chalk.gray('Set up domain at: https://resend.com/domains'));
777
+ console.log('');
778
+ const resendApiKey = await password({
779
+ message: 'Resend API key',
780
+ mask: '*',
781
+ validate: (value) => {
782
+ if (!value.trim())
783
+ return 'API key is required';
784
+ if (!value.startsWith('re_')) {
785
+ return 'Warning: Resend API keys typically start with "re_"';
786
+ }
787
+ return true;
788
+ },
789
+ });
790
+ const fromAddress = await input({
791
+ message: 'From email address',
792
+ default: 'noreply@yourdomain.com',
793
+ validate: (value) => {
794
+ if (!value.includes('@'))
795
+ return 'Please enter a valid email address';
796
+ return true;
797
+ },
798
+ });
799
+ const fromName = await input({
800
+ message: 'From display name (optional)',
801
+ default: 'Authrim',
802
+ });
803
+ emailConfig = {
804
+ provider: 'resend',
805
+ fromAddress,
806
+ fromName: fromName || undefined,
807
+ apiKey: resendApiKey,
808
+ };
809
+ console.log('');
810
+ console.log(chalk.yellow('⚠️ Domain verification required for sending from your own domain.'));
811
+ console.log(chalk.gray(' See: https://resend.com/docs/dashboard/domains/introduction'));
812
+ }
722
813
  // Create configuration
723
814
  const config = createDefaultConfig(envPrefix);
815
+ config.database = {
816
+ core: parseDbLocation(coreDbLocation),
817
+ pii: parseDbLocation(piiDbLocation),
818
+ };
819
+ config.features = {
820
+ ...config.features,
821
+ email: {
822
+ provider: emailConfig.provider,
823
+ fromAddress: emailConfig.fromAddress,
824
+ fromName: emailConfig.fromName,
825
+ configured: emailConfig.provider === 'resend',
826
+ },
827
+ };
724
828
  config.urls = {
725
829
  api: {
726
830
  custom: apiDomain || null,
@@ -750,6 +854,18 @@ async function runQuickSetup(options) {
750
854
  console.log(` Login UI: ${chalk.cyan(config.urls.loginUi.custom || config.urls.loginUi.auto)}`);
751
855
  console.log(` Admin UI: ${chalk.cyan(config.urls.adminUi.custom || config.urls.adminUi.auto)}`);
752
856
  console.log('');
857
+ console.log(chalk.bold('Email:'));
858
+ if (emailConfig.provider === 'resend') {
859
+ console.log(` Provider: ${chalk.cyan('Resend')}`);
860
+ console.log(` From Address: ${chalk.cyan(emailConfig.fromAddress)}`);
861
+ if (emailConfig.fromName) {
862
+ console.log(` From Name: ${chalk.cyan(emailConfig.fromName)}`);
863
+ }
864
+ }
865
+ else {
866
+ console.log(` Provider: ${chalk.gray('Not configured (configure later)')}`);
867
+ }
868
+ console.log('');
753
869
  const proceed = await confirm({
754
870
  message: 'Start setup with this configuration?',
755
871
  default: true,
@@ -758,6 +874,19 @@ async function runQuickSetup(options) {
758
874
  console.log(chalk.yellow('Setup cancelled.'));
759
875
  return;
760
876
  }
877
+ // Save email secrets if configured
878
+ if (emailConfig.provider === 'resend' && emailConfig.apiKey) {
879
+ const keysDir = `.keys/${envPrefix}`;
880
+ await import('node:fs/promises').then(async (fs) => {
881
+ await fs.mkdir(keysDir, { recursive: true });
882
+ await fs.writeFile(`${keysDir}/resend_api_key.txt`, emailConfig.apiKey.trim());
883
+ await fs.writeFile(`${keysDir}/email_from.txt`, emailConfig.fromAddress.trim());
884
+ if (emailConfig.fromName) {
885
+ await fs.writeFile(`${keysDir}/email_from_name.txt`, emailConfig.fromName.trim());
886
+ }
887
+ });
888
+ console.log(chalk.gray(`📧 Email secrets saved to ${keysDir}/`));
889
+ }
761
890
  // Run setup
762
891
  await executeSetup(config, cfApiToken, options.keep);
763
892
  }
@@ -1000,16 +1129,59 @@ async function runNormalSetup(options) {
1000
1129
  message: 'Enable Cloudflare R2? (for avatars)',
1001
1130
  default: false,
1002
1131
  });
1003
- const emailProvider = await select({
1132
+ const emailProviderChoice = await select({
1004
1133
  message: 'Select email provider',
1005
1134
  choices: [
1006
- { value: 'none', name: 'None (email disabled)' },
1135
+ { value: 'none', name: 'None (configure later)' },
1007
1136
  { value: 'resend', name: 'Resend' },
1008
- { value: 'sendgrid', name: 'SendGrid' },
1009
- { value: 'ses', name: 'AWS SES' },
1137
+ { value: 'sendgrid', name: 'SendGrid (coming soon)', disabled: true },
1138
+ { value: 'ses', name: 'AWS SES (coming soon)', disabled: true },
1010
1139
  ],
1011
1140
  default: 'none',
1012
1141
  });
1142
+ // Email configuration details
1143
+ let emailConfigNormal = { provider: 'none' };
1144
+ if (emailProviderChoice === 'resend') {
1145
+ console.log('');
1146
+ console.log(chalk.blue('━━━ Resend Configuration ━━━'));
1147
+ console.log(chalk.gray('Get your API key at: https://resend.com/api-keys'));
1148
+ console.log(chalk.gray('Set up domain at: https://resend.com/domains'));
1149
+ console.log('');
1150
+ const resendApiKey = await password({
1151
+ message: 'Resend API key',
1152
+ mask: '*',
1153
+ validate: (value) => {
1154
+ if (!value.trim())
1155
+ return 'API key is required';
1156
+ if (!value.startsWith('re_')) {
1157
+ return 'Warning: Resend API keys typically start with "re_"';
1158
+ }
1159
+ return true;
1160
+ },
1161
+ });
1162
+ const fromAddress = await input({
1163
+ message: 'From email address',
1164
+ default: 'noreply@yourdomain.com',
1165
+ validate: (value) => {
1166
+ if (!value.includes('@'))
1167
+ return 'Please enter a valid email address';
1168
+ return true;
1169
+ },
1170
+ });
1171
+ const fromName = await input({
1172
+ message: 'From display name (optional)',
1173
+ default: 'Authrim',
1174
+ });
1175
+ emailConfigNormal = {
1176
+ provider: 'resend',
1177
+ fromAddress,
1178
+ fromName: fromName || undefined,
1179
+ apiKey: resendApiKey,
1180
+ };
1181
+ console.log('');
1182
+ console.log(chalk.yellow('⚠️ Domain verification required for sending from your own domain.'));
1183
+ console.log(chalk.gray(' See: https://resend.com/docs/dashboard/domains/introduction'));
1184
+ }
1013
1185
  // Step 7: Advanced OIDC settings
1014
1186
  const configureOidc = await confirm({
1015
1187
  message: 'Configure OIDC settings? (token TTL, etc.)',
@@ -1096,9 +1268,54 @@ async function runNormalSetup(options) {
1096
1268
  });
1097
1269
  refreshTokenShards = parseInt(refreshTokenShardsStr, 10);
1098
1270
  }
1271
+ // Step 9: Database Configuration
1272
+ console.log('');
1273
+ console.log(chalk.blue('━━━ Database Configuration ━━━'));
1274
+ console.log(chalk.yellow('⚠️ Database region cannot be changed after creation.'));
1275
+ console.log('');
1276
+ const dbLocationChoices = [
1277
+ { name: 'Automatic (nearest to you)', value: 'auto' },
1278
+ { name: '── Location Hints ──', value: '__separator1__', disabled: true },
1279
+ { name: 'North America (West)', value: 'wnam' },
1280
+ { name: 'North America (East)', value: 'enam' },
1281
+ { name: 'Europe (West)', value: 'weur' },
1282
+ { name: 'Europe (East)', value: 'eeur' },
1283
+ { name: 'Asia Pacific', value: 'apac' },
1284
+ { name: 'Oceania', value: 'oc' },
1285
+ { name: '── Jurisdiction (Compliance) ──', value: '__separator2__', disabled: true },
1286
+ { name: 'EU Jurisdiction (GDPR compliance)', value: 'eu' },
1287
+ ];
1288
+ console.log(chalk.gray(' Core DB: Stores OAuth clients, tokens, sessions, audit logs'));
1289
+ const coreDbLocation = await select({
1290
+ message: 'Core Database region',
1291
+ choices: dbLocationChoices,
1292
+ default: 'auto',
1293
+ });
1294
+ console.log('');
1295
+ console.log(chalk.gray(' PII DB: Stores user profiles, credentials, personal data'));
1296
+ console.log(chalk.gray(' Consider your data protection requirements.'));
1297
+ const piiDbLocation = await select({
1298
+ message: 'PII Database region',
1299
+ choices: dbLocationChoices,
1300
+ default: 'auto',
1301
+ });
1302
+ // Parse location vs jurisdiction
1303
+ function parseDbLocationNormal(value) {
1304
+ if (value === 'eu') {
1305
+ return { location: 'auto', jurisdiction: 'eu' };
1306
+ }
1307
+ return {
1308
+ location: value,
1309
+ jurisdiction: 'none',
1310
+ };
1311
+ }
1099
1312
  // Create configuration
1100
1313
  const config = createDefaultConfig(envPrefix);
1101
1314
  config.profile = profile;
1315
+ config.database = {
1316
+ core: parseDbLocationNormal(coreDbLocation),
1317
+ pii: parseDbLocationNormal(piiDbLocation),
1318
+ };
1102
1319
  config.tenant = {
1103
1320
  name: tenantName,
1104
1321
  displayName: tenantDisplayName,
@@ -1141,7 +1358,12 @@ async function runNormalSetup(options) {
1141
1358
  config.features = {
1142
1359
  queue: { enabled: enableQueue },
1143
1360
  r2: { enabled: enableR2 },
1144
- email: { provider: emailProvider },
1361
+ email: {
1362
+ provider: emailConfigNormal.provider,
1363
+ fromAddress: emailConfigNormal.fromAddress,
1364
+ fromName: emailConfigNormal.fromName,
1365
+ configured: emailConfigNormal.provider === 'resend',
1366
+ },
1145
1367
  };
1146
1368
  // Show summary
1147
1369
  console.log('');
@@ -1190,7 +1412,18 @@ async function runNormalSetup(options) {
1190
1412
  console.log(chalk.bold('Feature Flags:'));
1191
1413
  console.log(` Queue: ${enableQueue ? chalk.green('Enabled') : chalk.gray('Disabled')}`);
1192
1414
  console.log(` R2: ${enableR2 ? chalk.green('Enabled') : chalk.gray('Disabled')}`);
1193
- console.log(` Email: ${chalk.cyan(emailProvider)}`);
1415
+ console.log('');
1416
+ console.log(chalk.bold('Email:'));
1417
+ if (emailConfigNormal.provider === 'resend') {
1418
+ console.log(` Provider: ${chalk.cyan('Resend')}`);
1419
+ console.log(` From Address: ${chalk.cyan(emailConfigNormal.fromAddress)}`);
1420
+ if (emailConfigNormal.fromName) {
1421
+ console.log(` From Name: ${chalk.cyan(emailConfigNormal.fromName)}`);
1422
+ }
1423
+ }
1424
+ else {
1425
+ console.log(` Provider: ${chalk.gray('Not configured (configure later)')}`);
1426
+ }
1194
1427
  console.log('');
1195
1428
  console.log(chalk.bold('OIDC Settings:'));
1196
1429
  console.log(` Access TTL: ${chalk.cyan(accessTokenTtl + 'sec')}`);
@@ -1202,6 +1435,20 @@ async function runNormalSetup(options) {
1202
1435
  console.log(` Auth Code: ${chalk.cyan(authCodeShards)} shards`);
1203
1436
  console.log(` Refresh Token: ${chalk.cyan(refreshTokenShards)} shards`);
1204
1437
  console.log('');
1438
+ console.log(chalk.bold('Database:'));
1439
+ const coreDbDisplay = coreDbLocation === 'eu'
1440
+ ? 'EU Jurisdiction'
1441
+ : coreDbLocation === 'auto'
1442
+ ? 'Automatic'
1443
+ : coreDbLocation.toUpperCase();
1444
+ const piiDbDisplay = piiDbLocation === 'eu'
1445
+ ? 'EU Jurisdiction'
1446
+ : piiDbLocation === 'auto'
1447
+ ? 'Automatic'
1448
+ : piiDbLocation.toUpperCase();
1449
+ console.log(` Core DB: ${chalk.cyan(coreDbDisplay)}`);
1450
+ console.log(` PII DB: ${chalk.cyan(piiDbDisplay)}`);
1451
+ console.log('');
1205
1452
  const proceed = await confirm({
1206
1453
  message: 'Start setup with this configuration?',
1207
1454
  default: true,
@@ -1210,6 +1457,19 @@ async function runNormalSetup(options) {
1210
1457
  console.log(chalk.yellow('Setup cancelled.'));
1211
1458
  return;
1212
1459
  }
1460
+ // Save email secrets if configured
1461
+ if (emailConfigNormal.provider === 'resend' && emailConfigNormal.apiKey) {
1462
+ const keysDir = `.keys/${envPrefix}`;
1463
+ await import('node:fs/promises').then(async (fs) => {
1464
+ await fs.mkdir(keysDir, { recursive: true });
1465
+ await fs.writeFile(`${keysDir}/resend_api_key.txt`, emailConfigNormal.apiKey.trim());
1466
+ await fs.writeFile(`${keysDir}/email_from.txt`, emailConfigNormal.fromAddress.trim());
1467
+ if (emailConfigNormal.fromName) {
1468
+ await fs.writeFile(`${keysDir}/email_from_name.txt`, emailConfigNormal.fromName.trim());
1469
+ }
1470
+ });
1471
+ console.log(chalk.gray(`📧 Email secrets saved to ${keysDir}/`));
1472
+ }
1213
1473
  await executeSetup(config, cfApiToken, options.keep);
1214
1474
  }
1215
1475
  // =============================================================================
@@ -1300,6 +1560,7 @@ async function executeSetup(config, cfApiToken, keepPath) {
1300
1560
  createKV: true,
1301
1561
  createQueues: config.features.queue?.enabled,
1302
1562
  createR2: config.features.r2?.enabled,
1563
+ databaseConfig: config.database,
1303
1564
  onProgress: (msg) => console.log(` ${msg}`),
1304
1565
  });
1305
1566
  }
@@ -1531,6 +1792,7 @@ async function handleRedeploy(config, configPath) {
1531
1792
  createKV: true,
1532
1793
  createQueues: config.features.queue?.enabled,
1533
1794
  createR2: config.features.r2?.enabled,
1795
+ databaseConfig: config.database,
1534
1796
  onProgress: (msg) => console.log(` ${msg}`),
1535
1797
  });
1536
1798
  // Create and save lock file
@@ -1848,7 +2110,12 @@ async function editFeatures(config) {
1848
2110
  });
1849
2111
  config.features.queue = { enabled: queueEnabled };
1850
2112
  config.features.r2 = { enabled: r2Enabled };
1851
- config.features.email = { provider: emailProvider };
2113
+ config.features.email = {
2114
+ provider: emailProvider,
2115
+ configured: config.features.email?.configured || false,
2116
+ fromAddress: config.features.email?.fromAddress,
2117
+ fromName: config.features.email?.fromName,
2118
+ };
1852
2119
  return true;
1853
2120
  }
1854
2121
  // =============================================================================