Skip to content

Commit 50d79c1

Browse files
fix: mutable reference headers #1095 (#1096)
Co-authored-by: Andrew Smith <[email protected]>
1 parent c8e6132 commit 50d79c1

File tree

4 files changed

+49
-15
lines changed

4 files changed

+49
-15
lines changed

supabase/_async/client.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import asyncio
2+
import copy
23
import re
34
from typing import Any, Dict, Optional, Union
45

@@ -68,8 +69,9 @@ def __init__(
6869

6970
self.supabase_url = supabase_url
7071
self.supabase_key = supabase_key
71-
self.options = options
72-
options.headers.update(self._get_auth_headers())
72+
self.options = copy.deepcopy(options)
73+
self.options.headers.update(self._get_auth_headers())
74+
7375
self.rest_url = f"{supabase_url}/rest/v1"
7476
self.realtime_url = f"{supabase_url}/realtime/v1".replace("http", "ws")
7577
self.auth_url = f"{supabase_url}/auth/v1"
@@ -79,12 +81,12 @@ def __init__(
7981
# Instantiate clients.
8082
self.auth = self._init_supabase_auth_client(
8183
auth_url=self.auth_url,
82-
client_options=options,
84+
client_options=self.options,
8385
)
8486
self.realtime = self._init_realtime_client(
8587
realtime_url=self.realtime_url,
8688
supabase_key=self.supabase_key,
87-
options=options.realtime if options else None,
89+
options=self.options.realtime if self.options else None,
8890
)
8991
self._postgrest = None
9092
self._storage = None
@@ -301,8 +303,9 @@ def _listen_to_auth_events(
301303
self._storage = None
302304
self._functions = None
303305
access_token = session.access_token if session else self.supabase_key
304-
305-
self.options.headers["Authorization"] = self._create_auth_header(access_token)
306+
auth_header = copy.deepcopy(self._create_auth_header(access_token))
307+
self.options.headers["Authorization"] = auth_header
308+
self.auth._headers["Authorization"] = auth_header
306309
asyncio.create_task(self.realtime.set_auth(access_token))
307310

308311

supabase/_sync/client.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import copy
12
import re
23
from typing import Any, Dict, Optional, Union
34

@@ -67,8 +68,9 @@ def __init__(
6768

6869
self.supabase_url = supabase_url
6970
self.supabase_key = supabase_key
70-
self.options = options
71-
options.headers.update(self._get_auth_headers())
71+
self.options = copy.deepcopy(options)
72+
self.options.headers.update(self._get_auth_headers())
73+
7274
self.rest_url = f"{supabase_url}/rest/v1"
7375
self.realtime_url = f"{supabase_url}/realtime/v1".replace("http", "ws")
7476
self.auth_url = f"{supabase_url}/auth/v1"
@@ -78,12 +80,12 @@ def __init__(
7880
# Instantiate clients.
7981
self.auth = self._init_supabase_auth_client(
8082
auth_url=self.auth_url,
81-
client_options=options,
83+
client_options=self.options,
8284
)
8385
self.realtime = self._init_realtime_client(
8486
realtime_url=self.realtime_url,
8587
supabase_key=self.supabase_key,
86-
options=options.realtime if options else None,
88+
options=self.options.realtime if self.options else None,
8789
)
8890
self._postgrest = None
8991
self._storage = None
@@ -300,8 +302,9 @@ def _listen_to_auth_events(
300302
self._storage = None
301303
self._functions = None
302304
access_token = session.access_token if session else self.supabase_key
305+
auth_header = copy.deepcopy(self._create_auth_header(access_token))
303306

304-
self.options.headers["Authorization"] = self._create_auth_header(access_token)
307+
self.options.headers["Authorization"] = auth_header
305308

306309

307310
def create_client(

tests/_async/test_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
async def test_incorrect_values_dont_instantiate_client() -> None:
88
"""Ensure we can't instantiate client with invalid values."""
99
try:
10-
client: AClient = create_async_client(None, None)
10+
client: AClient = await create_async_client(None, None)
1111
except ASupabaseException:
1212
pass
1313

tests/test_client.py

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from unittest.mock import MagicMock
66

77
import pytest
8+
from gotrue import SyncMemoryStorage
89

910
from supabase import Client, ClientOptions, SupabaseException, create_client
1011

@@ -70,7 +71,7 @@ def test_supports_setting_a_global_authorization_header() -> None:
7071
url = os.environ.get("SUPABASE_TEST_URL")
7172
key = os.environ.get("SUPABASE_TEST_KEY")
7273

73-
authorization = f"Bearer secretuserjwt"
74+
authorization = "Bearer secretuserjwt"
7475

7576
options = ClientOptions(headers={"Authorization": authorization})
7677

@@ -101,7 +102,6 @@ def test_updates_the_authorization_header_on_auth_events() -> None:
101102
mock_session = MagicMock(access_token="secretuserjwt")
102103
realtime_mock = MagicMock()
103104
client.realtime = realtime_mock
104-
105105
client._listen_to_auth_events("SIGNED_IN", mock_session)
106106

107107
updated_authorization = f"Bearer {mock_session.access_token}"
@@ -113,9 +113,37 @@ def test_updates_the_authorization_header_on_auth_events() -> None:
113113
assert (
114114
client.postgrest.session.headers.get("Authorization") == updated_authorization
115115
)
116-
117116
assert client.auth._headers.get("apiKey") == key
118117
assert client.auth._headers.get("Authorization") == updated_authorization
119118

120119
assert client.storage.session.headers.get("apiKey") == key
121120
assert client.storage.session.headers.get("Authorization") == updated_authorization
121+
122+
123+
def test_mutable_headers_issue():
124+
url = os.environ.get("SUPABASE_TEST_URL")
125+
key = os.environ.get("SUPABASE_TEST_KEY")
126+
127+
shared_options = ClientOptions(
128+
storage=SyncMemoryStorage(), headers={"Authorization": "Bearer initial-token"}
129+
)
130+
131+
client1 = create_client(url, key, shared_options)
132+
client2 = create_client(url, key, shared_options)
133+
134+
client1.options.headers["Authorization"] = "Bearer modified-token"
135+
136+
assert client2.options.headers["Authorization"] == "Bearer initial-token"
137+
assert client1.options.headers["Authorization"] == "Bearer modified-token"
138+
139+
140+
def test_global_authorization_header_issue():
141+
url = os.environ.get("SUPABASE_TEST_URL")
142+
key = os.environ.get("SUPABASE_TEST_KEY")
143+
144+
authorization = "Bearer secretuserjwt"
145+
options = ClientOptions(headers={"Authorization": authorization})
146+
147+
client = create_client(url, key, options)
148+
149+
assert client.options.headers.get("apiKey") == key

0 commit comments

Comments
 (0)