use crate::db::tenants::TenantDb;
use crate::models::TenantContext;
#[derive(Debug, thiserror::Error)]
pub enum McpAuthError {
#[error("No API key provided. Set ARES_API_KEY environment variable.")]
NoApiKey,
#[error("Invalid API key: {0}")]
InvalidKey(String),
#[error("Database error during auth: {0}")]
DbError(#[from] crate::types::AppError),
}
pub fn extract_api_key_from_env() -> Result<String, McpAuthError> {
std::env::var("ARES_API_KEY").map_err(|_| McpAuthError::NoApiKey)
}
pub async fn validate_mcp_api_key(
tenant_db: &TenantDb,
api_key: &str,
) -> Result<TenantContext, McpAuthError> {
if !api_key.starts_with("ares_") {
return Err(McpAuthError::InvalidKey(
"API key must start with 'ares_' prefix".to_string(),
));
}
let tenant = tenant_db
.verify_api_key(api_key)
.await
.map_err(|e| McpAuthError::InvalidKey(e.to_string()))?
.ok_or_else(|| McpAuthError::InvalidKey("API key not found or inactive".to_string()))?;
tracing::info!(
tenant_id = %tenant.tenant_id,
tier = %tenant.tier.as_str(),
"MCP connection authenticated"
);
Ok(tenant)
}
#[derive(Debug, Clone)]
pub struct McpSession {
pub tenant: TenantContext,
pub api_key: String,
pub eruka_workspace_id: String,
}
impl McpSession {
pub fn new(tenant: TenantContext, api_key: String) -> Self {
let eruka_workspace_id = tenant.tenant_id.clone();
Self {
tenant,
api_key,
eruka_workspace_id,
}
}
pub fn tenant_id(&self) -> &str {
&self.tenant.tenant_id
}
pub fn tier(&self) -> &str {
self.tenant.tier.as_str()
}
}