Skip to main content

rightkit_http/
secret.rs

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