~fnux/meta.sr.ht

c1eeb3a41523fa1bd2dfacfaf46a5295a4aebcee — Drew DeVault 8 months ago 97e0f47 0.50.0
Email user when security-sensitive actions occur
M metasrht/audit.py => metasrht/audit.py +14 -1
@@ 1,11 1,14 @@
from flask import request
from ipaddress import ip_address
from datetime import datetime, timedelta
from srht.config import cfg
from srht.database import db
from srht.oauth import current_user
from metasrht.email import send_email
from metasrht.types import AuditLogEntry

def audit_log(event_type, details=None, user=None):
def audit_log(event_type, details=None, user=None,
        email=False, subject=None, email_details=None):
    if not user:
        user = current_user
    if not user:


@@ 13,6 16,16 @@ def audit_log(event_type, details=None, user=None):
    addr = request.access_route[-1]
    event = AuditLogEntry(user.id, event_type, ip_address(addr), details)
    db.session.add(event)
    if email:
        if user.pgp_key:
            encrypt_key = user.pgp_key.key
        else:
            encrypt_key = None
        send_email("audit_event", user.email, subject, headers={
            "From": f"{cfg('mail', 'smtp-from')}",
            "To": f"{user.username} <{user.email}>",
            "Reply-To": f"{cfg('sr.ht', 'owner-name')} <{cfg('sr.ht', 'owner-email')}>",
        }, user=user, encrypt_key=encrypt_key, email_details=email_details)

def expire_audit_logs():
    cutoff = datetime.now() - timedelta(days=14)

M metasrht/blueprints/auth.py => metasrht/blueprints/auth.py +6 -2
@@ 390,7 390,9 @@ def totp_recovery_POST():
            return_to=return_to, **valid.kwargs)

    db.session.delete(factor)
    audit_log("TOTP recovery code used")
    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",
            email_details="Two-factor authentication recovery code used")
    session["notice"] = "TOTP has been disabled for your account."
    db.session.commit()



@@ 484,7 486,9 @@ def reset_POST(token):
    if not valid.ok:
        return render_template("reset.html", valid=valid)
    user.password = hash_password(password)
    audit_log("password reset", user=user)
    audit_log("password reset", user=user, email=True,
            subject=f"Your {cfg('sr.ht', 'site-name')} password has been reset",
            email_details="Account password reset")
    db.session.commit()
    login_user(user, set_cookie=True)
    print(f"Reset password: {user.username} ({user.email})")

M metasrht/blueprints/keys.py => metasrht/blueprints/keys.py +23 -2
@@ 3,6 3,7 @@ from metasrht.audit import audit_log
from metasrht.types import SSHKey, PGPKey
from metasrht.types import User, UserAuthFactor, FactorType
from metasrht.webhooks import UserWebhook
from srht.config import cfg
from srht.database import db
from srht.oauth import current_user, loginrequired
from srht.validation import Validation, valid_url


@@ 30,6 31,11 @@ def ssh_keys_POST():
            current_user=user, **valid.kwargs)
    db.session.add(key)
    db.session.commit()
    audit_log("SSH key added",
            details=f"Fingerprint {key.fingerprint}",
            email=True,
            subject=f"An SSH key was added to your {cfg('sr.ht', 'site-name')} account",
            email_details=f"SSH key {key.fingerprint} added")
    return redirect("/keys")

@keys.route("/keys/delete-ssh/<int:key_id>", methods=["POST"])


@@ 40,6 46,11 @@ def ssh_keys_delete(key_id):
        abort(404)
    key.delete()
    db.session.commit()
    audit_log("SSH key removed",
            details=f"Fingerprint {key.fingerprint}",
            email=True,
            subject=f"An SSH key was removed from your {cfg('sr.ht', 'site-name')} account",
            email_details=f"SSH key {key.fingerprint} removed")
    return redirect("/keys")

@keys.route("/keys/pgp-keys")


@@ 52,12 63,17 @@ def pgp_keys_GET():
def pgp_keys_POST():
    user = User.query.get(current_user.id)
    valid = Validation(request)
    pgp = PGPKey(user, valid)
    key = PGPKey(user, valid)
    if not valid.ok:
        return render_template("keys.html",
            current_user=user, **valid.kwargs)
    db.session.add(pgp)
    db.session.add(key)
    db.session.commit()
    audit_log("PGP key added",
            details=f"Key ID {key.key_id}",
            email=True,
            subject=f"A PGP key was added to your {cfg('sr.ht', 'site-name')} account",
            email_details=f"PGP key {key.key_id} added")
    return redirect("/keys")

@keys.route("/keys/delete-pgp/<int:key_id>", methods=["POST"])


@@ 72,4 88,9 @@ def pgp_keys_delete(key_id):
                current_user=user, tried_to_delete_key_in_use=True), 400
    key.delete()
    db.session.commit()
    audit_log("PGP key removed",
            details=f"Key ID {key.key_id}",
            email=True,
            subject=f"A PGP key was removed from your {cfg('sr.ht', 'site-name')} account",
            email_details=f"PGP key {key.key_id} removed")
    return redirect("/keys")

M metasrht/blueprints/security.py => metasrht/blueprints/security.py +6 -2
@@ 112,7 112,9 @@ def security_totp_enable_POST():
    factor.extra = hashed_codes

    db.session.add(factor)
    audit_log("enabled two factor auth", 'Enabled TOTP')
    audit_log("Enable TOTP", details="Enabled two-factor authentication",
            email=True, subject=f"TOTP has been enabled for your {cfg('sr.ht', 'site-name')} account",
            email_details="2FA via TOTP was enabled")
    db.session.commit()
    metrics.meta_totp_enabled.inc()



@@ 137,7 139,9 @@ def security_totp_disable_POST():
    if not factor:
        return redirect("/security")
    db.session.delete(factor)
    audit_log("disabled two factor auth", 'Disabled TOTP')
    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")

A metasrht/emails/audit_event => metasrht/emails/audit_event +12 -0
@@ 0,0 1,12 @@
~{{user.username}},

This email was sent to inform you that the following security-sensitive
event has taken place for your account on {{site-name}}:

{{email_details}}

If you did not expect this to occur, please reply to this email urgently
to contact support. Otherwise, no action is required.

-- 
{{owner-name}}