1use anyhow::Context;
2use serde::Deserialize;
3use std::collections::HashMap;
4use std::path::PathBuf;
5
6#[derive(Debug, Clone, Deserialize)]
8#[serde(tag = "type")]
9pub enum AuthCredential {
10 #[serde(rename = "api_key")]
11 ApiKey { key: String },
12 #[serde(rename = "oauth")]
13 Oauth {
14 access: String,
15 refresh: Option<String>,
16 expires: Option<i64>,
17 #[serde(rename = "enterpriseUrl")]
18 enterprise_url: Option<String>,
19 },
20}
21
22#[derive(Debug, Clone, Default, Deserialize)]
24pub struct AuthStorage(HashMap<String, AuthCredential>);
25
26impl AuthStorage {
27 pub fn load() -> anyhow::Result<Self> {
29 Self::load_from(Self::path()?)
30 }
31
32 pub fn load_from(path: std::path::PathBuf) -> anyhow::Result<Self> {
34 if !path.exists() {
35 return Ok(Self::default());
36 }
37 let content = std::fs::read_to_string(&path)
38 .with_context(|| format!("Failed to read {}", path.display()))?;
39 serde_json::from_str(&content)
40 .with_context(|| format!("Failed to parse {}", path.display()))
41 }
42
43 fn path() -> anyhow::Result<PathBuf> {
44 let dir = directories::BaseDirs::new().context("Could not determine home directory")?;
45 Ok(dir.home_dir().join(".rab").join("agent").join("auth.json"))
46 }
47
48 pub fn api_key(&self, provider: &str) -> Option<String> {
50 self.0.get(provider).and_then(|cred| match cred {
51 AuthCredential::ApiKey { key } => Some(key.clone()),
52 AuthCredential::Oauth { .. } => None,
53 })
54 }
55
56 pub fn oauth_token(&self, provider: &str) -> Option<String> {
58 self.0.get(provider).and_then(|cred| match cred {
59 AuthCredential::Oauth { access, .. } => Some(access.clone()),
60 AuthCredential::ApiKey { .. } => None,
61 })
62 }
63}