mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-08-31 08:39:24 +00:00
- #417: Add Docker auto-start login item hint for macOS in setup wizard - #338: Add clippy.toml with complexity thresholds for AI-assisted dev - #330: Add structured FallbackFailed error variant to ExtensionError - #358: Revoke credential mappings on extension removal (SharedCredentialRegistry) - #419: Detect conflicting cloudflared services during tunnel setup - #344: Improve embedding auth failure warning with configuration hint Co-authored-by: Claude Opus 4.6 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
fa52df593d
commit
7481aea083
@@ -77,7 +77,7 @@ impl SharedCredentialRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
/// Add credential mappings (called when WASM tools register).
|
||||
/// Add credential mappings tagged with an extension name (called when WASM tools register).
|
||||
pub fn add_mappings(&self, mappings: impl IntoIterator<Item = CredentialMapping>) {
|
||||
match self.mappings.write() {
|
||||
Ok(mut guard) => {
|
||||
@@ -93,6 +93,23 @@ impl SharedCredentialRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove all credential mappings whose `secret_name` matches any of the given names.
|
||||
///
|
||||
/// Called when an extension is unregistered/deactivated so its credential
|
||||
/// injection authority does not outlive the extension.
|
||||
pub fn remove_mappings_for_secrets(&self, secret_names: &[String]) {
|
||||
let mut guard = match self.mappings.write() {
|
||||
Ok(guard) => guard,
|
||||
Err(poisoned) => {
|
||||
tracing::warn!(
|
||||
"SharedCredentialRegistry RwLock poisoned during remove_mappings_for_secrets; recovering"
|
||||
);
|
||||
poisoned.into_inner()
|
||||
}
|
||||
};
|
||||
guard.retain(|m| !secret_names.contains(&m.secret_name));
|
||||
}
|
||||
|
||||
/// Check if any credential mapping matches this host (sync, for requires_approval).
|
||||
pub fn has_credentials_for_host(&self, host: &str) -> bool {
|
||||
let guard = match self.mappings.read() {
|
||||
@@ -564,6 +581,36 @@ mod tests {
|
||||
assert_eq!(found.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_shared_registry_remove_mappings_for_secrets() {
|
||||
let registry = SharedCredentialRegistry::new();
|
||||
registry.add_mappings(vec![
|
||||
CredentialMapping::bearer("openai_key", "api.openai.com"),
|
||||
CredentialMapping::bearer("gh_token", "*.github.com"),
|
||||
CredentialMapping::header("openai_org", "OpenAI-Organization", "api.openai.com"),
|
||||
]);
|
||||
|
||||
assert_eq!(registry.find_for_host("api.openai.com").len(), 2);
|
||||
assert!(registry.has_credentials_for_host("api.github.com"));
|
||||
|
||||
// Remove only mappings for openai secrets
|
||||
registry.remove_mappings_for_secrets(&["openai_key".to_string(), "openai_org".to_string()]);
|
||||
|
||||
// OpenAI mappings should be gone
|
||||
assert!(registry.find_for_host("api.openai.com").is_empty());
|
||||
// GitHub mapping should remain
|
||||
assert!(registry.has_credentials_for_host("api.github.com"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_shared_registry_remove_nonexistent_is_noop() {
|
||||
let registry = SharedCredentialRegistry::new();
|
||||
registry.add_mappings(vec![CredentialMapping::bearer("key1", "api.example.com")]);
|
||||
|
||||
registry.remove_mappings_for_secrets(&["nonexistent".to_string()]);
|
||||
assert_eq!(registry.find_for_host("api.example.com").len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_shared_registry_thread_safety() {
|
||||
use std::sync::Arc;
|
||||
|
||||
Reference in New Issue
Block a user