diff --git a/supabase/_async/client.py b/supabase/_async/client.py index 7fc722f6..bd34b084 100644 --- a/supabase/_async/client.py +++ b/supabase/_async/client.py @@ -1,4 +1,5 @@ import asyncio +import copy import re from typing import Any, Dict, List, Optional, Union @@ -69,8 +70,9 @@ def __init__( self.supabase_url = supabase_url self.supabase_key = supabase_key - self.options = options - options.headers.update(self._get_auth_headers()) + self.options = copy.deepcopy(options) + self.options.headers.update(self._get_auth_headers()) + self.rest_url = f"{supabase_url}/rest/v1" self.realtime_url = f"{supabase_url}/realtime/v1".replace("http", "ws") self.auth_url = f"{supabase_url}/auth/v1" @@ -80,12 +82,12 @@ def __init__( # Instantiate clients. self.auth = self._init_supabase_auth_client( auth_url=self.auth_url, - client_options=options, + client_options=self.options, ) self.realtime = self._init_realtime_client( realtime_url=self.realtime_url, supabase_key=self.supabase_key, - options=options.realtime if options else None, + options=self.options.realtime if self.options else None, ) self._postgrest = None self._storage = None @@ -294,8 +296,9 @@ def _listen_to_auth_events( self._storage = None self._functions = None access_token = session.access_token if session else self.supabase_key - - self.options.headers["Authorization"] = self._create_auth_header(access_token) + auth_header = copy.deepcopy(self._create_auth_header(access_token)) + self.options.headers["Authorization"] = auth_header + self.auth._headers["Authorization"] = auth_header asyncio.create_task(self.realtime.set_auth(access_token)) diff --git a/supabase/_sync/client.py b/supabase/_sync/client.py index d8da3a09..a477f843 100644 --- a/supabase/_sync/client.py +++ b/supabase/_sync/client.py @@ -1,3 +1,4 @@ +import copy import re from typing import Any, Dict, List, Optional, Union @@ -68,8 +69,9 @@ def __init__( self.supabase_url = supabase_url self.supabase_key = supabase_key - self.options = options - options.headers.update(self._get_auth_headers()) + self.options = copy.deepcopy(options) + self.options.headers.update(self._get_auth_headers()) + self.rest_url = f"{supabase_url}/rest/v1" self.realtime_url = f"{supabase_url}/realtime/v1".replace("http", "ws") self.auth_url = f"{supabase_url}/auth/v1" @@ -79,12 +81,12 @@ def __init__( # Instantiate clients. self.auth = self._init_supabase_auth_client( auth_url=self.auth_url, - client_options=options, + client_options=self.options, ) self.realtime = self._init_realtime_client( realtime_url=self.realtime_url, supabase_key=self.supabase_key, - options=options.realtime if options else None, + options=self.options.realtime if self.options else None, ) self._postgrest = None self._storage = None @@ -293,8 +295,9 @@ def _listen_to_auth_events( self._storage = None self._functions = None access_token = session.access_token if session else self.supabase_key + auth_header = copy.deepcopy(self._create_auth_header(access_token)) - self.options.headers["Authorization"] = self._create_auth_header(access_token) + self.options.headers["Authorization"] = auth_header def create_client( diff --git a/tests/test_client.py b/tests/test_client.py index d8f0be0c..de7a3cae 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -5,6 +5,7 @@ from unittest.mock import MagicMock import pytest +from gotrue import SyncMemoryStorage from supabase import Client, ClientOptions, SupabaseException, create_client @@ -101,7 +102,6 @@ def test_updates_the_authorization_header_on_auth_events() -> None: mock_session = MagicMock(access_token="secretuserjwt") realtime_mock = MagicMock() client.realtime = realtime_mock - client._listen_to_auth_events("SIGNED_IN", mock_session) updated_authorization = f"Bearer {mock_session.access_token}" @@ -113,9 +113,54 @@ def test_updates_the_authorization_header_on_auth_events() -> None: assert ( client.postgrest.session.headers.get("Authorization") == updated_authorization ) - assert client.auth._headers.get("apiKey") == key assert client.auth._headers.get("Authorization") == updated_authorization assert client.storage.session.headers.get("apiKey") == key assert client.storage.session.headers.get("Authorization") == updated_authorization + + +def test_mutable_headers_issue(): + url = os.environ.get("SUPABASE_TEST_URL") + key = os.environ.get("SUPABASE_TEST_KEY") + + shared_options = ClientOptions( + storage=SyncMemoryStorage(), headers={"Authorization": "Bearer initial-token"} + ) + + client1 = create_client(url, key, shared_options) + + client2 = create_client(url, key, shared_options) + + client1.options.headers["Authorization"] = "Bearer modified-token" + + assert client2.options.headers["Authorization"] == "Bearer initial-token" + + +def test_global_authorization_header_issue(): + url = os.environ.get("SUPABASE_TEST_URL") + key = os.environ.get("SUPABASE_TEST_KEY") + + authorization = "Bearer secretuserjwt" + options = ClientOptions(headers={"Authorization": authorization}) + + client = create_client(url, key, options) + + assert client.options.headers.get("apiKey") == key + + +def test_mutable_headers_issue(): + url = os.environ.get("SUPABASE_TEST_URL") + key = os.environ.get("SUPABASE_TEST_KEY") + + shared_options = ClientOptions( + headers={"Authorization": "Bearer initial-token", "x-site": "supanew.site"} + ) + + client1 = create_client(url, key, shared_options) + client2 = create_client(url, key, shared_options) + client1.options.replace({"headers": {"Authorization": "Bearer modified-token"}}) + + assert client2.options.headers["Authorization"] == "Bearer initial-token" + assert client2.options.headers["x-site"] == "supanew.site" + assert client1.options.headers["x-site"] == "supanew.site"