diff --git a/app/dashboard/__init__.py b/app/dashboard/__init__.py index 7a6fcabd..16ca9962 100644 --- a/app/dashboard/__init__.py +++ b/app/dashboard/__init__.py @@ -9,6 +9,7 @@ from .views import ( api_key, custom_domain, alias_contact_manager, + enter_sudo, mfa_setup, mfa_cancel, fido_setup, diff --git a/app/dashboard/templates/dashboard/enter_sudo.html b/app/dashboard/templates/dashboard/enter_sudo.html new file mode 100644 index 00000000..934f7aa3 --- /dev/null +++ b/app/dashboard/templates/dashboard/enter_sudo.html @@ -0,0 +1,31 @@ +{% extends 'default.html' %} +{% set active_page = "setting" %} +{% block title %} + SUDO MODE +{% endblock %} + + +{% block default_content %} +
+
+

Entering Sudo Mode

+

+ You are trying to change sensitive settings +

+

+ Please enter the password of your account so that we can ensure it's you. +

+ +
+ {{ password_check_form.csrf_token }} + +
Password
+ + {{ password_check_form.password(class="form-control", autofocus="true") }} + {{ render_field_errors(password_check_form.password) }} + +
+ +
+
+{% endblock %} \ No newline at end of file diff --git a/app/dashboard/views/enter_sudo.py b/app/dashboard/views/enter_sudo.py new file mode 100644 index 00000000..0e08dd42 --- /dev/null +++ b/app/dashboard/views/enter_sudo.py @@ -0,0 +1,55 @@ +from time import time + +from flask import render_template, flash, redirect, url_for, session, request +from flask_login import login_required, current_user +from flask_wtf import FlaskForm +from wtforms import PasswordField, validators +from functools import wraps + +from app.dashboard.base import dashboard_bp +from app.config import DEBUG +from app.log import LOG + + +class LoginForm(FlaskForm): + password = PasswordField("Password", validators=[validators.DataRequired()]) + + +@dashboard_bp.route("/enter_sudo", methods=["GET", "POST"]) +@login_required +def enter_sudo(): + password_check_form = LoginForm() + + if password_check_form.validate_on_submit(): + password = password_check_form.password.data + + if current_user.check_password(password): + session["sudo_time"] = int(time()) + + # User comes to sudo page from another page + next_url = request.args.get("next") + 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: + flash("Incorrect password", "warning") + + return render_template( + "dashboard/enter_sudo.html", password_check_form=password_check_form + ) + +def sudo_required(f): + @wraps(f) + def wrap(*args, **kwargs): + # Reset sudo mode in every 20s under dev mode + SUDO_GAP = 900 if not DEBUG else 20 + if "sudo_time" not in session or (time() - int(session["sudo_time"])) > SUDO_GAP: + return redirect( + url_for("dashboard.enter_sudo", next=request.path) + ) + return f(*args, **kwargs) + + return wrap \ No newline at end of file diff --git a/app/dashboard/views/fido_setup.py b/app/dashboard/views/fido_setup.py index 14c1b471..9107c146 100644 --- a/app/dashboard/views/fido_setup.py +++ b/app/dashboard/views/fido_setup.py @@ -1,6 +1,7 @@ import json import secrets import uuid +from time import time import webauthn from flask import render_template, flash, redirect, url_for, session @@ -13,7 +14,7 @@ from app.dashboard.base import dashboard_bp from app.extensions import db from app.log import LOG from app.models import FIDO - +from app.dashboard.views.enter_sudo import sudo_required class FidoTokenForm(FlaskForm): sk_assertion = HiddenField("sk_assertion", validators=[validators.DataRequired()]) @@ -21,6 +22,7 @@ class FidoTokenForm(FlaskForm): @dashboard_bp.route("/fido_setup", methods=["GET", "POST"]) @login_required +@sudo_required def fido_setup(): if not current_user.can_use_fido: flash( diff --git a/app/dashboard/views/mfa_cancel.py b/app/dashboard/views/mfa_cancel.py index d982b571..66c36c2a 100644 --- a/app/dashboard/views/mfa_cancel.py +++ b/app/dashboard/views/mfa_cancel.py @@ -7,6 +7,7 @@ from wtforms import StringField, validators from app.dashboard.base import dashboard_bp from app.extensions import db from app.models import RecoveryCode +from app.dashboard.views.enter_sudo import sudo_required class OtpTokenForm(FlaskForm): @@ -15,6 +16,7 @@ class OtpTokenForm(FlaskForm): @dashboard_bp.route("/mfa_cancel", methods=["GET", "POST"]) @login_required +@sudo_required def mfa_cancel(): if not current_user.enable_otp: flash("you don't have MFA enabled", "warning") diff --git a/app/dashboard/views/mfa_setup.py b/app/dashboard/views/mfa_setup.py index 255e58a1..2b7ac48c 100644 --- a/app/dashboard/views/mfa_setup.py +++ b/app/dashboard/views/mfa_setup.py @@ -7,6 +7,7 @@ from wtforms import StringField, validators from app.dashboard.base import dashboard_bp from app.extensions import db from app.log import LOG +from app.dashboard.views.enter_sudo import sudo_required class OtpTokenForm(FlaskForm): @@ -15,6 +16,7 @@ class OtpTokenForm(FlaskForm): @dashboard_bp.route("/mfa_setup", methods=["GET", "POST"]) @login_required +@sudo_required def mfa_setup(): if current_user.enable_otp: flash("you have already enabled MFA", "warning")