~fnux/meta.sr.ht

8103ed0e99a12bda6afc7df0a92d6f126f96d593 — наб 8 months ago c853a83 0.50.2
Require completion of 2FA challenge to disable 2FA

Ref: ~sircmpwn/meta.sr.ht#158
M metasrht/blueprints/auth.py => metasrht/blueprints/auth.py +18 -6
@@ 7,6 7,7 @@ from metasrht.auth import is_external_auth
from metasrht.auth.builtin import hash_password, check_password
from metasrht.auth_validation import validate_password
from metasrht.auth_validation import validate_username, validate_email
from metasrht.blueprints.security import metrics as security_metrics
from metasrht.email import send_email
from metasrht.totp import totp
from metasrht.types import User, UserType, Invite


@@ 307,11 308,11 @@ def totp_challenge_POST():
        return redirect("/login")
    valid = Validation(request)

    code = valid.require('code')

    code = valid.require("code")
    if not valid.ok:
        return render_template("totp-challenge.html",
            return_to=return_to, valid=valid)

    code = code.replace(" ", "")
    try:
        code = int(code)


@@ 329,9 330,9 @@ def totp_challenge_POST():
            'The code you entered is incorrect.', field='code')

    user = User.query.get(user_id)

    if not valid.ok:
        print(f"Login attempt failed (TOTP) for {user.username} ({user.email})")
        print(f"{challenge_type} attempt failed (TOTP) for " +
            f"{user.username} ({user.email})")
        return render_template("totp-challenge.html",
            valid=valid, return_to=return_to)



@@ 354,6 355,14 @@ def totp_challenge_POST():
        return redirect(return_to)
    elif challenge_type == "reset":
        return issue_reset(user)
    elif challenge_type == "disable_totp":
        db.session.delete(factor)
        audit_log("Disable TOTP", details="Disabled two-factor authentication",
                email=True, subject=f"TOTP has been disabled for your {cfg('sr.ht', 'site-name')} account",
                email_details="2FA via TOTP was disabled")
        db.session.commit()
        security_metrics.meta_totp_disabled.inc()
        return redirect(return_to)
    else:
        raise NotImplemented



@@ 393,6 402,8 @@ def totp_recovery_POST():
        return render_template("totp-recovery.html",
            return_to=return_to, **valid.kwargs)

    user = User.query.get(user_id)

    db.session.delete(factor)
    audit_log("TOTP recovery code used", user=user, email=True,
            subject=f"A recovery code was used for your {cfg('sr.ht', 'site-name')} account",


@@ 409,8 420,6 @@ def totp_recovery_POST():
    session.pop('return_to', None)
    session.pop('challenge_type', None)

    user = User.query.get(user_id)

    if challenge_type == "login":
        login_user(user, set_cookie=True)
        audit_log("logged in")


@@ 421,6 430,9 @@ def totp_recovery_POST():
        return redirect(return_to)
    elif challenge_type == "reset":
        return issue_reset(user)
    elif challenge_type == "disable_totp":
        security_metrics.meta_totp_disabled.inc()
        return redirect(return_to)
    else:
        raise NotImplemented


M metasrht/blueprints/security.py => metasrht/blueprints/security.py +9 -9
@@ 1,4 1,4 @@
from flask import Blueprint, render_template, request, redirect, abort, session
from flask import Blueprint, render_template, request, redirect, abort, url_for
from metasrht.audit import audit_log
from metasrht.auth.builtin import hash_password
from metasrht.qrcode import gen_qr


@@ 7,6 7,7 @@ from metasrht.types import User, UserAuthFactor, FactorType, AuditLogEntry
from prometheus_client import Counter
from srht.config import cfg
from srht.database import db
from srht.flask import session
from srht.oauth import current_user, loginrequired
from srht.validation import Validation, valid_url
from urllib.parse import quote


@@ 70,7 71,7 @@ def security_totp_enable_POST():

    secret = valid.require("secret")
    code = valid.require("code")
    

    if not valid.ok:
        return render_template("totp-enable.html",
            qrcode=totp_get_qrcode(secret),


@@ 135,10 136,9 @@ def security_totp_disable_POST():
        .filter(UserAuthFactor.factor_type == FactorType.totp)).one_or_none()
    if not factor:
        return redirect("/security")
    db.session.delete(factor)
    audit_log("Disable TOTP", details="Disabled two-factor authentication",
            email=True, subject=f"TOTP has been disabled for your {cfg('sr.ht', 'site-name')} account",
            email_details="2FA via TOTP was disabled")
    db.session.commit()
    metrics.meta_totp_disabled.inc()
    return redirect("/security")

    session["extra_factors"] = [factor.id]
    session["authorized_user"] = current_user.id
    session["challenge_type"] = "disable_totp"
    session["return_to"] = "/security"
    return redirect(url_for("auth.totp_challenge_GET"))

M metasrht/templates/totp-challenge.html => metasrht/templates/totp-challenge.html +3 -0
@@ 16,6 16,9 @@
      {% if challenge_type == "reset" %}
      This account has two-factor authentication enabled. You must complete a
      verification challenge in order to reset your password.
      {% elif challenge_type == "disable_totp" %}
      In order to disable two-factor authentication, you must be able to
      complete a verification challenge.
      {% endif %}
      Please enter your TOTP code to continue:
    </p>