claude_code_toolkit/traits/
secrets.rs1use super::{ Credentials, Secret, SyncResult, Target };
4use crate::error::Result;
5use async_trait::async_trait;
6use std::collections::HashMap;
7
8#[async_trait]
10pub trait SecretProvider: Send + Sync {
11 fn provider_name(&self) -> &str;
13
14 async fn sync_secrets(&self, secrets: &[Secret], targets: &[Target]) -> Result<SyncResult>;
16
17 async fn validate_access(&self, targets: &[Target]) -> Result<HashMap<String, bool>>;
19
20 async fn list_targets(&self, target_type: &str) -> Result<Vec<String>>;
22
23 async fn is_configured(&self) -> Result<bool>;
25}
26
27#[async_trait]
29pub trait SecretManager: Send + Sync {
30 fn register_provider(&mut self, provider: Box<dyn SecretProvider>);
32
33 fn get_provider(&self, name: &str) -> Option<&dyn SecretProvider>;
35
36 async fn sync_credentials(
38 &self,
39 credentials: &Credentials,
40 mapping: &SecretMapping
41 ) -> Result<SyncResult>;
42
43 async fn sync_credentials_to_targets(
45 &self,
46 credentials: &Credentials,
47 mapping: &SecretMapping,
48 targets: &[Target]
49 ) -> Result<SyncResult>;
50
51 async fn validate_targets(&self) -> Result<HashMap<String, bool>>;
53
54 fn list_providers(&self) -> Vec<&str>;
56}
57
58#[derive(Debug, Clone)]
60pub struct SecretMapping {
61 pub schema_name: String,
62 pub mappings: HashMap<String, String>,
63 pub templates: HashMap<String, String>,
64}
65
66impl SecretMapping {
67 pub fn new(schema_name: &str) -> Self {
68 Self {
69 schema_name: schema_name.to_string(),
70 mappings: HashMap::new(),
71 templates: HashMap::new(),
72 }
73 }
74
75 pub fn add_mapping(&mut self, field: &str, secret_name: &str) -> &mut Self {
76 self.mappings.insert(field.to_string(), secret_name.to_string());
77 self
78 }
79
80 pub fn get_secret_name(&self, field: &str) -> Option<&String> {
81 self.mappings.get(field)
82 }
83
84 pub fn to_secrets(&self, credentials: &Credentials) -> Vec<Secret> {
85 let mut secrets = Vec::new();
86
87 tracing::debug!("SecretMapping has {} mappings: {:?}", self.mappings.len(), self.mappings);
89
90 let access_token_name = self
92 .get_secret_name("accessToken")
93 .or_else(|| self.get_secret_name("access_token"));
94 if let Some(name) = access_token_name {
95 tracing::debug!("Found mapping for access token: {}", name);
96 secrets.push(Secret {
97 name: name.clone(),
98 value: credentials.access_token.clone(),
99 description: Some("Claude AI access token".to_string()),
100 });
101 } else {
102 tracing::debug!("No mapping found for access token");
103 }
104
105 let refresh_token_name = self
106 .get_secret_name("refreshToken")
107 .or_else(|| self.get_secret_name("refresh_token"));
108 if let (Some(token), Some(name)) = (&credentials.refresh_token, refresh_token_name) {
109 tracing::debug!("Found mapping for refresh token: {}", name);
110 secrets.push(Secret {
111 name: name.clone(),
112 value: token.clone(),
113 description: Some("Claude AI refresh token".to_string()),
114 });
115 } else {
116 tracing::debug!("No mapping found for refresh token or token is None");
117 }
118
119 let expires_at_name = self
120 .get_secret_name("expiresAt")
121 .or_else(|| self.get_secret_name("expires_at"));
122 if let (Some(expires), Some(name)) = (credentials.expires_at, expires_at_name) {
123 tracing::debug!("Found mapping for expires at: {}", name);
124 secrets.push(Secret {
125 name: name.clone(),
126 value: expires.to_string(),
127 description: Some("Claude AI token expiry timestamp".to_string()),
128 });
129 } else {
130 tracing::debug!("No mapping found for expires at or expires_at is None");
131 }
132
133 tracing::debug!("Generated {} secrets from credentials", secrets.len());
134 secrets
135 }
136}