- Move git_token and Bedrock credentials to OS keychain instead of storing in plaintext projects.json via skip_serializing + keyring - Fix project status stuck in Starting on container creation failure by resetting to Stopped on any error path - Add granular store methods to reduce TOCTOU race window - Add auth_mode, project path, and bedrock config change detection to container_needs_recreation with label-based fingerprinting - Fix mutex held across async Docker API call in exec resize by cloning exec_id under lock then releasing before API call - Add graceful shutdown via on_window_event to clean up exec sessions - Extract compute_env_fingerprint and merge_claude_instructions helpers to eliminate code duplication in container.rs - Remove unused thiserror dependency - Return error instead of falling back to CWD when data dir unavailable Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
85 lines
3.0 KiB
Rust
85 lines
3.0 KiB
Rust
const SERVICE_NAME: &str = "triple-c";
|
|
const API_KEY_USER: &str = "anthropic-api-key";
|
|
|
|
pub fn store_api_key(key: &str) -> Result<(), String> {
|
|
let entry = keyring::Entry::new(SERVICE_NAME, API_KEY_USER)
|
|
.map_err(|e| format!("Keyring error: {}", e))?;
|
|
entry
|
|
.set_password(key)
|
|
.map_err(|e| format!("Failed to store API key: {}", e))
|
|
}
|
|
|
|
pub fn get_api_key() -> Result<Option<String>, String> {
|
|
let entry = keyring::Entry::new(SERVICE_NAME, API_KEY_USER)
|
|
.map_err(|e| format!("Keyring error: {}", e))?;
|
|
match entry.get_password() {
|
|
Ok(key) => Ok(Some(key)),
|
|
Err(keyring::Error::NoEntry) => Ok(None),
|
|
Err(e) => Err(format!("Failed to retrieve API key: {}", e)),
|
|
}
|
|
}
|
|
|
|
pub fn delete_api_key() -> Result<(), String> {
|
|
let entry = keyring::Entry::new(SERVICE_NAME, API_KEY_USER)
|
|
.map_err(|e| format!("Keyring error: {}", e))?;
|
|
match entry.delete_credential() {
|
|
Ok(()) => Ok(()),
|
|
Err(keyring::Error::NoEntry) => Ok(()),
|
|
Err(e) => Err(format!("Failed to delete API key: {}", e)),
|
|
}
|
|
}
|
|
|
|
pub fn has_api_key() -> Result<bool, String> {
|
|
match get_api_key() {
|
|
Ok(Some(_)) => Ok(true),
|
|
Ok(None) => Ok(false),
|
|
Err(e) => Err(e),
|
|
}
|
|
}
|
|
|
|
/// Store a per-project secret in the OS keychain.
|
|
pub fn store_project_secret(project_id: &str, key_name: &str, value: &str) -> Result<(), String> {
|
|
let service = format!("triple-c-project-{}-{}", project_id, key_name);
|
|
let entry = keyring::Entry::new(&service, "secret")
|
|
.map_err(|e| format!("Keyring error: {}", e))?;
|
|
entry
|
|
.set_password(value)
|
|
.map_err(|e| format!("Failed to store project secret '{}': {}", key_name, e))
|
|
}
|
|
|
|
/// Retrieve a per-project secret from the OS keychain.
|
|
pub fn get_project_secret(project_id: &str, key_name: &str) -> Result<Option<String>, String> {
|
|
let service = format!("triple-c-project-{}-{}", project_id, key_name);
|
|
let entry = keyring::Entry::new(&service, "secret")
|
|
.map_err(|e| format!("Keyring error: {}", e))?;
|
|
match entry.get_password() {
|
|
Ok(value) => Ok(Some(value)),
|
|
Err(keyring::Error::NoEntry) => Ok(None),
|
|
Err(e) => Err(format!("Failed to retrieve project secret '{}': {}", key_name, e)),
|
|
}
|
|
}
|
|
|
|
/// Delete all known secrets for a project from the OS keychain.
|
|
pub fn delete_project_secrets(project_id: &str) -> Result<(), String> {
|
|
let secret_keys = [
|
|
"git-token",
|
|
"aws-access-key-id",
|
|
"aws-secret-access-key",
|
|
"aws-session-token",
|
|
"aws-bearer-token",
|
|
];
|
|
for key_name in &secret_keys {
|
|
let service = format!("triple-c-project-{}-{}", project_id, key_name);
|
|
let entry = keyring::Entry::new(&service, "secret")
|
|
.map_err(|e| format!("Keyring error: {}", e))?;
|
|
match entry.delete_credential() {
|
|
Ok(()) => {}
|
|
Err(keyring::Error::NoEntry) => {}
|
|
Err(e) => {
|
|
log::warn!("Failed to delete project secret '{}': {}", key_name, e);
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|