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 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}