Files
owlen/crates/owlen-core/src/credentials.rs
vikingowl 690f5c7056 feat(cli): add MCP management subcommand with add/list/remove commands
Introduce `McpCommand` enum and handlers in `owlen-cli` to manage MCP server registrations, including adding, listing, and removing servers across configuration scopes. Add scoped configuration support (`ScopedMcpServer`, `McpConfigScope`) and OAuth token handling in core config, alongside runtime refresh of MCP servers. Implement toast notifications in the TUI (`render_toasts`, `Toast`, `ToastLevel`) and integrate async handling for session events. Update config loading, validation, and schema versioning to accommodate new MCP scopes and resources. Add `httpmock` as a dev dependency for testing.
2025-10-13 17:54:14 +02:00

109 lines
3.3 KiB
Rust

use std::sync::Arc;
use serde::{Deserialize, Serialize};
use crate::{Error, Result, oauth::OAuthToken, storage::StorageManager};
#[derive(Serialize, Deserialize, Debug)]
pub struct ApiCredentials {
pub api_key: String,
pub endpoint: String,
}
pub const OLLAMA_CLOUD_CREDENTIAL_ID: &str = "provider_ollama_cloud";
pub struct CredentialManager {
storage: Arc<StorageManager>,
master_key: Arc<Vec<u8>>,
namespace: String,
}
impl CredentialManager {
pub fn new(storage: Arc<StorageManager>, master_key: Arc<Vec<u8>>) -> Self {
Self {
storage,
master_key,
namespace: "owlen".to_string(),
}
}
fn namespaced_key(&self, tool_name: &str) -> String {
format!("{}_{}", self.namespace, tool_name)
}
fn oauth_storage_key(&self, resource: &str) -> String {
self.namespaced_key(&format!("oauth_{resource}"))
}
pub async fn store_credentials(
&self,
tool_name: &str,
credentials: &ApiCredentials,
) -> Result<()> {
let key = self.namespaced_key(tool_name);
let payload = serde_json::to_vec(credentials).map_err(|e| {
Error::Storage(format!(
"Failed to serialize credentials for secure storage: {e}"
))
})?;
self.storage
.store_secure_item(&key, &payload, &self.master_key)
.await
}
pub async fn get_credentials(&self, tool_name: &str) -> Result<Option<ApiCredentials>> {
let key = self.namespaced_key(tool_name);
match self
.storage
.load_secure_item(&key, &self.master_key)
.await?
{
Some(bytes) => {
let creds = serde_json::from_slice(&bytes).map_err(|e| {
Error::Storage(format!("Failed to deserialize stored credentials: {e}"))
})?;
Ok(Some(creds))
}
None => Ok(None),
}
}
pub async fn delete_credentials(&self, tool_name: &str) -> Result<()> {
let key = self.namespaced_key(tool_name);
self.storage.delete_secure_item(&key).await
}
pub async fn store_oauth_token(&self, resource: &str, token: &OAuthToken) -> Result<()> {
let key = self.oauth_storage_key(resource);
let payload = serde_json::to_vec(token).map_err(|err| {
Error::Storage(format!(
"Failed to serialize OAuth token for secure storage: {err}"
))
})?;
self.storage
.store_secure_item(&key, &payload, &self.master_key)
.await
}
pub async fn load_oauth_token(&self, resource: &str) -> Result<Option<OAuthToken>> {
let key = self.oauth_storage_key(resource);
let raw = self
.storage
.load_secure_item(&key, &self.master_key)
.await?;
if let Some(bytes) = raw {
let token = serde_json::from_slice(&bytes).map_err(|err| {
Error::Storage(format!("Failed to deserialize stored OAuth token: {err}"))
})?;
Ok(Some(token))
} else {
Ok(None)
}
}
pub async fn delete_oauth_token(&self, resource: &str) -> Result<()> {
let key = self.oauth_storage_key(resource);
self.storage.delete_secure_item(&key).await
}
}