brainwires_network/auth/
client.rs1use anyhow::{Context, Result, anyhow};
7use regex::Regex;
8use reqwest::Client;
9
10use super::types::{AuthRequest, AuthResponse};
11
12pub struct AuthClient {
17 http_client: Client,
18 backend_url: String,
20 auth_endpoint: String,
22 api_key_pattern: Regex,
24}
25
26impl AuthClient {
27 pub fn new(backend_url: String, auth_endpoint: String, api_key_pattern: &str) -> Self {
34 Self {
35 http_client: Client::new(),
36 backend_url,
37 auth_endpoint,
38 api_key_pattern: Regex::new(api_key_pattern).expect("Invalid API key regex pattern"),
39 }
40 }
41
42 pub fn from_endpoints(endpoints: &dyn crate::traits::AuthEndpoints) -> Self {
44 Self::new(
45 endpoints.backend_url().to_string(),
46 endpoints.auth_endpoint(),
47 endpoints.api_key_pattern(),
48 )
49 }
50
51 pub fn validate_api_key_format(&self, api_key: &str) -> Result<()> {
53 if !self.api_key_pattern.is_match(api_key) {
54 return Err(anyhow!(
55 "Invalid API key format. Expected: bw_[env]_[32chars]"
56 ));
57 }
58 Ok(())
59 }
60
61 pub async fn authenticate(&self, api_key: &str) -> Result<AuthResponse> {
66 self.validate_api_key_format(api_key)?;
68
69 let url = format!("{}{}", self.backend_url, self.auth_endpoint);
71 let request = AuthRequest {
72 api_key: api_key.to_string(),
73 };
74
75 let response = self
76 .http_client
77 .post(&url)
78 .json(&request)
79 .send()
80 .await
81 .context("Failed to send authentication request")?;
82
83 if !response.status().is_success() {
84 let status = response.status();
85 let error_text = response
86 .text()
87 .await
88 .unwrap_or_else(|_| "Unknown error".to_string());
89
90 return Err(anyhow!(
91 "Authentication failed (status {}): {}",
92 status,
93 error_text
94 ));
95 }
96
97 let auth_response: AuthResponse = response
98 .json()
99 .await
100 .context("Failed to parse authentication response")?;
101
102 Ok(auth_response)
103 }
104
105 pub fn backend_url(&self) -> &str {
107 &self.backend_url
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114
115 fn make_client() -> AuthClient {
116 AuthClient::new(
117 "https://test.example.com".to_string(),
118 "/api/cli/auth".to_string(),
119 r"^bw_(prod|dev|test)_[a-z0-9]{32}$",
120 )
121 }
122
123 #[test]
124 fn test_validate_api_key_format() {
125 let client = make_client();
126
127 assert!(
129 client
130 .validate_api_key_format("bw_dev_12345678901234567890123456789012")
131 .is_ok()
132 );
133 assert!(
134 client
135 .validate_api_key_format("bw_prod_abcdefghijklmnopqrstuvwxyz123456")
136 .is_ok()
137 );
138 assert!(
139 client
140 .validate_api_key_format("bw_test_00000000000000000000000000000000")
141 .is_ok()
142 );
143
144 assert!(client.validate_api_key_format("invalid").is_err());
146 assert!(
147 client
148 .validate_api_key_format("bw_invalid_12345678901234567890123456789012")
149 .is_err()
150 );
151 assert!(client.validate_api_key_format("bw_dev_short").is_err());
152 assert!(
153 client
154 .validate_api_key_format("bw_dev_UPPERCASE0000000000000000000000")
155 .is_err()
156 );
157 }
158
159 #[test]
160 fn test_auth_client_new() {
161 let client = make_client();
162 assert_eq!(client.backend_url(), "https://test.example.com");
163 }
164
165 #[test]
166 fn test_validate_api_key_error_message() {
167 let client = make_client();
168 let result = client.validate_api_key_format("invalid_key");
169 assert!(result.is_err());
170 let error = result.unwrap_err();
171 assert!(error.to_string().contains("Invalid API key format"));
172 }
173
174 #[test]
175 fn test_validate_api_key_edge_cases() {
176 let client = make_client();
177
178 assert!(client.validate_api_key_format("").is_err());
179 assert!(client.validate_api_key_format(" ").is_err());
180 assert!(client.validate_api_key_format("bw_dev_123").is_err()); assert!(
182 client
183 .validate_api_key_format("bw_dev_123456789012345678901234567890123")
184 .is_err()
185 ); assert!(
187 client
188 .validate_api_key_format("dev_12345678901234567890123456789012")
189 .is_err()
190 ); assert!(
192 client
193 .validate_api_key_format("bw__12345678901234567890123456789012")
194 .is_err()
195 ); assert!(
197 client
198 .validate_api_key_format("bw_Dev_12345678901234567890123456789012")
199 .is_err()
200 ); }
202}