use zeroize::Zeroizing;
const KEYRING_SERVICE: &str = "gitlab-tracker-redmine";
fn account_for(redmine_url: &str) -> String {
format!("redmine_token::{}", redmine_url.trim_end_matches('/'))
}
pub fn get_or_prompt_token(redmine_url: &str) -> 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);
}
}
let account = account_for(redmine_url);
match keyring::Entry::new(KEYRING_SERVICE, &account) {
Ok(entry) => match entry.get_password() {
Ok(pwd) => {
let pwd = Zeroizing::new(pwd.trim().to_string());
if !pwd.is_empty() {
tracing::info!(url = %redmine_url, "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 for {redmine_url}.");
println!(" Leave empty to disable Redmine integration for this project.");
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, &account) {
Ok(entry) => match entry.set_password(&token) {
Ok(_) => {
tracing::info!(url = %redmine_url, "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(redmine_url: &str) -> bool {
keyring::Entry::new(KEYRING_SERVICE, &account_for(redmine_url))
.and_then(|e| e.delete_credential())
.is_ok()
}