Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle WebAuthn challenge having multiple factors #6945

Merged
merged 5 commits into from
Mar 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion edb/lib/ext/auth.edgeql
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ CREATE EXTENSION PACKAGE auth VERSION '1.0' {
create required property challenge: std::bytes {
create constraint exclusive;
};
create required link factor: ext::auth::WebAuthnFactor {
create required multi link factors: ext::auth::WebAuthnFactor {
create constraint exclusive;
};
};
Expand Down
9 changes: 5 additions & 4 deletions edb/server/protocol/auth_ext/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,13 +190,14 @@ class WebAuthnAuthenticationChallenge:
created_at: datetime.datetime
modified_at: datetime.datetime
challenge: bytes
factor: WebAuthnFactor
factors: list[WebAuthnFactor]

def __init__(self, *, id, created_at, modified_at, challenge, factor):
def __init__(self, *, id, created_at, modified_at, challenge, factors):
self.id = id
self.created_at = created_at
self.modified_at = modified_at
self.challenge = base64.b64decode(challenge)
self.factor = (
self.factors = [
WebAuthnFactor(**factor) if isinstance(factor, dict) else factor
)
for factor in factors
]
32 changes: 20 additions & 12 deletions edb/server/protocol/auth_ext/webauthn.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,18 +315,18 @@ async def create_authentication_options_for_email(
challenge := <bytes>$challenge,
user_handle := <bytes>$user_handle,
email := <str>$email,
factor := (
assert_exists(assert_single((
factors := (
assert_exists((
select ext::auth::WebAuthnFactor
filter .user_handle = user_handle
and .email = email
)))
))
)
insert ext::auth::WebAuthnAuthenticationChallenge {
challenge := challenge,
factor := factor,
factors := factors,
}
unless conflict on .factor
unless conflict on .factors
else (
update ext::auth::WebAuthnAuthenticationChallenge
set {
Expand Down Expand Up @@ -389,7 +389,7 @@ async def _get_authentication_challenge(
created_at,
modified_at,
challenge,
factor: {
factors: {
id,
created_at,
modified_at,
Expand All @@ -407,7 +407,7 @@ async def _get_authentication_challenge(
}
},
}
filter .factor.email = email and .factor.credential_id = credential_id;""",
filter .factors.email = email and .factors.credential_id = credential_id;""",
variables={
"email": email,
"credential_id": credential_id,
Expand Down Expand Up @@ -437,7 +437,7 @@ async def _delete_authentication_challenges(
email := <str>$email,
credential_id := <bytes>$credential_id,
delete ext::auth::WebAuthnAuthenticationChallenge
filter .factor.email = email and .factor.credential_id = credential_id;""",
filter .factors.email = email and .factors.credential_id = credential_id;""",
variables={
"email": email,
"credential_id": credential_id,
Expand All @@ -461,13 +461,21 @@ async def authenticate(
credential_id=credential.raw_id,
)

factor = next(
(
f
for f in authentication_challenge.factors
if f.credential_id == credential.raw_id
),
None,
)
assert factor is not None, "Missing factor for the given credential."

try:
webauthn.verify_authentication_response(
credential=credential,
expected_challenge=authentication_challenge.challenge,
credential_public_key=(
authentication_challenge.factor.public_key
),
credential_public_key=factor.public_key,
credential_current_sign_count=0,
expected_rp_id=self.provider.relying_party_id,
expected_origin=self.provider.relying_party_origin,
Expand All @@ -477,4 +485,4 @@ async def authenticate(
"Invalid authentication response. Please retry authentication."
)

return authentication_challenge.factor.identity
return factor.identity
4 changes: 2 additions & 2 deletions tests/test_http_ext_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -3807,8 +3807,8 @@ async def test_http_auth_ext_webauthn_authenticate_options(self):
SELECT EXISTS (
SELECT ext::auth::WebAuthnAuthenticationChallenge
filter .challenge = <bytes>$challenge
AND .factor.email = <str>$email
AND .factor.user_handle = <bytes>$user_handle
AND .factors.email = <str>$email
AND .factors.user_handle = <bytes>$user_handle
)
''',
challenge=challenge_bytes,
Expand Down