feat: require discoverable credentials and prefer user verification in WebAuthnService
This commit is contained in:
parent
8aebd04d2a
commit
2ffe968342
2 changed files with 44 additions and 1 deletions
|
|
@ -10,6 +10,8 @@ from fido2.webauthn import (
|
||||||
PublicKeyCredentialRpEntity,
|
PublicKeyCredentialRpEntity,
|
||||||
PublicKeyCredentialUserEntity,
|
PublicKeyCredentialUserEntity,
|
||||||
RegistrationResponse,
|
RegistrationResponse,
|
||||||
|
ResidentKeyRequirement,
|
||||||
|
UserVerificationRequirement,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -36,6 +38,8 @@ class WebAuthnService:
|
||||||
options, state = self._server.register_begin(
|
options, state = self._server.register_begin(
|
||||||
user=user,
|
user=user,
|
||||||
credentials=existing_credentials,
|
credentials=existing_credentials,
|
||||||
|
resident_key_requirement=ResidentKeyRequirement.REQUIRED,
|
||||||
|
user_verification=UserVerificationRequirement.PREFERRED,
|
||||||
)
|
)
|
||||||
return dict(options), state
|
return dict(options), state
|
||||||
|
|
||||||
|
|
@ -58,7 +62,10 @@ class WebAuthnService:
|
||||||
|
|
||||||
Returns (options_dict, state_dict).
|
Returns (options_dict, state_dict).
|
||||||
"""
|
"""
|
||||||
options, state = self._server.authenticate_begin(credentials=credentials)
|
options, state = self._server.authenticate_begin(
|
||||||
|
credentials=credentials,
|
||||||
|
user_verification=UserVerificationRequirement.PREFERRED,
|
||||||
|
)
|
||||||
return dict(options), state
|
return dict(options), state
|
||||||
|
|
||||||
def complete_authentication(
|
def complete_authentication(
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,23 @@ def test_complete_registration_returns_credential_data() -> None:
|
||||||
assert result.credential_data.credential_id == credential_id
|
assert result.credential_data.credential_id == credential_id
|
||||||
|
|
||||||
|
|
||||||
|
def test_begin_registration_requires_resident_key() -> None:
|
||||||
|
service = _make_service()
|
||||||
|
options, _state = service.begin_registration(user_id=b"user-123", username="alice")
|
||||||
|
pub_key = options["publicKey"]
|
||||||
|
auth_sel = pub_key["authenticatorSelection"]
|
||||||
|
assert auth_sel["residentKey"] == "required"
|
||||||
|
assert auth_sel["requireResidentKey"] is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_begin_registration_prefers_user_verification() -> None:
|
||||||
|
service = _make_service()
|
||||||
|
options, _state = service.begin_registration(user_id=b"user-123", username="alice")
|
||||||
|
pub_key = options["publicKey"]
|
||||||
|
auth_sel = pub_key["authenticatorSelection"]
|
||||||
|
assert auth_sel["userVerification"] == "preferred"
|
||||||
|
|
||||||
|
|
||||||
def test_begin_registration_with_existing_credentials() -> None:
|
def test_begin_registration_with_existing_credentials() -> None:
|
||||||
service = _make_service()
|
service = _make_service()
|
||||||
_, cred_id, _attested = _generate_credential()
|
_, cred_id, _attested = _generate_credential()
|
||||||
|
|
@ -157,6 +174,25 @@ def test_begin_registration_with_existing_credentials() -> None:
|
||||||
# --- Authentication tests ---
|
# --- Authentication tests ---
|
||||||
|
|
||||||
|
|
||||||
|
def test_begin_authentication_without_credentials() -> None:
|
||||||
|
"""Usernameless flow: no allowCredentials, browser shows passkey picker."""
|
||||||
|
service = _make_service()
|
||||||
|
options, state = service.begin_authentication()
|
||||||
|
assert "publicKey" in options
|
||||||
|
assert "challenge" in state
|
||||||
|
pub_key = options["publicKey"]
|
||||||
|
# allowCredentials should be absent or empty
|
||||||
|
allow = pub_key.get("allowCredentials", [])
|
||||||
|
assert allow is None or len(allow) == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_begin_authentication_prefers_user_verification() -> None:
|
||||||
|
service = _make_service()
|
||||||
|
options, _state = service.begin_authentication()
|
||||||
|
pub_key = options["publicKey"]
|
||||||
|
assert pub_key["userVerification"] == "preferred"
|
||||||
|
|
||||||
|
|
||||||
def test_begin_authentication_returns_options_and_state() -> None:
|
def test_begin_authentication_returns_options_and_state() -> None:
|
||||||
service = _make_service()
|
service = _make_service()
|
||||||
_, cred_id, _attested = _generate_credential()
|
_, cred_id, _attested = _generate_credential()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue