use zeroize::Zeroizing;
const KEYRING_SERVICE: &str = "gitlab-tracker-redmine";
const KEYRING_ACCOUNT: &str = "redmine_token";
pub fn get_or_prompt_token() -> Option<Zeroizing<String>> {
if let Ok(tok) = std::env::var("REDMINE_TOKEN") {
let tok = Zeroizing::new(tok.trim().to_string());
if !tok.is_empty() {
tracing::info!("REDMINE_TOKEN loaded from environment variable");
return Some(tok);
}
}
match keyring::Entry::new(KEYRING_SERVICE, KEYRING_ACCOUNT) {
Ok(entry) => match entry.get_password() {
Ok(pwd) => {
let pwd = Zeroizing::new(pwd.trim().to_string());
if !pwd.is_empty() {
tracing::info!("REDMINE_TOKEN loaded from OS keyring");
return Some(pwd);
}
tracing::debug!("Redmine keyring entry found but token is empty");
}
Err(e) => {
tracing::debug!(error = %e, "No Redmine token in OS keyring");
}
},
Err(e) => {
tracing::warn!(error = %e, "Failed to open Redmine keyring entry");
}
}
println!("🔑 No REDMINE_TOKEN found in environment or OS keyring.");
println!(" Leave empty to disable Redmine integration for this session.");
match rpassword::prompt_password("Redmine API token: ") {
Ok(raw) => {
let token = Zeroizing::new(raw.trim().to_string());
if token.is_empty() {
tracing::info!("Redmine integration disabled — no token provided");
return None;
}
match keyring::Entry::new(KEYRING_SERVICE, KEYRING_ACCOUNT) {
Ok(entry) => match entry.set_password(&token) {
Ok(_) => {
tracing::info!("Redmine token saved to OS keyring");
println!("✅ Redmine token securely saved to OS Keyring!\n");
}
Err(e) => {
tracing::error!(error = %e, "Failed to save Redmine token to OS keyring");
}
},
Err(e) => {
tracing::error!(error = %e, "Failed to open Redmine keyring entry for writing");
}
}
Some(token)
}
Err(e) => {
tracing::error!(error = %e, "Failed to read Redmine token from prompt");
None
}
}
}
pub fn delete_token() -> bool {
keyring::Entry::new(KEYRING_SERVICE, KEYRING_ACCOUNT)
.and_then(|e| e.delete_password())
.is_ok()
}