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::new(
170 std::io::ErrorKind::Other,
171 "DBus error",
172 )));
173 let cred_err: CredentialError = platform_err.into();
174 match cred_err {
175 CredentialError::StoreError(msg) => {
176 assert!(msg.contains("Platform error:"));
177 assert!(msg.contains("DBus error"));
178 }
179 _ => panic!("Expected StoreError variant"),
180 }
181 }
182
183 #[test]
184 fn test_from_error_no_entry() {
185 let no_entry_err = Error::NoEntry;
186 let cred_err: CredentialError = no_entry_err.into();
187 match cred_err {
188 CredentialError::NotFound => {}
189 _ => panic!("Expected NotFound variant"),
190 }
191 }
192
193 #[test]
194 fn test_from_error_invalid_credential() {
195 let bad_encoding_err = Error::BadEncoding(vec![0xFF, 0xFE]);
198 let cred_err: CredentialError = bad_encoding_err.into();
199 match cred_err {
200 CredentialError::StoreError(msg) => {
201 assert!(msg.contains("Credential store error:"));
202 }
203 _ => panic!("Expected StoreError variant"),
204 }
205 }
206
207 #[test]
208 fn test_from_error_no_storage_access() {
209 let no_storage_err = Error::NoStorageAccess(Box::new(std::io::Error::new(
212 std::io::ErrorKind::PermissionDenied,
213 "Access denied",
214 )));
215 let cred_err: CredentialError = no_storage_err.into();
216 match cred_err {
217 CredentialError::StoreError(msg) => {
218 assert!(msg.contains("Credential store error:"));
219 }
220 _ => panic!("Expected StoreError variant"),
221 }
222 }
223
224 #[test]
225 fn test_from_error_too_long() {
226 let too_long_err = Error::TooLong("username".to_string(), 255);
229 let cred_err: CredentialError = too_long_err.into();
230 match cred_err {
231 CredentialError::StoreError(msg) => {
232 assert!(msg.contains("Credential store error:"));
233 }
234 _ => panic!("Expected StoreError variant"),
235 }
236 }
237
238 #[test]
243 fn test_is_secure_store_available_returns_bool() {
244 let result = is_secure_store_available();
246 assert!(result == true || result == false);
247 }
248
249 #[test]
254 fn test_nvidia_key_names_are_consistent() {
255 let nvidia_key = "nvidia_api_key";
257 assert_eq!(nvidia_key, "nvidia_api_key");
258 }
259
260 #[test]
261 fn test_openai_key_names_are_consistent() {
262 let openai_key = "openai_api_key";
264 assert_eq!(openai_key, "openai_api_key");
265 }
266
267 #[test]
272 fn test_service_and_user_constants() {
273 assert_eq!(SERVICE_NAME, "pawan");
275 assert_eq!(USER, "api_keys");
276 }
277
278 #[test]
279 fn test_key_name_formatting() {
280 let key_name = "test_key";
282 let formatted = format!("{}_{}", USER, key_name);
283 assert_eq!(formatted, "api_keys_test_key");
284 }
285
286 #[test]
293 #[ignore] fn test_store_and_get_key() {
295 let key_name = "test_key_12345";
296 let test_key = "test_api_key_value";
297
298 store_api_key(key_name, test_key).expect("Failed to store key");
300
301 let retrieved = get_api_key(key_name).expect("Failed to retrieve key");
303 assert_eq!(retrieved, Some(test_key.to_string()));
304
305 delete_api_key(key_name).expect("Failed to delete key");
307 }
308
309 #[test]
310 #[ignore] fn test_get_nonexistent_key() {
312 let key_name = "nonexistent_key_12345";
313
314 let _ = delete_api_key(key_name);
316
317 let retrieved = get_api_key(key_name).expect("Failed to retrieve key");
319 assert_eq!(retrieved, None);
320 }
321
322 #[test]
323 #[ignore] fn test_delete_key() {
325 let key_name = "test_delete_key_12345";
326 let test_key = "test_key_value";
327
328 store_api_key(key_name, test_key).expect("Failed to store");
330 assert!(get_api_key(key_name).expect("Failed to get") == Some(test_key.to_string()));
331
332 delete_api_key(key_name).expect("Failed to delete");
334 assert_eq!(get_api_key(key_name).expect("Failed to get"), None);
335 }
336
337 #[test]
338 #[ignore] fn test_delete_nonexistent_key_succeeds() {
340 let key_name = "nonexistent_delete_key_12345";
342 let result = delete_api_key(key_name);
343 assert!(result.is_ok());
344 }
345
346 #[test]
347 #[ignore] fn test_nvidia_convenience_functions() {
349 let test_key = "nv-test-key-12345";
350
351 store_nvidia_api_key(test_key).expect("Failed to store nvidia key");
353
354 let retrieved = get_nvidia_api_key().expect("Failed to get nvidia key");
356 assert_eq!(retrieved, Some(test_key.to_string()));
357
358 delete_nvidia_api_key().expect("Failed to delete nvidia key");
360
361 assert_eq!(get_nvidia_api_key().expect("Failed to get"), None);
363 }
364
365 #[test]
366 #[ignore] fn test_openai_convenience_functions() {
368 let test_key = "oa-test-key-12345";
369
370 store_openai_api_key(test_key).expect("Failed to store openai key");
372
373 let retrieved = get_openai_api_key().expect("Failed to get openai key");
375 assert_eq!(retrieved, Some(test_key.to_string()));
376
377 delete_openai_api_key().expect("Failed to delete openai key");
379
380 assert_eq!(get_openai_api_key().expect("Failed to get"), None);
382 }
383}