feat(directory): improve OAuth client creation with better credential handling
Some checks failed
BotServer CI / build (push) Failing after 11s
Some checks failed
BotServer CI / build (push) Failing after 11s
- 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
This commit is contained in:
parent
9fc33725b7
commit
abedde3af7
3 changed files with 488 additions and 2 deletions
48
AGENTS.md
48
AGENTS.md
|
|
@ -1,5 +1,7 @@
|
|||
# General Bots AI Agent Guidelines
|
||||
|
||||
8080 is server 3000 is client ui
|
||||
To test web is http://localhost:3000 (botui!)
|
||||
Use apenas a lingua culta.
|
||||
> **⚠️ CRITICAL SECURITY WARNING**
|
||||
I AM IN DEV ENV, but sometimes, pasting from PROD, do not treat my env as prod! Just fix, to me and push to CI. So I can test in PROD, for a while.
|
||||
>Use Playwrigth MCP to start localhost:3000/<bot> now.
|
||||
|
|
@ -37,6 +39,50 @@ See botserver/src/drive/local_file_monitor.rs to see how to load from /opt/gbo/d
|
|||
|
||||
---
|
||||
|
||||
## 🔄 Reset Process Notes
|
||||
|
||||
### reset.sh Behavior
|
||||
- **Purpose**: Cleans and restarts the development environment
|
||||
- **Timeouts**: The script can timeout during "Step 3/4: Waiting for BotServer to bootstrap"
|
||||
- **Bootstrap Process**: Takes 3-5 minutes to install all components (Vault, PostgreSQL, Valkey, MinIO, Zitadel, LLM)
|
||||
|
||||
### Common Issues
|
||||
1. **Script Timeout**: reset.sh waits for "Bootstrap complete: admin user" message
|
||||
- If Zitadel isn't ready within 60s, admin user creation fails
|
||||
- Script continues waiting indefinitely
|
||||
- **Solution**: Check botserver.log for "Bootstrap process completed!" message
|
||||
|
||||
2. **Zitadel Not Ready**: "Bootstrap check failed (Zitadel may not be ready)"
|
||||
- Directory service may need more than 60 seconds to start
|
||||
- Admin user creation deferred
|
||||
- Services still start successfully
|
||||
|
||||
3. **Services Exit After Start**:
|
||||
- botserver/botui may exit after initial startup
|
||||
- Check logs for "dispatch failure" errors
|
||||
- Check Vault certificate errors: "tls: failed to verify certificate: x509"
|
||||
|
||||
### Manual Service Management
|
||||
```bash
|
||||
# If reset.sh times out, manually verify services:
|
||||
ps aux | grep -E "(botserver|botui)" | grep -v grep
|
||||
curl http://localhost:8080/health
|
||||
tail -f botserver.log botui.log
|
||||
|
||||
# Restart services manually:
|
||||
./restart.sh
|
||||
```
|
||||
|
||||
### Reset Verification
|
||||
After reset completes, verify:
|
||||
- ✅ PostgreSQL running (port 5432)
|
||||
- ✅ Valkey cache running (port 6379)
|
||||
- ✅ BotServer listening on port 8080
|
||||
- ✅ BotUI listening on port 3000
|
||||
- ✅ No errors in botserver.log
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Security Directives - MANDATORY
|
||||
|
||||
### 1. Error Handling - NO PANICS IN PRODUCTION
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 0b1b17406db9d4cc91c1a29cf549398e72fd111a
|
||||
Subproject commit 2c92a81302e1e2c5c533fd585e69a2b5beaace89
|
||||
440
zit.md
Normal file
440
zit.md
Normal file
|
|
@ -0,0 +1,440 @@
|
|||
# Zitadel OAuth Client Automatic Creation - Action Plan
|
||||
|
||||
## Current Status (March 1, 2026)
|
||||
|
||||
### ✅ FIXED: Health Check & Proxy Issues
|
||||
|
||||
**Problems Fixed:**
|
||||
1. Zitadel health checks used port **9000** but Zitadel runs on port **8300**
|
||||
2. BotUI proxy used `https://localhost:9000` but BotServer runs on `http://localhost:8080`
|
||||
3. Directory base URL used port 9000 instead of 8300
|
||||
|
||||
**Files Fixed:**
|
||||
1. `botserver/src/core/bootstrap/bootstrap_utils.rs` - Health check port 9000 → 8300
|
||||
2. `botserver/src/core/package_manager/installer.rs` - ZITADEL_EXTERNALPORT and check_cmd 9000 → 8300
|
||||
3. `botserver/src/core/directory/api.rs` - Health check URL to port 8300
|
||||
4. `botlib/src/http_client.rs` - DEFAULT_BOTSERVER_URL to http://localhost:8080
|
||||
5. `botserver/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:**
|
||||
```json
|
||||
{
|
||||
"error": "Authentication service not configured",
|
||||
"details": "OAuth client credentials not available"
|
||||
}
|
||||
```
|
||||
|
||||
**Root Cause:**
|
||||
- File `botserver-stack/conf/system/directory_config.json` is **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:**
|
||||
1. **Zitadel doesn't log credentials** in the expected format
|
||||
2. Log parsing returns `None`
|
||||
3. Without credentials, OAuth client creation fails
|
||||
4. Config file is never created
|
||||
5. **Chicken-and-egg problem persists**
|
||||
|
||||
### The Real Solution
|
||||
|
||||
**Instead of parsing logs, the bootstrap should:**
|
||||
1. **Generate admin credentials** using `generate_secure_password()`
|
||||
2. **Create admin user in Zitadel** using Zitadel's Management API
|
||||
3. **Use those exact credentials** to create OAuth client
|
||||
4. **Save config** to `botserver-stack/conf/system/directory_config.json`
|
||||
5. **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):**
|
||||
```rust
|
||||
// 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:**
|
||||
```rust
|
||||
// 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`
|
||||
|
||||
```rust
|
||||
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()`
|
||||
|
||||
```rust
|
||||
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.rs`
|
||||
- `botserver/src/core/bootstrap/bootstrap_manager.rs`
|
||||
- `botserver/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:
|
||||
```rust
|
||||
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:
|
||||
```rust
|
||||
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:
|
||||
```rust
|
||||
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`
|
||||
|
||||
```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.sh` or restart botserver
|
||||
- [ ] Verify admin user created in Zitadel
|
||||
- [ ] Verify OAuth application created in Zitadel
|
||||
- [ ] Verify `directory_config.json` exists with valid credentials
|
||||
- [ ] Verify credentials displayed in console
|
||||
- [ ] Verify `~/.gb-setup-credentials` file 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
|
||||
|
||||
1. **`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`
|
||||
|
||||
2. **`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
|
||||
|
||||
3. **`botserver/src/core/bootstrap/bootstrap_manager.rs`**
|
||||
- Update config path reference
|
||||
- Ensure proper error handling
|
||||
|
||||
4. **`botserver/src/main_module/bootstrap.rs`**
|
||||
- Update `init_directory_service()` to use new path
|
||||
|
||||
## Benefits of This Approach
|
||||
|
||||
1. **Fully Automatic** - No manual steps required
|
||||
2. **Reliable** - Doesn't depend on log parsing
|
||||
3. **Secure** - Generates strong passwords
|
||||
4. **Repeatable** - Works on every fresh install
|
||||
5. **User-Friendly** - Displays credentials clearly
|
||||
6. **Persistent** - Config saved in version-controlled location
|
||||
7. **Fast** - No waiting for log file parsing
|
||||
|
||||
## Migration from Old Setup
|
||||
|
||||
If `~/.gb-setup-credentials` exists but `directory_config.json` doesn't:
|
||||
|
||||
1. **Option A:** Use existing credentials
|
||||
- Read credentials from `~/.gb-setup-credentials`
|
||||
- Create OAuth client with those credentials
|
||||
- Save to `directory_config.json`
|
||||
|
||||
2. **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
|
||||
Loading…
Add table
Reference in a new issue