supabase_rust_auth/
lib.rs

1//! Supabase Auth client for Rust
2//!
3//! This crate provides authentication functionality for Supabase,
4//! including sign up, sign in, session management, and user operations.
5
6use reqwest::Client;
7use serde::{Deserialize, Serialize};
8use std::sync::Arc;
9use std::sync::RwLock;
10use thiserror::Error;
11
12/// エラー型
13#[derive(Error, Debug)]
14pub enum AuthError {
15    #[error("API error: {0}")]
16    ApiError(String),
17
18    #[error("Authentication error: {0}")]
19    AuthenticationError(String),
20
21    #[error("Network error: {0}")]
22    NetworkError(#[from] reqwest::Error),
23
24    #[error("JSON serialization error: {0}")]
25    SerializationError(#[from] serde_json::Error),
26
27    #[error("Missing session")]
28    MissingSession,
29
30    #[error("Invalid token: {0}")]
31    InvalidToken(String),
32}
33
34/// ユーザー情報
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct User {
37    pub id: String,
38    pub email: Option<String>,
39    pub phone: Option<String>,
40    pub app_metadata: serde_json::Value,
41    pub user_metadata: serde_json::Value,
42    pub created_at: String,
43    pub updated_at: String,
44}
45
46/// セッション情報
47#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct Session {
49    pub access_token: String,
50    pub refresh_token: String,
51    pub expires_in: i64,
52    pub token_type: String,
53    pub user: User,
54}
55
56/// サインイン認証情報
57#[derive(Debug, Serialize)]
58pub struct SignInCredentials {
59    pub email: String,
60    pub password: String,
61}
62
63/// クライアントオプション
64#[derive(Debug, Clone)]
65pub struct AuthOptions {
66    pub auto_refresh_token: bool,
67    pub persist_session: bool,
68    pub detect_session_in_url: bool,
69}
70
71impl Default for AuthOptions {
72    fn default() -> Self {
73        Self {
74            auto_refresh_token: true,
75            persist_session: true,
76            detect_session_in_url: true,
77        }
78    }
79}
80
81/// OAuth プロバイダ
82#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
83pub enum OAuthProvider {
84    Google,
85    Facebook,
86    Twitter,
87    Github,
88    Apple,
89    Discord,
90    Gitlab,
91    Bitbucket,
92    Linkedin,
93    Microsoft,
94    Slack,
95    Spotify,
96}
97
98impl OAuthProvider {
99    fn display(&self) -> &'static str {
100        match self {
101            Self::Google => "google",
102            Self::Facebook => "facebook",
103            Self::Twitter => "twitter",
104            Self::Github => "github",
105            Self::Apple => "apple",
106            Self::Discord => "discord",
107            Self::Gitlab => "gitlab",
108            Self::Bitbucket => "bitbucket",
109            Self::Linkedin => "linkedin",
110            Self::Microsoft => "microsoft",
111            Self::Slack => "slack",
112            Self::Spotify => "spotify",
113        }
114    }
115}
116
117/// OAuth サインイン設定
118#[derive(Debug, Clone, Serialize, Default)]
119pub struct OAuthSignInOptions {
120    pub redirect_to: Option<String>,
121    pub scopes: Option<String>,
122    pub provider_scope: Option<String>,
123    pub skip_browser_redirect: Option<bool>,
124}
125
126/// メール確認設定
127#[derive(Debug, Clone, Serialize, Default)]
128pub struct EmailConfirmOptions {
129    pub redirect_to: Option<String>,
130}
131
132/// MFAファクターのタイプ
133#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
134#[serde(rename_all = "lowercase")]
135pub enum MFAFactorType {
136    Totp,
137}
138
139/// MFAファクターの状態
140#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
141#[serde(rename_all = "lowercase")]
142pub enum MFAFactorStatus {
143    Unverified,
144    Verified,
145}
146
147/// MFAファクター情報
148#[derive(Debug, Clone, Serialize, Deserialize)]
149pub struct MFAFactor {
150    pub id: String,
151    pub friendly_name: Option<String>,
152    #[serde(rename = "factor_type")]
153    pub factor_type: MFAFactorType,
154    pub status: MFAFactorStatus,
155    pub created_at: String,
156    pub updated_at: String,
157}
158
159/// TOTP MFAチャレンジ
160#[derive(Debug, Clone, Serialize, Deserialize)]
161pub struct MFAChallenge {
162    pub id: String,
163    #[serde(rename = "factor_id")]
164    pub factor_id: String,
165    pub created_at: String,
166    pub expires_at: Option<String>,
167}
168
169/// MFAチャレンジ検証結果
170#[derive(Debug, Clone, Serialize, Deserialize)]
171pub struct MFAVerifyResponse {
172    pub access_token: String,
173    pub refresh_token: Option<String>,
174    #[serde(rename = "type")]
175    pub token_type: String,
176    pub expires_in: i64,
177}
178
179/// TOTP設定情報
180#[derive(Debug, Clone, Serialize, Deserialize)]
181pub struct TOTPSetupInfo {
182    pub qr_code: String,
183    pub secret: String,
184    pub uri: String,
185}
186
187/// 電話番号認証のレスポンス
188#[derive(Debug, Clone, Serialize, Deserialize)]
189pub struct PhoneVerificationResponse {
190    pub phone: String,
191    pub verification_id: String,
192    pub expires_at: String,
193}
194
195/// Auth クライアント
196pub struct Auth {
197    url: String,
198    key: String,
199    http_client: Client,
200    options: AuthOptions,
201    current_session: Arc<RwLock<Option<Session>>>,
202    admin: Option<AdminAuth>,
203}
204
205/// Auth Admin クライアント - 管理者用API
206pub struct AdminAuth {
207    url: String,
208    service_role_key: String,
209    http_client: Client,
210}
211
212// AdminAuth実装
213impl AdminAuth {
214    /// 新しいAdminAuthクライアントを作成
215    pub fn new(url: &str, service_role_key: &str, http_client: Client) -> Self {
216        Self {
217            url: url.to_string(),
218            service_role_key: service_role_key.to_string(),
219            http_client,
220        }
221    }
222
223    /// Gets a user by their ID.
224    ///
225    /// # Example
226    ///
227    /// ```no_run
228    /// # use supabase_rust_auth::{Auth, AdminAuth, AuthOptions, AuthError};
229    /// # use reqwest::Client;
230    /// #
231    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
232    /// let mut auth = Auth::new("https://example.supabase.co/auth/v1", "anon-key", Client::new(), AuthOptions::default());
233    ///
234    /// // Initialize the admin client using a service role key
235    /// let auth = auth.init_admin("your-service-role-key");
236    ///
237    /// if let Some(admin_auth) = auth.admin() {
238    ///     let user = admin_auth.get_user_by_id("some-user-id").await?;
239    ///     println!("User: {:?}", user);
240    /// } else {
241    ///     println!("Admin client not initialized");
242    /// }
243    /// # Ok(())
244    /// # }
245    /// ```
246    pub async fn get_user_by_id(&self, user_id: &str) -> Result<User, AuthError> {
247        let url = format!("{}/admin/users/{}", self.url, user_id);
248
249        let response = self
250            .http_client
251            .get(&url)
252            .header("apikey", &self.service_role_key)
253            .header(
254                "Authorization",
255                format!("Bearer {}", &self.service_role_key),
256            )
257            .send()
258            .await?;
259
260        if !response.status().is_success() {
261            let error_text = response.text().await.unwrap_or_default();
262            return Err(AuthError::ApiError(format!(
263                "Failed to get user: {}",
264                error_text
265            )));
266        }
267
268        let user_data = response.json::<serde_json::Value>().await?;
269
270        match serde_json::from_value::<User>(user_data) {
271            Ok(user) => Ok(user),
272            Err(err) => Err(AuthError::SerializationError(err)),
273        }
274    }
275
276    /// Lists users with pagination.
277    ///
278    /// # Example
279    ///
280    /// ```no_run
281    /// # use supabase_rust_auth::{Auth, AdminAuth, AuthOptions, AuthError};
282    /// # use reqwest::Client;
283    /// #
284    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
285    /// let mut auth = Auth::new("https://example.supabase.co/auth/v1", "anon-key", Client::new(), AuthOptions::default());
286    ///
287    /// // Initialize the admin client using a service role key
288    /// let auth = auth.init_admin("your-service-role-key");
289    ///
290    /// if let Some(admin_auth) = auth.admin() {
291    ///     let users = admin_auth.list_users(Some(1), Some(100)).await?;
292    ///     println!("Users: {:?}", users);
293    /// } else {
294    ///     println!("Admin client not initialized");
295    /// }
296    /// # Ok(())
297    /// # }
298    /// ```
299    pub async fn list_users(
300        &self,
301        page: Option<u32>,
302        per_page: Option<u32>,
303    ) -> Result<Vec<User>, AuthError> {
304        let page = page.unwrap_or(1);
305        let per_page = per_page.unwrap_or(50);
306
307        let url = format!(
308            "{}/admin/users?page={}&per_page={}",
309            self.url, page, per_page
310        );
311
312        let response = self
313            .http_client
314            .get(&url)
315            .header("apikey", &self.service_role_key)
316            .header(
317                "Authorization",
318                format!("Bearer {}", &self.service_role_key),
319            )
320            .send()
321            .await?;
322
323        if !response.status().is_success() {
324            let error_text = response.text().await.unwrap_or_default();
325            return Err(AuthError::ApiError(format!(
326                "Failed to list users: {}",
327                error_text
328            )));
329        }
330
331        let users_data = response.json::<serde_json::Value>().await?;
332
333        match serde_json::from_value::<Vec<User>>(users_data) {
334            Ok(users) => Ok(users),
335            Err(err) => Err(AuthError::SerializationError(err)),
336        }
337    }
338
339    /// 新しいユーザーを作成します
340    ///
341    /// # 引数
342    ///
343    /// * `email` - ユーザーのEメールアドレス
344    /// * `password` - ユーザーのパスワード(オプション)
345    /// * `user_metadata` - ユーザーのメタデータ(オプション)
346    /// * `email_confirm` - メールアドレスを確認済みとしてマークするかどうか(オプション、デフォルトはfalse)
347    ///
348    /// # 例
349    ///
350    /// ```no_run
351    /// # use supabase_rust_auth::{Auth, AdminAuth, AuthOptions, AuthError};
352    /// # use reqwest::Client;
353    /// #
354    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
355    /// let mut auth = Auth::new("https://example.supabase.co/auth/v1", "anon-key", Client::new(), AuthOptions::default());
356    ///
357    /// // Initialize the admin client using a service role key
358    /// let auth = auth.init_admin("your-service-role-key");
359    ///
360    /// if let Some(admin_auth) = auth.admin() {
361    ///     let metadata = serde_json::json!({
362    ///         "first_name": "John",
363    ///         "last_name": "Doe"
364    ///     });
365    ///
366    ///     let user = admin_auth.create_user(
367    ///         "user@example.com",
368    ///         Some("password123"),
369    ///         Some(metadata),
370    ///         Some(true)
371    ///     ).await?;
372    ///     println!("Created user: {:?}", user);
373    /// } else {
374    ///     println!("Admin client not initialized");
375    /// }
376    /// # Ok(())
377    /// # }
378    /// ```
379    pub async fn create_user(
380        &self,
381        email: &str,
382        password: Option<&str>,
383        user_metadata: Option<serde_json::Value>,
384        email_confirm: Option<bool>,
385    ) -> Result<User, AuthError> {
386        let url = format!("{}/admin/users", self.url);
387
388        let mut payload = serde_json::json!({
389            "email": email,
390            "email_confirm": email_confirm.unwrap_or(false)
391        });
392
393        if let Some(pw) = password {
394            payload["password"] = serde_json::Value::String(pw.to_string());
395        }
396
397        if let Some(metadata) = user_metadata {
398            payload["user_metadata"] = metadata;
399        }
400
401        let response = self
402            .http_client
403            .post(&url)
404            .header("apikey", &self.service_role_key)
405            .header(
406                "Authorization",
407                format!("Bearer {}", &self.service_role_key),
408            )
409            .json(&payload)
410            .send()
411            .await?;
412
413        if !response.status().is_success() {
414            let error_text = response.text().await.unwrap_or_default();
415            return Err(AuthError::ApiError(format!(
416                "Failed to create user: {}",
417                error_text
418            )));
419        }
420
421        let user_data = response.json::<serde_json::Value>().await?;
422
423        match serde_json::from_value::<User>(user_data) {
424            Ok(user) => Ok(user),
425            Err(err) => Err(AuthError::SerializationError(err)),
426        }
427    }
428
429    /// ユーザーを削除します
430    ///
431    /// # 引数
432    ///
433    /// * `user_id` - 削除するユーザーのID
434    ///
435    /// # 例
436    ///
437    /// ```no_run
438    /// # use supabase_rust_auth::{Auth, AdminAuth, AuthOptions, AuthError};
439    /// # use reqwest::Client;
440    /// #
441    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
442    /// let mut auth = Auth::new("https://example.supabase.co/auth/v1", "anon-key", Client::new(), AuthOptions::default());
443    ///
444    /// // Initialize the admin client using a service role key
445    /// let auth = auth.init_admin("your-service-role-key");
446    ///
447    /// if let Some(admin_auth) = auth.admin() {
448    ///     admin_auth.delete_user("some-user-id").await?;
449    ///     println!("User deleted");
450    /// } else {
451    ///     println!("Admin client not initialized");
452    /// }
453    /// # Ok(())
454    /// # }
455    /// ```
456    pub async fn delete_user(&self, user_id: &str) -> Result<(), AuthError> {
457        let url = format!("{}/admin/users/{}", self.url, user_id);
458
459        let response = self
460            .http_client
461            .delete(&url)
462            .header("apikey", &self.service_role_key)
463            .header(
464                "Authorization",
465                format!("Bearer {}", &self.service_role_key),
466            )
467            .send()
468            .await?;
469
470        if !response.status().is_success() {
471            let error_text = response.text().await.unwrap_or_default();
472            return Err(AuthError::ApiError(format!(
473                "Failed to delete user: {}",
474                error_text
475            )));
476        }
477
478        Ok(())
479    }
480
481    /// ユーザーの情報を更新します
482    ///
483    /// # 引数
484    ///
485    /// * `user_id` - 更新するユーザーのID
486    /// * `attributes` - 更新するユーザー属性(email, password, user_metadata, email_confirm, phone_confirm など)
487    ///
488    /// # 例
489    ///
490    /// ```no_run
491    /// # use supabase_rust_auth::{Auth, AdminAuth, AuthOptions, AuthError};
492    /// # use reqwest::Client;
493    /// #
494    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
495    /// let mut auth = Auth::new("https://example.supabase.co/auth/v1", "anon-key", Client::new(), AuthOptions::default());
496    ///
497    /// // Initialize the admin client using a service role key
498    /// let auth = auth.init_admin("your-service-role-key");
499    ///
500    /// if let Some(admin_auth) = auth.admin() {
501    ///     let attributes = serde_json::json!({
502    ///         "email": "newemail@example.com",
503    ///         "user_metadata": {
504    ///             "first_name": "Jane",
505    ///             "last_name": "Smith"
506    ///         },
507    ///         "email_confirm": true
508    ///     });
509    ///
510    ///     let user = admin_auth.update_user(
511    ///         "some-user-id",
512    ///         attributes
513    ///     ).await?;
514    ///     println!("Updated user: {:?}", user);
515    /// } else {
516    ///     println!("Admin client not initialized");
517    /// }
518    /// # Ok(())
519    /// # }
520    /// ```
521    pub async fn update_user(
522        &self,
523        user_id: &str,
524        attributes: serde_json::Value,
525    ) -> Result<User, AuthError> {
526        let url = format!("{}/admin/users/{}", self.url, user_id);
527
528        let response = self
529            .http_client
530            .put(&url)
531            .header("apikey", &self.service_role_key)
532            .header(
533                "Authorization",
534                format!("Bearer {}", &self.service_role_key),
535            )
536            .json(&attributes)
537            .send()
538            .await?;
539
540        if !response.status().is_success() {
541            let error_text = response.text().await.unwrap_or_default();
542            return Err(AuthError::ApiError(format!(
543                "Failed to update user: {}",
544                error_text
545            )));
546        }
547
548        let user_data = response.json::<serde_json::Value>().await?;
549
550        match serde_json::from_value::<User>(user_data) {
551            Ok(user) => Ok(user),
552            Err(err) => Err(AuthError::SerializationError(err)),
553        }
554    }
555
556    /// メール招待リンクを送信します
557    ///
558    /// # 引数
559    ///
560    /// * `email` - 招待するユーザーのEメールアドレス
561    /// * `redirect_to` - 認証後のリダイレクト先URL(オプション)
562    ///
563    /// # 例
564    ///
565    /// ```no_run
566    /// # use supabase_rust_auth::{Auth, AdminAuth, AuthOptions, AuthError};
567    /// # use reqwest::Client;
568    /// #
569    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
570    /// let mut auth = Auth::new("https://example.supabase.co/auth/v1", "anon-key", Client::new(), AuthOptions::default());
571    ///
572    /// // Initialize the admin client using a service role key
573    /// let auth = auth.init_admin("your-service-role-key");
574    ///
575    /// if let Some(admin_auth) = auth.admin() {
576    ///     let user = admin_auth.invite_user_by_email(
577    ///         "user@example.com",
578    ///         Some("https://your-app.com/welcome")
579    ///     ).await?;
580    ///     println!("Invited user: {:?}", user);
581    /// } else {
582    ///     println!("Admin client not initialized");
583    /// }
584    /// # Ok(())
585    /// # }
586    /// ```
587    pub async fn invite_user_by_email(
588        &self,
589        email: &str,
590        redirect_to: Option<&str>,
591    ) -> Result<User, AuthError> {
592        let url = format!("{}/admin/users/invite", self.url);
593
594        let mut payload = serde_json::json!({
595            "email": email
596        });
597
598        if let Some(redirect) = redirect_to {
599            payload["redirect_to"] = serde_json::Value::String(redirect.to_string());
600        }
601
602        let response = self
603            .http_client
604            .post(&url)
605            .header("apikey", &self.service_role_key)
606            .header(
607                "Authorization",
608                format!("Bearer {}", &self.service_role_key),
609            )
610            .json(&payload)
611            .send()
612            .await?;
613
614        if !response.status().is_success() {
615            let error_text = response.text().await.unwrap_or_default();
616            return Err(AuthError::ApiError(format!(
617                "Failed to invite user: {}",
618                error_text
619            )));
620        }
621
622        let user_data = response.json::<serde_json::Value>().await?;
623
624        match serde_json::from_value::<User>(user_data) {
625            Ok(user) => Ok(user),
626            Err(err) => Err(AuthError::SerializationError(err)),
627        }
628    }
629
630    /// ユーザーのMFAファクターを削除します
631    ///
632    /// # 引数
633    ///
634    /// * `user_id` - ユーザーのID
635    /// * `factor_id` - 削除するMFAファクターのID
636    ///
637    /// # 例
638    ///
639    /// ```no_run
640    /// # use supabase_rust_auth::{Auth, AdminAuth, AuthOptions, AuthError};
641    /// # use reqwest::Client;
642    /// #
643    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
644    /// let mut auth = Auth::new("https://example.supabase.co/auth/v1", "anon-key", Client::new(), AuthOptions::default());
645    ///
646    /// // Initialize the admin client using a service role key
647    /// let auth = auth.init_admin("your-service-role-key");
648    ///
649    /// if let Some(admin_auth) = auth.admin() {
650    ///     admin_auth.delete_user_factor(
651    ///         "some-user-id",
652    ///         "factor-id"
653    ///     ).await?;
654    ///     println!("User factor deleted");
655    /// } else {
656    ///     println!("Admin client not initialized");
657    /// }
658    /// # Ok(())
659    /// # }
660    /// ```
661    pub async fn delete_user_factor(
662        &self,
663        user_id: &str,
664        factor_id: &str,
665    ) -> Result<(), AuthError> {
666        let url = format!("{}/admin/users/{}/factors/{}", self.url, user_id, factor_id);
667
668        let response = self
669            .http_client
670            .delete(&url)
671            .header("apikey", &self.service_role_key)
672            .header(
673                "Authorization",
674                format!("Bearer {}", &self.service_role_key),
675            )
676            .send()
677            .await?;
678
679        if !response.status().is_success() {
680            let error_text = response.text().await.unwrap_or_default();
681            return Err(AuthError::ApiError(format!(
682                "Failed to delete user factor: {}",
683                error_text
684            )));
685        }
686
687        Ok(())
688    }
689
690    /// メールリンクを生成します (マジックリンク, パスワードリセットなど)
691    ///
692    /// # 引数
693    ///
694    /// * `email` - ユーザーのEメールアドレス
695    /// * `type` - リンクの種類 ("signup", "magiclink", "recovery", "invite")
696    /// * `redirect_to` - 認証後のリダイレクト先URL(オプション)
697    ///
698    /// # 例
699    ///
700    /// ```no_run
701    /// # use supabase_rust_auth::{Auth, AdminAuth, AuthOptions, AuthError};
702    /// # use reqwest::Client;
703    /// #
704    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
705    /// let mut auth = Auth::new("https://example.supabase.co/auth/v1", "anon-key", Client::new(), AuthOptions::default());
706    ///
707    /// // Initialize the admin client using a service role key
708    /// let auth = auth.init_admin("your-service-role-key");
709    ///
710    /// if let Some(admin_auth) = auth.admin() {
711    ///     let link = admin_auth.generate_link(
712    ///         "user@example.com",
713    ///         "magiclink",
714    ///         Some("https://your-app.com/welcome")
715    ///     ).await?;
716    ///     println!("Generated link: {}", link);
717    /// } else {
718    ///     println!("Admin client not initialized");
719    /// }
720    /// # Ok(())
721    /// # }
722    /// ```
723    pub async fn generate_link(
724        &self,
725        email: &str,
726        link_type: &str,
727        redirect_to: Option<&str>,
728    ) -> Result<String, AuthError> {
729        let url = format!("{}/admin/users/generate_link", self.url);
730
731        let mut payload = serde_json::json!({
732            "email": email,
733            "type": link_type
734        });
735
736        if let Some(redirect) = redirect_to {
737            payload["redirect_to"] = serde_json::Value::String(redirect.to_string());
738        }
739
740        let response = self
741            .http_client
742            .post(&url)
743            .header("apikey", &self.service_role_key)
744            .header(
745                "Authorization",
746                format!("Bearer {}", &self.service_role_key),
747            )
748            .json(&payload)
749            .send()
750            .await?;
751
752        if !response.status().is_success() {
753            let error_text = response.text().await.unwrap_or_default();
754            return Err(AuthError::ApiError(format!(
755                "Failed to generate link: {}",
756                error_text
757            )));
758        }
759
760        let data = response.json::<serde_json::Value>().await?;
761
762        match data.get("action_link") {
763            Some(link) => match link.as_str() {
764                Some(s) => Ok(s.to_string()),
765                None => Err(AuthError::ApiError("Invalid link format".to_string())),
766            },
767            None => Err(AuthError::ApiError("No link returned".to_string())),
768        }
769    }
770}
771
772impl Auth {
773    /// 新しい Auth クライアントを作成
774    pub fn new(url: &str, key: &str, http_client: Client, options: AuthOptions) -> Self {
775        Self {
776            url: url.to_string(),
777            key: key.to_string(),
778            http_client: http_client.clone(),
779            options,
780            current_session: Arc::new(RwLock::new(None)),
781            admin: None,
782        }
783    }
784
785    /// 管理者用APIクライアントを初期化
786    ///
787    /// # 引数
788    ///
789    /// * `service_role_key` - サービスロールキー(重要: クライアント側では使用せず、サーバー側でのみ使用する)
790    ///
791    /// # 例
792    ///
793    /// ```no_run
794    /// # use supabase_rust_auth::{Auth, AuthOptions};
795    /// # use reqwest::Client;
796    /// #
797    /// # fn example() {
798    /// # let auth = Auth::new("https://example.supabase.co/auth/v1", "anon-key", Client::new(), AuthOptions::default());
799    /// # let mut auth = auth;
800    /// let auth = auth.init_admin("your-service-role-key");
801    /// # }
802    /// ```
803    pub fn init_admin(&mut self, service_role_key: &str) -> &Self {
804        self.admin = Some(AdminAuth::new(
805            &self.url,
806            service_role_key,
807            self.http_client.clone(),
808        ));
809        self
810    }
811
812    /// 管理者用APIクライアントを取得
813    ///
814    /// # 例
815    ///
816    /// ```no_run
817    /// # use supabase_rust_auth::{Auth, AuthOptions};
818    /// # use reqwest::Client;
819    /// #
820    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
821    /// # let mut auth = Auth::new("https://example.supabase.co/auth/v1", "anon-key", Client::new(), AuthOptions::default());
822    /// # let mut auth = auth.init_admin("your-service-role-key");
823    /// if let Some(admin) = auth.admin() {
824    ///     // Use admin API here
825    ///     // let users = admin.list_users(None, None).await?;
826    /// }
827    /// # Ok(())
828    /// # }
829    /// ```
830    pub fn admin(&self) -> Option<&AdminAuth> {
831        self.admin.as_ref()
832    }
833
834    /// ユーザー登録
835    pub async fn sign_up(&self, email: &str, password: &str) -> Result<Session, AuthError> {
836        let url = format!("{}/auth/v1/signup", self.url);
837
838        let payload = serde_json::json!({
839            "email": email,
840            "password": password,
841        });
842
843        let response = self
844            .http_client
845            .post(&url)
846            .header("apikey", &self.key)
847            .header("Content-Type", "application/json")
848            .json(&payload)
849            .send()
850            .await?;
851
852        if !response.status().is_success() {
853            let error_text = response.text().await?;
854            return Err(AuthError::ApiError(error_text));
855        }
856
857        let session: Session = response.json().await?;
858
859        // セッションを保存
860        if self.options.persist_session {
861            let mut write_guard = self.current_session.write().unwrap();
862            *write_guard = Some(session.clone());
863        }
864
865        Ok(session)
866    }
867
868    /// メール・パスワードでログイン
869    pub async fn sign_in_with_password(
870        &self,
871        email: &str,
872        password: &str,
873    ) -> Result<Session, AuthError> {
874        let url = format!("{}/auth/v1/token?grant_type=password", self.url);
875
876        let payload = serde_json::json!({
877            "email": email,
878            "password": password,
879        });
880
881        let response = self
882            .http_client
883            .post(&url)
884            .header("apikey", &self.key)
885            .header("Content-Type", "application/json")
886            .json(&payload)
887            .send()
888            .await?;
889
890        if !response.status().is_success() {
891            let error_text = response.text().await?;
892            return Err(AuthError::ApiError(error_text));
893        }
894
895        let session: Session = response.json().await?;
896
897        // セッションを保存
898        if self.options.persist_session {
899            let mut write_guard = self.current_session.write().unwrap();
900            *write_guard = Some(session.clone());
901        }
902
903        Ok(session)
904    }
905
906    /// 現在のセッションを取得
907    pub fn get_session(&self) -> Option<Session> {
908        let read_guard = self.current_session.read().unwrap();
909        read_guard.clone()
910    }
911
912    /// 現在のユーザーを取得
913    pub async fn get_user(&self) -> Result<User, AuthError> {
914        let session = self.get_session().ok_or(AuthError::MissingSession)?;
915
916        let url = format!("{}/auth/v1/user", self.url);
917
918        let response = self
919            .http_client
920            .get(&url)
921            .header("apikey", &self.key)
922            .header("Authorization", format!("Bearer {}", session.access_token))
923            .send()
924            .await?;
925
926        if !response.status().is_success() {
927            let error_text = response.text().await?;
928            return Err(AuthError::ApiError(error_text));
929        }
930
931        let user: User = response.json().await?;
932
933        Ok(user)
934    }
935
936    /// セッションをリフレッシュ
937    pub async fn refresh_session(&self) -> Result<Session, AuthError> {
938        let session = self.get_session().ok_or(AuthError::MissingSession)?;
939
940        let url = format!("{}/auth/v1/token?grant_type=refresh_token", self.url);
941
942        let payload = serde_json::json!({
943            "refresh_token": session.refresh_token,
944        });
945
946        let response = self
947            .http_client
948            .post(&url)
949            .header("apikey", &self.key)
950            .header("Content-Type", "application/json")
951            .json(&payload)
952            .send()
953            .await?;
954
955        if !response.status().is_success() {
956            let error_text = response.text().await?;
957            return Err(AuthError::ApiError(error_text));
958        }
959
960        let new_session: Session = response.json().await?;
961
962        // セッションを更新
963        if self.options.persist_session {
964            let mut write_guard = self.current_session.write().unwrap();
965            *write_guard = Some(new_session.clone());
966        }
967
968        Ok(new_session)
969    }
970
971    /// サインアウト
972    pub async fn sign_out(&self) -> Result<(), AuthError> {
973        let session = self.get_session().ok_or(AuthError::MissingSession)?;
974
975        let url = format!("{}/auth/v1/logout", self.url);
976
977        let response = self
978            .http_client
979            .post(&url)
980            .header("apikey", &self.key)
981            .header("Authorization", format!("Bearer {}", session.access_token))
982            .send()
983            .await?;
984
985        if !response.status().is_success() {
986            let error_text = response.text().await?;
987            return Err(AuthError::ApiError(error_text));
988        }
989
990        // セッションをクリア
991        let mut write_guard = self.current_session.write().unwrap();
992        *write_guard = None;
993
994        Ok(())
995    }
996
997    /// パスワードリセットメールの送信
998    pub async fn reset_password_for_email(&self, email: &str) -> Result<(), AuthError> {
999        let url = format!("{}/auth/v1/recover", self.url);
1000
1001        let payload = serde_json::json!({
1002            "email": email,
1003        });
1004
1005        let response = self
1006            .http_client
1007            .post(&url)
1008            .header("apikey", &self.key)
1009            .header("Content-Type", "application/json")
1010            .json(&payload)
1011            .send()
1012            .await?;
1013
1014        if !response.status().is_success() {
1015            let error_text = response.text().await?;
1016            return Err(AuthError::ApiError(error_text));
1017        }
1018
1019        Ok(())
1020    }
1021
1022    /// OAuth プロバイダを通じたサインインのためのURL生成
1023    pub fn get_oauth_sign_in_url(
1024        &self,
1025        provider: OAuthProvider,
1026        options: Option<OAuthSignInOptions>,
1027    ) -> String {
1028        let provider_id = provider.display();
1029        let options = options.unwrap_or_default();
1030
1031        let mut url = format!("{}/auth/v1/authorize?provider={}", self.url, provider_id);
1032
1033        if let Some(redirect_to) = options.redirect_to {
1034            url.push_str(&format!(
1035                "&redirect_to={}",
1036                urlencoding::encode(&redirect_to)
1037            ));
1038        }
1039
1040        if let Some(scopes) = options.scopes {
1041            url.push_str(&format!("&scopes={}", urlencoding::encode(&scopes)));
1042        }
1043
1044        if let Some(provider_scope) = options.provider_scope {
1045            url.push_str(&format!(
1046                "&provider_scope={}",
1047                urlencoding::encode(&provider_scope)
1048            ));
1049        }
1050
1051        url
1052    }
1053
1054    /// OAuthで認証をリクエスト
1055    pub async fn sign_in_with_oauth(
1056        &self,
1057        provider: OAuthProvider,
1058        options: Option<OAuthSignInOptions>,
1059    ) -> Result<String, AuthError> {
1060        // OAuth認証URLを生成
1061        let url = self.get_oauth_sign_in_url(provider, options.clone());
1062
1063        // 自動リダイレクトオプション
1064        let skip_browser_redirect = options
1065            .and_then(|opt| opt.skip_browser_redirect)
1066            .unwrap_or(false);
1067
1068        if skip_browser_redirect {
1069            return Ok(url);
1070        }
1071
1072        // 通常はクライアント側でURLにリダイレクトする必要があるため、
1073        // ここではURLを返します。Rustの場合、環境によって適切なブラウザ起動方法が異なります。
1074        Ok(url)
1075    }
1076
1077    /// OAuthコールバックからのコードを処理してセッション取得
1078    pub async fn exchange_code_for_session(&self, code: &str) -> Result<Session, AuthError> {
1079        let url = format!("{}/auth/v1/token?grant_type=authorization_code", self.url);
1080
1081        let payload = serde_json::json!({
1082            "code": code,
1083        });
1084
1085        let response = self
1086            .http_client
1087            .post(&url)
1088            .header("apikey", &self.key)
1089            .header("Content-Type", "application/json")
1090            .json(&payload)
1091            .send()
1092            .await?;
1093
1094        if !response.status().is_success() {
1095            let error_text = response.text().await?;
1096            return Err(AuthError::ApiError(error_text));
1097        }
1098
1099        let session: Session = response.json().await?;
1100
1101        // セッションを保存
1102        if self.options.persist_session {
1103            let mut write_guard = self.current_session.write().unwrap();
1104            *write_guard = Some(session.clone());
1105        }
1106
1107        Ok(session)
1108    }
1109
1110    /// MFAで保護されたサインイン - 最初のステップ(パスワードでの認証)
1111    ///
1112    /// このメソッドは通常のサインインプロセスと同様ですが、ユーザーが
1113    /// MFAを有効化している場合は、次のステップで検証が必要なチャレンジを返します。
1114    pub async fn sign_in_with_password_mfa(
1115        &self,
1116        email: &str,
1117        password: &str,
1118    ) -> Result<Result<Session, MFAChallenge>, AuthError> {
1119        let url = format!("{}/auth/v1/token?grant_type=password", self.url);
1120
1121        let payload = serde_json::json!({
1122            "email": email,
1123            "password": password,
1124        });
1125
1126        let response = self
1127            .http_client
1128            .post(&url)
1129            .header("apikey", &self.key)
1130            .header("Content-Type", "application/json")
1131            .json(&payload)
1132            .send()
1133            .await?;
1134
1135        // サインイン結果をパース
1136        let status = response.status();
1137        let body = response.text().await?;
1138
1139        if status.is_success() {
1140            // 通常のサインイン成功(MFAが必要ない)
1141            let session: Session = serde_json::from_str(&body)?;
1142
1143            // セッションを保存
1144            if self.options.persist_session {
1145                let mut write_guard = self.current_session.write().unwrap();
1146                *write_guard = Some(session.clone());
1147            }
1148
1149            Ok(Ok(session))
1150        } else if status.as_u16() == 401 {
1151            // MFA認証が必要かチェック
1152            if let Ok(challenge) = serde_json::from_str::<MFAChallenge>(&body) {
1153                // MFAチャレンジ
1154                Ok(Err(challenge))
1155            } else {
1156                // 通常の認証エラー
1157                Err(AuthError::ApiError(body))
1158            }
1159        } else {
1160            // その他のエラー
1161            Err(AuthError::ApiError(body))
1162        }
1163    }
1164
1165    /// MFAチャレンジの検証 - 第二ステップ(コードによる検証)
1166    pub async fn verify_mfa_challenge(
1167        &self,
1168        challenge_id: &str,
1169        code: &str,
1170    ) -> Result<Session, AuthError> {
1171        let url = format!("{}/auth/v1/mfa/verify", self.url);
1172
1173        let payload = serde_json::json!({
1174            "challenge_id": challenge_id,
1175            "code": code,
1176        });
1177
1178        let response = self
1179            .http_client
1180            .post(&url)
1181            .header("apikey", &self.key)
1182            .header("Content-Type", "application/json")
1183            .json(&payload)
1184            .send()
1185            .await?;
1186
1187        if !response.status().is_success() {
1188            let error_text = response.text().await?;
1189            return Err(AuthError::ApiError(error_text));
1190        }
1191
1192        let verify_response: MFAVerifyResponse = response.json().await?;
1193
1194        // セッションオブジェクトに変換
1195        let user = self
1196            .get_user_by_token(&verify_response.access_token)
1197            .await?;
1198
1199        let session = Session {
1200            access_token: verify_response.access_token,
1201            refresh_token: verify_response.refresh_token.unwrap_or_default(),
1202            expires_in: verify_response.expires_in,
1203            token_type: verify_response.token_type,
1204            user,
1205        };
1206
1207        // セッションを保存
1208        if self.options.persist_session {
1209            let mut write_guard = self.current_session.write().unwrap();
1210            *write_guard = Some(session.clone());
1211        }
1212
1213        Ok(session)
1214    }
1215
1216    /// MFAファクターを登録する
1217    pub async fn enroll_totp(&self) -> Result<TOTPSetupInfo, AuthError> {
1218        let session = self.get_session().ok_or(AuthError::MissingSession)?;
1219
1220        let url = format!("{}/auth/v1/mfa/totp", self.url);
1221
1222        let response = self
1223            .http_client
1224            .post(&url)
1225            .header("apikey", &self.key)
1226            .header("Authorization", format!("Bearer {}", session.access_token))
1227            .send()
1228            .await?;
1229
1230        if !response.status().is_success() {
1231            let error_text = response.text().await?;
1232            return Err(AuthError::ApiError(error_text));
1233        }
1234
1235        let setup_info: TOTPSetupInfo = response.json().await?;
1236
1237        Ok(setup_info)
1238    }
1239
1240    /// TOTP MFAファクターを検証して有効化
1241    pub async fn verify_totp(&self, factor_id: &str, code: &str) -> Result<MFAFactor, AuthError> {
1242        let session = self.get_session().ok_or(AuthError::MissingSession)?;
1243
1244        let url = format!("{}/auth/v1/mfa/totp/verify", self.url);
1245
1246        let payload = serde_json::json!({
1247            "factor_id": factor_id,
1248            "code": code,
1249        });
1250
1251        let response = self
1252            .http_client
1253            .post(&url)
1254            .header("apikey", &self.key)
1255            .header("Authorization", format!("Bearer {}", session.access_token))
1256            .header("Content-Type", "application/json")
1257            .json(&payload)
1258            .send()
1259            .await?;
1260
1261        if !response.status().is_success() {
1262            let error_text = response.text().await?;
1263            return Err(AuthError::ApiError(error_text));
1264        }
1265
1266        let factor: MFAFactor = response.json().await?;
1267
1268        Ok(factor)
1269    }
1270
1271    /// ユーザーの登録済みMFAファクター一覧を取得
1272    pub async fn list_factors(&self) -> Result<Vec<MFAFactor>, AuthError> {
1273        let session = self.get_session().ok_or(AuthError::MissingSession)?;
1274
1275        let url = format!("{}/auth/v1/mfa/factors", self.url);
1276
1277        let response = self
1278            .http_client
1279            .get(&url)
1280            .header("apikey", &self.key)
1281            .header("Authorization", format!("Bearer {}", session.access_token))
1282            .send()
1283            .await?;
1284
1285        if !response.status().is_success() {
1286            let error_text = response.text().await?;
1287            return Err(AuthError::ApiError(error_text));
1288        }
1289
1290        let factors: Vec<MFAFactor> = response.json().await?;
1291
1292        Ok(factors)
1293    }
1294
1295    /// MFAファクターを無効化(削除)
1296    pub async fn unenroll_factor(&self, factor_id: &str) -> Result<(), AuthError> {
1297        let session = self.get_session().ok_or(AuthError::MissingSession)?;
1298
1299        let url = format!("{}/auth/v1/mfa/factors/{}", self.url, factor_id);
1300
1301        let response = self
1302            .http_client
1303            .delete(&url)
1304            .header("apikey", &self.key)
1305            .header("Authorization", format!("Bearer {}", session.access_token))
1306            .send()
1307            .await?;
1308
1309        if !response.status().is_success() {
1310            let error_text = response.text().await?;
1311            return Err(AuthError::ApiError(error_text));
1312        }
1313
1314        Ok(())
1315    }
1316
1317    /// トークンを使ってユーザー情報を取得(内部メソッド)
1318    async fn get_user_by_token(&self, token: &str) -> Result<User, AuthError> {
1319        let url = format!("{}/auth/v1/user", self.url);
1320
1321        let response = self
1322            .http_client
1323            .get(&url)
1324            .header("apikey", &self.key)
1325            .header("Authorization", format!("Bearer {}", token))
1326            .send()
1327            .await?;
1328
1329        if !response.status().is_success() {
1330            let error_text = response.text().await?;
1331            return Err(AuthError::ApiError(error_text));
1332        }
1333
1334        let user: User = response.json().await?;
1335
1336        Ok(user)
1337    }
1338
1339    /// 匿名認証でサインイン
1340    pub async fn sign_in_anonymously(&self) -> Result<Session, AuthError> {
1341        let endpoint = format!("{}/auth/v1/signup", self.url);
1342
1343        let response = self
1344            .http_client
1345            .post(&endpoint)
1346            .header("apikey", &self.key)
1347            .header("Content-Type", "application/json")
1348            .json(&serde_json::json!({
1349                "data": {}
1350            }))
1351            .send()
1352            .await?;
1353
1354        if !response.status().is_success() {
1355            let error_msg = response.text().await?;
1356            return Err(AuthError::ApiError(error_msg));
1357        }
1358
1359        let session: Session = response.json().await?;
1360
1361        // セッションを保存
1362        if self.options.persist_session {
1363            let mut writable_session = self.current_session.write().unwrap();
1364            *writable_session = Some(session.clone());
1365        }
1366
1367        Ok(session)
1368    }
1369
1370    /// メール確認のリクエストを送信する
1371    ///
1372    /// # Arguments
1373    ///
1374    /// * `email` - 確認メールを送信するメールアドレス
1375    /// * `options` - オプション設定(リダイレクトURLなど)
1376    ///
1377    /// # Example
1378    ///
1379    /// ```no_run
1380    /// # use supabase_rust_auth::{Auth, AuthOptions, EmailConfirmOptions};
1381    /// # use reqwest::Client;
1382    /// #
1383    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
1384    /// # let auth = Auth::new("https://example.supabase.co/auth/v1", "anon-key", Client::new(), AuthOptions::default());
1385    /// let options = EmailConfirmOptions {
1386    ///     redirect_to: Some("https://example.com/confirm-success".to_string()),
1387    /// };
1388    ///
1389    /// auth.send_confirm_email_request("user@example.com", Some(options)).await?;
1390    /// # Ok(())
1391    /// # }
1392    /// ```
1393    pub async fn send_confirm_email_request(
1394        &self,
1395        email: &str,
1396        options: Option<EmailConfirmOptions>,
1397    ) -> Result<(), AuthError> {
1398        let endpoint = format!("{}/auth/v1/signup", self.url);
1399
1400        let mut payload = serde_json::json!({
1401            "email": email,
1402            "data": {}
1403        });
1404
1405        if let Some(opts) = options {
1406            if let Some(redirect_to) = opts.redirect_to {
1407                payload["options"] = serde_json::json!({
1408                    "redirect_to": redirect_to
1409                });
1410            }
1411        }
1412
1413        let response = self
1414            .http_client
1415            .post(&endpoint)
1416            .header("apikey", &self.key)
1417            .header("Content-Type", "application/json")
1418            .json(&payload)
1419            .send()
1420            .await?;
1421
1422        if !response.status().is_success() {
1423            let error_msg = response.text().await?;
1424            return Err(AuthError::ApiError(error_msg));
1425        }
1426
1427        Ok(())
1428    }
1429
1430    /// メール確認トークンを検証する
1431    ///
1432    /// # Arguments
1433    ///
1434    /// * `token` - メール確認用のトークン(確認リンクから取得)
1435    ///
1436    /// # Example
1437    ///
1438    /// ```no_run
1439    /// # use supabase_rust_auth::{Auth, AuthOptions};
1440    /// # use reqwest::Client;
1441    /// #
1442    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
1443    /// # let auth = Auth::new("https://example.supabase.co/auth/v1", "anon-key", Client::new(), AuthOptions::default());
1444    /// let session = auth.verify_email("confirmation-token-from-email").await?;
1445    /// println!("Email verified for user: {:?}", session.user.email);
1446    /// # Ok(())
1447    /// # }
1448    /// ```
1449    pub async fn verify_email(&self, token: &str) -> Result<Session, AuthError> {
1450        let endpoint = format!("{}/auth/v1/verify", self.url);
1451
1452        let response = self
1453            .http_client
1454            .post(&endpoint)
1455            .header("apikey", &self.key)
1456            .header("Content-Type", "application/json")
1457            .json(&serde_json::json!({
1458                "type": "signup",
1459                "token": token
1460            }))
1461            .send()
1462            .await?;
1463
1464        if !response.status().is_success() {
1465            let error_msg = response.text().await?;
1466            return Err(AuthError::ApiError(error_msg));
1467        }
1468
1469        let session: Session = response.json().await?;
1470
1471        // セッションを保存
1472        if self.options.persist_session {
1473            let mut writable_session = self.current_session.write().unwrap();
1474            *writable_session = Some(session.clone());
1475        }
1476
1477        Ok(session)
1478    }
1479
1480    /// パスワードリセット後にリセットトークンを検証する
1481    ///
1482    /// # Arguments
1483    ///
1484    /// * `token` - パスワードリセット用のトークン(リセットリンクから取得)
1485    /// * `new_password` - 新しいパスワード
1486    ///
1487    /// # Example
1488    ///
1489    /// ```no_run
1490    /// # use supabase_rust_auth::{Auth, AuthOptions};
1491    /// # use reqwest::Client;
1492    /// #
1493    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
1494    /// # let auth = Auth::new("https://example.supabase.co/auth/v1", "anon-key", Client::new(), AuthOptions::default());
1495    /// let session = auth.verify_password_reset("reset-token-from-email", "new-secure-password").await?;
1496    /// println!("Password reset for user: {:?}", session.user.email);
1497    /// # Ok(())
1498    /// # }
1499    /// ```
1500    pub async fn verify_password_reset(
1501        &self,
1502        token: &str,
1503        new_password: &str,
1504    ) -> Result<Session, AuthError> {
1505        let endpoint = format!("{}/auth/v1/verify", self.url);
1506
1507        let response = self
1508            .http_client
1509            .post(&endpoint)
1510            .header("apikey", &self.key)
1511            .header("Content-Type", "application/json")
1512            .json(&serde_json::json!({
1513                "type": "recovery",
1514                "token": token,
1515                "password": new_password
1516            }))
1517            .send()
1518            .await?;
1519
1520        if !response.status().is_success() {
1521            let error_msg = response.text().await?;
1522            return Err(AuthError::ApiError(error_msg));
1523        }
1524
1525        let session: Session = response.json().await?;
1526
1527        // セッションを保存
1528        if self.options.persist_session {
1529            let mut writable_session = self.current_session.write().unwrap();
1530            *writable_session = Some(session.clone());
1531        }
1532
1533        Ok(session)
1534    }
1535
1536    pub async fn send_verification_code(
1537        &self,
1538        phone: &str,
1539    ) -> Result<PhoneVerificationResponse, AuthError> {
1540        let url = format!("{}/auth/v1/otp", self.url);
1541
1542        let payload = serde_json::json!({
1543            "phone": phone,
1544            "channel": "sms"
1545        });
1546
1547        let response = self
1548            .http_client
1549            .post(&url)
1550            .header("apikey", &self.key)
1551            .header("Content-Type", "application/json")
1552            .json(&payload)
1553            .send()
1554            .await?;
1555
1556        if !response.status().is_success() {
1557            let error_text = response.text().await?;
1558            return Err(AuthError::ApiError(error_text));
1559        }
1560
1561        let verification: PhoneVerificationResponse = response.json().await?;
1562        Ok(verification)
1563    }
1564
1565    /// 電話番号と検証コードでサインイン
1566    pub async fn verify_phone_code(
1567        &self,
1568        phone: &str,
1569        verification_id: &str,
1570        code: &str,
1571    ) -> Result<Session, AuthError> {
1572        let url = format!("{}/auth/v1/verify", self.url);
1573
1574        let payload = serde_json::json!({
1575            "phone": phone,
1576            "verification_id": verification_id,
1577            "code": code,
1578            "type": "sms"
1579        });
1580
1581        let response = self
1582            .http_client
1583            .post(&url)
1584            .header("apikey", &self.key)
1585            .header("Content-Type", "application/json")
1586            .json(&payload)
1587            .send()
1588            .await?;
1589
1590        if !response.status().is_success() {
1591            let error_text = response.text().await?;
1592            return Err(AuthError::ApiError(error_text));
1593        }
1594
1595        let session: Session = response.json().await?;
1596
1597        // セッションを保存
1598        if self.options.persist_session {
1599            let mut write_guard = self.current_session.write().unwrap();
1600            *write_guard = Some(session.clone());
1601        }
1602
1603        Ok(session)
1604    }
1605}
1606
1607#[cfg(test)]
1608mod tests {
1609    use super::*;
1610    use wiremock::matchers::{method, path};
1611    use wiremock::{Mock, MockServer, ResponseTemplate};
1612    // http::Responseを明示的にインポート
1613
1614    #[test]
1615    fn test_sign_up() {
1616        tokio_test::block_on(async {
1617            let mock_server = MockServer::start().await;
1618
1619            let response_body = serde_json::json!({
1620                "access_token": "test_access_token",
1621                "refresh_token": "test_refresh_token",
1622                "expires_in": 3600,
1623                "token_type": "bearer",
1624                "user": {
1625                    "id": "test_user_id",
1626                    "email": "test@example.com",
1627                    "phone": null,
1628                    "app_metadata": {},
1629                    "user_metadata": {},
1630                    "created_at": "2021-01-01T00:00:00Z",
1631                    "updated_at": "2021-01-01T00:00:00Z"
1632                }
1633            });
1634
1635            Mock::given(method("POST"))
1636                .and(path("/auth/v1/signup"))
1637                .respond_with(ResponseTemplate::new(200).set_body_json(&response_body))
1638                .mount(&mock_server)
1639                .await;
1640
1641            let http_client = Client::new();
1642            let auth = Auth::new(
1643                &mock_server.uri(),
1644                "test_key",
1645                http_client,
1646                AuthOptions::default(),
1647            );
1648
1649            let result = auth.sign_up("test@example.com", "password123").await;
1650
1651            assert!(result.is_ok());
1652            let session = result.unwrap();
1653            assert_eq!(session.access_token, "test_access_token");
1654            assert_eq!(session.user.email, Some("test@example.com".to_string()));
1655        });
1656    }
1657
1658    #[test]
1659    fn test_oauth_sign_in_url() {
1660        tokio_test::block_on(async {
1661            let client = Client::new();
1662            let auth = Auth::new(
1663                "https://example.supabase.co",
1664                "test-key",
1665                client,
1666                AuthOptions::default(),
1667            );
1668
1669            let url = auth.get_oauth_sign_in_url(super::OAuthProvider::Google, None);
1670            assert!(url.contains("provider=google"));
1671
1672            let options = super::OAuthSignInOptions {
1673                redirect_to: Some("https://example.com/callback".to_string()),
1674                scopes: Some("email profile".to_string()),
1675                ..Default::default()
1676            };
1677
1678            let url_with_options =
1679                auth.get_oauth_sign_in_url(super::OAuthProvider::Github, Some(options));
1680            assert!(url_with_options.contains("provider=github"));
1681            assert!(url_with_options.contains("redirect_to="));
1682            assert!(url_with_options.contains("scopes="));
1683        });
1684    }
1685}