def login(): challenge = session.get('challenge') credential_id = request.json.get('id') user_handle = request.json.get('userHandle') client_data = request.json.get('clientData') auth_data = request.json.get('authData') signature = request.json.get('signature') assertion_client_extensions = request.json.get('assertionClientExtensions') user = next((u for u in ADMIN_USERS.values() if u.credential_id == credential_id), None) if user is None: logger.info(f"Login start failed: no user with credential ID {credential_id}") return jsonify(status='failure', error='User with given credential_id not found') logger.info(f"Login submitted for user {user.username} with challenge {challenge}") webauthn_user = webauthn.WebAuthnUser(user_id=user.id, username=user.username, display_name=user.username, icon_url=ICON_URL, credential_id=user.credential_id, public_key=user.public_key, sign_count=0, rp_id=RELYING_PARTY_ID) webauthn_assertion_response = webauthn.WebAuthnAssertionResponse( webauthn_user=webauthn_user, assertion_response={'id': user.credential_id, 'userHandle': user_handle, 'clientData': client_data, 'authData': auth_data, 'signature': signature, 'assertionClientExtensions': assertion_client_extensions}, challenge=challenge, origin=ORIGIN, ) try: webauthn_assertion_response.verify() except Exception as e: logger.info(f"Login failed: {e}") return jsonify(status='failure', error=str(e)) logger.info(f"Login completed for user {user.username}") login_user(user, remember=False) return jsonify(status='success')
def verify_assertion(request): print("verify_assertion") challenge = request.session.get('challenge', False) assertion_response = request.POST credential_id = assertion_response.get('id') user = User.objects.filter(credential_id=credential_id).first() if not user: return JsonResponse({'fail': 'User does not exist.'}) webauthn_user = webauthn.WebAuthnUser( user.ukey, user.username, user.display_name, user.icon_url, user.credential_id, user.pub_key, user.sign_count, user.rp_id) webauthn_assertion_response = webauthn.WebAuthnAssertionResponse( webauthn_user, assertion_response, challenge, ORIGIN, uv_required=False) # User Verification try: sign_count = webauthn_assertion_response.verify() except Exception as e: return JsonResponse({'fail': 'Assertion failed. Error: {}'.format(e)}) # Update counter. user.sign_count = sign_count user.save() login(request, user) return JsonResponse({ 'success': 'Successfully authenticated as {}'.format(user.username) })
def post(self, request, *args, **kwargs): r = request.POST.get("webauthn", "") valid = False if 'webauthn_challenge' in self.request.session and r.startswith('{'): challenge = self.request.session['webauthn_challenge'] resp = json.loads(r) try: devices = [ WebAuthnDevice.objects.get(user=self.request.user, credential_id=resp.get("id")) ] except WebAuthnDevice.DoesNotExist: devices = U2FDevice.objects.filter(user=self.request.user) for d in devices: try: wu = d.webauthnuser if isinstance(d, U2FDevice): # RP_ID needs to be appId for U2F devices, but we can't # set it that way in U2FDevice.webauthnuser, since that # breaks the frontend part. wu.rp_id = settings.SITE_URL webauthn_assertion_response = webauthn.WebAuthnAssertionResponse( wu, resp, challenge, settings.SITE_URL, uv_required=False # User Verification ) sign_count = webauthn_assertion_response.verify() except Exception: logger.exception('U2F login failed') else: if isinstance(d, WebAuthnDevice): d.sign_count = sign_count d.save() valid = True break valid = valid or self.form.is_valid() if valid: t = int(time.time()) request.session['pretix_auth_login_time'] = t request.session['pretix_auth_last_used'] = t next_url = get_auth_backends()[ request.user.auth_backend].get_next_url(request) if next_url and url_has_allowed_host_and_scheme( next_url, allowed_hosts=None): return redirect(next_url) return redirect(reverse('control:index')) else: messages.error( request, _('The password you entered was invalid, please try again.')) return self.get(request, *args, **kwargs)
def login(): challenge = session.get('challenge') credential_id = request.json.get('id') user_handle = request.json.get('userHandle') client_data = request.json.get('clientData') auth_data = request.json.get('authData') signature = request.json.get('signature') assertion_client_extensions = request.json.get('assertionClientExtensions') user = next((u for u in USERS.values() if u['credential_id'] == credential_id), None) if user is None: return jsonify(status='failure', error='User with given credential ID not found') user_id, username, credential_id, public_key = user['id'], user['username'], user['credential_id'], user['public_key'] webauthn_user = webauthn.WebAuthnUser(user_id=user_id, username=username, display_name=username, icon_url=ICON_URL, credential_id=credential_id, public_key=public_key, sign_count=0, rp_id=RELYING_PARTY_ID) webauthn_assertion_response = webauthn.WebAuthnAssertionResponse( webauthn_user=webauthn_user, assertion_response={'id': credential_id, 'userHandle': user_handle, 'clientData': client_data, 'authData': auth_data, 'signature': signature, 'assertionClientExtensions': assertion_client_extensions}, challenge=challenge, origin=ORIGIN, ) try: webauthn_assertion_response.verify() except Exception as e: return jsonify(status='failure', error=str(e)) return jsonify(status='success', user=user)
def verify_assertion_response(assertion, *, challenge, user, origin, icon_url, rp_id): """ Validates the challenge and assertion information sent from the client during authentication. Returns an updated signage count on success. Raises AuthenticationRejectedException on failure. """ webauthn_users = _get_webauthn_users(user, icon_url=icon_url, rp_id=rp_id) cred_ids = [cred.credential_id for cred in webauthn_users] for webauthn_user in webauthn_users: response = pywebauthn.WebAuthnAssertionResponse( webauthn_user, assertion, _webauthn_b64encode(challenge.encode()).decode(), origin, allow_credentials=cred_ids, ) try: return (webauthn_user.credential_id, response.verify()) except _AuthenticationRejectedException: pass # If we exit the loop, then we've failed to verify the assertion against # any of the user's WebAuthn credentials. Fail. raise AuthenticationRejectedException("Invalid WebAuthn credential")
def verify_assertion(): challenge = session.get('challenge') assertion_response = request.form credential_id = assertion_response.get('id') user = User.query.filter_by(credential_id=credential_id).first() if not user: return make_response(jsonify({'fail': 'User does not exist.'}), 401) webauthn_user = webauthn.WebAuthnUser(user.ukey, user.username, user.display_name, user.icon_url, user.credential_id, user.pub_key, user.sign_count, user.rp_id) webauthn_assertion_response = webauthn.WebAuthnAssertionResponse( webauthn_user, assertion_response, challenge, ORIGIN, uv_required=False) # User Verification try: sign_count = webauthn_assertion_response.verify() except Exception as e: return jsonify({'fail': 'Assertion failed. Error: {}'.format(e)}) # Update counter. user.sign_count = sign_count db.session.add(user) db.session.commit() login_user(user) return jsonify( {'success': 'Successfully authenticated as {}'.format(user.username)})
def post(self, request, *args, **kwargs): token = request.POST.get('token', '').strip().replace(' ', '') valid = False if 'webauthn_challenge' in self.request.session and token.startswith( '{'): challenge = self.request.session['webauthn_challenge'] resp = json.loads(self.request.POST.get("token")) try: devices = [ WebAuthnDevice.objects.get(user=self.user, credential_id=resp.get("id")) ] except WebAuthnDevice.DoesNotExist: devices = U2FDevice.objects.filter(user=self.user) for d in devices: try: wu = d.webauthnuser if isinstance(d, U2FDevice): # RP_ID needs to be appId for U2F devices, but we can't # set it that way in U2FDevice.webauthnuser, since that # breaks the frontend part. wu.rp_id = settings.SITE_URL webauthn_assertion_response = webauthn.WebAuthnAssertionResponse( wu, resp, challenge, settings.SITE_URL, uv_required=False # User Verification ) sign_count = webauthn_assertion_response.verify() except Exception: logger.exception('U2F login failed') else: if isinstance(d, WebAuthnDevice): d.sign_count = sign_count d.save() valid = True break else: valid = match_token(self.user, token) if valid: auth_login(request, self.user) request.session['pretix_auth_login_time'] = int(time.time()) del request.session['pretix_auth_2fa_user'] del request.session['pretix_auth_2fa_time'] if "next" in request.GET and is_safe_url(request.GET.get("next"), allowed_hosts=None): return redirect(request.GET.get("next")) return redirect(reverse('control:index')) else: messages.error(request, _('Invalid code, please try again.')) return redirect('control:auth.login.2fa')
def clean(self): totp_or_scratch_code = self.cleaned_data.get('totp_or_scratch_code') if self.profile.is_webauthn_enabled and self.cleaned_data.get( 'webauthn_response'): if len(self.cleaned_data['webauthn_response']) > 65536: raise ValidationError(_('Invalid WebAuthn response.')) if not self.webauthn_challenge: raise ValidationError(_('No WebAuthn challenge issued.')) response = json.loads(self.cleaned_data['webauthn_response']) try: credential = self.profile.webauthn_credentials.get( cred_id=response.get('id', '')) except WebAuthnCredential.DoesNotExist: raise ValidationError(_('Invalid WebAuthn credential ID.')) user = credential.webauthn_user # Work around a useless check in the webauthn package. user.credential_id = credential.cred_id assertion = webauthn.WebAuthnAssertionResponse( webauthn_user=user, assertion_response=response.get('response'), challenge=self.webauthn_challenge, origin=self.webauthn_origin, uv_required=False, ) try: sign_count = assertion.verify() except Exception as e: raise ValidationError(str(e)) credential.counter = sign_count credential.save(update_fields=['counter']) elif totp_or_scratch_code: if self.profile.is_totp_enabled and self.profile.check_totp_code( totp_or_scratch_code): return elif self.profile.scratch_codes and totp_or_scratch_code in json.loads( self.profile.scratch_codes): scratch_codes = json.loads(self.profile.scratch_codes) scratch_codes.remove(totp_or_scratch_code) self.profile.scratch_codes = json.dumps(scratch_codes) self.profile.save(update_fields=['scratch_codes']) return elif self.profile.is_totp_enabled: raise ValidationError( _('Invalid two-factor authentication token or scratch code.' )) else: raise ValidationError(_('Invalid scratch code.')) else: raise ValidationError( _('Must specify either totp_token or webauthn_response.'))
def login(): claims = current_custom_claims() if not claims.get("is_login", False): return "", 401 data = LoginSchema().load(request.get_json()) email = claims["email"] credential_id = data["id"] authenticator = Authenticator.query.filter( Authenticator.credential_id == credential_id, Authenticator.user.has(email=email), ).first() if not authenticator: return {"error": "authenticator not found"}, 401 webauthn_user = webauthn.WebAuthnUser( user_id=authenticator.user.id, username=authenticator.user.email, display_name=authenticator.user.name, icon_url=app.config["WEBAUTHN_ICON_URL"], credential_id=authenticator.credential_id, public_key=authenticator.public_key, sign_count=authenticator.sign_count, rp_id=app.config["WEBAUTHN_RP_ID"], ) response = webauthn.WebAuthnAssertionResponse( webauthn_user=webauthn_user, challenge=claims["challenge"], assertion_response=data, origin=app.config["WEBAUTHN_ORIGIN"], ) try: sign_count = response.verify() except Exception as exc: return {"error": str(exc)}, 401 authenticator.sign_count = sign_count authenticator.last_used_at = datetime.now(timezone.utc) db.session.add(authenticator) db.session.commit() return ( { "token": guard.encode_jwt_token(authenticator.user), "user": UserSchema().dump(authenticator.user), "authenticator": AuthenticatorSchema().dump(authenticator), }, 200, )
def verify_assertion(): jsonData = request.get_json() AuthenticatorAttestationResponse = jsonData['AuthenticatorAttestationResponse'] clientDataJSON = AuthenticatorAttestationResponse['clientDataJSON'] clientDataJSON_padding = clientDataJSON.ljust((int)(math.ceil(len(clientDataJSON) / 4)) * 4, '=') clientDataJSON = base64.b64decode(clientDataJSON_padding).decode('utf8') clientDataJSONparsed = json.loads(clientDataJSON) retrievedChallenge = clientDataJSONparsed['challenge'] try: data = database.query_db("select * from PublicKeyCredentialCreationOptions where challenge=?",[retrievedChallenge])[0] except: return jsonify({"Error:","Could not find challenge"}),500 #DELETE from table database.delete_db("delete from PublicKeyCredentialCreationOptions where challenge=?",[retrievedChallenge]) signature = AuthenticatorAttestationResponse['signature'] assertion_response = {'clientData':AuthenticatorAttestationResponse['clientDataJSON'],'authData':AuthenticatorAttestationResponse['authenticatorData'],'signature':signature,'userHandle':AuthenticatorAttestationResponse['userHandle']} credential_id = AuthenticatorAttestationResponse.get('id') user = database.query_db("select * from Users where credential_id=?",[credential_id])[0] if len(user)==0: return make_response(jsonify({'fail': 'User does not exist.'}), 401) webauthn_user = webauthn.WebAuthnUser( user[0], user[0], user[2], user[6], user[4], user[3], user[5], user[7]) webauthn_assertion_response = webauthn.WebAuthnAssertionResponse( webauthn_user, assertion_response, retrievedChallenge, ORIGIN, uv_required=False) # User Verification sign_count = webauthn_assertion_response.verify() try: sign_count = webauthn_assertion_response.verify() except Exception as e: print(e) return make_response(jsonify({'fail': 'Assertion failed'}),500) # Update counter. #Update database update = database.insert_db("update Users SET sign_count=? where username=?",[sign_count,user[1]]) identityObj={'username':user[1],'id':user[0],'displayname':user[2]} expires = datetime.timedelta(hours=2) print(identityObj) jwt = create_access_token(identity=identityObj,expires_delta=expires) return jsonify({ 'success': 'Successfully authenticated as {}'.format(user[1]), 'jwt':jwt, 'username': user[1] })
def webauthn_finish_login(): user = User() cookie = None challenge = session.get('challenge') password = session.get('login_password') assertion_response = request.form credential_id = assertion_response.get('id') db, person = auth.getPersonByCredentialID(credential_id) if not person: return make_response(jsonify({'fail': 'User does not exist.'}), 401) webauthn_user = webauthn.WebAuthnUser( person.ukey, person.username, person.display_name, person.icon_url, person.credential_id, person.pub_key, person.sign_count, person.rp_id) webauthn_assertion_response = webauthn.WebAuthnAssertionResponse( webauthn_user, assertion_response, challenge, ORIGIN, uv_required=False) # User Verification try: sign_count = webauthn_assertion_response.verify() except Exception as e: return make_response(jsonify({'fail': 'Assertion failed. Error: {}'.format(e)}), 401) # Update counter. person.sign_count = sign_count db.commit() # TODO: Is this check not performing anything useful? if not person.username: return make_response(jsonify({'fail': 'You must supply a username to log in.'}), 401) elif not password: return make_response(jsonify({'fail': 'You must supply a password to log in.'}), 401) else: cookie = user.checkLogin(person.username, password) if not cookie: return make_response(jsonify({'fail': 'Invalid username or password.'}), 401) nexturl = request.values.get('nexturl', https_url_for('index')) response = make_response(jsonify({'nexturl': nexturl}), 200) ## Be careful not to include semicolons in cookie value; see ## https://github.com/mitsuhiko/werkzeug/issues/226 for more ## details. response.set_cookie('PyZoobarLogin', cookie) return response
def get_assertion_response(self): credential = self.test_validate_registration() webauthn_user = webauthn.WebAuthnUser( USER_ID, USER_NAME, USER_DISPLAY_NAME, ICON_URL, credential.credential_id.decode(), credential.public_key, credential.sign_count, credential.rp_id) webauthn_assertion_response = webauthn.WebAuthnAssertionResponse( webauthn_user, copy(ASSERTION_RESPONSE_TMPL), ASSERTION_CHALLENGE, ORIGIN, uv_required=False) return webauthn_assertion_response
def webauthn_verify_login(): '''Verify the user's credential attesttion during the login process''' challenge = session['login']['challenge'] assertion_response = request.form credential_id = assertion_response.get('id') # Ensure a matching user exists user = User.query.filter_by(credential_id=credential_id).first() if not user: flash('User does not exist') return make_response(jsonify({'redirect': url_for('login')}), 401) # TODO: determine if this info should be stored at the session level webauthn_user = webauthn.WebAuthnUser( user_id=user.ukey, username=user.email, display_name=user.display_name, icon_url=user.icon_url, credential_id=user.credential_id, public_key=user.public_key, sign_count=user.sign_count, rp_id=user.rp_id, ) webauthn_assertion_response = webauthn.WebAuthnAssertionResponse( webauthn_user=webauthn_user, assertion_response=assertion_response, challenge=challenge, origin=ORIGIN, uv_required=False, ) # User Verification try: sign_count = webauthn_assertion_response.verify() except Exception as e: flash(f'Assertion failed. Error: {e}') return make_response(jsonify({'redirect': url_for('login')}), 401) # Update counter. user.sign_count = sign_count db.session.add(user) db.session.commit() # Clear login session info session.pop('login', None) login_user(user) return jsonify({'redirect': url_for('profile')})
def clean(self): if self.profile.is_webauthn_enabled and self.cleaned_data.get( 'webauthn_response'): if len(self.cleaned_data['webauthn_response']) > 65536: raise ValidationError(_('Invalid WebAuthn response.')) if not self.webauthn_challenge: raise ValidationError(_('No WebAuthn challenge issued.')) response = json.loads(self.cleaned_data['webauthn_response']) try: credential = self.profile.webauthn_credentials.get( cred_id=response.get('id', '')) except WebAuthnCredential.DoesNotExist: raise ValidationError(_('Invalid WebAuthn credential ID.')) user = credential.webauthn_user # Work around a useless check in the webauthn package. user.credential_id = credential.cred_id assertion = webauthn.WebAuthnAssertionResponse( webauthn_user=user, assertion_response=response.get('response'), challenge=self.webauthn_challenge, origin=self.webauthn_origin, uv_required=False, ) try: sign_count = assertion.verify() except Exception as e: raise ValidationError(str(e)) credential.counter = sign_count credential.save(update_fields=['counter']) elif self.profile.is_totp_enabled and self.cleaned_data.get( 'totp_token'): if pyotp.TOTP(self.profile.totp_key).verify( self.cleaned_data['totp_token'], valid_window=self.TOLERANCE): return raise ValidationError( _('Invalid two-factor authentication token.')) else: raise ValidationError( _('Must specify either totp_token or webauthn_response.'))
def verify_assertion(election): challenge = session.get('challenge') assertion_response = request.form credential_id = assertion_response.get('id') user = Authority.query.filter( Election.name == election, Authority.credential_id == credential_id).first() if not user: return make_response(jsonify({'fail': 'User does not exist.'}), 401) webauthn_user = webauthn.WebAuthnUser(user.ukey, user.email, user.name, user.icon_url, user.credential_id, user.pub_key, user.sign_count, user.rp_id) http = request.url.split("://") origin = f"{http[0]}://{election}.{ORIGIN}" webauthn_assertion_response = webauthn.WebAuthnAssertionResponse( webauthn_user, assertion_response, challenge, origin, uv_required=False) # User Verification try: sign_count = webauthn_assertion_response.verify() except Exception as e: return jsonify({'fail': 'Assertion failed. Error: {}'.format(e)}) # Update counter user.sign_count = sign_count db.session.add(user) db.session.commit() login_user(user) return jsonify( {'success': 'Successfully authenticated as {}'.format(user.email)})
def verify(self, request, otp_device, data): challenge = request.session.get('challenge', '').rstrip('=') ukey = base64.urlsafe_b64encode(str(request.user.id).encode('utf-8')) webauthn_user = webauthn.WebAuthnUser( ukey, otp_device.user.username, otp_device.user.username, otp_device.data['icon_url'], otp_device.data['credential_id'], otp_device.data['pub_key'], otp_device.counter, otp_device.data['rp_id']) webauthn_assertion_response = webauthn.WebAuthnAssertionResponse( webauthn_user, data, challenge, self._get_origin(request), uv_required=False) # User Verification try: sign_count = webauthn_assertion_response.verify() except AuthenticationRejectedException as e: raise ValidationError(str(e)) otp_device.counter = sign_count otp_device.save() return {}
def login_webauthn_user_assertion_verify(request): current_site_details = get_current_site(request) assertion_challenge = request.session["challenge"] assertion_client_response = json.loads( request.POST["signedAssertionCredentials"]) webauthn_user = WebAuthnProfile.objects.filter( credential_id=assertion_client_response["id"]).first() origin = request.build_absolute_uri('/').strip("/") if DEBUG: # For angular dev server frontend - need to remove this later origin = origin.replace("8000", "4200").replace("http", "https") if webauthn_user is None: return JsonResponse({"error": "Invalid User"}) generated_webauthn_user = webauthn.WebAuthnUser( user_id=webauthn_user.webauthn_ukey, username=webauthn_user.user.username, display_name=webauthn_user.display_name, icon_url= "https://rajeevkr.me/assets/img/portfolio/IMG_20180302_232843.jpg", credential_id=webauthn_user.credential_id, public_key=webauthn_user.user_public_key, sign_count=webauthn_user.signature_counter, rp_id=current_site_details.domain.split(":")[0]) generated_webauthn_user_assertion_response = webauthn.WebAuthnAssertionResponse( webauthn_user=generated_webauthn_user, assertion_response=assertion_client_response, challenge=assertion_challenge, origin=origin, uv_required=False) try: signature_counter = generated_webauthn_user_assertion_response.verify() except Exception as e: traceback.print_exc() return JsonResponse({"error": f"Login assertion failed due to {e}"}, status=400) webauthn_user.signature_counter = signature_counter webauthn_user.save() return JsonResponse(webauthn_user.hackyDict())
def verify_assertion(): challenge = session['challenge'] assertion_response = request.form credential_id = assertion_response.get('id') publickey_redential = PublicKeyCredential.query.filter_by( credential_id=credential_id).first() if not publickey_redential: return make_response( jsonify({ "status": "failed", "msg": "No pubkey found" }), 401) user = publickey_redential.user name = publickey_redential.email webauthn_user = webauthn.WebAuthnUser(publickey_redential.ukey, name, user.display_name, "", credential_id, publickey_redential.pub_key, 0, publickey_redential.rp_id) webauthn_assertion_response = webauthn.WebAuthnAssertionResponse( webauthn_user, assertion_response, challenge, request.host_url[:-1], uv_required=False) try: webauthn_assertion_response.verify() except Exception as e: return make_response( jsonify({ 'status': 'failed', 'msg': '{}'.format(e) }), 401) session['register_username'] = name session['register_display_name'] = user.display_name session['register_ukey'] = publickey_redential.ukey login_user(user) return jsonify({"status": "success"})
def fido(): # passed from login page user_id = session.get(MFA_USER_ID) # user access this page directly without passing by login page if not user_id: flash("Unknown error, redirect back to main page", "warning") return redirect(url_for("auth.login")) user = User.get(user_id) if not (user and user.fido_enabled()): flash("Only user with security key linked should go to this page", "warning") return redirect(url_for("auth.login")) auto_activate = True fido_token_form = FidoTokenForm() next_url = request.args.get("next") webauthn_user = webauthn.WebAuthnUser( user.fido_uuid, user.email, user.name if user.name else user.email, False, user.fido_credential_id, user.fido_pk, user.fido_sign_count, RP_ID, ) # Handling POST requests if fido_token_form.validate_on_submit(): try: sk_assertion = json.loads(fido_token_form.sk_assertion.data) except Exception as e: flash("Key verification failed. Error: Invalid Payload", "warning") return redirect(url_for("auth.login")) challenge = session["fido_challenge"] webauthn_assertion_response = webauthn.WebAuthnAssertionResponse( webauthn_user, sk_assertion, challenge, URL, uv_required=False) try: new_sign_count = webauthn_assertion_response.verify() except Exception as e: LOG.error( f"An error occurred in WebAuthn verification process: {e}") flash("Key verification failed.", "warning") auto_activate = False else: user.fido_sign_count = new_sign_count db.session.commit() del session[MFA_USER_ID] login_user(user) flash(f"Welcome back {user.name}!", "success") # User comes to login page from another page if next_url: LOG.debug("redirect user to %s", next_url) return redirect(next_url) else: LOG.debug("redirect user to dashboard") return redirect(url_for("dashboard.index")) # Prepare information for key registration process session.pop("challenge", None) challenge = secrets.token_urlsafe(32) session["fido_challenge"] = challenge.rstrip("=") webauthn_assertion_options = webauthn.WebAuthnAssertionOptions( webauthn_user, challenge) webauthn_assertion_options = webauthn_assertion_options.assertion_dict return render_template( "auth/fido.html", fido_token_form=fido_token_form, webauthn_assertion_options=webauthn_assertion_options, enable_otp=user.enable_otp, auto_activate=auto_activate, )
def webauthn_finish_transfer(): cookie = None challenge = session.get('challenge') amount = session.get('transfer_amount') recipient = session.get('transfer_recipient') clientExtensions = session.get('clientExtensions') assertion_response = request.form credential_id = assertion_response.get('id') # Make sure action is performed on correct user if g.user.person.credential_id != credential_id: return make_response( jsonify({ 'fail': 'Credential ID does not match that of logged in user.' }), 401) db, person = auth.getPersonByCredentialID(credential_id) if not person: return make_response(jsonify({'fail': 'User does not exist.'}), 401) webauthn_user = webauthn.WebAuthnUser(person.ukey, person.username, person.display_name, person.icon_url, person.credential_id, person.pub_key, person.sign_count, person.rp_id) def verify_authenticator_extensions_fn(client_data, expected_authenticator_extensions): client_data_extensions = client_data.get('clientExtensions') # Make sure that the extensions dicts have the same keys if client_data_extensions.keys( ) != expected_authenticator_extensions.keys(): return False # Make sure that the key is only `txAuthSimple` for now if client_data_extensions.keys() != {'txAuthSimple'}: return False # Test the `txAuthSimple` extension, except for line breaks if client_data_extensions['txAuthSimple'].replace('\n', '') != \ expected_authenticator_extensions['txAuthSimple'].replace('\n', ''): return False # All passed return True webauthn_assertion_response = webauthn.WebAuthnAssertionResponse( webauthn_user, assertion_response, challenge, ORIGIN, uv_required=False, # User Verification expected_assertion_authenticator_extensions=clientExtensions, verify_authenticator_extensions_fn=verify_authenticator_extensions_fn, ) try: sign_count = webauthn_assertion_response.verify() except Exception as e: return make_response( jsonify({'fail': 'Assertion failed. Error: {}'.format(e)}), 401) # Update counter. person.sign_count = sign_count db.commit() # Perform the zoobar transfer bank.transfer(g.user.person.username, recipient, amount) nexturl = request.values.get('nexturl', https_url_for('transfer_page')) response = make_response(jsonify({'nexturl': nexturl}), 200) return response
def fido(): # passed from login page user_id = session.get(MFA_USER_ID) # user access this page directly without passing by login page if not user_id: flash("Unknown error, redirect back to main page", "warning") return redirect(url_for("auth.login")) user = User.get(user_id) if not (user and user.fido_enabled()): flash("Only user with security key linked should go to this page", "warning") return redirect(url_for("auth.login")) auto_activate = True fido_token_form = FidoTokenForm() next_url = request.args.get("next") if request.cookies.get("mfa"): browser = MfaBrowser.get_by(token=request.cookies.get("mfa")) if browser and not browser.is_expired() and browser.user_id == user.id: login_user(user) flash(f"Welcome back {user.name}!", "success") # Redirect user to correct page return redirect(next_url or url_for("dashboard.index")) else: # Trigger rate limiter g.deduct_limit = True # Handling POST requests if fido_token_form.validate_on_submit(): try: sk_assertion = json.loads(fido_token_form.sk_assertion.data) except Exception as e: flash("Key verification failed. Error: Invalid Payload", "warning") return redirect(url_for("auth.login")) challenge = session["fido_challenge"] try: fido_key = Fido.get_by(uuid=user.fido_uuid, credential_id=sk_assertion["id"]) webauthn_user = webauthn.WebAuthnUser( user.fido_uuid, user.email, user.name if user.name else user.email, False, fido_key.credential_id, fido_key.public_key, fido_key.sign_count, RP_ID, ) webauthn_assertion_response = webauthn.WebAuthnAssertionResponse( webauthn_user, sk_assertion, challenge, URL, uv_required=False) new_sign_count = webauthn_assertion_response.verify() except Exception as e: LOG.exception( f"An error occurred in WebAuthn verification process: {e}") flash("Key verification failed.", "warning") # Trigger rate limiter g.deduct_limit = True auto_activate = False else: user.fido_sign_count = new_sign_count db.session.commit() del session[MFA_USER_ID] login_user(user) flash(f"Welcome back {user.name}!", "success") # Redirect user to correct page response = make_response( redirect(next_url or url_for("dashboard.index"))) if fido_token_form.remember.data: browser = MfaBrowser.create_new(user=user) db.session.commit() response.set_cookie( "mfa", value=browser.token, expires=browser.expires.datetime, secure=True if URL.startswith("https") else False, httponly=True, samesite="Lax", ) return response # Prepare information for key registration process session.pop("challenge", None) challenge = secrets.token_urlsafe(32) session["fido_challenge"] = challenge.rstrip("=") fidos = Fido.filter_by(uuid=user.fido_uuid).all() webauthn_users = [] for fido in fidos: webauthn_users.append( webauthn.WebAuthnUser( user.fido_uuid, user.email, user.name if user.name else user.email, False, fido.credential_id, fido.public_key, fido.sign_count, RP_ID, )) webauthn_assertion_options = webauthn.WebAuthnAssertionOptions( webauthn_users, challenge) webauthn_assertion_options = webauthn_assertion_options.assertion_dict return render_template( "auth/fido.html", fido_token_form=fido_token_form, webauthn_assertion_options=webauthn_assertion_options, enable_otp=user.enable_otp, auto_activate=auto_activate, next_url=next_url, )
def webauthn_verify_assertion(request): challenge = request.session.get("challenge") assertion_response = request.POST credential_id = assertion_response.get("id") user = util.get_user(request) username = user.get_username() display_name = user.get_full_name() key = WebAuthnKey.objects.filter(credential_id=credential_id, user=user).first() if not key: return JsonResponse({"fail": "Key does not exist."}, status=400) webauthn_user = webauthn.WebAuthnUser( key.ukey, username, display_name, settings.WEBAUTHN_ICON_URL, key.credential_id, key.public_key, key.sign_count, settings.RELYING_PARTY_ID, ) webauthn_assertion_response = webauthn.WebAuthnAssertionResponse( webauthn_user, assertion_response, challenge, util.get_origin(request), uv_required=False, # User Verification ) try: sign_count = webauthn_assertion_response.verify() except Exception as e: return JsonResponse({"fail": "Assertion failed. Error: {}".format(e)}, status=400) # Update counter. key.sign_count = sign_count key.last_used = now() key.save() try: del request.session["kagi_pre_verify_user_pk"] del request.session["kagi_pre_verify_user_backend"] del request.session["challenge"] except KeyError: # pragma: no cover pass auth.login(request, user) redirect_to = request.POST.get( auth.REDIRECT_FIELD_NAME, request.GET.get(auth.REDIRECT_FIELD_NAME, "")) if not is_safe_url(url=redirect_to, allowed_hosts=[request.get_host()]): redirect_to = resolve_url(django_settings.LOGIN_REDIRECT_URL) return JsonResponse({ "success": "Successfully authenticated as {}".format(username), "redirect_to": redirect_to, })