Skip to main content

supabase_client_auth/
admin.rs

1use serde_json::json;
2
3use crate::client::AuthClient;
4use crate::error::AuthError;
5use crate::params::{
6    AdminCreateUserParams, AdminUpdateUserParams, CreateOAuthClientParams, GenerateLinkParams,
7    UpdateOAuthClientParams,
8};
9use crate::types::{AdminUserListResponse, Factor, OAuthClient, OAuthClientListResponse, User};
10
11/// Admin client for Supabase GoTrue admin operations.
12///
13/// Requires a `service_role` key. These operations should only be used server-side.
14///
15/// Mirrors `supabase.auth.admin`.
16///
17/// # Example
18/// ```ignore
19/// let admin = auth.admin();
20/// let users = admin.list_users(None, None).await?;
21/// ```
22#[derive(Debug)]
23pub struct AdminClient<'a> {
24    auth: &'a AuthClient,
25    service_role_key: Option<&'a str>,
26}
27
28impl<'a> AdminClient<'a> {
29    /// Create an admin client using the auth client's API key as the service role key.
30    pub(crate) fn new(auth: &'a AuthClient) -> Self {
31        Self {
32            auth,
33            service_role_key: None,
34        }
35    }
36
37    /// Create an admin client with an explicit service role key.
38    pub(crate) fn with_key(auth: &'a AuthClient, key: &'a str) -> Self {
39        Self {
40            auth,
41            service_role_key: Some(key),
42        }
43    }
44
45    fn bearer_token(&self) -> &str {
46        self.service_role_key.unwrap_or(self.auth.api_key())
47    }
48
49    /// List all users (paginated).
50    ///
51    /// Mirrors `supabase.auth.admin.listUsers({ page, perPage })`.
52    pub async fn list_users(
53        &self,
54        page: Option<u32>,
55        per_page: Option<u32>,
56    ) -> Result<AdminUserListResponse, AuthError> {
57        let mut url = self.auth.url("/admin/users");
58        {
59            let mut pairs = url.query_pairs_mut();
60            if let Some(page) = page {
61                pairs.append_pair("page", &page.to_string());
62            }
63            if let Some(per_page) = per_page {
64                pairs.append_pair("per_page", &per_page.to_string());
65            }
66        }
67
68        let resp = self
69            .auth
70            .http()
71            .get(url)
72            .bearer_auth(self.bearer_token())
73            .send()
74            .await?;
75
76        let status = resp.status().as_u16();
77        if status >= 400 {
78            return Err(parse_admin_error(status, resp).await);
79        }
80
81        let list: AdminUserListResponse = resp.json().await?;
82        Ok(list)
83    }
84
85    /// Get a user by their ID.
86    ///
87    /// Mirrors `supabase.auth.admin.getUserById(uid)`.
88    pub async fn get_user_by_id(&self, user_id: &str) -> Result<User, AuthError> {
89        let url = self.auth.url(&format!("/admin/users/{}", user_id));
90        let resp = self
91            .auth
92            .http()
93            .get(url)
94            .bearer_auth(self.bearer_token())
95            .send()
96            .await?;
97        self.auth.handle_user_response(resp).await
98    }
99
100    /// Create a new user (admin).
101    ///
102    /// Does not send confirmation emails. Use `invite_user_by_email` for that.
103    ///
104    /// Mirrors `supabase.auth.admin.createUser(attributes)`.
105    pub async fn create_user(
106        &self,
107        params: AdminCreateUserParams,
108    ) -> Result<User, AuthError> {
109        let url = self.auth.url("/admin/users");
110        let resp = self
111            .auth
112            .http()
113            .post(url)
114            .bearer_auth(self.bearer_token())
115            .json(&params)
116            .send()
117            .await?;
118        self.auth.handle_user_response(resp).await
119    }
120
121    /// Update a user by their ID (admin).
122    ///
123    /// Changes are applied immediately without confirmation flows.
124    ///
125    /// Mirrors `supabase.auth.admin.updateUserById(uid, attributes)`.
126    pub async fn update_user_by_id(
127        &self,
128        user_id: &str,
129        params: AdminUpdateUserParams,
130    ) -> Result<User, AuthError> {
131        let url = self.auth.url(&format!("/admin/users/{}", user_id));
132        let resp = self
133            .auth
134            .http()
135            .put(url)
136            .bearer_auth(self.bearer_token())
137            .json(&params)
138            .send()
139            .await?;
140        self.auth.handle_user_response(resp).await
141    }
142
143    /// Delete a user by their ID (hard delete).
144    ///
145    /// Mirrors `supabase.auth.admin.deleteUser(id)`.
146    pub async fn delete_user(&self, user_id: &str) -> Result<(), AuthError> {
147        self.delete_user_with_options(user_id, false).await
148    }
149
150    /// Delete a user by their ID with soft-delete option.
151    ///
152    /// When `soft_delete` is true, the user is soft-deleted (identifiable via hashed ID).
153    ///
154    /// Mirrors `supabase.auth.admin.deleteUser(id, shouldSoftDelete)`.
155    pub async fn delete_user_with_options(
156        &self,
157        user_id: &str,
158        soft_delete: bool,
159    ) -> Result<(), AuthError> {
160        let url = self.auth.url(&format!("/admin/users/{}", user_id));
161
162        let body = if soft_delete {
163            json!({ "should_soft_delete": true })
164        } else {
165            json!({})
166        };
167
168        let resp = self
169            .auth
170            .http()
171            .delete(url)
172            .bearer_auth(self.bearer_token())
173            .json(&body)
174            .send()
175            .await?;
176        self.auth.handle_empty_response(resp).await
177    }
178
179    /// Invite a user by email.
180    ///
181    /// Sends a confirmation/invitation email to the user.
182    pub async fn invite_user_by_email(
183        &self,
184        email: &str,
185        redirect_to: Option<&str>,
186    ) -> Result<User, AuthError> {
187        let mut body = json!({ "email": email });
188        if let Some(redirect) = redirect_to {
189            body["redirect_to"] = json!(redirect);
190        }
191
192        let url = self.auth.url("/invite");
193        let resp = self
194            .auth
195            .http()
196            .post(url)
197            .bearer_auth(self.bearer_token())
198            .json(&body)
199            .send()
200            .await?;
201        self.auth.handle_user_response(resp).await
202    }
203
204    /// Generate a link (signup, invite, magic link, recovery, email change).
205    pub async fn generate_link(
206        &self,
207        params: GenerateLinkParams,
208    ) -> Result<serde_json::Value, AuthError> {
209        let url = self.auth.url("/admin/generate_link");
210        let resp = self
211            .auth
212            .http()
213            .post(url)
214            .bearer_auth(self.bearer_token())
215            .json(&params)
216            .send()
217            .await?;
218
219        let status = resp.status().as_u16();
220        if status >= 400 {
221            return Err(parse_admin_error(status, resp).await);
222        }
223
224        let value: serde_json::Value = resp.json().await?;
225        Ok(value)
226    }
227
228    // ─── MFA Admin ─────────────────────────────────────────────
229
230    /// List MFA factors for a user (admin).
231    ///
232    /// Mirrors `supabase.auth.admin.mfa.listFactors()`.
233    pub async fn mfa_list_factors(&self, user_id: &str) -> Result<Vec<Factor>, AuthError> {
234        let url = self
235            .auth
236            .url(&format!("/admin/users/{}/factors", user_id));
237        let resp = self
238            .auth
239            .http()
240            .get(url)
241            .bearer_auth(self.bearer_token())
242            .send()
243            .await?;
244
245        let status = resp.status().as_u16();
246        if status >= 400 {
247            return Err(parse_admin_error(status, resp).await);
248        }
249
250        let factors: Vec<Factor> = resp.json().await?;
251        Ok(factors)
252    }
253
254    // ─── OAuth Client Management ─────────────────────────────
255
256    /// List all registered OAuth clients (paginated).
257    ///
258    /// Mirrors `supabase.auth.admin.oauth.listClients()`.
259    pub async fn oauth_list_clients(
260        &self,
261        page: Option<u32>,
262        per_page: Option<u32>,
263    ) -> Result<OAuthClientListResponse, AuthError> {
264        let mut url = self.auth.url("/admin/oauth/clients");
265        {
266            let mut pairs = url.query_pairs_mut();
267            if let Some(page) = page {
268                pairs.append_pair("page", &page.to_string());
269            }
270            if let Some(per_page) = per_page {
271                pairs.append_pair("per_page", &per_page.to_string());
272            }
273        }
274
275        let resp = self
276            .auth
277            .http()
278            .get(url)
279            .bearer_auth(self.bearer_token())
280            .send()
281            .await?;
282
283        let status = resp.status().as_u16();
284        if status >= 400 {
285            return Err(parse_admin_error(status, resp).await);
286        }
287
288        let list: OAuthClientListResponse = resp.json().await?;
289        Ok(list)
290    }
291
292    /// Create a new OAuth client.
293    ///
294    /// Mirrors `supabase.auth.admin.oauth.createClient()`.
295    pub async fn oauth_create_client(
296        &self,
297        params: CreateOAuthClientParams,
298    ) -> Result<OAuthClient, AuthError> {
299        let url = self.auth.url("/admin/oauth/clients");
300        let resp = self
301            .auth
302            .http()
303            .post(url)
304            .bearer_auth(self.bearer_token())
305            .json(&params)
306            .send()
307            .await?;
308
309        let status = resp.status().as_u16();
310        if status >= 400 {
311            return Err(parse_admin_error(status, resp).await);
312        }
313
314        let client: OAuthClient = resp.json().await?;
315        Ok(client)
316    }
317
318    /// Get an OAuth client by its client ID.
319    ///
320    /// Mirrors `supabase.auth.admin.oauth.getClient()`.
321    pub async fn oauth_get_client(
322        &self,
323        client_id: &str,
324    ) -> Result<OAuthClient, AuthError> {
325        let url = self.auth.url(&format!("/admin/oauth/clients/{}", client_id));
326        let resp = self
327            .auth
328            .http()
329            .get(url)
330            .bearer_auth(self.bearer_token())
331            .send()
332            .await?;
333
334        let status = resp.status().as_u16();
335        if status >= 400 {
336            return Err(parse_admin_error(status, resp).await);
337        }
338
339        let client: OAuthClient = resp.json().await?;
340        Ok(client)
341    }
342
343    /// Update an OAuth client.
344    ///
345    /// Mirrors `supabase.auth.admin.oauth.updateClient()`.
346    pub async fn oauth_update_client(
347        &self,
348        client_id: &str,
349        params: UpdateOAuthClientParams,
350    ) -> Result<OAuthClient, AuthError> {
351        let url = self.auth.url(&format!("/admin/oauth/clients/{}", client_id));
352        let resp = self
353            .auth
354            .http()
355            .put(url)
356            .bearer_auth(self.bearer_token())
357            .json(&params)
358            .send()
359            .await?;
360
361        let status = resp.status().as_u16();
362        if status >= 400 {
363            return Err(parse_admin_error(status, resp).await);
364        }
365
366        let client: OAuthClient = resp.json().await?;
367        Ok(client)
368    }
369
370    /// Delete an OAuth client.
371    ///
372    /// Mirrors `supabase.auth.admin.oauth.deleteClient()`.
373    pub async fn oauth_delete_client(
374        &self,
375        client_id: &str,
376    ) -> Result<(), AuthError> {
377        let url = self.auth.url(&format!("/admin/oauth/clients/{}", client_id));
378        let resp = self
379            .auth
380            .http()
381            .delete(url)
382            .bearer_auth(self.bearer_token())
383            .send()
384            .await?;
385        self.auth.handle_empty_response(resp).await
386    }
387
388    /// Regenerate the client secret for an OAuth client.
389    ///
390    /// Mirrors `supabase.auth.admin.oauth.regenerateClientSecret()`.
391    pub async fn oauth_regenerate_client_secret(
392        &self,
393        client_id: &str,
394    ) -> Result<OAuthClient, AuthError> {
395        let url = self.auth.url(&format!(
396            "/admin/oauth/clients/{}/regenerate_secret",
397            client_id
398        ));
399        let resp = self
400            .auth
401            .http()
402            .post(url)
403            .bearer_auth(self.bearer_token())
404            .send()
405            .await?;
406
407        let status = resp.status().as_u16();
408        if status >= 400 {
409            return Err(parse_admin_error(status, resp).await);
410        }
411
412        let client: OAuthClient = resp.json().await?;
413        Ok(client)
414    }
415
416    /// Delete an MFA factor for a user (admin).
417    ///
418    /// Mirrors `supabase.auth.admin.mfa.deleteFactor()`.
419    pub async fn mfa_delete_factor(
420        &self,
421        user_id: &str,
422        factor_id: &str,
423    ) -> Result<(), AuthError> {
424        let url = self
425            .auth
426            .url(&format!("/admin/users/{}/factors/{}", user_id, factor_id));
427        let resp = self
428            .auth
429            .http()
430            .delete(url)
431            .bearer_auth(self.bearer_token())
432            .send()
433            .await?;
434        self.auth.handle_empty_response(resp).await
435    }
436}
437
438async fn parse_admin_error(status: u16, resp: reqwest::Response) -> AuthError {
439    use crate::error::GoTrueErrorResponse;
440
441    match resp.json::<GoTrueErrorResponse>().await {
442        Ok(err_resp) => {
443            let error_code = err_resp.error_code.as_deref().map(|s| s.into());
444            AuthError::Api {
445                status,
446                message: err_resp.error_message(),
447                error_code,
448            }
449        }
450        Err(_) => AuthError::Api {
451            status,
452            message: format!("HTTP {}", status),
453            error_code: None,
454        },
455    }
456}