qualia_client_core/
provider_credentials.rs1use keyring::Entry;
9
10const SERVICE: &str = "qualia_db_provider_credentials";
11
12fn valid_connection_id(id: &str) -> bool {
13 !id.is_empty()
14 && id.len() <= 80
15 && id
16 .bytes()
17 .all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit() || byte == b'-')
18}
19
20fn entry(id: &str) -> Result<Entry, String> {
21 if !valid_connection_id(id) {
22 return Err("connection ID must use lowercase letters, digits, and hyphens (max 80)".into());
23 }
24 Entry::new(SERVICE, id).map_err(|error| format!("OS keychain unavailable: {error}"))
25}
26
27pub fn store_bearer_credential(id: &str, secret: &str) -> Result<(), String> {
29 if secret.trim().is_empty() {
30 return Err("credential cannot be empty".into());
31 }
32 entry(id)?
33 .set_password(secret)
34 .map_err(|error| format!("could not save credential in the OS keychain: {error}"))
35}
36
37pub fn remove_bearer_credential(id: &str) -> Result<(), String> {
39 match entry(id)?.delete_credential() {
40 Ok(()) | Err(keyring::Error::NoEntry) => Ok(()),
41 Err(error) => Err(format!("could not remove OS-keychain credential: {error}")),
42 }
43}
44
45pub(crate) fn bearer_credential(id: &str) -> Result<String, String> {
48 entry(id)?
49 .get_password()
50 .map_err(|error| match error {
51 keyring::Error::NoEntry => "no credential is saved for this connection".to_string(),
52 other => format!("could not read OS-keychain credential: {other}"),
53 })
54}
55
56#[cfg(test)]
57mod tests {
58 use super::valid_connection_id;
59
60 #[test]
61 fn connection_ids_are_bounded_and_path_safe() {
62 assert!(valid_connection_id("openai-research"));
63 assert!(!valid_connection_id("OpenAI"));
64 assert!(!valid_connection_id("../../escape"));
65 assert!(!valid_connection_id(""));
66 }
67}