Skip to content

Commit c7b35ab

Browse files
committed
fix: gate 401 re-auth retry on requires_authorization
- A 401 on a requires_authorization=False call now surfaces as-is instead of triggering a re-auth and replay - Anonymous 401s (e.g. admin-install on an already-configured instance) no longer misreport as bad credentials - Avoids a wasted login round-trip on calls the caller explicitly marked as not needing auth - Test asserts a single request and no /api/auth/token/login/ replay on an anonymous 401 Closes #9
1 parent 908b0a6 commit c7b35ab

2 files changed

Lines changed: 39 additions & 1 deletion

File tree

datamasque/client/base.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,14 @@ def send() -> Response:
229229
raise DataMasqueTransportError(f"Failed to reach DataMasque server at {url}: {e}") from e
230230

231231
response = send()
232-
if response.status_code == 401:
232+
if response.status_code == 401 and requires_authorization:
233+
# Token-expiry recovery: re-auth and replay. Only meaningful when the
234+
# caller actually sent a token; on `requires_authorization=False`
235+
# calls a 401 means the server itself is rejecting anonymous access
236+
# (e.g. admin-install on an already-configured instance), and
237+
# re-authing with whatever creds the client happens to hold would
238+
# both misdiagnose the failure and emit a misleading
239+
# "credentials are incorrect" error to the user.
233240
logger.debug("Re-authenticating")
234241
self.authenticate()
235242
# Reset file pointers so the retry doesn't send empty files

tests/test_base.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,37 @@ def test_token_source_called_again_on_401_retry():
280280
assert client.token == "Token t2"
281281

282282

283+
def test_401_does_not_retry_when_requires_authorization_is_false(client):
284+
"""
285+
A 401 on an anonymous request must surface as-is, not trigger a re-auth retry.
286+
287+
`/api/users/admin-install/` returns 401 once any user exists -- the endpoint
288+
is gated on "no user has been created yet" and DRF treats it as a normal
289+
auth-required endpoint thereafter. Re-authing on that 401 would both
290+
misdiagnose the failure ("login credentials are correct") and waste a
291+
round-trip on a call the caller said doesn't need auth.
292+
"""
293+
with requests_mock.Mocker() as m:
294+
m.post(
295+
"http://test-server/api/users/admin-install/",
296+
status_code=401,
297+
json={"detail": "Authentication credentials were not provided."},
298+
)
299+
300+
with pytest.raises(DataMasqueApiError) as excinfo:
301+
client.make_request(
302+
"POST",
303+
"/api/users/admin-install/",
304+
data={"email": "x@y", "username": "x", "password": "p", "re_password": "p", "allowed_hosts": []},
305+
requires_authorization=False,
306+
)
307+
308+
assert excinfo.value.response.status_code == 401
309+
# Exactly one request: no re-auth roundtrip to /api/auth/token/login/ and no replay.
310+
assert m.call_count == 1
311+
assert m.request_history[0].path == "/api/users/admin-install/"
312+
313+
283314
def test_token_source_callable_exception_propagates():
284315
"""Errors from `token_source` are surfaced to the caller, not swallowed."""
285316

0 commit comments

Comments
 (0)