use std::sync::OnceLock;
use keyring_core::Entry;
use evault_core::crypto::{ExposeSecret, SecretString};
use evault_core::error::SecretError;
use evault_core::model::VarId;
use evault_core::traits::SecretStore;
use crate::errors::{is_not_found, map};
pub const DEFAULT_SERVICE: &str = "evault";
pub struct OsKeyringSecretStore {
service: String,
}
impl std::fmt::Debug for OsKeyringSecretStore {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("OsKeyringSecretStore")
.finish_non_exhaustive()
}
}
impl OsKeyringSecretStore {
pub fn new() -> Result<Self, SecretError> {
Self::with_service(DEFAULT_SERVICE)
}
pub fn with_service(service: impl Into<String>) -> Result<Self, SecretError> {
let service = service.into();
if service.is_empty() {
return Err(SecretError::Backend("empty_service".into()));
}
ensure_native_backend()?;
Ok(Self { service })
}
fn entry(&self, id: VarId) -> Result<Entry, SecretError> {
Entry::new(&self.service, &id.as_uuid().to_string()).map_err(map)
}
}
fn ensure_native_backend() -> Result<(), SecretError> {
static INIT: OnceLock<Result<(), SecretError>> = OnceLock::new();
let result = INIT.get_or_init(|| keyring::use_native_store(true).map_err(map));
match result {
Ok(()) => Ok(()),
Err(SecretError::Unavailable) => Err(SecretError::Unavailable),
Err(SecretError::Backend(s)) => Err(SecretError::Backend(s.clone())),
Err(_) => Err(SecretError::Backend("keyring init".into())),
}
}
impl SecretStore for OsKeyringSecretStore {
fn put(&self, id: VarId, value: SecretString) -> Result<(), SecretError> {
let entry = self.entry(id)?;
entry.set_password(value.expose_secret()).map_err(map)
}
fn get(&self, id: VarId) -> Result<Option<SecretString>, SecretError> {
let entry = self.entry(id)?;
match entry.get_password() {
Ok(s) => Ok(Some(SecretString::from(s))),
Err(e) if is_not_found(&e) => Ok(None),
Err(e) => Err(map(e)),
}
}
fn delete(&self, id: VarId) -> Result<(), SecretError> {
let entry = self.entry(id)?;
match entry.delete_credential() {
Ok(()) => Ok(()),
Err(e) if is_not_found(&e) => Ok(()),
Err(e) => Err(map(e)),
}
}
}