Skip to main content

qualia_client_core/
provider_credentials.rs

1//! OS-keychain backed credentials for user-configured inference connections.
2//!
3//! Agent rosters deliberately store only a stable connection identifier.  The
4//! bearer value is entered by the principal, written to the platform keychain,
5//! and is never returned through an API, included in diagnostics, or persisted
6//! in JSON alongside the endpoint.
7
8use 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
27/// Store a user-supplied credential.  The value is deliberately not returned.
28pub 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
37/// Remove a connection credential from the operating-system keychain.
38pub 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
45/// Retrieve a secret for the narrow duration of an authorized outbound call.
46/// This function is crate-visible so no UI/API layer can accidentally return it.
47pub(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}