claude_agent/auth/
config.rs1use std::collections::HashMap;
4
5use crate::client::BetaConfig;
6
7pub const DEFAULT_USER_AGENT: &str = "claude-cli/2.0.76 (external, cli)";
8pub const DEFAULT_APP_IDENTIFIER: &str = "cli";
9pub const CLAUDE_CODE_BETA: &str = "claude-code-20250219";
10
11#[derive(Debug, Clone)]
12pub struct OAuthConfig {
13 pub user_agent: String,
14 pub app_identifier: String,
15 pub url_params: HashMap<String, String>,
16 pub extra_headers: HashMap<String, String>,
17}
18
19impl Default for OAuthConfig {
20 fn default() -> Self {
21 Self {
22 user_agent: DEFAULT_USER_AGENT.to_string(),
23 app_identifier: DEFAULT_APP_IDENTIFIER.to_string(),
24 url_params: [("beta".to_string(), "true".to_string())]
25 .into_iter()
26 .collect(),
27 extra_headers: [(
28 "anthropic-dangerous-direct-browser-access".to_string(),
29 "true".to_string(),
30 )]
31 .into_iter()
32 .collect(),
33 }
34 }
35}
36
37impl OAuthConfig {
38 pub fn from_env() -> Self {
39 let mut config = Self::default();
40
41 if let Ok(ua) = std::env::var("CLAUDE_AGENT_USER_AGENT") {
42 config.user_agent = ua;
43 }
44 if let Ok(app) = std::env::var("CLAUDE_AGENT_APP_IDENTIFIER") {
45 config.app_identifier = app;
46 }
47
48 config
49 }
50
51 pub fn builder() -> OAuthConfigBuilder {
52 OAuthConfigBuilder::default()
53 }
54
55 pub fn build_beta_header(&self, base: &BetaConfig) -> String {
56 let mut beta = base.clone();
57 beta.add(crate::client::BetaFeature::OAuth);
58 beta.add_custom(CLAUDE_CODE_BETA);
59 beta.header_value().unwrap_or_default()
60 }
61
62 pub fn build_url(&self, base_url: &str, endpoint: &str) -> String {
63 let url = format!("{}{}", base_url, endpoint);
64 if self.url_params.is_empty() {
65 url
66 } else {
67 let params: Vec<String> = self
68 .url_params
69 .iter()
70 .map(|(k, v)| format!("{}={}", k, v))
71 .collect();
72 format!("{}?{}", url, params.join("&"))
73 }
74 }
75
76 pub fn apply_headers(
77 &self,
78 req: reqwest::RequestBuilder,
79 token: &str,
80 api_version: &str,
81 beta: &BetaConfig,
82 ) -> reqwest::RequestBuilder {
83 let mut r = req
84 .header("Authorization", format!("Bearer {}", token))
85 .header("anthropic-version", api_version)
86 .header("content-type", "application/json")
87 .header("user-agent", &self.user_agent)
88 .header("x-app", &self.app_identifier);
89
90 for (k, v) in &self.extra_headers {
91 r = r.header(k.as_str(), v.as_str());
92 }
93
94 let beta_header = self.build_beta_header(beta);
95 if !beta_header.is_empty() {
96 r = r.header("anthropic-beta", beta_header);
97 }
98
99 r
100 }
101}
102
103pub struct OAuthConfigBuilder {
104 config: OAuthConfig,
105}
106
107impl Default for OAuthConfigBuilder {
108 fn default() -> Self {
109 Self {
110 config: OAuthConfig::from_env(),
111 }
112 }
113}
114
115impl OAuthConfigBuilder {
116 pub fn user_agent(mut self, ua: impl Into<String>) -> Self {
117 self.config.user_agent = ua.into();
118 self
119 }
120
121 pub fn app_identifier(mut self, app: impl Into<String>) -> Self {
122 self.config.app_identifier = app.into();
123 self
124 }
125
126 pub fn url_param(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
127 self.config.url_params.insert(key.into(), value.into());
128 self
129 }
130
131 pub fn header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
132 self.config.extra_headers.insert(key.into(), value.into());
133 self
134 }
135
136 pub fn build(self) -> OAuthConfig {
137 self.config
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[test]
146 fn test_default_config() {
147 let config = OAuthConfig::default();
148 assert_eq!(config.user_agent, DEFAULT_USER_AGENT);
149 assert_eq!(config.app_identifier, DEFAULT_APP_IDENTIFIER);
150 }
151
152 #[test]
153 fn test_builder() {
154 let config = OAuthConfig::builder().user_agent("my-app/1.0").build();
155
156 assert_eq!(config.user_agent, "my-app/1.0");
157 }
158
159 #[test]
160 fn test_url_params() {
161 let config = OAuthConfig::default();
162 assert_eq!(config.url_params.get("beta"), Some(&"true".to_string()));
163 }
164}