From 3dc25d8ea7d23cfa8fa3c528bc52a47e424d8843 Mon Sep 17 00:00:00 2001 From: lebaudantoine Date: Thu, 8 Oct 2026 20:12:03 +0200 Subject: [PATCH] =?UTF-8?q?fixup!=20=E2=9C=A8(backend)=20introduce=20a=20t?= =?UTF-8?q?oken=20exchange=20endpoint=20for=20iframe=20embeds?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/core/external_api/viewsets.py | 6 +++- src/backend/core/services/transit_code.py | 2 +- .../core/tests/services/test_transit_code.py | 2 +- .../core/tests/test_external_api_users.py | 28 +++++++++++++++++++ 4 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/backend/core/external_api/viewsets.py b/src/backend/core/external_api/viewsets.py index 75facc02b..aaa5d2956 100644 --- a/src/backend/core/external_api/viewsets.py +++ b/src/backend/core/external_api/viewsets.py @@ -303,7 +303,11 @@ class UserViewSet(viewsets.GenericViewSet): POST /api/v1.0/users/exchange-access-token/ for a JWT access token, equivalent to session-cookie authentication and never exposed in a URL. """ - client_id = (request.auth or {}).get("client_id", "unknown") + + if not request.auth or not request.auth.get("client_id"): + raise drf_exceptions.AuthenticationFailed("Invalid application token.") + + client_id = request.auth["client_id"] code = TransitCodeService().create_code(request.user, client_id=client_id) diff --git a/src/backend/core/services/transit_code.py b/src/backend/core/services/transit_code.py index b1ca25960..73b267429 100644 --- a/src/backend/core/services/transit_code.py +++ b/src/backend/core/services/transit_code.py @@ -27,7 +27,7 @@ class TransitCodeService: digest = hashlib.sha256(code.encode("utf-8")).hexdigest() return f"{settings.TRANSIT_CODE_CACHE_PREFIX}:{digest}" - def create_code(self, user, client_id="unknown"): + def create_code(self, user, client_id): """Generate a transit code for a user, and store it. The code expires after TRANSIT_CODE_TTL seconds. diff --git a/src/backend/core/tests/services/test_transit_code.py b/src/backend/core/tests/services/test_transit_code.py index 21c7c4c61..b26c4c930 100644 --- a/src/backend/core/tests/services/test_transit_code.py +++ b/src/backend/core/tests/services/test_transit_code.py @@ -17,7 +17,7 @@ def test_create_code_returns_unique_opaque_codes(): user = UserFactory() service = TransitCodeService() - codes = {service.create_code(user) for _ in range(5)} + codes = {service.create_code(user, "my-app") for _ in range(5)} assert len(codes) == 5 for code in codes: diff --git a/src/backend/core/tests/test_external_api_users.py b/src/backend/core/tests/test_external_api_users.py index a609bf574..da17ae589 100644 --- a/src/backend/core/tests/test_external_api_users.py +++ b/src/backend/core/tests/test_external_api_users.py @@ -207,3 +207,31 @@ def test_api_users_transit_code_rejects_addons_token(): response = client.post("/external-api/v1.0/users/transit-code/") assert response.status_code == 401 + + +@pytest.mark.parametrize("auth", [None, {}, {"client_id": ""}, {"client_id": None}]) +def test_api_users_transit_code_missing_client_id(auth): + """No transit code should be minted without a client_id in the token.""" + user = UserFactory() + + client = APIClient() + client.credentials(HTTP_AUTHORIZATION="Bearer token") + + with ( + mock.patch( + "core.external_api.authentication.ApplicationJWTAuthentication.authenticate", + return_value=(user, auth), + ), + mock.patch( + "core.external_api.permissions.HasRequiredUserScope.has_permission", + return_value=True, + ), + mock.patch.object( + TransitCodeService, "create_code", return_value="code" + ) as mock_create_code, + ): + response = client.post("/external-api/v1.0/users/transit-code/") + + assert response.status_code == 401 + assert response.json() == {"detail": "Invalid application token."} + mock_create_code.assert_not_called()