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 mut serializer = url::form_urlencoded::Serializer::new(String::new());
68 for (k, v) in &self.url_params {
69 serializer.append_pair(k, v);
70 }
71 format!("{}?{}", url, serializer.finish())
72 }
73 }
74
75 pub fn apply_headers(
76 &self,
77 req: reqwest::RequestBuilder,
78 token: &str,
79 api_version: &str,
80 beta: &BetaConfig,
81 ) -> reqwest::RequestBuilder {
82 let mut r = req
83 .header("Authorization", format!("Bearer {}", token))
84 .header("anthropic-version", api_version)
85 .header("content-type", "application/json")
86 .header("user-agent", &self.user_agent)
87 .header("x-app", &self.app_identifier);
88
89 for (k, v) in &self.extra_headers {
90 r = r.header(k.as_str(), v.as_str());
91 }
92
93 let beta_header = self.build_beta_header(beta);
94 if !beta_header.is_empty() {
95 r = r.header("anthropic-beta", beta_header);
96 }
97
98 r
99 }
100}
101
102pub struct OAuthConfigBuilder {
103 config: OAuthConfig,
104}
105
106impl Default for OAuthConfigBuilder {
107 fn default() -> Self {
108 Self {
109 config: OAuthConfig::from_env(),
110 }
111 }
112}
113
114impl OAuthConfigBuilder {
115 pub fn user_agent(mut self, ua: impl Into<String>) -> Self {
116 self.config.user_agent = ua.into();
117 self
118 }
119
120 pub fn app_identifier(mut self, app: impl Into<String>) -> Self {
121 self.config.app_identifier = app.into();
122 self
123 }
124
125 pub fn url_param(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
126 self.config.url_params.insert(key.into(), value.into());
127 self
128 }
129
130 pub fn header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
131 self.config.extra_headers.insert(key.into(), value.into());
132 self
133 }
134
135 pub fn build(self) -> OAuthConfig {
136 self.config
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143
144 #[test]
145 fn test_default_config() {
146 let config = OAuthConfig::default();
147 assert_eq!(config.user_agent, DEFAULT_USER_AGENT);
148 assert_eq!(config.app_identifier, DEFAULT_APP_IDENTIFIER);
149 }
150
151 #[test]
152 fn test_builder() {
153 let config = OAuthConfig::builder().user_agent("my-app/1.0").build();
154
155 assert_eq!(config.user_agent, "my-app/1.0");
156 }
157
158 #[test]
159 fn test_url_params() {
160 let config = OAuthConfig::default();
161 assert_eq!(config.url_params.get("beta"), Some(&"true".to_string()));
162 }
163}