From 056d690db0dd415699d0e3caee0adae828afa602 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Thu, 23 Apr 2026 17:46:06 -0400 Subject: [PATCH] Fully fix webauthn as implemented --- confluent_server/confluent/httpapi.py | 2 +- confluent_server/confluent/webauthn.py | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/confluent_server/confluent/httpapi.py b/confluent_server/confluent/httpapi.py index 3c6fc311..9c07bca6 100644 --- a/confluent_server/confluent/httpapi.py +++ b/confluent_server/confluent/httpapi.py @@ -999,7 +999,7 @@ async def resourcehandler_backend(req, make_response): url = reqpath url = url.replace('.json', '') url = url.replace('.html', '') - if url == '/sessions/current/info': + if url == '/sessions/current/info' or url.startswith('/sessions/current/webauthn/validate/'): rsp = await make_response('application/json', 200, cookies=cookies) sessinfo = {'username': authorized['username']} if 'authtoken' in authorized: diff --git a/confluent_server/confluent/webauthn.py b/confluent_server/confluent/webauthn.py index 86afc1eb..b1cc52f2 100644 --- a/confluent_server/confluent/webauthn.py +++ b/confluent_server/confluent/webauthn.py @@ -172,10 +172,14 @@ def b64decode(data: str) -> bytes: data += '=' * (-len(data) % 4) # Pad with '='s return base64.urlsafe_b64decode(data) -async def registration_response(request, username, APP_RELYING_PARTY, APP_ORIGIN): - cdj = request['response']['clientDataJSON'] +def get_challenge_from_response(rsp): + cdj = rsp['response']['clientDataJSON'] cdata = json.loads(b64decode(cdj)) challenge = b64decode(cdata['challenge']) + return challenge + +async def registration_response(request, username, APP_RELYING_PARTY, APP_ORIGIN): + challenge = get_challenge_from_response(request) if challenge not in challenges: raise Exception("Could not find challenge") chausername = challenges.pop(challenge, None) @@ -227,15 +231,18 @@ def authentication_response(request, username, APP_RELYING_PARTY, APP_ORIGIN): user_model = User.get(username) if not user_model: raise Exception("Invalid Username") + challenge = get_challenge_from_response(request) + expected_username = challenges.pop(challenge, None) + if expected_username is None: + raise Exception("No matching challenge") print(repr(request)) - raise Exception("Nope") credential_model = User.get_credential(credential_id=None, username=username) if not credential_model: raise Exception("No credential for user") verification = verify_authentication_response( credential=request, - expected_challenge=challenge_model.request, + expected_challenge=challenge, expected_rp_id=APP_RELYING_PARTY.id, expected_origin=APP_ORIGIN, credential_public_key = credential_model.credential_public_key, @@ -243,7 +250,6 @@ def authentication_response(request, username, APP_RELYING_PARTY, APP_ORIGIN): require_user_verification = True ) - return {"verified": True} class RpEntity(object): @@ -294,7 +300,7 @@ async def handle_api_request(url, req, username, cfm, reqbody, authorized): rsp = authentication_response(req, username, APP_RELYING_PARTY, APP_ORIGIN) if rsp == 'Timeout': raise Exception('Authentication timed out') - elif rsp['verified']: + elif rsp['verified'] and authorized is not None: sessinfo = {'username': username} if 'authtoken' in authorized: sessinfo['authtoken'] = authorized['authtoken']