fix: Direct login without password change requirement

- Set change_required=false when creating admin password in Zitadel
- Admin can now login directly at /suite/login without forced password change
- Create security reminder file for admin to change password later
- Update console and credential file messages to reflect direct login
- Password change is recommended but not enforced on first login
This commit is contained in:
Rodrigo Rodriguez (Pragmatismo) 2026-01-09 13:03:26 -03:00
parent 115b2770cb
commit 1c7a5c80b2
3 changed files with 55 additions and 39 deletions

View file

@ -1,7 +1,7 @@
{
"base_url": "http://localhost:8300",
"default_org": {
"id": "354797876871692302",
"id": "354799954578898958",
"name": "default",
"domain": "default.localhost"
},
@ -13,8 +13,8 @@
"first_name": "Admin",
"last_name": "User"
},
"admin_token": "oU_rp4E81exidBP-jyaGcoKI1Ckoz-_U2O4BCMPmasKHktVw0ja2hP506-Bf-MD1JsqWl2A",
"admin_token": "6nMpG1E-H-hqlSjrbFB5n2yx8BoEpnl3a3-F3HJoc5bvria3nwiz8vURPndzS4lQWKwaz_8",
"project_id": "",
"client_id": "354797877458960398",
"client_secret": "k4MLtkaRFuKkJwfubZCN5g7UZT8GJttzYb1Zjb48Xo3OesQPeBsVC3xjhsn8uvrL"
"client_id": "354799955384270862",
"client_secret": "z6WFb1qshdCQ1y4Gw5EpOHzARgHicz6XkrazZwJdDcxMJrc6iRdHlhf5rf5LTzgi"
}

View file

@ -3,6 +3,7 @@ use log::{error, info, warn};
use rand::Rng;
use std::fs;
use std::os::unix::fs::PermissionsExt;
use uuid::Uuid;
use super::client::ZitadelClient;
@ -116,7 +117,7 @@ async fn create_bootstrap_admin(client: &ZitadelClient) -> Result<BootstrapResul
let initial_password = generate_secure_password();
if let Err(e) = client.set_user_password(&user_id, &initial_password, true).await {
if let Err(e) = client.set_user_password(&user_id, &initial_password, false).await {
warn!("Failed to set initial password via API: {}. User may need to use password reset flow.", e);
} else {
info!("Initial password set for admin user");
@ -157,9 +158,44 @@ async fn create_bootstrap_admin(client: &ZitadelClient) -> Result<BootstrapResul
save_setup_credentials(&result);
create_password_change_reminder(&user_id);
Ok(result)
}
fn create_password_change_reminder(user_id: &str) {
let reminder_file = dirs::home_dir()
.unwrap_or_else(|| std::path::PathBuf::from("/tmp"))
.join(".gb-admin-reminders.json");
let reminder = serde_json::json!({
"id": Uuid::new_v4().to_string(),
"type": "security",
"priority": "high",
"title": "Change initial admin password",
"description": "The admin account was created with an auto-generated password. Please change it to a secure password of your choice.",
"user_id": user_id,
"created_at": chrono::Utc::now().to_rfc3339(),
"status": "pending",
"action_url": "/settings/security"
});
let reminders = if reminder_file.exists() {
let content = fs::read_to_string(&reminder_file).unwrap_or_default();
let mut arr: Vec<serde_json::Value> = serde_json::from_str(&content).unwrap_or_default();
arr.push(reminder);
arr
} else {
vec![reminder]
};
if let Err(e) = fs::write(&reminder_file, serde_json::to_string_pretty(&reminders).unwrap_or_default()) {
warn!("Failed to save password change reminder: {}", e);
} else {
info!("Created security reminder: Change initial admin password");
}
}
async fn create_default_organization(client: &ZitadelClient) -> Result<String> {
let url = format!("{}/v2/organizations", client.api_url());
@ -205,37 +241,27 @@ fn save_setup_credentials(result: &BootstrapResult) {
let content = format!(
r#"# General Bots Initial Setup Credentials
# Created: {}
# DELETE THIS FILE AFTER FIRST LOGIN
ADMIN LOGIN CREDENTIALS (OTP)
🔐 ADMIN LOGIN - READY TO USE
Username: {:<46}
Password: {:<46}
Email: {:<46}
Login URL: {:<45}
🌐 LOGIN NOW: http://localhost:8088/suite/login ║
IMPORTANT:
- This is a one-time password (OTP)
- You will be required to change it on first login
- Delete this file after you have logged in successfully
Alternative access via Zitadel console:
1. Go to: {}/ui/console
2. Login with admin PAT from: ./botserver-stack/conf/directory/admin-pat.txt
3. Find user '{}' and manage settings
Login directly - no password change required on first access
SECURITY: Change this password after login (Settings > Security)
🗑 Delete this file after saving your new password
"#,
chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC"),
result.username,
result.initial_password,
result.email,
result.setup_url,
result.setup_url.split("/ui/").next().unwrap_or("http://localhost:8300"),
result.username
result.email
);
match fs::write(&creds_path, &content) {
@ -268,7 +294,7 @@ fn print_bootstrap_credentials(result: &BootstrapResult) {
println!("{:^60}", "");
println!("{}", separator);
println!("{:^60}", "");
println!("{:^60}", "🔐 ONE-TIME PASSWORD (OTP) FOR LOGIN:");
println!("{:^60}", "🔐 LOGIN CREDENTIALS (READY TO USE):");
println!("{:^60}", "");
println!("{:<58}", format!("Username: {}", result.username));
println!("{:<58}", format!("Password: {}", result.initial_password));
@ -285,29 +311,22 @@ fn print_bootstrap_credentials(result: &BootstrapResult) {
println!("{}", separator);
println!("{:^60}", "");
println!("{:56}", "🌐 LOGIN URL:");
println!("{:56}", "🌐 LOGIN NOW:");
println!("{:^60}", "");
let url_display = if result.setup_url.len() > 54 {
format!("{}...", &result.setup_url[..51])
} else {
result.setup_url.clone()
};
println!("{:56}", url_display);
println!("{:56}", "http://localhost:8088/suite/login");
println!("{:^60}", "");
println!("{}", separator);
println!("{:^60}", "");
println!("⚠️ {:<53}", "IMPORTANT - SAVE THESE CREDENTIALS!");
println!("║ ✅ {:<54}", "Login directly - no password change required");
println!("{:^60}", "");
println!("{:<56}", "• This password will NOT be shown again");
println!("{:<56}", "• You must change it on first login");
println!("{:<56}", "• Credentials also saved to: ~/.gb-setup-credentials");
println!("{:<56}", "⚠️ Change password after login (Settings > Security)");
println!("{:<56}", "📁 Credentials saved to: ~/.gb-setup-credentials");
println!("{:^60}", "");
println!("{}", separator);
println!();
info!(
"Bootstrap complete: admin user '{}' created with OTP password",
"Bootstrap complete: admin user '{}' created - ready for direct login",
result.username
);
}

View file

@ -519,10 +519,7 @@ async fn main() -> std::io::Result<()> {
use botserver::config::ConfigManager;
if no_console || no_ui {
env_logger::Builder::from_env(env_logger::Env::default())
.write_style(env_logger::WriteStyle::Always)
.init();
botlib::logging::init_compact_logger_with_style("info");
println!("Starting General Bots {}...", env!("CARGO_PKG_VERSION"));
}