From 650d6e35f048de9ba9d93e0d12dd0111e91798b8 Mon Sep 17 00:00:00 2001 From: devStorm <59678453+developStorm@users.noreply.github.com> Date: Tue, 5 May 2020 05:03:29 -0700 Subject: [PATCH] FIDO login middleware --- app/auth/__init__.py | 1 + app/auth/templates/auth/fido.html | 60 ++++++++++ app/auth/views/fido.py | 105 ++++++++++++++++++ .../templates/dashboard/fido_setup.html | 2 +- app/dashboard/views/fido_setup.py | 4 +- 5 files changed, 169 insertions(+), 3 deletions(-) create mode 100644 app/auth/templates/auth/fido.html create mode 100644 app/auth/views/fido.py diff --git a/app/auth/__init__.py b/app/auth/__init__.py index 322cbd81..6a51b706 100644 --- a/app/auth/__init__.py +++ b/app/auth/__init__.py @@ -11,5 +11,6 @@ from .views import ( facebook, change_email, mfa, + fido, social, ) diff --git a/app/auth/templates/auth/fido.html b/app/auth/templates/auth/fido.html new file mode 100644 index 00000000..abde9d4f --- /dev/null +++ b/app/auth/templates/auth/fido.html @@ -0,0 +1,60 @@ +{% extends "single.html" %} + + +{% block title %} + Verify Your Security Key +{% endblock %} + +{% block head %} + + +{% endblock %} + +{% block single_content %} +
+ +
+ Your account is protected with your security key (WebAuthn).

+ Follow your browser's steps to continue the sign-in process. +
+ +
+ {{ fido_token_form.csrf_token }} + {{ fido_token_form.sk_assertion(class="form-control", placeholder="") }} +
+
+ +
+ + + +
+ +{% endblock %} \ No newline at end of file diff --git a/app/auth/views/fido.py b/app/auth/views/fido.py new file mode 100644 index 00000000..e4ef5ddd --- /dev/null +++ b/app/auth/views/fido.py @@ -0,0 +1,105 @@ +import json +import secrets +import webauthn +from app.config import URL as SITE_URL +from urllib.parse import urlparse + +from flask import request, render_template, redirect, url_for, flash, session +from flask_login import login_user +from flask_wtf import FlaskForm +from wtforms import HiddenField, validators + +from app.auth.base import auth_bp +from app.config import MFA_USER_ID +from app.log import LOG +from app.models import User +from app.extensions import db + + +class FidoTokenForm(FlaskForm): + sk_assertion = HiddenField("sk_assertion", validators=[validators.DataRequired()]) + + +@auth_bp.route("/fido", methods=["GET", "POST"]) +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_uuid is not None)): + flash("Only user with security key linked should go to this page", "warning") + return redirect(url_for("auth.login")) + + fido_token_form = FidoTokenForm() + + next_url = request.args.get("next") + + rp_id = urlparse(SITE_URL).hostname + + webauthn_user = webauthn.WebAuthnUser( + user.fido_uuid, user.email, user.name, 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 registration failed. Error: Invalid Payload', "warning") + return redirect(url_for("dashboard.index")) + + challenge = session['fido_challenge'] + credential_id = sk_assertion['id'] + + webauthn_assertion_response = webauthn.WebAuthnAssertionResponse( + webauthn_user, + sk_assertion, + challenge, + SITE_URL, + uv_required=False + ) + + new_sign_count = False + new_sign_count = webauthn_assertion_response.verify() + try: + pass + except Exception as e: + flash('Key verification failed. Error: {}'.format(e), "warning") + + if new_sign_count != False: + 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")) + else: + # Verification failed, put else here to make structure clear + pass + + # Prepare infomation 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) \ No newline at end of file diff --git a/app/dashboard/templates/dashboard/fido_setup.html b/app/dashboard/templates/dashboard/fido_setup.html index 713c29b2..3c8b66e5 100644 --- a/app/dashboard/templates/dashboard/fido_setup.html +++ b/app/dashboard/templates/dashboard/fido_setup.html @@ -20,7 +20,7 @@ {{ fido_token_form.sk_assertion(class="form-control", placeholder="") }}
- +