Skip to main content

claude_agent/
auth.rs

1// Layer 7: THE AUTHENTICATION GATE
2// Triple-lock: API Key -> Organization -> Validation
3// CLI mode: validates `claude` binary exists
4// API mode: requires ANTHROPIC_API_KEY
5
6use anyhow::{bail, Result};
7
8use crate::inference::InferenceBackend;
9
10#[derive(Debug, Clone)]
11pub struct AuthState {
12    pub api_key: Option<String>,
13    pub backend: InferenceBackend,
14}
15
16pub struct AuthGate {
17    state: AuthState,
18}
19
20impl Default for AuthGate {
21    fn default() -> Self {
22        Self::new()
23    }
24}
25
26impl AuthGate {
27    pub fn new() -> Self {
28        Self {
29            state: AuthState {
30                api_key: None,
31                backend: InferenceBackend::Cli,
32            },
33        }
34    }
35
36    pub fn authenticate(&mut self) -> Result<&AuthState> {
37        let api_key = std::env::var("ANTHROPIC_API_KEY").ok();
38        let force_api = std::env::var("AGENT_BACKEND").as_deref() == Ok("api");
39
40        if force_api {
41            if api_key.is_none() {
42                bail!(
43                    "API backend requested but ANTHROPIC_API_KEY not set.\n\
44                     Set it with: export ANTHROPIC_API_KEY=<your-key>\n\
45                     Or remove AGENT_BACKEND=api to use CLI mode."
46                );
47            }
48            self.state = AuthState {
49                api_key,
50                backend: InferenceBackend::Api,
51            };
52        } else {
53            // CLI mode — verify `claude` binary exists
54            let claude_exists = std::process::Command::new("which")
55                .arg("claude")
56                .output()
57                .map(|o| o.status.success())
58                .unwrap_or(false);
59
60            if !claude_exists {
61                bail!(
62                    "claude CLI not found in PATH.\n\
63                     Install Claude Code: npm install -g @anthropic-ai/claude-code\n\
64                     Or set AGENT_BACKEND=api with ANTHROPIC_API_KEY for direct API mode."
65                );
66            }
67
68            self.state = AuthState {
69                api_key,
70                backend: InferenceBackend::Cli,
71            };
72        }
73
74        Ok(&self.state)
75    }
76
77    pub fn api_key(&self) -> Option<&str> {
78        self.state.api_key.as_deref()
79    }
80
81    pub fn backend(&self) -> InferenceBackend {
82        self.state.backend
83    }
84
85}