1use super::HingeClient;
2use crate::errors::HingeError;
3use crate::models::{HingeAuthToken, LoginTokens, SendbirdAuthToken};
4use crate::storage::Storage;
5use chrono::Utc;
6use serde_json::json;
7
8impl<S: Storage + Clone> HingeClient<S> {
9 async fn ensure_device_registered(&mut self) -> Result<(), HingeError> {
10 if self.installed {
11 return Ok(());
12 }
13
14 let url = format!("{}/identity/install", self.settings.base_url);
15 let body = json!({"installId": self.install_id});
16 let res = self
17 .http_post(&url, &body)
18 .await
19 .map_err(|e| HingeError::Http(format!("Failed to register device: {}", e)))?;
20
21 if !res.status().is_success() {
22 return Err(HingeError::Http(format!(
23 "Device registration failed with status {}",
24 res.status()
25 )));
26 }
27 self.installed = true;
28 Ok(())
29 }
30
31 pub async fn initiate_login(&mut self) -> Result<(), HingeError> {
32 self.ensure_device_registered().await?;
33
34 let url = format!("{}/auth/sms/v2/initiate", self.settings.base_url);
35 let body = json!({"deviceId": self.device_id, "phoneNumber": self.phone_number});
36 let res = self
37 .http_post(&url, &body)
38 .await
39 .map_err(|e| HingeError::Http(format!("Failed to initiate SMS login: {}", e)))?;
40
41 if !res.status().is_success() {
42 return Err(HingeError::Http(format!(
43 "SMS initiation failed with status {}",
44 res.status()
45 )));
46 }
47 Ok(())
48 }
49
50 pub async fn submit_otp(&mut self, otp: &str) -> Result<LoginTokens, HingeError> {
51 let url = format!("{}/auth/sms/v2", self.settings.base_url);
52 let body = json!({
53 "installId": self.install_id,
54 "deviceId": self.device_id,
55 "phoneNumber": self.phone_number,
56 "otp": otp,
57 });
58 let res = self.http_post(&url, &body).await?;
59
60 if res.status() == reqwest::StatusCode::PRECONDITION_FAILED {
61 let v: serde_json::Value = res.json().await?;
62 let case_id = v
63 .get("caseId")
64 .and_then(|v| v.as_str())
65 .unwrap_or("")
66 .to_string();
67 let email = v
68 .get("email")
69 .and_then(|v| v.as_str())
70 .unwrap_or("")
71 .to_string();
72 return Err(HingeError::Email2FA { case_id, email });
73 }
74 if !res.status().is_success() {
75 return Err(HingeError::Http(format!("status {}", res.status())));
76 }
77
78 let v = self.parse_response::<LoginTokens>(res).await?;
79 if let Some(t) = v.hinge_auth_token.clone() {
80 self.hinge_auth = Some(t);
81 }
82 if let Some(t) = v.sendbird_auth_token.clone() {
83 self.sendbird_auth = Some(t);
84 }
85 Ok(v)
86 }
87
88 pub async fn submit_email_code(
89 &mut self,
90 case_id: &str,
91 email_code: &str,
92 ) -> Result<LoginTokens, HingeError> {
93 let url = format!("{}/auth/device/validate", self.settings.base_url);
94 let body = json!({
95 "installId": self.install_id,
96 "code": email_code,
97 "caseId": case_id,
98 "deviceId": self.device_id,
99 });
100 let res = self.http_post(&url, &body).await?;
101 if !res.status().is_success() {
102 return Err(HingeError::Http(format!("status {}", res.status())));
103 }
104
105 let t = self.parse_response::<HingeAuthToken>(res).await?;
106 self.hinge_auth = Some(t);
107 let _ = self.authenticate_with_sendbird().await;
108 Ok(LoginTokens {
109 hinge_auth_token: self.hinge_auth.clone(),
110 sendbird_auth_token: self.sendbird_auth.clone(),
111 })
112 }
113
114 pub(super) async fn authenticate_with_sendbird(&mut self) -> Result<(), HingeError> {
115 let _hinge = self
116 .hinge_auth
117 .as_ref()
118 .ok_or_else(|| HingeError::Auth("hinge token missing".into()))?;
119 let url = format!("{}/message/authenticate", self.settings.base_url);
120 let res = self
121 .http
122 .post(url)
123 .headers(self.default_headers()?)
124 .json(&json!({"refresh": false}))
125 .send()
126 .await?;
127
128 if !res.status().is_success() {
129 return Err(HingeError::Http(format!("status {}", res.status())));
130 }
131 let v = self.parse_response::<SendbirdAuthToken>(res).await?;
132 self.sendbird_auth = Some(v);
133
134 if self.auto_persist
135 && let Some(path) = &self.session_path
136 {
137 let _ = self.save_session(path);
138 }
139 Ok(())
140 }
141
142 pub async fn is_session_valid(&mut self) -> Result<bool, HingeError> {
143 if self.hinge_auth.is_none() {
144 log::warn!("Hinge token is empty, session is invalid.");
145 return Ok(false);
146 }
147
148 if self.sendbird_auth.is_none() {
149 log::warn!("Sendbird JWT is empty, reauthenticating...");
150 if let Err(e) = self.authenticate_with_sendbird().await {
151 log::error!("Failed to reauthenticate with Sendbird: {}", e);
152 return Ok(false);
153 }
154 }
155
156 let now = Utc::now();
157 let hinge_token_valid = self
158 .hinge_auth
159 .as_ref()
160 .is_some_and(|hinge_auth| hinge_auth.expires > now);
161 let sendbird_needs_refresh = self
162 .sendbird_auth
163 .as_ref()
164 .is_none_or(|sb_auth| sb_auth.expires <= now);
165
166 if sendbird_needs_refresh {
167 log::warn!("Sendbird JWT has expired or is missing, reauthenticating...");
168 if let Err(e) = self.authenticate_with_sendbird().await {
169 log::error!("Failed to reauthenticate with Sendbird: {}", e);
170 return Ok(false);
171 }
172 }
173
174 let sendbird_token_valid = self
175 .sendbird_auth
176 .as_ref()
177 .is_some_and(|sb_auth| sb_auth.expires > now);
178 let is_valid = hinge_token_valid && sendbird_token_valid;
179 log::info!(
180 "Session validity check: is_valid={}, hinge_token_valid={}, sendbird_token_valid={}",
181 is_valid,
182 hinge_token_valid,
183 sendbird_token_valid
184 );
185
186 Ok(is_valid)
187 }
188}