diff --git a/app/config.py b/app/config.py index 89bd3dc6..77dcb641 100644 --- a/app/config.py +++ b/app/config.py @@ -113,3 +113,4 @@ AVATAR_URL_EXPIRATION = 3600 * 24 * 7 # 1h*24h/d*7d=1week # session key HIGHLIGHT_GEN_EMAIL_ID = "highlight_gen_email_id" +MFA_USER_ID = "mfa_user_id" diff --git a/app/dashboard/__init__.py b/app/dashboard/__init__.py index f8576933..8af6f572 100644 --- a/app/dashboard/__init__.py +++ b/app/dashboard/__init__.py @@ -9,4 +9,5 @@ from .views import ( api_key, custom_domain, alias_contact_manager, + mfa_setup, ) diff --git a/app/dashboard/templates/dashboard/mfa_setup.html b/app/dashboard/templates/dashboard/mfa_setup.html new file mode 100644 index 00000000..55def8fb --- /dev/null +++ b/app/dashboard/templates/dashboard/mfa_setup.html @@ -0,0 +1,51 @@ +{% extends 'default.html' %} + +{% block title %} + MFA Setup +{% endblock %} + +{% block head %} + +{% endblock %} + +{% block default_content %} +
+

Two Factor Authentication

+

Please open a TOTP application (Google Authenticator, Authy, etc) + on your smartphone and scan the following QR Code: +

+ + + + + +
+ Or you can use the manual entry with the following key: +
+ +
+ {{ current_user.otp_secret }} +
+ + +
+ {{ otp_token_form.csrf_token }} + +
Token
+
Please enter the 6-digit number displayed on your phone.
+ + {{ otp_token_form.token(class="form-control", placeholder="") }} + {{ render_field_errors(otp_token_form.token) }} + +
+ + +
+{% endblock %} diff --git a/app/dashboard/views/mfa_setup.py b/app/dashboard/views/mfa_setup.py new file mode 100644 index 00000000..0d17de2e --- /dev/null +++ b/app/dashboard/views/mfa_setup.py @@ -0,0 +1,49 @@ +import pyotp +from flask import render_template, flash, redirect, url_for +from flask_login import login_required, current_user +from flask_wtf import FlaskForm +from wtforms import StringField, validators + +from app.dashboard.base import dashboard_bp +from app.extensions import db +from app.log import LOG + + +class OtpTokenForm(FlaskForm): + token = StringField("Token", validators=[validators.DataRequired()]) + + +@dashboard_bp.route("/mfa_setup", methods=["GET", "POST"]) +@login_required +def mfa_setup(): + if current_user.enable_otp: + flash("you have already enabled MFA", "warning") + return redirect(url_for("dashboard.index")) + + otp_token_form = OtpTokenForm() + + if not current_user.otp_secret: + LOG.d("Generate otp_secret for user %s", current_user) + current_user.otp_secret = pyotp.random_base32() + db.session.commit() + + totp = pyotp.TOTP(current_user.otp_secret) + + if otp_token_form.validate_on_submit(): + token = otp_token_form.token.data + + if totp.verify(token): + current_user.enable_otp = True + db.session.commit() + flash("2FA has been activated", "success") + return redirect(url_for("dashboard.index")) + else: + flash("Incorrect token", "warning") + + otp_uri = pyotp.totp.TOTP(current_user.otp_secret).provisioning_uri( + name=current_user.email, issuer_name="SimpleLogin" + ) + + return render_template( + "dashboard/mfa_setup.html", otp_token_form=otp_token_form, otp_uri=otp_uri + )