Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[wip] Guilherme/add 3p auth #1067

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 21 additions & 15 deletions supabase/_async/client.py
Original file line number Diff line number Diff line change
@@ -16,17 +16,11 @@
from storage3.constants import DEFAULT_TIMEOUT as DEFAULT_STORAGE_CLIENT_TIMEOUT
from supafunc import AsyncFunctionsClient

from ..lib.client_options import AsyncClientOptions as ClientOptions
from ..lib.client_options import ClientOptions
from ..utils import AuthProxy, SupabaseException
from .auth_client import AsyncSupabaseAuthClient


# Create an exception class when user does not provide a valid url or key.
class SupabaseException(Exception):
def __init__(self, message: str):
self.message = message
super().__init__(self.message)


class AsyncClient:
"""Supabase client class."""

@@ -78,10 +72,15 @@ def __init__(
self.functions_url = f"{supabase_url}/functions/v1"

# Instantiate clients.
self.auth = self._init_supabase_auth_client(
auth_url=self.auth_url,
client_options=options,
)
if not options.access_token:
self.auth = self._init_supabase_auth_client(
auth_url=self.auth_url,
client_options=options,
)
else:
self.access_token = options.access_token
self.auth = AuthProxy()

self.realtime = self._init_realtime_client(
realtime_url=self.realtime_url,
supabase_key=self.supabase_key,
@@ -90,7 +89,9 @@ def __init__(
self._postgrest = None
self._storage = None
self._functions = None
self.auth.on_auth_state_change(self._listen_to_auth_events)

if not options.access_token:
self.auth.on_auth_state_change(self._listen_to_auth_events)

@classmethod
async def create(
@@ -104,8 +105,13 @@ async def create(

if auth_header is None:
try:
session = await client.auth.get_session()
session_access_token = client._create_auth_header(session.access_token)
if not options.access_token:
session = await client.auth.get_session()
session_access_token = client._create_auth_header(
session.access_token
)
else:
session_access_token = options.access_token
except Exception as err:
session_access_token = None

36 changes: 21 additions & 15 deletions supabase/_sync/client.py
Original file line number Diff line number Diff line change
@@ -15,17 +15,11 @@
from storage3.constants import DEFAULT_TIMEOUT as DEFAULT_STORAGE_CLIENT_TIMEOUT
from supafunc import SyncFunctionsClient

from ..lib.client_options import SyncClientOptions as ClientOptions
from ..lib.client_options import ClientOptions
from ..utils import AuthProxy, SupabaseException
from .auth_client import SyncSupabaseAuthClient


# Create an exception class when user does not provide a valid url or key.
class SupabaseException(Exception):
def __init__(self, message: str):
self.message = message
super().__init__(self.message)


class SyncClient:
"""Supabase client class."""

@@ -77,10 +71,15 @@ def __init__(
self.functions_url = f"{supabase_url}/functions/v1"

# Instantiate clients.
self.auth = self._init_supabase_auth_client(
auth_url=self.auth_url,
client_options=options,
)
if not options.access_token:
self.auth = self._init_supabase_auth_client(
auth_url=self.auth_url,
client_options=options,
)
else:
self.access_token = options.access_token
self.auth = AuthProxy()

self.realtime = self._init_realtime_client(
realtime_url=self.realtime_url,
supabase_key=self.supabase_key,
@@ -89,7 +88,9 @@ def __init__(
self._postgrest = None
self._storage = None
self._functions = None
self.auth.on_auth_state_change(self._listen_to_auth_events)

if not options.access_token:
self.auth.on_auth_state_change(self._listen_to_auth_events)

@classmethod
def create(
@@ -103,8 +104,13 @@ def create(

if auth_header is None:
try:
session = client.auth.get_session()
session_access_token = client._create_auth_header(session.access_token)
if not options.access_token:
session = client.auth.get_session()
session_access_token = client._create_auth_header(
session.access_token
)
else:
session_access_token = options.access_token
except Exception as err:
session_access_token = None

19 changes: 18 additions & 1 deletion supabase/lib/client_options.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from dataclasses import dataclass, field
from typing import Dict, Optional, Union
from typing import Awaitable, Callable, Dict, Optional, Union

from gotrue import (
AsyncMemoryStorage,
@@ -59,6 +59,15 @@ class ClientOptions:
flow_type: AuthFlowType = "pkce"
"""flow type to use for authentication"""

access_token: Union[Callable[[], str], None] = None
"""Optional function for using a third-party authentication system with Supabase.
The function should return an access token or ID token (JWT) by obtaining it from the third-party auth client library.
Note that this funciton may be called concurrently and many times.
Use memoization and locking techniques if this is not supported by the clinet libraries.

When set, the `auth` namespace of the Supabase client cannot be used.
Create another client if you wish to use Supabase Auth and third-party authentications concurrently in the same application."""

def replace(
self,
schema: Optional[str] = None,
@@ -74,6 +83,7 @@ def replace(
int, float, Timeout
] = DEFAULT_STORAGE_CLIENT_TIMEOUT,
flow_type: Optional[AuthFlowType] = None,
access_token: Union[Callable[[], str], None] = None,
) -> "ClientOptions":
"""Create a new SupabaseClientOptions with changes"""
client_options = ClientOptions()
@@ -92,6 +102,7 @@ def replace(
storage_client_timeout or self.storage_client_timeout
)
client_options.flow_type = flow_type or self.flow_type
client_options.access_token = access_token or self.access_token
return client_options


@@ -100,6 +111,8 @@ class AsyncClientOptions(ClientOptions):
storage: AsyncSupportedStorage = field(default_factory=AsyncMemoryStorage)
"""A storage provider. Used to store the logged in session."""

access_token: Union[Callable[[], Awaitable[str]], None] = None

def replace(
self,
schema: Optional[str] = None,
@@ -115,6 +128,7 @@ def replace(
int, float, Timeout
] = DEFAULT_STORAGE_CLIENT_TIMEOUT,
flow_type: Optional[AuthFlowType] = None,
access_token: Union[Callable[[], Awaitable[str]], None] = None,
) -> "AsyncClientOptions":
"""Create a new SupabaseClientOptions with changes"""
client_options = AsyncClientOptions()
@@ -133,6 +147,7 @@ def replace(
storage_client_timeout or self.storage_client_timeout
)
client_options.flow_type = flow_type or self.flow_type
client_options.access_token = access_token or self.access_token
return client_options


@@ -153,6 +168,7 @@ def replace(
int, float, Timeout
] = DEFAULT_STORAGE_CLIENT_TIMEOUT,
flow_type: Optional[AuthFlowType] = None,
access_token: Union[Callable[[], Awaitable[str]], None] = None,
) -> "SyncClientOptions":
"""Create a new SupabaseClientOptions with changes"""
client_options = SyncClientOptions()
@@ -171,4 +187,5 @@ def replace(
storage_client_timeout or self.storage_client_timeout
)
client_options.flow_type = flow_type or self.flow_type
client_options.access_token = access_token or self.access_token
return client_options
12 changes: 12 additions & 0 deletions supabase/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Create an exception class when user does not provide a valid url or key.
class SupabaseException(Exception):
def __init__(self, message: str):
self.message = message
super().__init__(self.message)


class AuthProxy:
def __getattr__(self, attr):
raise SupabaseException(
f"Supabase Client is configured with the access_token option, accessing supabase.auth.{attr} is not possible.",
)
18 changes: 18 additions & 0 deletions tests/test_client.py
Original file line number Diff line number Diff line change
@@ -119,3 +119,21 @@ def test_updates_the_authorization_header_on_auth_events() -> None:

assert client.storage.session.headers.get("apiKey") == key
assert client.storage.session.headers.get("Authorization") == updated_authorization


def test_init_client_with_access_token() -> None:
url = os.environ.get("SUPABASE_TEST_URL")
key = os.environ.get("SUPABASE_TEST_KEY")

client = create_client(
url, key, options=ClientOptions(access_token=lambda: "secretuserjwt")
)

assert client.access_token is not None

with pytest.raises(SupabaseException) as e:
client.auth.get_user()
assert (
str(e.value.message)
== "Supabase Client is configured with the access_token option, accessing supabase.auth.get_user is not possible."
)