1use keyring::{Entry, Error};
10use thiserror::Error;
11use tracing::warn;
12
13const SERVICE_NAME: &str = "pawan";
14const USER: &str = "api_keys";
15
16#[derive(Error, Debug)]
18pub enum CredentialError {
19 #[error("Failed to access credential store: {0}")]
20 StoreError(String),
21
22 #[error("Credential not found")]
23 NotFound,
24
25 #[error("Invalid credential data")]
26 InvalidData,
27}
28
29impl From<Error> for CredentialError {
30 fn from(err: Error) -> Self {
31 match err {
32 Error::PlatformFailure(e) => {
33 CredentialError::StoreError(format!("Platform error: {}", e))
34 }
35 Error::NoEntry => CredentialError::NotFound,
36 _ => CredentialError::StoreError(format!("Credential store error: {}", err)),
37 }
38 }
39}
40
41pub fn store_api_key(key_name: &str, api_key: &str) -> Result<(), CredentialError> {
51 let entry = Entry::new(SERVICE_NAME, &format!("{}_{}", USER, key_name))?;
52 entry.set_password(api_key)?;
53 warn!("API key '{}' stored securely", key_name);
54 Ok(())
55}
56
57pub fn get_api_key(key_name: &str) -> Result<Option<String>, CredentialError> {
67 let entry = Entry::new(SERVICE_NAME, &format!("{}_{}", USER, key_name))?;
68
69 match entry.get_password() {
70 Ok(key) => Ok(Some(key)),
71 Err(Error::NoEntry) => Ok(None),
72 Err(e) => Err(e.into()),
73 }
74}
75
76pub fn delete_api_key(key_name: &str) -> Result<(), CredentialError> {
85 let entry = Entry::new(SERVICE_NAME, &format!("{}_{}", USER, key_name))?;
86
87 match entry.delete_credential() {
88 Ok(()) => {
89 warn!("API key '{}' deleted from secure store", key_name);
90 Ok(())
91 }
92 Err(Error::NoEntry) => Ok(()),
93 Err(e) => Err(e.into()),
94 }
95}
96
97pub fn is_secure_store_available() -> bool {
103 let test_entry = Entry::new(SERVICE_NAME, "test_check");
104 test_entry.is_ok()
105}
106
107pub fn store_nvidia_api_key(key: &str) -> Result<(), CredentialError> {
109 store_api_key("nvidia_api_key", key)
110}
111
112pub fn get_nvidia_api_key() -> Result<Option<String>, CredentialError> {
113 get_api_key("nvidia_api_key")
114}
115
116pub fn delete_nvidia_api_key() -> Result<(), CredentialError> {
117 delete_api_key("nvidia_api_key")
118}
119
120pub fn store_openai_api_key(key: &str) -> Result<(), CredentialError> {
122 store_api_key("openai_api_key", key)
123}
124
125pub fn get_openai_api_key() -> Result<Option<String>, CredentialError> {
126 get_api_key("openai_api_key")
127}
128
129pub fn delete_openai_api_key() -> Result<(), CredentialError> {
130 delete_api_key("openai_api_key")
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136 use keyring::Error;
137
138 #[test]
143 fn test_credential_error_store_error() {
144 let err = CredentialError::StoreError("test error".to_string());
145 assert_eq!(
146 err.to_string(),
147 "Failed to access credential store: test error"
148 );
149 }
150
151 #[test]
152 fn test_credential_error_not_found() {
153 let err = CredentialError::NotFound;
154 assert_eq!(err.to_string(), "Credential not found");
155 }
156
157 #[test]
158 fn test_credential_error_invalid_data() {
159 let err = CredentialError::InvalidData;
160 assert_eq!(err.to_string(), "Invalid credential data");
161 }
162
163 #[test]
168 fn test_from_error_platform_failure() {
169 let platform_err = Error::PlatformFailure(Box::new(std::io::Error::other("DBus error")));
170 let cred_err: CredentialError = platform_err.into();
171 match cred_err {
172 CredentialError::StoreError(msg) => {
173 assert!(msg.contains("Platform error:"));
174 assert!(msg.contains("DBus error"));
175 }
176 _ => panic!("Expected StoreError variant"),
177 }
178 }
179
180 #[test]
181 fn test_from_error_no_entry() {
182 let no_entry_err = Error::NoEntry;
183 let cred_err: CredentialError = no_entry_err.into();
184 match cred_err {
185 CredentialError::NotFound => {}
186 _ => panic!("Expected NotFound variant"),
187 }
188 }
189
190 #[test]
191 fn test_from_error_invalid_credential() {
192 let bad_encoding_err = Error::BadEncoding(vec![0xFF, 0xFE]);
195 let cred_err: CredentialError = bad_encoding_err.into();
196 match cred_err {
197 CredentialError::StoreError(msg) => {
198 assert!(msg.contains("Credential store error:"));
199 }
200 _ => panic!("Expected StoreError variant"),
201 }
202 }
203
204 #[test]
205 fn test_from_error_no_storage_access() {
206 let no_storage_err = Error::NoStorageAccess(Box::new(std::io::Error::new(
209 std::io::ErrorKind::PermissionDenied,
210 "Access denied",
211 )));
212 let cred_err: CredentialError = no_storage_err.into();
213 match cred_err {
214 CredentialError::StoreError(msg) => {
215 assert!(msg.contains("Credential store error:"));
216 }
217 _ => panic!("Expected StoreError variant"),
218 }
219 }
220
221 #[test]
222 fn test_from_error_too_long() {
223 let too_long_err = Error::TooLong("username".to_string(), 255);
226 let cred_err: CredentialError = too_long_err.into();
227 match cred_err {
228 CredentialError::StoreError(msg) => {
229 assert!(msg.contains("Credential store error:"));
230 }
231 _ => panic!("Expected StoreError variant"),
232 }
233 }
234
235 #[test]
240 fn test_is_secure_store_available_returns_bool() {
241 let _: bool = is_secure_store_available();
243 }
244
245 #[test]
250 fn test_nvidia_key_names_are_consistent() {
251 let nvidia_key = "nvidia_api_key";
253 assert_eq!(nvidia_key, "nvidia_api_key");
254 }
255
256 #[test]
257 fn test_openai_key_names_are_consistent() {
258 let openai_key = "openai_api_key";
260 assert_eq!(openai_key, "openai_api_key");
261 }
262
263 #[test]
268 fn test_service_and_user_constants() {
269 assert_eq!(SERVICE_NAME, "pawan");
271 assert_eq!(USER, "api_keys");
272 }
273
274 #[test]
275 fn test_key_name_formatting() {
276 let key_name = "test_key";
278 let formatted = format!("{}_{}", USER, key_name);
279 assert_eq!(formatted, "api_keys_test_key");
280 }
281
282 #[test]
289 #[ignore] fn test_store_and_get_key() {
291 let key_name = "test_key_12345";
292 let test_key = "test_api_key_value";
293
294 store_api_key(key_name, test_key).expect("Failed to store key");
296
297 let retrieved = get_api_key(key_name).expect("Failed to retrieve key");
299 assert_eq!(retrieved, Some(test_key.to_string()));
300
301 delete_api_key(key_name).expect("Failed to delete key");
303 }
304
305 #[test]
306 #[ignore] fn test_get_nonexistent_key() {
308 let key_name = "nonexistent_key_12345";
309
310 let _ = delete_api_key(key_name);
312
313 let retrieved = get_api_key(key_name).expect("Failed to retrieve key");
315 assert_eq!(retrieved, None);
316 }
317
318 #[test]
319 #[ignore] fn test_delete_key() {
321 let key_name = "test_delete_key_12345";
322 let test_key = "test_key_value";
323
324 store_api_key(key_name, test_key).expect("Failed to store");
326 assert!(get_api_key(key_name).expect("Failed to get") == Some(test_key.to_string()));
327
328 delete_api_key(key_name).expect("Failed to delete");
330 assert_eq!(get_api_key(key_name).expect("Failed to get"), None);
331 }
332
333 #[test]
334 #[ignore] fn test_delete_nonexistent_key_succeeds() {
336 let key_name = "nonexistent_delete_key_12345";
338 let result = delete_api_key(key_name);
339 assert!(result.is_ok());
340 }
341
342 #[test]
343 #[ignore] fn test_nvidia_convenience_functions() {
345 let test_key = "nv-test-key-12345";
346
347 store_nvidia_api_key(test_key).expect("Failed to store nvidia key");
349
350 let retrieved = get_nvidia_api_key().expect("Failed to get nvidia key");
352 assert_eq!(retrieved, Some(test_key.to_string()));
353
354 delete_nvidia_api_key().expect("Failed to delete nvidia key");
356
357 assert_eq!(get_nvidia_api_key().expect("Failed to get"), None);
359 }
360
361 #[test]
362 #[ignore] fn test_openai_convenience_functions() {
364 let test_key = "oa-test-key-12345";
365
366 store_openai_api_key(test_key).expect("Failed to store openai key");
368
369 let retrieved = get_openai_api_key().expect("Failed to get openai key");
371 assert_eq!(retrieved, Some(test_key.to_string()));
372
373 delete_openai_api_key().expect("Failed to delete openai key");
375
376 assert_eq!(get_openai_api_key().expect("Failed to get"), None);
378 }
379}