From c8b59d07e649e2528207ccaabfbcefb895d83bf5 Mon Sep 17 00:00:00 2001 From: timu-jesse-ezell <125536055+timu-jesse-ezell@users.noreply.github.com> Date: Thu, 30 Oct 2025 14:05:01 -0700 Subject: [PATCH 1/3] Return auth_response instead of response on sign-in --- src/auth/src/supabase_auth/_async/gotrue_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/auth/src/supabase_auth/_async/gotrue_client.py b/src/auth/src/supabase_auth/_async/gotrue_client.py index 9d1a5855..e3cc9759 100644 --- a/src/auth/src/supabase_auth/_async/gotrue_client.py +++ b/src/auth/src/supabase_auth/_async/gotrue_client.py @@ -1184,7 +1184,7 @@ async def exchange_code_for_session(self, params: CodeExchangeParams): if auth_response.session: await self._save_session(auth_response.session) self._notify_all_subscribers("SIGNED_IN", auth_response.session) - return response + return auth_response async def _fetch_jwks(self, kid: str, jwks: JWKSet) -> JWK: jwk: Optional[JWK] = None From d9bec94527484c848d9c95e65edc120b35ef1e24 Mon Sep 17 00:00:00 2001 From: Andrew Smith Date: Fri, 31 Oct 2025 10:38:31 +0000 Subject: [PATCH 2/3] chore(auth): build sync client --- src/auth/src/supabase_auth/_sync/gotrue_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/auth/src/supabase_auth/_sync/gotrue_client.py b/src/auth/src/supabase_auth/_sync/gotrue_client.py index 9e5ea677..b330b37e 100644 --- a/src/auth/src/supabase_auth/_sync/gotrue_client.py +++ b/src/auth/src/supabase_auth/_sync/gotrue_client.py @@ -1184,7 +1184,7 @@ def exchange_code_for_session(self, params: CodeExchangeParams): if auth_response.session: self._save_session(auth_response.session) self._notify_all_subscribers("SIGNED_IN", auth_response.session) - return response + return auth_response def _fetch_jwks(self, kid: str, jwks: JWKSet) -> JWK: jwk: Optional[JWK] = None From 77fde81f34d913e2fd25e187956c4e6a103e8610 Mon Sep 17 00:00:00 2001 From: Leonardo Santiago Date: Fri, 31 Oct 2025 09:47:35 -0300 Subject: [PATCH 3/3] chore(auth): add type annotations for function --- .../supabase_auth/_async/gotrue_admin_api.py | 35 +++++++--------- .../_async/gotrue_admin_oauth_api.py | 3 +- .../src/supabase_auth/_async/gotrue_client.py | 4 +- .../supabase_auth/_sync/gotrue_admin_api.py | 35 +++++++--------- .../_sync/gotrue_admin_oauth_api.py | 3 +- .../src/supabase_auth/_sync/gotrue_client.py | 14 ++----- src/auth/src/supabase_auth/helpers.py | 3 +- src/auth/src/supabase_auth/types.py | 6 ++- .../tests/_async/test_gotrue_admin_api.py | 3 ++ src/auth/tests/_sync/test_gotrue.py | 4 +- src/auth/tests/_sync/test_gotrue_admin_api.py | 41 ++++++++++--------- 11 files changed, 72 insertions(+), 79 deletions(-) diff --git a/src/auth/src/supabase_auth/_async/gotrue_admin_api.py b/src/auth/src/supabase_auth/_async/gotrue_admin_api.py index 84e2deb8..4d80f4f5 100644 --- a/src/auth/src/supabase_auth/_async/gotrue_admin_api.py +++ b/src/auth/src/supabase_auth/_async/gotrue_admin_api.py @@ -6,10 +6,10 @@ from pydantic import TypeAdapter from ..helpers import ( - validate_uuid, model_validate, parse_link_response, parse_user_response, + validate_uuid, ) from ..http_clients import AsyncClient from ..types import ( @@ -57,15 +57,15 @@ def __init__( ) # TODO(@o-santi): why is is this done this way? self.mfa = AsyncGoTrueAdminMFAAPI() - self.mfa.list_factors = self._list_factors # type: ignore - self.mfa.delete_factor = self._delete_factor # type: ignore + self.mfa.list_factors = self._list_factors # type: ignore + self.mfa.delete_factor = self._delete_factor # type: ignore self.oauth = AsyncGoTrueAdminOAuthAPI() - self.oauth.list_clients = self._list_oauth_clients # type: ignore - self.oauth.create_client = self._create_oauth_client # type: ignore - self.oauth.get_client = self._get_oauth_client # type: ignore - self.oauth.update_client = self._update_oauth_client # type: ignore - self.oauth.delete_client = self._delete_oauth_client # type: ignore - self.oauth.regenerate_client_secret = self._regenerate_oauth_client_secret # type: ignore + self.oauth.list_clients = self._list_oauth_clients # type: ignore + self.oauth.create_client = self._create_oauth_client # type: ignore + self.oauth.get_client = self._get_oauth_client # type: ignore + self.oauth.update_client = self._update_oauth_client # type: ignore + self.oauth.delete_client = self._delete_oauth_client # type: ignore + self.oauth.regenerate_client_secret = self._regenerate_oauth_client_secret # type: ignore async def sign_out(self, jwt: str, scope: SignOutScope = "global") -> None: """ @@ -276,9 +276,8 @@ async def _create_oauth_client( body=params, ) - return OAuthClientResponse( - client=model_validate(OAuthClient, response.content) - ) + return OAuthClientResponse(client=model_validate(OAuthClient, response.content)) + async def _get_oauth_client( self, client_id: str, @@ -295,9 +294,7 @@ async def _get_oauth_client( "GET", f"admin/oauth/clients/{client_id}", ) - return OAuthClientResponse( - client=model_validate(OAuthClient, response.content) - ) + return OAuthClientResponse(client=model_validate(OAuthClient, response.content)) async def _update_oauth_client( self, @@ -317,9 +314,7 @@ async def _update_oauth_client( f"admin/oauth/clients/{client_id}", body=params, ) - return OAuthClientResponse( - client=model_validate(OAuthClient, response.content) - ) + return OAuthClientResponse(client=model_validate(OAuthClient, response.content)) async def _delete_oauth_client( self, @@ -354,6 +349,4 @@ async def _regenerate_oauth_client_secret( "POST", f"admin/oauth/clients/{client_id}/regenerate_secret", ) - return OAuthClientResponse( - client=model_validate(OAuthClient, response.content) - ) + return OAuthClientResponse(client=model_validate(OAuthClient, response.content)) diff --git a/src/auth/src/supabase_auth/_async/gotrue_admin_oauth_api.py b/src/auth/src/supabase_auth/_async/gotrue_admin_oauth_api.py index 3b4e1c8e..d0fffe9b 100644 --- a/src/auth/src/supabase_auth/_async/gotrue_admin_oauth_api.py +++ b/src/auth/src/supabase_auth/_async/gotrue_admin_oauth_api.py @@ -1,3 +1,5 @@ +from typing import Optional + from ..types import ( CreateOAuthClientParams, OAuthClientListResponse, @@ -5,7 +7,6 @@ PageParams, UpdateOAuthClientParams, ) -from typing import Optional class AsyncGoTrueAdminOAuthAPI: diff --git a/src/auth/src/supabase_auth/_async/gotrue_client.py b/src/auth/src/supabase_auth/_async/gotrue_client.py index e3cc9759..bb6da182 100644 --- a/src/auth/src/supabase_auth/_async/gotrue_client.py +++ b/src/auth/src/supabase_auth/_async/gotrue_client.py @@ -1165,7 +1165,9 @@ async def _get_url_for_provider( query = query.set("provider", provider) return f"{url}?{query}", query - async def exchange_code_for_session(self, params: CodeExchangeParams): + async def exchange_code_for_session( + self, params: CodeExchangeParams + ) -> AuthResponse: code_verifier = params.get("code_verifier") or await self._storage.get_item( f"{self._storage_key}-code-verifier" ) diff --git a/src/auth/src/supabase_auth/_sync/gotrue_admin_api.py b/src/auth/src/supabase_auth/_sync/gotrue_admin_api.py index 0007b0c5..8e554500 100644 --- a/src/auth/src/supabase_auth/_sync/gotrue_admin_api.py +++ b/src/auth/src/supabase_auth/_sync/gotrue_admin_api.py @@ -6,10 +6,10 @@ from pydantic import TypeAdapter from ..helpers import ( - validate_uuid, model_validate, parse_link_response, parse_user_response, + validate_uuid, ) from ..http_clients import SyncClient from ..types import ( @@ -57,15 +57,15 @@ def __init__( ) # TODO(@o-santi): why is is this done this way? self.mfa = SyncGoTrueAdminMFAAPI() - self.mfa.list_factors = self._list_factors # type: ignore - self.mfa.delete_factor = self._delete_factor # type: ignore + self.mfa.list_factors = self._list_factors # type: ignore + self.mfa.delete_factor = self._delete_factor # type: ignore self.oauth = SyncGoTrueAdminOAuthAPI() - self.oauth.list_clients = self._list_oauth_clients # type: ignore - self.oauth.create_client = self._create_oauth_client # type: ignore - self.oauth.get_client = self._get_oauth_client # type: ignore - self.oauth.update_client = self._update_oauth_client # type: ignore - self.oauth.delete_client = self._delete_oauth_client # type: ignore - self.oauth.regenerate_client_secret = self._regenerate_oauth_client_secret # type: ignore + self.oauth.list_clients = self._list_oauth_clients # type: ignore + self.oauth.create_client = self._create_oauth_client # type: ignore + self.oauth.get_client = self._get_oauth_client # type: ignore + self.oauth.update_client = self._update_oauth_client # type: ignore + self.oauth.delete_client = self._delete_oauth_client # type: ignore + self.oauth.regenerate_client_secret = self._regenerate_oauth_client_secret # type: ignore def sign_out(self, jwt: str, scope: SignOutScope = "global") -> None: """ @@ -276,9 +276,8 @@ def _create_oauth_client( body=params, ) - return OAuthClientResponse( - client=model_validate(OAuthClient, response.content) - ) + return OAuthClientResponse(client=model_validate(OAuthClient, response.content)) + def _get_oauth_client( self, client_id: str, @@ -295,9 +294,7 @@ def _get_oauth_client( "GET", f"admin/oauth/clients/{client_id}", ) - return OAuthClientResponse( - client=model_validate(OAuthClient, response.content) - ) + return OAuthClientResponse(client=model_validate(OAuthClient, response.content)) def _update_oauth_client( self, @@ -317,9 +314,7 @@ def _update_oauth_client( f"admin/oauth/clients/{client_id}", body=params, ) - return OAuthClientResponse( - client=model_validate(OAuthClient, response.content) - ) + return OAuthClientResponse(client=model_validate(OAuthClient, response.content)) def _delete_oauth_client( self, @@ -354,6 +349,4 @@ def _regenerate_oauth_client_secret( "POST", f"admin/oauth/clients/{client_id}/regenerate_secret", ) - return OAuthClientResponse( - client=model_validate(OAuthClient, response.content) - ) + return OAuthClientResponse(client=model_validate(OAuthClient, response.content)) diff --git a/src/auth/src/supabase_auth/_sync/gotrue_admin_oauth_api.py b/src/auth/src/supabase_auth/_sync/gotrue_admin_oauth_api.py index 56f1b5c1..a050da4f 100644 --- a/src/auth/src/supabase_auth/_sync/gotrue_admin_oauth_api.py +++ b/src/auth/src/supabase_auth/_sync/gotrue_admin_oauth_api.py @@ -1,3 +1,5 @@ +from typing import Optional + from ..types import ( CreateOAuthClientParams, OAuthClientListResponse, @@ -5,7 +7,6 @@ PageParams, UpdateOAuthClientParams, ) -from typing import Optional class SyncGoTrueAdminOAuthAPI: diff --git a/src/auth/src/supabase_auth/_sync/gotrue_client.py b/src/auth/src/supabase_auth/_sync/gotrue_client.py index b330b37e..a0026826 100644 --- a/src/auth/src/supabase_auth/_sync/gotrue_client.py +++ b/src/auth/src/supabase_auth/_sync/gotrue_client.py @@ -441,9 +441,7 @@ def sign_in_with_oauth( ) return OAuthResponse(provider=provider, url=url_with_qs) - def link_identity( - self, credentials: SignInWithOAuthCredentials - ) -> OAuthResponse: + def link_identity(self, credentials: SignInWithOAuthCredentials) -> OAuthResponse: provider = credentials["provider"] options = credentials.get("options", {}) redirect_to = options.get("redirect_to") @@ -743,9 +741,7 @@ def set_session(self, access_token: str, refresh_token: str) -> AuthResponse: self._notify_all_subscribers("TOKEN_REFRESHED", session) return AuthResponse(session=session, user=session.user) - def refresh_session( - self, refresh_token: Optional[str] = None - ) -> AuthResponse: + def refresh_session(self, refresh_token: Optional[str] = None) -> AuthResponse: """ Returns a new session, regardless of expiry status. @@ -1153,9 +1149,7 @@ def _get_url_for_provider( if self._flow_type == "pkce": code_verifier = generate_pkce_verifier() code_challenge = generate_pkce_challenge(code_verifier) - self._storage.set_item( - f"{self._storage_key}-code-verifier", code_verifier - ) + self._storage.set_item(f"{self._storage_key}-code-verifier", code_verifier) code_challenge_method = ( "plain" if code_verifier == code_challenge else "s256" ) @@ -1165,7 +1159,7 @@ def _get_url_for_provider( query = query.set("provider", provider) return f"{url}?{query}", query - def exchange_code_for_session(self, params: CodeExchangeParams): + def exchange_code_for_session(self, params: CodeExchangeParams) -> AuthResponse: code_verifier = params.get("code_verifier") or self._storage.get_item( f"{self._storage_key}-code-verifier" ) diff --git a/src/auth/src/supabase_auth/helpers.py b/src/auth/src/supabase_auth/helpers.py index c3ad4e42..05c117c9 100644 --- a/src/auth/src/supabase_auth/helpers.py +++ b/src/auth/src/supabase_auth/helpers.py @@ -299,8 +299,9 @@ def is_valid_uuid(value: str) -> bool: except ValueError: return False + def validate_uuid(id: str | None) -> None: if id is None: raise ValueError("Invalid id, id is None") if not is_valid_uuid(id): - raise ValueError(f"Invalid id, '{id}' is not a valid uuid") \ No newline at end of file + raise ValueError(f"Invalid id, '{id}' is not a valid uuid") diff --git a/src/auth/src/supabase_auth/types.py b/src/auth/src/supabase_auth/types.py index 5306c359..de5fd7f6 100644 --- a/src/auth/src/supabase_auth/types.py +++ b/src/auth/src/supabase_auth/types.py @@ -893,7 +893,9 @@ class JWKSet(TypedDict): Only relevant when the OAuth 2.1 server is enabled in Supabase Auth. """ -OAuthClientTokenEndpointAuthMethod = Literal["none", "client_secret_basic", "client_secret_post"] +OAuthClientTokenEndpointAuthMethod = Literal[ + "none", "client_secret_basic", "client_secret_post" +] """ OAuth client token endpoint authentication method. Only relevant when the OAuth 2.1 server is enabled in Supabase Auth. @@ -957,6 +959,7 @@ class CreateOAuthClientParams(BaseModel): scope: Optional[str] = None """Space-separated list of scope values""" + class UpdateOAuthClientParams(BaseModel): """ Parameters for updating an existing OAuth client. @@ -974,6 +977,7 @@ class UpdateOAuthClientParams(BaseModel): grant_types: Optional[List[OAuthClientGrantType]] = None """Array of allowed grant types""" + class OAuthClientResponse(BaseModel): """ Response type for OAuth client operations. diff --git a/src/auth/tests/_async/test_gotrue_admin_api.py b/src/auth/tests/_async/test_gotrue_admin_api.py index ab13de1f..4c54dc0e 100644 --- a/src/auth/tests/_async/test_gotrue_admin_api.py +++ b/src/auth/tests/_async/test_gotrue_admin_api.py @@ -10,6 +10,7 @@ AuthWeakPasswordError, ) from supabase_auth.types import CreateOAuthClientParams, UpdateOAuthClientParams + from .clients import ( auth_client, auth_client_with_session, @@ -649,6 +650,7 @@ async def test_get_oauth_client(): assert response.client is not None assert response.client.client_id == client_id + # Server is not yet released, so this test is not yet relevant. # async def test_update_oauth_client(): # """Test updating an OAuth client.""" @@ -671,6 +673,7 @@ async def test_get_oauth_client(): # assert response.client is not None # assert response.client.client_name == "Updated Test OAuth Client" + async def test_delete_oauth_client(): """Test deleting an OAuth client.""" # First create a client diff --git a/src/auth/tests/_sync/test_gotrue.py b/src/auth/tests/_sync/test_gotrue.py index e8318fa0..f15dcbd8 100644 --- a/src/auth/tests/_sync/test_gotrue.py +++ b/src/auth/tests/_sync/test_gotrue.py @@ -331,9 +331,7 @@ def test_exchange_code_for_session(): client._flow_type = "pkce" # Test the PKCE URL generation which is needed for exchange_code_for_session - url, params = client._get_url_for_provider( - f"{client._url}/authorize", "github", {} - ) + url, params = client._get_url_for_provider(f"{client._url}/authorize", "github", {}) # Verify PKCE parameters were added assert "code_challenge" in params diff --git a/src/auth/tests/_sync/test_gotrue_admin_api.py b/src/auth/tests/_sync/test_gotrue_admin_api.py index fc19c377..3f4e63b3 100644 --- a/src/auth/tests/_sync/test_gotrue_admin_api.py +++ b/src/auth/tests/_sync/test_gotrue_admin_api.py @@ -10,6 +10,7 @@ AuthWeakPasswordError, ) from supabase_auth.types import CreateOAuthClientParams, UpdateOAuthClientParams + from .clients import ( auth_client, auth_client_with_session, @@ -142,11 +143,13 @@ def test_modify_confirm_email_using_update_user_by_id(): def test_invalid_credential_sign_in_with_phone(): try: - response = client_api_auto_confirm_off_signups_enabled_client().sign_in_with_password( - { - "phone": "+123456789", - "password": "strong_pwd", - } + response = ( + client_api_auto_confirm_off_signups_enabled_client().sign_in_with_password( + { + "phone": "+123456789", + "password": "strong_pwd", + } + ) ) except AuthApiError as e: assert e.to_dict() @@ -154,11 +157,13 @@ def test_invalid_credential_sign_in_with_phone(): def test_invalid_credential_sign_in_with_email(): try: - response = client_api_auto_confirm_off_signups_enabled_client().sign_in_with_password( - { - "email": "unknown_user@unknowndomain.com", - "password": "strong_pwd", - } + response = ( + client_api_auto_confirm_off_signups_enabled_client().sign_in_with_password( + { + "email": "unknown_user@unknowndomain.com", + "password": "strong_pwd", + } + ) ) except AuthApiError as e: assert e.to_dict() @@ -359,12 +364,10 @@ def test_sign_in_with_sso(): def test_sign_in_with_oauth(): - assert ( - client_api_auto_confirm_off_signups_enabled_client().sign_in_with_oauth( - { - "provider": "google", - } - ) + assert client_api_auto_confirm_off_signups_enabled_client().sign_in_with_oauth( + { + "provider": "google", + } ) @@ -649,6 +652,7 @@ def test_get_oauth_client(): assert response.client is not None assert response.client.client_id == client_id + # Server is not yet released, so this test is not yet relevant. # async def test_update_oauth_client(): # """Test updating an OAuth client.""" @@ -671,6 +675,7 @@ def test_get_oauth_client(): # assert response.client is not None # assert response.client.client_name == "Updated Test OAuth Client" + def test_delete_oauth_client(): """Test deleting an OAuth client.""" # First create a client @@ -697,8 +702,6 @@ def test_regenerate_oauth_client_secret(): ) if create_response.client: client_id = create_response.client.client_id - response = service_role_api_client().oauth.regenerate_client_secret( - client_id - ) + response = service_role_api_client().oauth.regenerate_client_secret(client_id) assert response.client is not None assert response.client.client_secret is not None