- Updated setup_directory() to try multiple credential sources: 1. Existing config file 2. Zitadel log extraction 3. Default credentials 4. Helpful error message if all fail - Made ensure_admin_token() async to actually authenticate with credentials - Added test_zitadel_credentials() helper function - Improved error messages for debugging This addresses the issue where OAuth client creation was failing because credentials couldn't be extracted from Zitadel logs. Related: zit.md plan for automatic OAuth client creation
14 KiB
Zitadel OAuth Client Automatic Creation - Action Plan
Current Status (March 1, 2026)
✅ FIXED: Health Check & Proxy Issues
Problems Fixed:
- Zitadel health checks used port 9000 but Zitadel runs on port 8300
- BotUI proxy used
https://localhost:9000but BotServer runs onhttp://localhost:8080 - Directory base URL used port 9000 instead of 8300
Files Fixed:
botserver/src/core/bootstrap/bootstrap_utils.rs- Health check port 9000 → 8300botserver/src/core/package_manager/installer.rs- ZITADEL_EXTERNALPORT and check_cmd 9000 → 8300botserver/src/core/directory/api.rs- Health check URL to port 8300botlib/src/http_client.rs- DEFAULT_BOTSERVER_URL to http://localhost:8080botserver/src/core/urls.rs- DIRECTORY_BASE to port 8300
Results:
- ✅ Zitadel health check: 2 seconds (was 300 seconds)
- ✅ BotUI proxy: correct routing to BotServer
- ✅ Bootstrap completes successfully
- ✅ No more 502 Bad Gateway errors
❌ REMAINING: OAuth Client Not Created
Problem:
{
"error": "Authentication service not configured",
"details": "OAuth client credentials not available"
}
Root Cause:
- File
botserver-stack/conf/system/directory_config.jsonis MISSING - Bootstrap cannot extract Zitadel credentials from logs
- OAuth client creation fails
- Login fails
Root Cause Analysis
Why the Previous Fix Failed
The commit 86cfccc2 (Jan 6, 2026) added:
extract_initial_admin_from_log()to parse Zitadel logs- Password grant authentication support
- Directory config saving
But it doesn't work because:
- Zitadel doesn't log credentials in the expected format
- Log parsing returns
None - Without credentials, OAuth client creation fails
- Config file is never created
- Chicken-and-egg problem persists
The Real Solution
Instead of parsing logs, the bootstrap should:
- Generate admin credentials using
generate_secure_password() - Create admin user in Zitadel using Zitadel's Management API
- Use those exact credentials to create OAuth client
- Save config to
botserver-stack/conf/system/directory_config.json - Display credentials to user via console and
~/.gb-setup-credentials
Automatic Solution Design
Architecture
Bootstrap Flow (First Run):
1. Start Zitadel service
2. Wait for Zitadel to be ready (health check)
3. Check if directory_config.json exists
- If YES: Load config, skip creation
- If NO: Proceed to step 4
4. Generate admin credentials (username, email, password)
5. Create admin user in Zitadel via Management API
6. Create OAuth application via Management API
7. Save directory_config.json to botserver-stack/conf/system/
8. Display credentials to user
9. Continue bootstrap
Bootstrap Flow (Subsequent Runs):
1. Start Zitadel service
2. Wait for Zitadel to be ready
3. Check if directory_config.json exists
- If YES: Load config, verify OAuth client
- If NO: Run first-run flow
4. Continue bootstrap
Key Changes Required
1. Fix setup_directory() in mod.rs
Current approach (broken):
// Try to extract credentials from log
let credentials = extract_initial_admin_from_log(&log_path);
if let Some((email, password)) = credentials {
// Use credentials
}
New approach:
// Check if config exists
let config_path = PathBuf::from("botserver-stack/conf/system/directory_config.json");
if config_path.exists() {
// Load existing config
return load_config(&config_path);
}
// Generate new credentials
let username = "admin";
let email = "admin@localhost";
let password = generate_secure_password();
// Create admin user in Zitadel
let setup = DirectorySetup::new_with_credentials(
base_url,
Some((email.clone(), password.clone()))
);
let admin_user = setup.create_admin_user(username, email, &password).await?;
// Create OAuth client
let oauth_client = setup.create_oauth_application().await?;
// Save config
let config = DirectoryConfig {
base_url,
admin_token: admin_user.pat_token,
client_id: oauth_client.client_id,
client_secret: oauth_client.client_secret,
// ... other fields
};
save_config(&config_path, &config)?;
// Display credentials to user
print_bootstrap_credentials(&config, &password);
Ok(config)
2. Add create_admin_user() to DirectorySetup
impl DirectorySetup {
pub async fn create_admin_user(
&self,
username: &str,
email: &str,
password: &str,
) -> Result<AdminUser> {
// Use Zitadel Management API to create user
// Endpoint: POST /management/v1/users/human
let user_payload = json!({
"userName": username,
"profile": {
"firstName": "Admin",
"lastName": "User"
},
"email": {
"email": email,
"isEmailVerified": true
},
"password": password,
"passwordChangeRequired": false
});
let response = self.client
.post(format!("{}/management/v1/users/human", self.base_url))
.json(&user_payload)
.send()
.await?;
// Extract user ID and create PAT token
// ...
}
}
3. Ensure Directory Creation in save_config()
fn save_config(path: &Path, config: &DirectoryConfig) -> Result<()> {
// Create parent directory if it doesn't exist
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)
.map_err(|e| anyhow!("Failed to create config directory: {}", e))?;
}
// Write config
let json = serde_json::to_string_pretty(config)?;
fs::write(path, json)
.map_err(|e| anyhow!("Failed to write config file: {}", e))?;
info!("Saved Directory configuration to {}", path.display());
Ok(())
}
4. Update Config File Path
Old path: config/directory_config.json
New path: botserver-stack/conf/system/directory_config.json
Update all references in:
botserver/src/core/package_manager/mod.rsbotserver/src/core/bootstrap/bootstrap_manager.rsbotserver/src/main_module/bootstrap.rs
Implementation Steps
Step 1: Create Admin User via API
File: botserver/src/core/package_manager/setup/directory_setup.rs
Add method to create admin user:
pub async fn create_admin_user(
&self,
username: &str,
email: &str,
password: &str,
) -> Result<AdminUser> {
// Implementation using Zitadel Management API
}
Step 2: Update setup_directory()
File: botserver/src/core/package_manager/mod.rs
Replace log parsing with direct user creation:
pub async fn setup_directory() -> Result<DirectoryConfig> {
let config_path = PathBuf::from("botserver-stack/conf/system/directory_config.json");
// Check existing config
if config_path.exists() {
return load_config(&config_path);
}
// Generate credentials
let password = generate_secure_password();
let email = "admin@localhost";
let username = "admin";
// Create admin and OAuth client
let setup = DirectorySetup::new(base_url);
let admin = setup.create_admin_user(username, email, &password).await?;
let oauth = setup.create_oauth_application(&admin.token).await?;
// Save config
let config = DirectoryConfig { /* ... */ };
save_config(&config_path, &config)?;
// Display credentials
print_credentials(username, email, &password);
Ok(config)
}
Step 3: Fix save_config()
File: botserver/src/core/package_manager/setup/directory_setup.rs
Ensure parent directory exists:
async fn save_config_internal(&self, config: &DirectoryConfig) -> Result<()> {
let path = PathBuf::from("botserver-stack/conf/system/directory_config.json");
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
let json = serde_json::to_string_pretty(config)?;
fs::write(&path, json)?;
Ok(())
}
Step 4: Remove Log Parsing
File: botserver/src/core/package_manager/mod.rs
Delete or deprecate extract_initial_admin_from_log() function - it's not reliable.
Config File Structure
Location: botserver-stack/conf/system/directory_config.json
{
"base_url": "http://localhost:8300",
"default_org": {
"id": "<organization_id>",
"name": "General Bots",
"domain": "localhost"
},
"default_user": {
"id": "<user_id>",
"username": "admin",
"email": "admin@localhost",
"password": "",
"first_name": "Admin",
"last_name": "User"
},
"admin_token": "<personal_access_token>",
"project_id": "<project_id>",
"client_id": "<oauth_client_id>",
"client_secret": "<oauth_client_secret>"
}
Expected Bootstrap Flow
First Run (No Config)
[Bootstrap] Starting Zitadel/Directory service...
[Bootstrap] Directory service started, waiting for readiness...
[Bootstrap] Zitadel/Directory service is responding
[Bootstrap] No directory_config.json found, initializing new setup
[Bootstrap] Generated admin password: Xk9#mP2$vL5@nQ8&
[Bootstrap] Creating admin user in Zitadel...
[Bootstrap] Admin user created: admin@localhost
[Bootstrap] Creating OAuth application...
[Bootstrap] OAuth client created: client_id=123456789
[Bootstrap] Saved Directory configuration to botserver-stack/conf/system/directory_config.json
╔════════════════════════════════════════════════════════════╗
║ 🔐 ADMIN LOGIN - READY TO USE ║
╠════════════════════════════════════════════════════════════╣
║ ║
║ Username: admin ║
║ Password: Xk9#mP2$vL5@nQ8& ║
║ Email: admin@localhost ║
║ ║
║ 🌐 LOGIN NOW: http://localhost:3000/suite/login ║
║ ║
╚════════════════════════════════════════════════════════════╝
[Bootstrap] OAuth client created successfully
[Bootstrap] Bootstrap process completed!
Subsequent Runs (Config Exists)
[Bootstrap] Starting Zitadel/Directory service...
[Bootstrap] Directory service started, waiting for readiness...
[Bootstrap] Zitadel/Directory service is responding
[Bootstrap] Loading existing Directory configuration
[Bootstrap] OAuth client verified: client_id=123456789
[Bootstrap] Bootstrap process completed!
Testing Checklist
- Delete existing
botserver-stack/conf/system/directory_config.json - Run
./reset.shor restart botserver - Verify admin user created in Zitadel
- Verify OAuth application created in Zitadel
- Verify
directory_config.jsonexists with valid credentials - Verify credentials displayed in console
- Verify
~/.gb-setup-credentialsfile created - Test login with displayed credentials
- Verify login returns valid token
- Restart botserver again
- Verify config is loaded (not recreated)
- Verify login still works
Files to Modify
-
botserver/src/core/package_manager/mod.rs- Update
setup_directory()to generate credentials - Remove
extract_initial_admin_from_log()or mark deprecated - Update config path to
botserver-stack/conf/system/directory_config.json
- Update
-
botserver/src/core/package_manager/setup/directory_setup.rs- Add
create_admin_user()method - Update
save_config_internal()to create parent directories - Update config path
- Add
-
botserver/src/core/bootstrap/bootstrap_manager.rs- Update config path reference
- Ensure proper error handling
-
botserver/src/main_module/bootstrap.rs- Update
init_directory_service()to use new path
- Update
Benefits of This Approach
- Fully Automatic - No manual steps required
- Reliable - Doesn't depend on log parsing
- Secure - Generates strong passwords
- Repeatable - Works on every fresh install
- User-Friendly - Displays credentials clearly
- Persistent - Config saved in version-controlled location
- Fast - No waiting for log file parsing
Migration from Old Setup
If ~/.gb-setup-credentials exists but directory_config.json doesn't:
-
Option A: Use existing credentials
- Read credentials from
~/.gb-setup-credentials - Create OAuth client with those credentials
- Save to
directory_config.json
- Read credentials from
-
Option B: Create new setup
- Ignore old credentials
- Generate new admin password
- Update or replace old credentials file
- Save to
directory_config.json
Recommendation: Option A (use existing credentials if available)
Summary
Problem: OAuth client not created because bootstrap can't extract Zitadel credentials from logs.
Solution: Generate credentials programmatically, create admin user via API, create OAuth client, save config automatically.
Result: Fully automatic, reliable bootstrap that creates all necessary credentials and configuration without manual intervention.
Timeline:
- Implementation: 2-4 hours
- Testing: 1 hour
- Total: 3-5 hours
Priority: HIGH - Blocking login functionality