1use reqwest::header::{HeaderValue, COOKIE};
2
3use crate::client::EeroClient;
4use crate::error::{Error, Result};
5use crate::types::auth::{LoginRequest, LoginResponse, VerifyRequest};
6use crate::types::envelope::ApiResponse;
7
8fn extract_session_cookie(headers: &reqwest::header::HeaderMap) -> Option<String> {
13 headers
14 .get_all(reqwest::header::SET_COOKIE)
15 .iter()
16 .filter_map(|val| val.to_str().ok())
17 .find_map(|cookie_str| {
18 let pair = cookie_str.split(';').next()?;
19 let (name, value) = pair.split_once('=')?;
20 if name.trim() == "s" {
21 Some(value.trim().to_string())
22 } else {
23 None
24 }
25 })
26}
27
28impl EeroClient {
29 #[tracing::instrument(skip(self, login))]
34 pub async fn login(&self, login: &str) -> Result<LoginResponse> {
35 let url = self.url("/login");
36 let body = LoginRequest {
37 login: login.to_string(),
38 };
39
40 let body_json = serde_json::to_string(&body)?;
41 tracing::debug!(request_body = %body_json, "login request");
42 let resp = self
43 .http_client()
44 .post(&url)
45 .header("content-type", "application/json")
46 .body(body_json)
47 .send()
48 .await?;
49 tracing::debug!(status = %resp.status(), response_headers = ?resp.headers(), "login response");
50 let text = resp.text().await?;
51 tracing::debug!(body = %text, "login response body");
52 let api_resp: ApiResponse<LoginResponse> = serde_json::from_str(&text)?;
53
54 let data = Self::unwrap_response(api_resp)?;
55 self.credentials()
56 .set_user_token(&data.user_token)
57 .await?;
58 Ok(data)
59 }
60
61 #[tracing::instrument(skip(self, code))]
66 pub async fn verify(&self, code: &str) -> Result<()> {
67 let user_token = self
68 .credentials()
69 .get_user_token()
70 .await?
71 .ok_or(Error::NotAuthenticated)?;
72
73 let url = self.url("/login/verify");
74 let body = VerifyRequest {
75 code: code.to_string(),
76 };
77
78 let cookie = HeaderValue::from_str(&format!("s={user_token}"))
79 .map_err(|e| Error::CredentialStore(e.to_string()))?;
80 let body_json = serde_json::to_string(&body)?;
81 tracing::debug!(request_headers = ?cookie, request_body = %body_json, "verify request");
82 let resp = self
83 .http_client()
84 .post(&url)
85 .header(COOKIE, cookie)
86 .header("content-type", "application/json")
87 .body(body_json)
88 .send()
89 .await?;
90
91 tracing::debug!(status = %resp.status(), response_headers = ?resp.headers(), "verify response");
95 let session_token = extract_session_cookie(resp.headers());
96
97 let text = resp.text().await?;
98 tracing::debug!(body = %text, "verify response body");
99 let api_resp: ApiResponse<serde_json::Value> = serde_json::from_str(&text)?;
100 let code = api_resp.meta.code;
101 if !(200..300).contains(&code) {
102 return Err(Error::Api {
103 code,
104 message: api_resp
105 .meta
106 .error
107 .unwrap_or_else(|| "verification failed".into()),
108 });
109 }
110
111 if let Some(token) = session_token {
114 self.credentials().set_session_token(&token).await?;
115 } else {
116 let user_token = self.credentials().get_user_token().await?;
117 if let Some(token) = user_token {
118 self.credentials().set_session_token(&token).await?;
119 }
120 }
121
122 self.credentials().delete_user_token().await?;
124
125 Ok(())
126 }
127
128 #[tracing::instrument(skip(self))]
130 pub async fn logout(&self) -> Result<()> {
131 let url = self.url("/logout");
132 let _ = self.post_empty::<serde_json::Value>(&url).await;
134
135 self.credentials().delete_session_token().await?;
136 self.credentials().delete_user_token().await?;
137 Ok(())
138 }
139}