Skip to main content

rab/
auth.rs

1use anyhow::Context;
2use serde::Deserialize;
3use std::collections::HashMap;
4use std::path::PathBuf;
5
6/// Credential for a provider (mirrors pi's auth.json schema).
7#[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/// Auth storage loaded from ~/.rab/auth.json.
23#[derive(Debug, Clone, Default, Deserialize)]
24pub struct AuthStorage(HashMap<String, AuthCredential>);
25
26impl AuthStorage {
27    /// Load auth from `~/.rab/agent/auth.json`. Returns empty if file doesn't exist.
28    pub fn load() -> anyhow::Result<Self> {
29        Self::load_from(Self::path()?)
30    }
31
32    /// Load auth from an explicit path (for testing).
33    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    /// Get the API key for a provider. Returns None if not configured or if OAuth.
49    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    /// Get the OAuth access token for a provider. Returns None if not configured or if API key.
57    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}