1use crate::{
4 error::{Error, Result},
5 types::{SupabaseConfig, Timestamp},
6};
7use chrono::Utc;
8use reqwest::Client as HttpClient;
9use serde::{Deserialize, Serialize};
10use std::sync::{Arc, RwLock};
11use tracing::{debug, info, warn};
12use uuid::Uuid;
13
14#[derive(Debug, Clone)]
16pub struct Auth {
17 http_client: Arc<HttpClient>,
18 config: Arc<SupabaseConfig>,
19 session: Arc<RwLock<Option<Session>>>,
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct User {
25 pub id: Uuid,
26 pub email: Option<String>,
27 pub phone: Option<String>,
28 pub email_confirmed_at: Option<Timestamp>,
29 pub phone_confirmed_at: Option<Timestamp>,
30 pub created_at: Timestamp,
31 pub updated_at: Timestamp,
32 pub last_sign_in_at: Option<Timestamp>,
33 pub app_metadata: serde_json::Value,
34 pub user_metadata: serde_json::Value,
35 pub aud: String,
36 pub role: Option<String>,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct Session {
42 pub access_token: String,
43 pub refresh_token: String,
44 pub expires_in: i64,
45 pub expires_at: Timestamp,
46 pub token_type: String,
47 pub user: User,
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct AuthResponse {
53 pub user: Option<User>,
54 pub session: Option<Session>,
55}
56
57#[derive(Debug, Serialize)]
59struct SignUpRequest {
60 email: String,
61 password: String,
62 #[serde(skip_serializing_if = "Option::is_none")]
63 data: Option<serde_json::Value>,
64 #[serde(skip_serializing_if = "Option::is_none")]
65 redirect_to: Option<String>,
66}
67
68#[derive(Debug, Serialize)]
70struct SignInRequest {
71 email: String,
72 password: String,
73}
74
75#[derive(Debug, Serialize)]
77struct PasswordResetRequest {
78 email: String,
79 #[serde(skip_serializing_if = "Option::is_none")]
80 redirect_to: Option<String>,
81}
82
83#[derive(Debug, Serialize)]
85struct RefreshTokenRequest {
86 refresh_token: String,
87}
88
89#[derive(Debug, Serialize)]
91struct UpdateUserRequest {
92 #[serde(skip_serializing_if = "Option::is_none")]
93 email: Option<String>,
94 #[serde(skip_serializing_if = "Option::is_none")]
95 password: Option<String>,
96 #[serde(skip_serializing_if = "Option::is_none")]
97 data: Option<serde_json::Value>,
98}
99
100impl Auth {
101 pub fn new(config: Arc<SupabaseConfig>, http_client: Arc<HttpClient>) -> Result<Self> {
103 debug!("Initializing Auth module");
104
105 Ok(Self {
106 http_client,
107 config,
108 session: Arc::new(RwLock::new(None)),
109 })
110 }
111
112 pub async fn sign_up_with_email_and_password(
114 &self,
115 email: &str,
116 password: &str,
117 ) -> Result<AuthResponse> {
118 self.sign_up_with_email_password_and_data(email, password, None, None)
119 .await
120 }
121
122 pub async fn sign_up_with_email_password_and_data(
124 &self,
125 email: &str,
126 password: &str,
127 data: Option<serde_json::Value>,
128 redirect_to: Option<String>,
129 ) -> Result<AuthResponse> {
130 debug!("Signing up user with email: {}", email);
131
132 let payload = SignUpRequest {
133 email: email.to_string(),
134 password: password.to_string(),
135 data,
136 redirect_to,
137 };
138
139 let response = self
140 .http_client
141 .post(format!("{}/auth/v1/signup", self.config.url))
142 .json(&payload)
143 .send()
144 .await?;
145
146 if !response.status().is_success() {
147 let status = response.status();
148 let error_msg = match response.text().await {
149 Ok(text) => text,
150 Err(_) => format!("Sign up failed with status: {}", status),
151 };
152 return Err(Error::auth(error_msg));
153 }
154
155 let auth_response: AuthResponse = response.json().await?;
156
157 if let Some(ref session) = auth_response.session {
158 self.set_session(session.clone()).await?;
159 info!("User signed up successfully");
160 }
161
162 Ok(auth_response)
163 }
164
165 pub async fn sign_in_with_email_and_password(
167 &self,
168 email: &str,
169 password: &str,
170 ) -> Result<AuthResponse> {
171 debug!("Signing in user with email: {}", email);
172
173 let payload = SignInRequest {
174 email: email.to_string(),
175 password: password.to_string(),
176 };
177
178 let response = self
179 .http_client
180 .post(format!("{}/auth/v1/token", self.config.url))
181 .header("grant_type", "password")
182 .json(&payload)
183 .send()
184 .await?;
185
186 if !response.status().is_success() {
187 let status = response.status();
188 let error_msg = match response.text().await {
189 Ok(text) => text,
190 Err(_) => format!("Sign in failed with status: {}", status),
191 };
192 return Err(Error::auth(error_msg));
193 }
194
195 let auth_response: AuthResponse = response.json().await?;
196
197 if let Some(ref session) = auth_response.session {
198 self.set_session(session.clone()).await?;
199 info!("User signed in successfully");
200 }
201
202 Ok(auth_response)
203 }
204
205 pub async fn sign_out(&self) -> Result<()> {
207 debug!("Signing out user");
208
209 let session = self.get_session()?;
210
211 let response = self
212 .http_client
213 .post(format!("{}/auth/v1/logout", self.config.url))
214 .header("Authorization", format!("Bearer {}", session.access_token))
215 .send()
216 .await?;
217
218 if !response.status().is_success() {
219 warn!("Sign out request failed with status: {}", response.status());
220 }
221
222 self.clear_session().await?;
223 info!("User signed out successfully");
224
225 Ok(())
226 }
227
228 pub async fn reset_password_for_email(&self, email: &str) -> Result<()> {
230 self.reset_password_for_email_with_redirect(email, None)
231 .await
232 }
233
234 pub async fn reset_password_for_email_with_redirect(
236 &self,
237 email: &str,
238 redirect_to: Option<String>,
239 ) -> Result<()> {
240 debug!("Requesting password reset for email: {}", email);
241
242 let payload = PasswordResetRequest {
243 email: email.to_string(),
244 redirect_to,
245 };
246
247 let response = self
248 .http_client
249 .post(format!("{}/auth/v1/recover", self.config.url))
250 .json(&payload)
251 .send()
252 .await?;
253
254 if !response.status().is_success() {
255 let status = response.status();
256 let error_msg = match response.text().await {
257 Ok(text) => text,
258 Err(_) => format!("Password reset failed with status: {}", status),
259 };
260 return Err(Error::auth(error_msg));
261 }
262
263 info!("Password reset email sent successfully");
264 Ok(())
265 }
266
267 pub async fn update_user(
269 &self,
270 email: Option<String>,
271 password: Option<String>,
272 data: Option<serde_json::Value>,
273 ) -> Result<AuthResponse> {
274 debug!("Updating user information");
275
276 let session = self.get_session()?;
277
278 let payload = UpdateUserRequest {
279 email,
280 password,
281 data,
282 };
283
284 let response = self
285 .http_client
286 .put(format!("{}/auth/v1/user", self.config.url))
287 .header("Authorization", format!("Bearer {}", session.access_token))
288 .json(&payload)
289 .send()
290 .await?;
291
292 if !response.status().is_success() {
293 let status = response.status();
294 let error_msg = match response.text().await {
295 Ok(text) => text,
296 Err(_) => format!("User update failed with status: {}", status),
297 };
298 return Err(Error::auth(error_msg));
299 }
300
301 let auth_response: AuthResponse = response.json().await?;
302
303 if let Some(ref session) = auth_response.session {
304 self.set_session(session.clone()).await?;
305 }
306
307 info!("User updated successfully");
308 Ok(auth_response)
309 }
310
311 pub async fn refresh_session(&self) -> Result<AuthResponse> {
313 debug!("Refreshing session token");
314
315 let current_session = self.get_session()?;
316
317 let payload = RefreshTokenRequest {
318 refresh_token: current_session.refresh_token.clone(),
319 };
320
321 let response = self
322 .http_client
323 .post(format!("{}/auth/v1/token", self.config.url))
324 .header("grant_type", "refresh_token")
325 .json(&payload)
326 .send()
327 .await?;
328
329 if !response.status().is_success() {
330 let status = response.status();
331 let error_msg = match response.text().await {
332 Ok(text) => text,
333 Err(_) => format!("Token refresh failed with status: {}", status),
334 };
335 return Err(Error::auth(error_msg));
336 }
337
338 let auth_response: AuthResponse = response.json().await?;
339
340 if let Some(ref session) = auth_response.session {
341 self.set_session(session.clone()).await?;
342 info!("Session refreshed successfully");
343 }
344
345 Ok(auth_response)
346 }
347
348 pub async fn current_user(&self) -> Result<Option<User>> {
350 let session_guard = self
351 .session
352 .read()
353 .map_err(|_| Error::auth("Failed to read session"))?;
354 Ok(session_guard.as_ref().map(|s| s.user.clone()))
355 }
356
357 pub fn get_session(&self) -> Result<Session> {
359 let session_guard = self
360 .session
361 .read()
362 .map_err(|_| Error::auth("Failed to read session"))?;
363 session_guard
364 .as_ref()
365 .cloned()
366 .ok_or_else(|| Error::auth("No active session"))
367 }
368
369 pub async fn set_session(&self, session: Session) -> Result<()> {
371 let mut session_guard = self
372 .session
373 .write()
374 .map_err(|_| Error::auth("Failed to write session"))?;
375 *session_guard = Some(session);
376 Ok(())
377 }
378
379 pub async fn set_session_token(&self, token: &str) -> Result<()> {
381 debug!("Setting session from token");
382
383 let user_response = self
384 .http_client
385 .get(format!("{}/auth/v1/user", self.config.url))
386 .header("Authorization", format!("Bearer {}", token))
387 .send()
388 .await?;
389
390 if !user_response.status().is_success() {
391 return Err(Error::auth("Invalid token"));
392 }
393
394 let user: User = user_response.json().await?;
395
396 let session = Session {
397 access_token: token.to_string(),
398 refresh_token: String::new(),
399 expires_in: 3600,
400 expires_at: Utc::now() + chrono::Duration::seconds(3600),
401 token_type: "bearer".to_string(),
402 user,
403 };
404
405 self.set_session(session).await?;
406 Ok(())
407 }
408
409 pub async fn clear_session(&self) -> Result<()> {
411 let mut session_guard = self
412 .session
413 .write()
414 .map_err(|_| Error::auth("Failed to write session"))?;
415 *session_guard = None;
416 Ok(())
417 }
418
419 pub fn is_authenticated(&self) -> bool {
421 let session_guard = self.session.read().unwrap_or_else(|_| {
422 warn!("Failed to read session lock");
423 self.session.read().unwrap()
424 });
425
426 match session_guard.as_ref() {
427 Some(session) => {
428 let now = Utc::now();
429 session.expires_at > now
430 }
431 None => false,
432 }
433 }
434
435 pub fn needs_refresh(&self) -> bool {
437 let session_guard = match self.session.read() {
438 Ok(guard) => guard,
439 Err(_) => return false,
440 };
441
442 match session_guard.as_ref() {
443 Some(session) => {
444 let now = Utc::now();
445 let threshold =
446 chrono::Duration::seconds(self.config.auth_config.refresh_threshold as i64);
447 session.expires_at - now < threshold
448 }
449 None => false,
450 }
451 }
452
453 pub async fn auto_refresh(&self) -> Result<()> {
455 if !self.config.auth_config.auto_refresh_token || !self.needs_refresh() {
456 return Ok(());
457 }
458
459 debug!("Auto-refreshing token");
460 self.refresh_session().await.map(|_| ())
461 }
462}