supabase_auth_redux/lib.rs
1//! # Supabase Auth Rust Client
2//!
3//! A Rust client library for interacting with the Supabase Auth API.
4//!
5//! This library provides a simple and type-safe way to integrate Supabase authentication
6//! into your Rust applications, supporting common authentication flows like signup,
7//! signin, token refresh, and user management.
8//!
9//! ## Example
10//!
11//! ```rust,no_run
12//! use supabase_auth_redux::{AuthClient, AuthError, IdType};
13//!
14//! # async fn example() -> Result<(), AuthError> {
15//! // Initialize the client
16//! let auth_client = AuthClient::new("https://your-project.supabase.co", "your-anon-key")?;
17//!
18//! // Sign up a new user
19//! let (user, access_token) = auth_client
20//! .signup(
21//! IdType::Email("user@example.com".to_string()),
22//! "secure_password".to_string(),
23//! None,
24//! )
25//! .await?;
26//!
27//! // Sign in an existing user
28//! let token_response = auth_client
29//! .signin_with_password(
30//! IdType::Email("user@example.com".to_string()),
31//! "secure_password".to_string(),
32//! )
33//! .await?;
34//! # Ok(())
35//! # }
36//! ```
37
38#![warn(clippy::all)]
39#![warn(missing_docs)]
40
41use std::fmt::{Debug, Display, Formatter};
42
43use postgrest::Postgrest;
44use serde::{Deserialize, Serialize};
45use thiserror::Error;
46
47pub use error::AuthError;
48pub use models::token::TokenResponse;
49pub use models::user::UserSchema as User;
50
51// Re-export for backward compatibility
52#[allow(unused)]
53#[deprecated(
54 since = "0.1.0",
55 note = "Use specific error types from AuthError instead"
56)]
57pub use GoTrueErrorResponse as Error;
58
59mod delete_user;
60mod error;
61mod get_user;
62mod logout;
63pub mod models;
64mod refresh_token;
65mod signin_with_password;
66mod signup;
67mod util;
68
69/// The main authentication client for interacting with Supabase Auth API
70///
71/// This client handles all authentication operations including user signup,
72/// signin, token management, and user administration.
73#[derive(Clone)]
74pub struct AuthClient {
75 /// HTTP client for making API requests
76 http_client: reqwest::Client,
77 /// Base URL of the Supabase API (e.g., `https://your-project.supabase.co`)
78 supabase_api_url: String,
79 /// Anonymous key for public API access
80 supabase_anon_key: String,
81 /// Optional service role key for admin operations
82 supabase_service_role_key: Option<String>,
83 /// PostgREST client for direct database queries
84 postgrest_client: Postgrest,
85}
86
87impl Debug for AuthClient {
88 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
89 f.write_str("AuthClient")
90 }
91}
92
93impl AuthClient {
94 /// Creates a new authentication client with the given API URL and anonymous key
95 ///
96 /// # Arguments
97 ///
98 /// * `api_url` - The base URL of your Supabase instance
99 /// * `anon_key` - The anonymous key for your Supabase project
100 ///
101 /// # Example
102 ///
103 /// ```rust,no_run
104 /// use supabase_auth_redux::AuthClient;
105 ///
106 /// let client = AuthClient::new("https://your-project.supabase.co", "your-anon-key")
107 /// .expect("Failed to create auth client");
108 /// ```
109 pub fn new(api_url: &str, anon_key: &str) -> Result<Self, AuthError> {
110 if api_url.is_empty() {
111 return Err(AuthError::InvalidParameters);
112 }
113 if anon_key.is_empty() {
114 return Err(AuthError::InvalidParameters);
115 }
116
117 Ok(Self {
118 http_client: reqwest::Client::new(),
119 supabase_api_url: api_url.to_owned(),
120 supabase_anon_key: anon_key.to_owned(),
121 supabase_service_role_key: None,
122 postgrest_client: Postgrest::new(format!("{}/rest/v1/", api_url.to_owned()))
123 .schema("auth")
124 .insert_header("apikey", anon_key),
125 })
126 }
127
128 /// Creates a new builder for constructing an AuthClient with additional options
129 ///
130 /// # Example
131 ///
132 /// ```rust,no_run
133 /// use supabase_auth_redux::AuthClient;
134 ///
135 /// let client = AuthClient::builder()
136 /// .api_url("https://your-project.supabase.co")
137 /// .anon_key("your-anon-key")
138 /// .service_role_key("your-service-role-key")
139 /// .build()
140 /// .expect("Failed to create auth client");
141 /// ```
142 pub fn builder() -> AuthClientBuilder {
143 AuthClientBuilder::default()
144 }
145}
146
147/// Builder for constructing an AuthClient with custom configuration
148#[derive(Default)]
149pub struct AuthClientBuilder {
150 /// API URL for the Supabase instance
151 api_url: Option<String>,
152 /// Anonymous key for the Supabase project
153 anon_key: Option<String>,
154 /// Optional service role key for admin operations
155 service_role_key: Option<String>,
156}
157
158impl AuthClientBuilder {
159 /// Sets the API URL for the Supabase instance
160 pub fn api_url(mut self, url: &str) -> Self {
161 self.api_url = Some(url.to_string());
162 self
163 }
164
165 /// Sets the anonymous key for the Supabase project
166 pub fn anon_key(mut self, key: &str) -> Self {
167 self.anon_key = Some(key.to_string());
168 self
169 }
170
171 /// Sets the service role key for admin operations
172 pub fn service_role_key(mut self, key: &str) -> Self {
173 self.service_role_key = Some(key.to_string());
174 self
175 }
176
177 /// Builds the AuthClient with the configured settings
178 ///
179 /// # Errors
180 ///
181 /// Returns `AuthError::InvalidParameters` if required fields are missing
182 pub fn build(self) -> Result<AuthClient, AuthError> {
183 let api_url = self.api_url.ok_or(AuthError::InvalidParameters)?;
184 let anon_key = self.anon_key.ok_or(AuthError::InvalidParameters)?;
185
186 Ok(AuthClient {
187 http_client: reqwest::Client::new(),
188 supabase_api_url: api_url.clone(),
189 supabase_anon_key: anon_key.clone(),
190 supabase_service_role_key: self.service_role_key,
191 postgrest_client: Postgrest::new(format!("{}/rest/v1/", api_url))
192 .schema("auth")
193 .insert_header("apikey", &anon_key),
194 })
195 }
196}
197
198/// Error response from the GoTrue/Supabase Auth API
199#[derive(Debug, Error, Deserialize, Serialize)]
200pub struct GoTrueErrorResponse {
201 /// Error code number from the API
202 pub code: Option<u8>,
203 /// Primary error message
204 pub error: Option<String>,
205 /// Detailed error description
206 pub error_description: Option<String>,
207 /// Alternative error message field used by some endpoints
208 pub msg: Option<String>,
209}
210
211impl Display for GoTrueErrorResponse {
212 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
213 if let Some(ref e) = self.error {
214 f.write_str(e)?;
215 return Ok(());
216 }
217 if let Some(ref msg) = self.msg {
218 f.write_str(msg)?;
219 return Ok(());
220 }
221 Err(std::fmt::Error)
222 }
223}
224
225/// Identifier type for authentication operations
226#[derive(Debug)]
227pub enum IdType {
228 /// Email-based authentication
229 Email(String),
230 /// Phone number-based authentication
231 PhoneNumber(String),
232}