Skip to main content

neuron_secret_keystore/
lib.rs

1#![deny(missing_docs)]
2//! Stub secret resolver for OS keystore (macOS Keychain, Windows DPAPI, Linux Secret Service).
3//!
4//! This crate provides the correct trait impl shape for an OS keystore resolver.
5//! The actual platform keychain integration is not implemented — all resolve calls
6//! return `SecretError::BackendError`.
7//!
8//! No auth dependency — the OS keystore uses system-level authentication.
9
10use async_trait::async_trait;
11use layer0::secret::SecretSource;
12use neuron_secret::{SecretError, SecretLease, SecretResolver};
13
14/// Stub resolver for OS keystore.
15pub struct KeystoreResolver;
16
17impl KeystoreResolver {
18    /// Create a new OS keystore resolver (stub).
19    pub fn new() -> Self {
20        Self
21    }
22}
23
24impl Default for KeystoreResolver {
25    fn default() -> Self {
26        Self::new()
27    }
28}
29
30#[async_trait]
31impl SecretResolver for KeystoreResolver {
32    async fn resolve(&self, source: &SecretSource) -> Result<SecretLease, SecretError> {
33        match source {
34            SecretSource::OsKeystore { service } => Err(SecretError::BackendError(format!(
35                "KeystoreResolver is a stub — would resolve service {service:?}"
36            ))),
37            _ => Err(SecretError::NoResolver("os_keystore".into())),
38        }
39    }
40}
41
42#[cfg(test)]
43mod tests {
44    use super::*;
45    use std::sync::Arc;
46
47    fn _assert_send_sync<T: Send + Sync>() {}
48
49    #[test]
50    fn object_safety() {
51        _assert_send_sync::<Box<dyn SecretResolver>>();
52        _assert_send_sync::<Arc<dyn SecretResolver>>();
53        let _: Arc<dyn SecretResolver> = Arc::new(KeystoreResolver::new());
54    }
55
56    #[tokio::test]
57    async fn matches_keystore_source() {
58        let resolver = KeystoreResolver::new();
59        let source = SecretSource::OsKeystore {
60            service: "my-app".into(),
61        };
62        let err = resolver.resolve(&source).await.unwrap_err();
63        assert!(matches!(err, SecretError::BackendError(_)));
64        assert!(err.to_string().contains("stub"));
65    }
66
67    #[tokio::test]
68    async fn rejects_wrong_source() {
69        let resolver = KeystoreResolver::new();
70        let source = SecretSource::Vault {
71            mount: "secret".into(),
72            path: "data/key".into(),
73        };
74        let err = resolver.resolve(&source).await.unwrap_err();
75        assert!(matches!(err, SecretError::NoResolver(_)));
76    }
77}