supabase_rust/
auth.rs

1use jsonwebtoken::{DecodingKey, Validation, Algorithm, decode};
2use reqwest::{Error, Response};
3use serde::{Deserialize, Serialize};
4
5use crate::Supabase;
6
7#[derive(Serialize, Deserialize)]
8pub struct Password {
9    email: String,
10    password: String,
11}
12
13#[derive(Serialize, Deserialize)]
14pub struct RefreshToken {
15    refresh_token: String,
16}
17
18#[derive(Debug, Serialize, Deserialize)]
19pub struct Claims {
20    pub sub: String,
21    pub email: String,
22    pub exp: usize,
23}
24
25impl Clone for Claims {
26    fn clone(&self) -> Self {
27        Self {
28            sub: self.sub.clone(),
29            email: self.email.clone(),
30            exp: self.exp,
31        }
32    }
33}
34
35impl Supabase {
36    pub async fn jwt_valid(
37        &self,
38        jwt: &str,
39    ) -> Result<Claims, jsonwebtoken::errors::Error> {
40        let secret = self.jwt.clone();
41
42        let decoding_key = DecodingKey::from_secret(secret.as_ref()).into();
43        let validation = Validation::new(Algorithm::HS256);
44        let decoded_token = decode::<Claims>(&jwt, &decoding_key, &validation);
45
46        match decoded_token {
47            Ok(token_data) => {
48                println!("Token is valid. Claims: {:?}", token_data.claims);
49                Ok(token_data.claims)
50            }
51            Err(err) => {
52                println!("Error decoding token: {:?}", err);
53                Err(err)
54            }
55        }
56    }
57
58    pub async fn sign_in_password(
59        &self,
60        email: &str,
61        password: &str,
62    ) -> Result<Response, Error> {
63        let request_url: String = format!("{}/auth/v1/token?grant_type=password", self.url);
64        let response: Response = self
65            .client
66            .post(&request_url)
67            .header("apikey", &self.api_key)
68            .header("Content-Type", "application/json")
69            .json(&Password {
70                email: email.to_string(),
71                password: password.to_string(),
72            })
73            .send()
74            .await?;
75        Ok(response)
76    }
77
78    // This test will fail unless you disable "Enable automatic reuse detection" in Supabase
79    pub async fn refresh_token(&self, refresh_token: &str) -> Result<Response, Error> {
80        let request_url: String = format!("{}/auth/v1/token?grant_type=refresh_token", self.url);
81        let response: Response = self
82            .client
83            .post(&request_url)
84            .header("apikey", &self.api_key)
85            .header("Content-Type", "application/json")
86            .json(&RefreshToken {
87                refresh_token: refresh_token.to_string(),
88            })
89            .send()
90            .await?;
91        Ok(response)
92    }
93
94    pub async fn logout(&self) -> Result<Response, Error> {
95        let request_url: String = format!("{}/auth/v1/logout", self.url);
96        let token = self.bearer_token.clone().unwrap();
97        let response: Response = self
98            .client
99            .post(&request_url)
100            .header("apikey", &self.api_key)
101            .header("Content-Type", "application/json")
102            .bearer_auth(token)
103            .send()
104            .await?;
105        Ok(response)
106    }
107
108    pub async fn signup_email_password(
109        &self,
110        email: &str,
111        password: &str,
112    ) -> Result<Response, Error> {
113        let request_url: String = format!("{}/auth/v1/signup", self.url);
114        let response: Response = self
115            .client
116            .post(&request_url)
117            .header("apikey", &self.api_key)
118            .header("Content-Type", "application/json")
119            .json(&Password {
120                email: email.to_string(),
121                password: password.to_string(),
122            })
123            .send()
124            .await?;
125        Ok(response)
126    }
127}
128
129#[cfg(test)]
130mod tests {
131    use super::*;
132
133    async fn client() -> Supabase {
134        Supabase::new(None, None, None)
135    }
136
137    async fn sign_in_password() -> Response {
138        let client: Supabase = client().await;
139
140        let test_email: String = std::env::var("SUPABASE_TEST_EMAIL").unwrap_or_else(|_| String::new());
141        let test_pass: String= std::env::var("SUPABASE_TEST_PASS").unwrap_or_else(|_| String::new());
142        client.sign_in_password(&test_email, &test_pass).await.unwrap()
143    }
144
145    #[tokio::test]
146    async fn test_token_with_password() {
147        let response: Response = sign_in_password().await;
148
149        let json_response: serde_json::Value = response.json().await.unwrap();
150        let token: &str = json_response["access_token"].as_str().unwrap();
151        let refresh_token: &str = json_response["refresh_token"].as_str().unwrap();
152
153        assert!(token.len() > 0);
154        assert!(refresh_token.len() > 0);
155    }
156
157    #[tokio::test]
158    async fn test_refresh() {
159        let response: Response = sign_in_password().await;
160
161        let json_response: serde_json::Value = response.json().await.unwrap();
162        let refresh_token: &str = json_response["refresh_token"].as_str().unwrap();
163
164        let response: Response = client().await.refresh_token(&refresh_token).await.unwrap();
165        if response.status() == 400 {
166            println!("Skipping test_refresh() because automatic reuse detection is enabled in Supabase");
167            return;
168        }
169
170        let json_response: serde_json::Value = response.json().await.unwrap();
171        let token: &str = json_response["access_token"].as_str().unwrap();
172
173        assert!(token.len() > 0);
174    }
175
176    #[tokio::test]
177    async fn test_logout() {
178        let response: Response = sign_in_password().await;
179
180        let json_response: serde_json::Value = response.json().await.unwrap();
181        let access_token: &str = json_response["access_token"].as_str().unwrap();
182        let mut client: Supabase = client().await;
183        client.bearer_token = Some(access_token.to_string());
184
185        let response: Response = client.logout().await.unwrap();
186
187        assert!(response.status() == 204);
188    }
189
190    #[tokio::test]
191    async fn test_signup_email_password() {
192        use rand::{thread_rng, Rng, distributions::Alphanumeric};
193
194        let client: Supabase = client().await;
195
196        let rand_string: String = thread_rng()
197            .sample_iter(&Alphanumeric)
198            .take(20)
199            .map(char::from)
200            .collect();
201
202        let random_email: String = format!("{}@a-rust-domain-that-does-not-exist.com", rand_string);
203        let random_pass: String = rand_string;
204
205        let test_email: String = random_email;
206        let test_pass: String= random_pass;
207        let response: Response = client.signup_email_password(&test_email, &test_pass).await.unwrap();
208
209        assert!(response.status() == 200);
210    }
211
212    #[tokio::test]
213    async fn test_authenticate_token() {
214        let client: Supabase = client().await;
215        let response: Response = sign_in_password().await;
216
217        let json_response: serde_json::Value = response.json().await.unwrap();
218        let token: &str = json_response["access_token"].as_str().unwrap();
219
220        let response = client.jwt_valid(token).await;
221
222        match response {
223            Ok(_) => {
224                assert!(true);
225            },
226            Err(_) => {
227                assert!(false);
228            }
229        }
230    }
231
232}