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!(
181 "{}/auth/v1/token?grant_type=password",
182 self.config.url
183 ))
184 .json(&payload)
185 .send()
186 .await?;
187
188 if !response.status().is_success() {
189 let status = response.status();
190 let error_msg = match response.text().await {
191 Ok(text) => text,
192 Err(_) => format!("Sign in failed with status: {}", status),
193 };
194 return Err(Error::auth(error_msg));
195 }
196
197 let auth_response: AuthResponse = response.json().await?;
198
199 if let Some(ref session) = auth_response.session {
200 self.set_session(session.clone()).await?;
201 info!("User signed in successfully");
202 }
203
204 Ok(auth_response)
205 }
206
207 pub async fn sign_out(&self) -> Result<()> {
209 debug!("Signing out user");
210
211 let session = self.get_session()?;
212
213 let response = self
214 .http_client
215 .post(format!("{}/auth/v1/logout", self.config.url))
216 .header("Authorization", format!("Bearer {}", session.access_token))
217 .send()
218 .await?;
219
220 if !response.status().is_success() {
221 warn!("Sign out request failed with status: {}", response.status());
222 }
223
224 self.clear_session().await?;
225 info!("User signed out successfully");
226
227 Ok(())
228 }
229
230 pub async fn reset_password_for_email(&self, email: &str) -> Result<()> {
232 self.reset_password_for_email_with_redirect(email, None)
233 .await
234 }
235
236 pub async fn reset_password_for_email_with_redirect(
238 &self,
239 email: &str,
240 redirect_to: Option<String>,
241 ) -> Result<()> {
242 debug!("Requesting password reset for email: {}", email);
243
244 let payload = PasswordResetRequest {
245 email: email.to_string(),
246 redirect_to,
247 };
248
249 let response = self
250 .http_client
251 .post(format!("{}/auth/v1/recover", self.config.url))
252 .json(&payload)
253 .send()
254 .await?;
255
256 if !response.status().is_success() {
257 let status = response.status();
258 let error_msg = match response.text().await {
259 Ok(text) => text,
260 Err(_) => format!("Password reset failed with status: {}", status),
261 };
262 return Err(Error::auth(error_msg));
263 }
264
265 info!("Password reset email sent successfully");
266 Ok(())
267 }
268
269 pub async fn update_user(
271 &self,
272 email: Option<String>,
273 password: Option<String>,
274 data: Option<serde_json::Value>,
275 ) -> Result<AuthResponse> {
276 debug!("Updating user information");
277
278 let session = self.get_session()?;
279
280 let payload = UpdateUserRequest {
281 email,
282 password,
283 data,
284 };
285
286 let response = self
287 .http_client
288 .put(format!("{}/auth/v1/user", self.config.url))
289 .header("Authorization", format!("Bearer {}", session.access_token))
290 .json(&payload)
291 .send()
292 .await?;
293
294 if !response.status().is_success() {
295 let status = response.status();
296 let error_msg = match response.text().await {
297 Ok(text) => text,
298 Err(_) => format!("User update failed with status: {}", status),
299 };
300 return Err(Error::auth(error_msg));
301 }
302
303 let auth_response: AuthResponse = response.json().await?;
304
305 if let Some(ref session) = auth_response.session {
306 self.set_session(session.clone()).await?;
307 }
308
309 info!("User updated successfully");
310 Ok(auth_response)
311 }
312
313 pub async fn refresh_session(&self) -> Result<AuthResponse> {
315 debug!("Refreshing session token");
316
317 let current_session = self.get_session()?;
318
319 let payload = RefreshTokenRequest {
320 refresh_token: current_session.refresh_token.clone(),
321 };
322
323 let response = self
324 .http_client
325 .post(format!(
326 "{}/auth/v1/token?grant_type=refresh_token",
327 self.config.url
328 ))
329 .json(&payload)
330 .send()
331 .await?;
332
333 if !response.status().is_success() {
334 let status = response.status();
335 let error_msg = match response.text().await {
336 Ok(text) => text,
337 Err(_) => format!("Token refresh failed with status: {}", status),
338 };
339 return Err(Error::auth(error_msg));
340 }
341
342 let auth_response: AuthResponse = response.json().await?;
343
344 if let Some(ref session) = auth_response.session {
345 self.set_session(session.clone()).await?;
346 info!("Session refreshed successfully");
347 }
348
349 Ok(auth_response)
350 }
351
352 pub async fn current_user(&self) -> Result<Option<User>> {
354 let session_guard = self
355 .session
356 .read()
357 .map_err(|_| Error::auth("Failed to read session"))?;
358 Ok(session_guard.as_ref().map(|s| s.user.clone()))
359 }
360
361 pub fn get_session(&self) -> Result<Session> {
363 let session_guard = self
364 .session
365 .read()
366 .map_err(|_| Error::auth("Failed to read session"))?;
367 session_guard
368 .as_ref()
369 .cloned()
370 .ok_or_else(|| Error::auth("No active session"))
371 }
372
373 pub async fn set_session(&self, session: Session) -> Result<()> {
375 let mut session_guard = self
376 .session
377 .write()
378 .map_err(|_| Error::auth("Failed to write session"))?;
379 *session_guard = Some(session);
380 Ok(())
381 }
382
383 pub async fn set_session_token(&self, token: &str) -> Result<()> {
385 debug!("Setting session from token");
386
387 let user_response = self
388 .http_client
389 .get(format!("{}/auth/v1/user", self.config.url))
390 .header("Authorization", format!("Bearer {}", token))
391 .send()
392 .await?;
393
394 if !user_response.status().is_success() {
395 return Err(Error::auth("Invalid token"));
396 }
397
398 let user: User = user_response.json().await?;
399
400 let session = Session {
401 access_token: token.to_string(),
402 refresh_token: String::new(),
403 expires_in: 3600,
404 expires_at: Utc::now() + chrono::Duration::seconds(3600),
405 token_type: "bearer".to_string(),
406 user,
407 };
408
409 self.set_session(session).await?;
410 Ok(())
411 }
412
413 pub async fn clear_session(&self) -> Result<()> {
415 let mut session_guard = self
416 .session
417 .write()
418 .map_err(|_| Error::auth("Failed to write session"))?;
419 *session_guard = None;
420 Ok(())
421 }
422
423 pub fn is_authenticated(&self) -> bool {
425 let session_guard = self.session.read().unwrap_or_else(|_| {
426 warn!("Failed to read session lock");
427 self.session.read().unwrap()
428 });
429
430 match session_guard.as_ref() {
431 Some(session) => {
432 let now = Utc::now();
433 session.expires_at > now
434 }
435 None => false,
436 }
437 }
438
439 pub fn needs_refresh(&self) -> bool {
441 let session_guard = match self.session.read() {
442 Ok(guard) => guard,
443 Err(_) => return false,
444 };
445
446 match session_guard.as_ref() {
447 Some(session) => {
448 let now = Utc::now();
449 let threshold =
450 chrono::Duration::seconds(self.config.auth_config.refresh_threshold as i64);
451 session.expires_at - now < threshold
452 }
453 None => false,
454 }
455 }
456
457 pub async fn auto_refresh(&self) -> Result<()> {
459 if !self.config.auth_config.auto_refresh_token || !self.needs_refresh() {
460 return Ok(());
461 }
462
463 debug!("Auto-refreshing token");
464 self.refresh_session().await.map(|_| ())
465 }
466}