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) => CredentialError::StoreError(format!("Platform error: {}", e)),
33 Error::NoEntry => CredentialError::NotFound,
34 _ => CredentialError::StoreError(format!("Credential store error: {}", err)),
35 }
36 }
37}
38
39pub fn store_api_key(key_name: &str, api_key: &str) -> Result<(), CredentialError> {
49 let entry = Entry::new(SERVICE_NAME, &format!("{}_{}", USER, key_name))?;
50 entry.set_password(api_key)?;
51 warn!("API key '{}' stored securely", key_name);
52 Ok(())
53}
54
55pub fn get_api_key(key_name: &str) -> Result<Option<String>, CredentialError> {
65 let entry = Entry::new(SERVICE_NAME, &format!("{}_{}", USER, key_name))?;
66
67 match entry.get_password() {
68 Ok(key) => Ok(Some(key)),
69 Err(Error::NoEntry) => Ok(None),
70 Err(e) => Err(e.into()),
71 }
72}
73
74pub fn delete_api_key(key_name: &str) -> Result<(), CredentialError> {
83 let entry = Entry::new(SERVICE_NAME, &format!("{}_{}", USER, key_name))?;
84
85 match entry.delete_credential() {
86 Ok(()) => {
87 warn!("API key '{}' deleted from secure store", key_name);
88 Ok(())
89 }
90 Err(Error::NoEntry) => Ok(()),
91 Err(e) => Err(e.into()),
92 }
93}
94
95pub fn is_secure_store_available() -> bool {
101 let test_entry = Entry::new(SERVICE_NAME, "test_check");
102 test_entry.is_ok()
103}
104
105pub fn store_nvidia_api_key(key: &str) -> Result<(), CredentialError> {
107 store_api_key("nvidia_api_key", key)
108}
109
110pub fn get_nvidia_api_key() -> Result<Option<String>, CredentialError> {
111 get_api_key("nvidia_api_key")
112}
113
114pub fn delete_nvidia_api_key() -> Result<(), CredentialError> {
115 delete_api_key("nvidia_api_key")
116}
117
118pub fn store_openai_api_key(key: &str) -> Result<(), CredentialError> {
120 store_api_key("openai_api_key", key)
121}
122
123pub fn get_openai_api_key() -> Result<Option<String>, CredentialError> {
124 get_api_key("openai_api_key")
125}
126
127pub fn delete_openai_api_key() -> Result<(), CredentialError> {
128 delete_api_key("openai_api_key")
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134 use keyring::Error;
135
136 #[test]
141 fn test_credential_error_store_error() {
142 let err = CredentialError::StoreError("test error".to_string());
143 assert_eq!(err.to_string(), "Failed to access credential store: test error");
144 }
145
146 #[test]
147 fn test_credential_error_not_found() {
148 let err = CredentialError::NotFound;
149 assert_eq!(err.to_string(), "Credential not found");
150 }
151
152 #[test]
153 fn test_credential_error_invalid_data() {
154 let err = CredentialError::InvalidData;
155 assert_eq!(err.to_string(), "Invalid credential data");
156 }
157
158 #[test]
163 fn test_from_error_platform_failure() {
164 let platform_err = Error::PlatformFailure(Box::new(std::io::Error::new(
165 std::io::ErrorKind::Other,
166 "DBus error"
167 )));
168 let cred_err: CredentialError = platform_err.into();
169 match cred_err {
170 CredentialError::StoreError(msg) => {
171 assert!(msg.contains("Platform error:"));
172 assert!(msg.contains("DBus error"));
173 }
174 _ => panic!("Expected StoreError variant"),
175 }
176 }
177
178 #[test]
179 fn test_from_error_no_entry() {
180 let no_entry_err = Error::NoEntry;
181 let cred_err: CredentialError = no_entry_err.into();
182 match cred_err {
183 CredentialError::NotFound => {}
184 _ => panic!("Expected NotFound variant"),
185 }
186 }
187
188 #[test]
189 fn test_from_error_invalid_credential() {
190 let bad_encoding_err = Error::BadEncoding(vec![0xFF, 0xFE]);
193 let cred_err: CredentialError = bad_encoding_err.into();
194 match cred_err {
195 CredentialError::StoreError(msg) => {
196 assert!(msg.contains("Credential store error:"));
197 }
198 _ => panic!("Expected StoreError variant"),
199 }
200 }
201
202 #[test]
203 fn test_from_error_no_storage_access() {
204 let no_storage_err = Error::NoStorageAccess(Box::new(std::io::Error::new(
207 std::io::ErrorKind::PermissionDenied,
208 "Access denied"
209 )));
210 let cred_err: CredentialError = no_storage_err.into();
211 match cred_err {
212 CredentialError::StoreError(msg) => {
213 assert!(msg.contains("Credential store error:"));
214 }
215 _ => panic!("Expected StoreError variant"),
216 }
217 }
218
219 #[test]
220 fn test_from_error_too_long() {
221 let too_long_err = Error::TooLong("username".to_string(), 255);
224 let cred_err: CredentialError = too_long_err.into();
225 match cred_err {
226 CredentialError::StoreError(msg) => {
227 assert!(msg.contains("Credential store error:"));
228 }
229 _ => panic!("Expected StoreError variant"),
230 }
231 }
232
233 #[test]
238 fn test_is_secure_store_available_returns_bool() {
239 let result = is_secure_store_available();
241 assert!(result == true || result == false);
242 }
243
244 #[test]
249 fn test_nvidia_key_names_are_consistent() {
250 let nvidia_key = "nvidia_api_key";
252 assert_eq!(nvidia_key, "nvidia_api_key");
253 }
254
255 #[test]
256 fn test_openai_key_names_are_consistent() {
257 let openai_key = "openai_api_key";
259 assert_eq!(openai_key, "openai_api_key");
260 }
261
262 #[test]
267 fn test_service_and_user_constants() {
268 assert_eq!(SERVICE_NAME, "pawan");
270 assert_eq!(USER, "api_keys");
271 }
272
273 #[test]
274 fn test_key_name_formatting() {
275 let key_name = "test_key";
277 let formatted = format!("{}_{}", USER, key_name);
278 assert_eq!(formatted, "api_keys_test_key");
279 }
280
281 #[test]
288 #[ignore] fn test_store_and_get_key() {
290 let key_name = "test_key_12345";
291 let test_key = "test_api_key_value";
292
293 store_api_key(key_name, test_key).expect("Failed to store key");
295
296 let retrieved = get_api_key(key_name).expect("Failed to retrieve key");
298 assert_eq!(retrieved, Some(test_key.to_string()));
299
300 delete_api_key(key_name).expect("Failed to delete key");
302 }
303
304 #[test]
305 #[ignore] fn test_get_nonexistent_key() {
307 let key_name = "nonexistent_key_12345";
308
309 let _ = delete_api_key(key_name);
311
312 let retrieved = get_api_key(key_name).expect("Failed to retrieve key");
314 assert_eq!(retrieved, None);
315 }
316
317 #[test]
318 #[ignore] fn test_delete_key() {
320 let key_name = "test_delete_key_12345";
321 let test_key = "test_key_value";
322
323 store_api_key(key_name, test_key).expect("Failed to store");
325 assert!(get_api_key(key_name).expect("Failed to get") == Some(test_key.to_string()));
326
327 delete_api_key(key_name).expect("Failed to delete");
329 assert_eq!(get_api_key(key_name).expect("Failed to get"), None);
330 }
331
332 #[test]
333 #[ignore] fn test_delete_nonexistent_key_succeeds() {
335 let key_name = "nonexistent_delete_key_12345";
337 let result = delete_api_key(key_name);
338 assert!(result.is_ok());
339 }
340
341 #[test]
342 #[ignore] fn test_nvidia_convenience_functions() {
344 let test_key = "nv-test-key-12345";
345
346 store_nvidia_api_key(test_key).expect("Failed to store nvidia key");
348
349 let retrieved = get_nvidia_api_key().expect("Failed to get nvidia key");
351 assert_eq!(retrieved, Some(test_key.to_string()));
352
353 delete_nvidia_api_key().expect("Failed to delete nvidia key");
355
356 assert_eq!(get_nvidia_api_key().expect("Failed to get"), None);
358 }
359
360 #[test]
361 #[ignore] fn test_openai_convenience_functions() {
363 let test_key = "oa-test-key-12345";
364
365 store_openai_api_key(test_key).expect("Failed to store openai key");
367
368 let retrieved = get_openai_api_key().expect("Failed to get openai key");
370 assert_eq!(retrieved, Some(test_key.to_string()));
371
372 delete_openai_api_key().expect("Failed to delete openai key");
374
375 assert_eq!(get_openai_api_key().expect("Failed to get"), None);
377 }
378}