Skip to main content

rightkit_http/
secret.rs

1//! Secret handling shared by the net crates. `rightkit-secrets` (built in
2//! parallel) supplies the production [`SecretStore`] implementation (Keychain /
3//! Credential Manager); this crate only defines the interface so net crates do
4//! not depend on unfinished code.
5
6use std::collections::HashMap;
7use std::fmt;
8use std::sync::Mutex;
9
10/// A string that never prints itself.
11#[derive(Clone, PartialEq, Eq)]
12pub struct Secret(String);
13
14impl Secret {
15    pub fn new(value: impl Into<String>) -> Self {
16        Secret(value.into())
17    }
18    /// The only way to read the value. Call at the point of use (header write).
19    pub fn expose(&self) -> &str {
20        &self.0
21    }
22    pub fn is_empty(&self) -> bool {
23        self.0.is_empty()
24    }
25}
26
27impl fmt::Debug for Secret {
28    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29        f.write_str("Secret([redacted])")
30    }
31}
32
33impl fmt::Display for Secret {
34    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35        f.write_str("[redacted]")
36    }
37}
38
39impl From<String> for Secret {
40    fn from(v: String) -> Self {
41        Secret(v)
42    }
43}
44
45#[derive(Debug, Clone, PartialEq, Eq)]
46pub struct SecretError(pub String);
47
48impl fmt::Display for SecretError {
49    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50        write!(f, "secret store: {}", self.0)
51    }
52}
53impl std::error::Error for SecretError {}
54
55/// Service/account keyed secret storage. Implemented by `rightkit-secrets`.
56pub trait SecretStore: Send + Sync {
57    fn get(&self, service: &str, account: &str) -> Result<Option<Secret>, SecretError>;
58    fn set(&self, service: &str, account: &str, value: &Secret) -> Result<(), SecretError>;
59    fn delete(&self, service: &str, account: &str) -> Result<(), SecretError>;
60}
61
62/// In-memory store for tests and ephemeral sessions.
63#[derive(Default)]
64pub struct MemorySecretStore {
65    inner: Mutex<HashMap<(String, String), Secret>>,
66}
67
68impl MemorySecretStore {
69    pub fn new() -> Self {
70        Self::default()
71    }
72}
73
74impl SecretStore for MemorySecretStore {
75    fn get(&self, service: &str, account: &str) -> Result<Option<Secret>, SecretError> {
76        Ok(self
77            .inner
78            .lock()
79            .unwrap()
80            .get(&(service.to_string(), account.to_string()))
81            .cloned())
82    }
83    fn set(&self, service: &str, account: &str, value: &Secret) -> Result<(), SecretError> {
84        self.inner
85            .lock()
86            .unwrap()
87            .insert((service.to_string(), account.to_string()), value.clone());
88        Ok(())
89    }
90    fn delete(&self, service: &str, account: &str) -> Result<(), SecretError> {
91        self.inner
92            .lock()
93            .unwrap()
94            .remove(&(service.to_string(), account.to_string()));
95        Ok(())
96    }
97}