raylim Claude Opus 4.6 commited on
Commit
bce9604
·
1 Parent(s): 9adb105

fix: stop using request.username on HF Spaces (returns Space owner, not visitor)

Browse files

On HF Spaces, request.username returns the Space owner's username for
all visitors. This caused every user to appear as "raylim" regardless
of who was actually logged in. Now only OAuthProfile from
gr.LoginButton() is used to identify users on HF Spaces.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

src/mosaic/telemetry/utils.py CHANGED
@@ -113,16 +113,17 @@ class UserInfo:
113
 
114
 
115
  def extract_user_info(request, is_hf_spaces: bool = False, profile=None) -> UserInfo:
116
- """Extract user info from Gradio OAuth profile or request object.
117
 
118
  With gr.LoginButton(), Gradio provides user info via gr.OAuthProfile
119
  (injected automatically into event handlers). The profile object has
120
  a `username` attribute with the HuggingFace username.
121
 
122
- Falls back to request.username for non-OAuth auth setups.
 
123
 
124
  Args:
125
- request: Gradio request object (can be None)
126
  is_hf_spaces: Whether running on HuggingFace Spaces (only extract on HF)
127
  profile: Gradio OAuthProfile object (injected by LoginButton OAuth flow)
128
 
@@ -137,7 +138,9 @@ def extract_user_info(request, is_hf_spaces: bool = False, profile=None) -> User
137
  if not is_hf_spaces:
138
  return UserInfo()
139
 
140
- # Primary: extract from OAuthProfile (set by gr.LoginButton OAuth flow)
 
 
141
  if profile is not None:
142
  try:
143
  username = getattr(profile, "username", None)
@@ -147,30 +150,5 @@ def extract_user_info(request, is_hf_spaces: bool = False, profile=None) -> User
147
  except Exception as e:
148
  logger.debug(f"Could not extract username from OAuthProfile: {e}")
149
 
150
- # Fallback: try request.username (works with Gradio's built-in auth= parameter)
151
- if request is None:
152
- logger.debug(
153
- "Cannot extract user info: request and profile are both None/empty"
154
- )
155
- return UserInfo()
156
-
157
- try:
158
- username = None
159
-
160
- try:
161
- username = request.username
162
- except Exception as auth_error:
163
- logger.debug(f"Could not access request.username: {auth_error}")
164
-
165
- if username:
166
- logger.info(f"Extracted user from request.username: {username}")
167
- return UserInfo(is_logged_in=True, username=username)
168
- else:
169
- logger.debug(
170
- "User info not available: no OAuthProfile and request.username is None"
171
- )
172
- return UserInfo()
173
-
174
- except Exception as e:
175
- logger.error(f"Unexpected error extracting user info: {e}")
176
- return UserInfo()
 
113
 
114
 
115
  def extract_user_info(request, is_hf_spaces: bool = False, profile=None) -> UserInfo:
116
+ """Extract user info from Gradio OAuth profile.
117
 
118
  With gr.LoginButton(), Gradio provides user info via gr.OAuthProfile
119
  (injected automatically into event handlers). The profile object has
120
  a `username` attribute with the HuggingFace username.
121
 
122
+ Does NOT use request.username, which on HF Spaces returns the Space
123
+ owner's username rather than the visitor's.
124
 
125
  Args:
126
+ request: Gradio request object (unused, kept for API compatibility)
127
  is_hf_spaces: Whether running on HuggingFace Spaces (only extract on HF)
128
  profile: Gradio OAuthProfile object (injected by LoginButton OAuth flow)
129
 
 
138
  if not is_hf_spaces:
139
  return UserInfo()
140
 
141
+ # On HF Spaces, only OAuthProfile reliably identifies the logged-in user.
142
+ # request.username returns the Space owner's username (not the visitor's),
143
+ # so we must NOT fall back to it.
144
  if profile is not None:
145
  try:
146
  username = getattr(profile, "username", None)
 
150
  except Exception as e:
151
  logger.debug(f"Could not extract username from OAuthProfile: {e}")
152
 
153
+ logger.debug("User not logged in: no OAuthProfile available")
154
+ return UserInfo()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/mosaic/ui/user_tabs.py CHANGED
@@ -32,18 +32,20 @@ LOCAL_DEBUG_USERNAME = "local_user"
32
  def _get_username(request: gr.Request = None, profile=None) -> tuple[str, bool]:
33
  """Get username for storage operations.
34
 
35
- Uses the same approach as telemetry's extract_user_info: try
36
- OAuthProfile first (reliable on HF Spaces), then fall back to
37
- request.username.
38
 
39
  Returns:
40
  Tuple of (username, is_local_mode)
41
- - In HF Spaces: (profile.username or request.username, False) if logged in
42
  - Locally: (LOCAL_DEBUG_USERNAME, True)
43
  - HF Spaces not logged in: (None, False)
44
  """
45
  if IS_HF_SPACES:
46
- # Primary: OAuthProfile (set by gr.LoginButton OAuth flow)
 
 
47
  if profile is not None:
48
  try:
49
  username = getattr(profile, "username", None)
@@ -52,13 +54,6 @@ def _get_username(request: gr.Request = None, profile=None) -> tuple[str, bool]:
52
  except Exception:
53
  pass
54
 
55
- # Fallback: request.username
56
- try:
57
- if request and request.username:
58
- return (request.username, False)
59
- except Exception:
60
- pass
61
-
62
  return (None, False)
63
  else:
64
  # Local mode - use debug username
 
32
  def _get_username(request: gr.Request = None, profile=None) -> tuple[str, bool]:
33
  """Get username for storage operations.
34
 
35
+ On HF Spaces, uses OAuthProfile from gr.LoginButton() to identify
36
+ the logged-in user. Does NOT use request.username, which returns
37
+ the Space owner's username rather than the visitor's.
38
 
39
  Returns:
40
  Tuple of (username, is_local_mode)
41
+ - In HF Spaces: (profile.username, False) if logged in
42
  - Locally: (LOCAL_DEBUG_USERNAME, True)
43
  - HF Spaces not logged in: (None, False)
44
  """
45
  if IS_HF_SPACES:
46
+ # On HF Spaces, only OAuthProfile reliably identifies the logged-in user.
47
+ # request.username returns the Space owner's username (not the visitor's),
48
+ # so we must NOT fall back to it.
49
  if profile is not None:
50
  try:
51
  username = getattr(profile, "username", None)
 
54
  except Exception:
55
  pass
56
 
 
 
 
 
 
 
 
57
  return (None, False)
58
  else:
59
  # Local mode - use debug username
tests/telemetry/test_utils.py CHANGED
@@ -182,9 +182,9 @@ class TestExtractUserInfo:
182
  return MockRequest(username)
183
 
184
  def test_extract_user_info_with_logged_in_user(self):
185
- """Test extraction with a logged-in user."""
186
- request = self._create_mock_request("testuser123")
187
- user_info = extract_user_info(request, is_hf_spaces=True)
188
 
189
  assert user_info.is_logged_in is True
190
  assert user_info.username == "testuser123"
@@ -229,9 +229,9 @@ class TestExtractUserInfo:
229
 
230
  def test_extract_user_info_with_special_characters(self):
231
  """Test extraction with username containing special characters."""
232
- request = self._create_mock_request("user-name_123")
233
 
234
- user_info = extract_user_info(request, is_hf_spaces=True)
235
 
236
  assert user_info.is_logged_in is True
237
  assert user_info.username == "user-name_123"
@@ -274,14 +274,14 @@ class TestExtractUserInfo:
274
  assert user_info.is_logged_in is True
275
  assert user_info.username == "oauth_user"
276
 
277
- def test_extract_user_info_falls_back_to_request(self):
278
- """Test fallback to request.username when no OAuthProfile."""
279
- request = self._create_mock_request("request_user")
280
 
281
  user_info = extract_user_info(request, is_hf_spaces=True, profile=None)
282
 
283
- assert user_info.is_logged_in is True
284
- assert user_info.username == "request_user"
285
 
286
  def test_extract_user_info_no_profile_not_hf_spaces(self):
287
  """Test that profile is ignored when not on HF Spaces."""
 
182
  return MockRequest(username)
183
 
184
  def test_extract_user_info_with_logged_in_user(self):
185
+ """Test extraction with a logged-in user via OAuthProfile."""
186
+ profile = self._create_mock_profile("testuser123")
187
+ user_info = extract_user_info(None, is_hf_spaces=True, profile=profile)
188
 
189
  assert user_info.is_logged_in is True
190
  assert user_info.username == "testuser123"
 
229
 
230
  def test_extract_user_info_with_special_characters(self):
231
  """Test extraction with username containing special characters."""
232
+ profile = self._create_mock_profile("user-name_123")
233
 
234
+ user_info = extract_user_info(None, is_hf_spaces=True, profile=profile)
235
 
236
  assert user_info.is_logged_in is True
237
  assert user_info.username == "user-name_123"
 
274
  assert user_info.is_logged_in is True
275
  assert user_info.username == "oauth_user"
276
 
277
+ def test_extract_user_info_ignores_request_username(self):
278
+ """Test that request.username is NOT used (it returns Space owner, not visitor)."""
279
+ request = self._create_mock_request("space_owner")
280
 
281
  user_info = extract_user_info(request, is_hf_spaces=True, profile=None)
282
 
283
+ assert user_info.is_logged_in is False
284
+ assert user_info.username is None
285
 
286
  def test_extract_user_info_no_profile_not_hf_spaces(self):
287
  """Test that profile is ignored when not on HF Spaces."""
tests/test_ui_user_storage.py CHANGED
@@ -86,11 +86,22 @@ class TestUsernameExtraction:
86
 
87
  @patch("mosaic.ui.user_tabs.IS_HF_SPACES", True)
88
  def test_hf_logged_in_returns_username(self, mock_request_hf_logged_in):
89
- """HF Spaces logged in should return request username."""
90
- username, is_local = _get_username(mock_request_hf_logged_in)
 
 
 
 
91
  assert username == "test_user"
92
  assert is_local is False
93
 
 
 
 
 
 
 
 
94
  @patch("mosaic.ui.user_tabs.IS_HF_SPACES", True)
95
  def test_hf_not_logged_in_returns_none(self, mock_request_hf_not_logged_in):
96
  """HF Spaces not logged in should return None."""
 
86
 
87
  @patch("mosaic.ui.user_tabs.IS_HF_SPACES", True)
88
  def test_hf_logged_in_returns_username(self, mock_request_hf_logged_in):
89
+ """HF Spaces logged in should return OAuthProfile username, not request.username."""
90
+
91
+ class MockProfile:
92
+ username = "test_user"
93
+
94
+ username, is_local = _get_username(mock_request_hf_logged_in, profile=MockProfile())
95
  assert username == "test_user"
96
  assert is_local is False
97
 
98
+ @patch("mosaic.ui.user_tabs.IS_HF_SPACES", True)
99
+ def test_hf_request_username_ignored(self, mock_request_hf_logged_in):
100
+ """HF Spaces should NOT use request.username (returns Space owner)."""
101
+ username, is_local = _get_username(mock_request_hf_logged_in, profile=None)
102
+ assert username is None
103
+ assert is_local is False
104
+
105
  @patch("mosaic.ui.user_tabs.IS_HF_SPACES", True)
106
  def test_hf_not_logged_in_returns_none(self, mock_request_hf_not_logged_in):
107
  """HF Spaces not logged in should return None."""