66from pydantic import TypeAdapter
77
88from ..helpers import (
9- is_valid_uuid ,
9+ validate_uuid ,
1010 model_validate ,
1111 parse_link_response ,
1212 parse_user_response ,
1818 AuthMFAAdminDeleteFactorResponse ,
1919 AuthMFAAdminListFactorsParams ,
2020 AuthMFAAdminListFactorsResponse ,
21+ CreateOAuthClientParams ,
2122 GenerateLinkParams ,
2223 GenerateLinkResponse ,
2324 InviteUserByEmailOptions ,
25+ OAuthClient ,
26+ OAuthClientListResponse ,
27+ OAuthClientResponse ,
28+ PageParams ,
2429 SignOutScope ,
30+ UpdateOAuthClientParams ,
2531 User ,
2632 UserList ,
2733 UserResponse ,
2834)
2935from .gotrue_admin_mfa_api import AsyncGoTrueAdminMFAAPI
36+ from .gotrue_admin_oauth_api import AsyncGoTrueAdminOAuthAPI
3037from .gotrue_base_api import AsyncGoTrueBaseAPI
3138
3239
@@ -50,8 +57,15 @@ def __init__(
5057 )
5158 # TODO(@o-santi): why is is this done this way?
5259 self .mfa = AsyncGoTrueAdminMFAAPI ()
53- self .mfa .list_factors = self ._list_factors # type: ignore
54- self .mfa .delete_factor = self ._delete_factor # type: ignore
60+ self .mfa .list_factors = self ._list_factors # type: ignore
61+ self .mfa .delete_factor = self ._delete_factor # type: ignore
62+ self .oauth = AsyncGoTrueAdminOAuthAPI ()
63+ self .oauth .list_clients = self ._list_oauth_clients # type: ignore
64+ self .oauth .create_client = self ._create_oauth_client # type: ignore
65+ self .oauth .get_client = self ._get_oauth_client # type: ignore
66+ self .oauth .update_client = self ._update_oauth_client # type: ignore
67+ self .oauth .delete_client = self ._delete_oauth_client # type: ignore
68+ self .oauth .regenerate_client_secret = self ._regenerate_oauth_client_secret # type: ignore
5569
5670 async def sign_out (self , jwt : str , scope : SignOutScope = "global" ) -> None :
5771 """
@@ -139,7 +153,7 @@ async def get_user_by_id(self, uid: str) -> UserResponse:
139153 This function should only be called on a server.
140154 Never expose your `service_role` key in the browser.
141155 """
142- self . _validate_uuid (uid )
156+ validate_uuid (uid )
143157
144158 response = await self ._request (
145159 "GET" ,
@@ -158,7 +172,7 @@ async def update_user_by_id(
158172 This function should only be called on a server.
159173 Never expose your `service_role` key in the browser.
160174 """
161- self . _validate_uuid (uid )
175+ validate_uuid (uid )
162176 response = await self ._request (
163177 "PUT" ,
164178 f"admin/users/{ uid } " ,
@@ -173,15 +187,15 @@ async def delete_user(self, id: str, should_soft_delete: bool = False) -> None:
173187 This function should only be called on a server.
174188 Never expose your `service_role` key in the browser.
175189 """
176- self . _validate_uuid (id )
190+ validate_uuid (id )
177191 body = {"should_soft_delete" : should_soft_delete }
178192 await self ._request ("DELETE" , f"admin/users/{ id } " , body = body )
179193
180194 async def _list_factors (
181195 self ,
182196 params : AuthMFAAdminListFactorsParams ,
183197 ) -> AuthMFAAdminListFactorsResponse :
184- self . _validate_uuid (params .get ("user_id" ))
198+ validate_uuid (params .get ("user_id" ))
185199 response = await self ._request (
186200 "GET" ,
187201 f"admin/users/{ params .get ('user_id' )} /factors" ,
@@ -192,16 +206,154 @@ async def _delete_factor(
192206 self ,
193207 params : AuthMFAAdminDeleteFactorParams ,
194208 ) -> AuthMFAAdminDeleteFactorResponse :
195- self . _validate_uuid (params .get ("user_id" ))
196- self . _validate_uuid (params .get ("id" ))
209+ validate_uuid (params .get ("user_id" ))
210+ validate_uuid (params .get ("id" ))
197211 response = await self ._request (
198212 "DELETE" ,
199213 f"admin/users/{ params .get ('user_id' )} /factors/{ params .get ('id' )} " ,
200214 )
201215 return model_validate (AuthMFAAdminDeleteFactorResponse , response .content )
202216
203- def _validate_uuid (self , id : str | None ) -> None :
204- if id is None :
205- raise ValueError ("Invalid id, id cannot be none" )
206- if not is_valid_uuid (id ):
207- raise ValueError (f"Invalid id, '{ id } ' is not a valid uuid" )
217+ async def _list_oauth_clients (
218+ self ,
219+ params : PageParams | None = None ,
220+ ) -> OAuthClientListResponse :
221+ """
222+ Lists all OAuth clients with optional pagination.
223+ Only relevant when the OAuth 2.1 server is enabled in Supabase Auth.
224+
225+ This function should only be called on a server.
226+ Never expose your `service_role` key in the browser.
227+ """
228+ if params :
229+ query = QueryParams (page = params .page , per_page = params .per_page )
230+ else :
231+ query = None
232+ response = await self ._request (
233+ "GET" ,
234+ "admin/oauth/clients" ,
235+ query = query ,
236+ no_resolve_json = True ,
237+ )
238+
239+ result = model_validate (OAuthClientListResponse , response .content )
240+
241+ # Parse pagination headers
242+ total = response .headers .get ("x-total-count" )
243+ if total :
244+ result .total = int (total )
245+
246+ links = response .headers .get ("link" )
247+ if links :
248+ for link in links .split ("," ):
249+ parts = link .split (";" )
250+ if len (parts ) >= 2 :
251+ page_match = parts [0 ].split ("page=" )
252+ if len (page_match ) >= 2 :
253+ page_num = int (page_match [1 ].split ("&" )[0 ].rstrip (">" ))
254+ rel = parts [1 ].split ("=" )[1 ].strip ('"' )
255+ if rel == "next" :
256+ result .next_page = page_num
257+ elif rel == "last" :
258+ result .last_page = page_num
259+
260+ return result
261+
262+ async def _create_oauth_client (
263+ self ,
264+ params : CreateOAuthClientParams ,
265+ ) -> OAuthClientResponse :
266+ """
267+ Creates a new OAuth client.
268+ Only relevant when the OAuth 2.1 server is enabled in Supabase Auth.
269+
270+ This function should only be called on a server.
271+ Never expose your `service_role` key in the browser.
272+ """
273+ response = await self ._request (
274+ "POST" ,
275+ "admin/oauth/clients" ,
276+ body = params ,
277+ )
278+
279+ return OAuthClientResponse (
280+ client = model_validate (OAuthClient , response .content )
281+ )
282+ async def _get_oauth_client (
283+ self ,
284+ client_id : str ,
285+ ) -> OAuthClientResponse :
286+ """
287+ Gets details of a specific OAuth client.
288+ Only relevant when the OAuth 2.1 server is enabled in Supabase Auth.
289+
290+ This function should only be called on a server.
291+ Never expose your `service_role` key in the browser.
292+ """
293+ validate_uuid (client_id )
294+ response = await self ._request (
295+ "GET" ,
296+ f"admin/oauth/clients/{ client_id } " ,
297+ )
298+ return OAuthClientResponse (
299+ client = model_validate (OAuthClient , response .content )
300+ )
301+
302+ async def _update_oauth_client (
303+ self ,
304+ client_id : str ,
305+ params : UpdateOAuthClientParams ,
306+ ) -> OAuthClientResponse :
307+ """
308+ Updates an OAuth client.
309+ Only relevant when the OAuth 2.1 server is enabled in Supabase Auth.
310+
311+ This function should only be called on a server.
312+ Never expose your `service_role` key in the browser.
313+ """
314+ validate_uuid (client_id )
315+ response = await self ._request (
316+ "PUT" ,
317+ f"admin/oauth/clients/{ client_id } " ,
318+ body = params ,
319+ )
320+ return OAuthClientResponse (
321+ client = model_validate (OAuthClient , response .content )
322+ )
323+
324+ async def _delete_oauth_client (
325+ self ,
326+ client_id : str ,
327+ ) -> None :
328+ """
329+ Deletes an OAuth client.
330+ Only relevant when the OAuth 2.1 server is enabled in Supabase Auth.
331+
332+ This function should only be called on a server.
333+ Never expose your `service_role` key in the browser.
334+ """
335+ validate_uuid (client_id )
336+ await self ._request (
337+ "DELETE" ,
338+ f"admin/oauth/clients/{ client_id } " ,
339+ )
340+
341+ async def _regenerate_oauth_client_secret (
342+ self ,
343+ client_id : str ,
344+ ) -> OAuthClientResponse :
345+ """
346+ Regenerates the secret for an OAuth client.
347+ Only relevant when the OAuth 2.1 server is enabled in Supabase Auth.
348+
349+ This function should only be called on a server.
350+ Never expose your `service_role` key in the browser.
351+ """
352+ validate_uuid (client_id )
353+ response = await self ._request (
354+ "POST" ,
355+ f"admin/oauth/clients/{ client_id } /regenerate_secret" ,
356+ )
357+ return OAuthClientResponse (
358+ client = model_validate (OAuthClient , response .content )
359+ )
0 commit comments