diff --git a/hc/accounts/decorators.py b/hc/accounts/decorators.py new file mode 100644 index 00000000..5c253136 --- /dev/null +++ b/hc/accounts/decorators.py @@ -0,0 +1,47 @@ +from functools import wraps +import secrets + +from django.core.signing import TimestampSigner, SignatureExpired +from django.shortcuts import redirect, render +from hc.lib import emails + + +def _session_unsign(request, key, max_age): + if key not in request.session: + return None + + try: + return TimestampSigner().unsign(request.session[key], max_age=max_age) + except SignatureExpired: + pass + + +def require_sudo_mode(f): + @wraps(f) + def wrapper(request, *args, **kwds): + assert request.user.is_authenticated + + # is sudo mode active and has not expired yet? + if _session_unsign(request, "sudo", 1800) == "active": + return f(request, *args, **kwds) + + # has the user submitted a code to enter sudo mode? + if "sudo_code" in request.POST: + ours = _session_unsign(request, "sudo_code", 900) + if ours and ours == request.POST["sudo_code"]: + request.session.pop("sudo_code") + request.session["sudo"] = TimestampSigner().sign("active") + return redirect(request.path) + + if not _session_unsign(request, "sudo_code", 900): + code = "%06d" % secrets.randbelow(1000000) + request.session["sudo_code"] = TimestampSigner().sign(code) + emails.sudo_code(request.user.email, {"sudo_code": code}) + + ctx = {} + if "sudo_code" in request.POST: + ctx["wrong_code"] = True + + return render(request, "accounts/sudo.html", ctx) + + return wrapper diff --git a/hc/lib/emails.py b/hc/lib/emails.py index d62d82bb..79bbcf0d 100644 --- a/hc/lib/emails.py +++ b/hc/lib/emails.py @@ -92,3 +92,7 @@ def sms_limit(to, ctx): def call_limit(to, ctx): send("phone-call-limit", to, ctx) + + +def sudo_code(to, ctx): + send("sudo-code", to, ctx) diff --git a/templates/accounts/sudo.html b/templates/accounts/sudo.html new file mode 100644 index 00000000..1fcccefa --- /dev/null +++ b/templates/accounts/sudo.html @@ -0,0 +1,39 @@ +{% extends "base.html" %} + +{% block content %} +
+ We have sent a confirmation code to your email + address. Please enter it below to continue: +
+ + + +