use foundation_core::valtron::Stream;
#[cfg(feature = "turso")]
use foundation_db::StorageBackend;
use foundation_db::{core::AsyncKeyValueStore, KeyValueStore, StorageError, StorageProvider};
use serde::{Deserialize, Serialize};
use crate::shared::oauth_token::OAuthToken;
pub trait CredentialStore: Send + Sync {
fn get<V: for<'de> Deserialize<'de> + Send + 'static>(
&self,
key: &str,
) -> Result<Option<V>, CredentialStoreError>;
fn set<V: Serialize + Send + 'static>(
&self,
key: &str,
value: V,
) -> Result<(), CredentialStoreError>;
fn delete(&self, key: &str) -> Result<(), CredentialStoreError>;
fn exists(&self, key: &str) -> Result<bool, CredentialStoreError>;
fn list_keys(&self, prefix: Option<&str>) -> Result<Vec<String>, CredentialStoreError>;
}
#[async_trait::async_trait(?Send)]
pub trait AsyncCredentialStore: Sync {
async fn get_async<V: for<'de> Deserialize<'de> + Send + 'static>(
&self,
key: &str,
) -> Result<Option<V>, CredentialStoreError>;
async fn set_async<V: Serialize + Send + 'static>(
&self,
key: &str,
value: V,
) -> Result<(), CredentialStoreError>;
async fn delete_async(&self, key: &str) -> Result<(), CredentialStoreError>;
async fn exists_async(&self, key: &str) -> Result<bool, CredentialStoreError>;
async fn list_keys_async(&self, prefix: Option<&str>) -> Result<Vec<String>, CredentialStoreError>;
}
#[derive(derive_more::From, Debug)]
pub enum CredentialStoreError {
Storage(StorageError),
#[from(ignore)]
Serialization(String),
#[from(ignore)]
NotFound(String),
#[from(ignore)]
Generic(String),
}
impl core::fmt::Display for CredentialStoreError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
CredentialStoreError::Storage(e) => write!(f, "Storage error: {e}"),
CredentialStoreError::Serialization(s) => write!(f, "Serialization error: {s}"),
CredentialStoreError::NotFound(s) => write!(f, "Credential not found: {s}"),
CredentialStoreError::Generic(s) => write!(f, "Credential store error: {s}"),
}
}
}
impl std::error::Error for CredentialStoreError {}
pub struct CredentialStorage {
storage: StorageProvider,
}
impl CredentialStorage {
#[must_use]
pub fn new(storage: StorageProvider) -> Self {
Self { storage }
}
#[cfg(feature = "turso")]
pub fn turso(url: &str) -> Result<Self, CredentialStoreError> {
let storage = StorageProvider::new(StorageBackend::Turso {
url: url.to_string(),
})?;
Ok(Self { storage })
}
#[must_use]
pub fn memory() -> Self {
Self {
storage: StorageProvider::memory(),
}
}
#[must_use]
pub fn provider(&self) -> &StorageProvider {
&self.storage
}
}
impl CredentialStore for CredentialStorage {
fn get<V: for<'de> Deserialize<'de> + Send + 'static>(
&self,
key: &str,
) -> Result<Option<V>, CredentialStoreError> {
self.storage.get(key).map_err(|e| match e {
StorageError::NotFound(_) => CredentialStoreError::NotFound(key.to_string()),
other => CredentialStoreError::Storage(other),
})
}
fn set<V: Serialize + Send + 'static>(
&self,
key: &str,
value: V,
) -> Result<(), CredentialStoreError> {
self.storage
.set(key, value)
.map_err(CredentialStoreError::Storage)
}
fn delete(&self, key: &str) -> Result<(), CredentialStoreError> {
self.storage
.delete(key)
.map_err(CredentialStoreError::Storage)
}
fn exists(&self, key: &str) -> Result<bool, CredentialStoreError> {
self.storage
.exists(key)
.map_err(CredentialStoreError::Storage)
}
fn list_keys(&self, prefix: Option<&str>) -> Result<Vec<String>, CredentialStoreError> {
let stream = self
.storage
.list_keys(prefix)
.map_err(CredentialStoreError::Storage)?;
stream
.flat_map(|stream_item| match stream_item {
Stream::Next(Ok(result)) => vec![Ok(result)],
Stream::Next(Err(e)) => vec![Err(CredentialStoreError::Storage(e))],
_ => vec![],
})
.collect::<Result<Vec<_>, _>>()
}
}
#[async_trait::async_trait(?Send)]
impl AsyncCredentialStore for CredentialStorage {
async fn get_async<V: for<'de> Deserialize<'de> + Send + 'static>(
&self,
key: &str,
) -> Result<Option<V>, CredentialStoreError> {
self.storage.get_async(key)
.await
.map_err(|e| match e {
StorageError::NotFound(_) => CredentialStoreError::NotFound(key.to_string()),
other => CredentialStoreError::Storage(other),
})
}
async fn set_async<V: Serialize + Send + 'static>(
&self,
key: &str,
value: V,
) -> Result<(), CredentialStoreError> {
self.storage.set_async(key, value)
.await
.map_err(CredentialStoreError::Storage)
}
async fn delete_async(&self, key: &str) -> Result<(), CredentialStoreError> {
self.storage.delete_async(key)
.await
.map_err(CredentialStoreError::Storage)
}
async fn exists_async(&self, key: &str) -> Result<bool, CredentialStoreError> {
self.storage.exists_async(key)
.await
.map_err(CredentialStoreError::Storage)
}
async fn list_keys_async(&self, prefix: Option<&str>) -> Result<Vec<String>, CredentialStoreError> {
self.storage.list_keys_async(prefix)
.await
.map_err(CredentialStoreError::Storage)?
.collect_all()
.await
.map_err(CredentialStoreError::Storage)
}
}
pub trait OAuthTokenStore: CredentialStore {
fn store_oauth_token(
&self,
provider: &str,
token: &OAuthToken,
) -> Result<(), CredentialStoreError> {
let key = format!("oauth:token:{provider}");
self.set(&key, token.clone())
}
fn get_oauth_token(&self, provider: &str) -> Result<Option<OAuthToken>, CredentialStoreError> {
let key = format!("oauth:token:{provider}");
self.get(&key)
}
fn delete_oauth_token(&self, provider: &str) -> Result<(), CredentialStoreError> {
let key = format!("oauth:token:{provider}");
self.delete(&key)
}
fn store_oauth_state(
&self,
state: &str,
code_verifier: &str,
expires_at: i64,
) -> Result<(), CredentialStoreError> {
let key = format!("oauth:state:{state}");
let value = OAuthState {
code_verifier: code_verifier.to_string(),
expires_at,
};
self.set(&key, value)
}
fn get_oauth_state(&self, state: &str) -> Result<Option<OAuthState>, CredentialStoreError> {
let key = format!("oauth:state:{state}");
self.get(&key)
}
fn delete_oauth_state(&self, state: &str) -> Result<(), CredentialStoreError> {
let key = format!("oauth:state:{state}");
self.delete(&key)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OAuthState {
pub code_verifier: String,
pub expires_at: i64,
}
impl OAuthState {
#[must_use]
pub fn is_expired(&self) -> bool {
let now = chrono::Utc::now().timestamp();
now >= self.expires_at
}
}
impl<T: CredentialStore> OAuthTokenStore for T {}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StoredCredential<T> {
pub data: T,
pub created_at: i64,
pub last_accessed_at: Option<i64>,
pub metadata: Option<serde_json::Value>,
}
impl<T> StoredCredential<T> {
#[must_use]
pub fn new(data: T) -> Self {
let now = chrono::Utc::now().timestamp();
Self {
data,
created_at: now,
last_accessed_at: None,
metadata: None,
}
}
pub fn mark_accessed(&mut self) {
self.last_accessed_at = Some(chrono::Utc::now().timestamp());
}
}