Skip to content

Commit d12a42d

Browse files
committed
Only store valid transports values.
1 parent b929a60 commit d12a42d

2 files changed

Lines changed: 49 additions & 2 deletions

File tree

hypha/apply/users/passkey_views.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
AuthenticatorAssertionResponse,
2929
AuthenticatorAttestationResponse,
3030
AuthenticatorSelectionCriteria,
31+
AuthenticatorTransport,
3132
PublicKeyCredentialDescriptor,
3233
RegistrationCredential,
3334
ResidentKeyRequirement,
@@ -72,6 +73,15 @@ def _load_challenge(request, key: str) -> bytes:
7273
return base64.b64decode(encoded)
7374

7475

76+
_VALID_TRANSPORTS = {t.value for t in AuthenticatorTransport}
77+
78+
79+
def _clean_transports(raw) -> list[str]:
80+
if not isinstance(raw, list):
81+
return []
82+
return [t for t in raw if isinstance(t, str) and t in _VALID_TRANSPORTS]
83+
84+
7585
# ---------------------------------------------------------------------------
7686
# Registration — requires an authenticated user
7787
# ---------------------------------------------------------------------------
@@ -131,6 +141,7 @@ def passkey_register_complete(request):
131141
except PermissionDenied:
132142
return JsonResponse({"error": _("No active WebAuthn challenge")}, status=400)
133143

144+
transports = _clean_transports(data.get("response", {}).get("transports"))
134145
try:
135146
credential = RegistrationCredential(
136147
id=data["id"],
@@ -140,7 +151,7 @@ def passkey_register_complete(request):
140151
attestation_object=base64url_to_bytes(
141152
data["response"]["attestationObject"]
142153
),
143-
transports=data["response"].get("transports", []),
154+
transports=transports,
144155
),
145156
)
146157
verification = verify_registration_response(
@@ -168,7 +179,7 @@ def passkey_register_complete(request):
168179
credential_id=bytes_to_base64url(verification.credential_id),
169180
public_key=bytes_to_base64url(verification.credential_public_key),
170181
sign_count=verification.sign_count,
171-
transports=data["response"].get("transports", []),
182+
transports=transports,
172183
)
173184
except Exception:
174185
logger.warning(

hypha/apply/users/tests/test_passkey_views.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,42 @@ def test_empty_name_gets_date_default(self, mock_verify):
206206
passkey = self.user.passkeys.first()
207207
self.assertTrue(passkey.name.startswith("Passkey "))
208208

209+
@patch("hypha.apply.users.passkey_views.verify_registration_response")
210+
def test_unknown_transports_are_filtered_out(self, mock_verify):
211+
mock_verify.return_value = MagicMock(
212+
credential_id=b"cred",
213+
credential_public_key=b"pubkey",
214+
sign_count=0,
215+
)
216+
self._set_challenge()
217+
payload = self._payload()
218+
payload["response"]["transports"] = ["internal", "junk-value", "usb", 123]
219+
response = self.client.post(
220+
REGISTER_COMPLETE_URL,
221+
data=json.dumps(payload),
222+
content_type="application/json",
223+
)
224+
self.assertEqual(response.status_code, 200)
225+
self.assertEqual(self.user.passkeys.first().transports, ["internal", "usb"])
226+
227+
@patch("hypha.apply.users.passkey_views.verify_registration_response")
228+
def test_non_list_transports_is_stored_as_empty_list(self, mock_verify):
229+
mock_verify.return_value = MagicMock(
230+
credential_id=b"cred",
231+
credential_public_key=b"pubkey",
232+
sign_count=0,
233+
)
234+
self._set_challenge()
235+
payload = self._payload()
236+
payload["response"]["transports"] = "internal" # not a list
237+
response = self.client.post(
238+
REGISTER_COMPLETE_URL,
239+
data=json.dumps(payload),
240+
content_type="application/json",
241+
)
242+
self.assertEqual(response.status_code, 200)
243+
self.assertEqual(self.user.passkeys.first().transports, [])
244+
209245
@patch("hypha.apply.users.passkey_views.verify_registration_response")
210246
def test_verification_failure_returns_400_and_saves_nothing(self, mock_verify):
211247
mock_verify.side_effect = Exception("crypto error")

0 commit comments

Comments
 (0)