From e3aedd3b037f708e42f63b5304966864bf09f044 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C4=93teris=20Caune?= Date: Fri, 13 Nov 2020 11:08:06 +0200 Subject: [PATCH] Add require_sudo_mode decorator Planning to use it for sensitive operations (add/remove security keys), change email, change password, close account. The decorator sends a six-digit confirmation code to user's email and renders a form for entering it back. If the user enters the correct code, the decorators sets a sudo=active marker in user's session, valid for 30 minutes. --- hc/accounts/decorators.py | 47 +++++++++++++++++++++++ hc/lib/emails.py | 4 ++ templates/accounts/sudo.html | 39 +++++++++++++++++++ templates/emails/sudo-code-body-html.html | 27 +++++++++++++ templates/emails/sudo-code-body-text.html | 12 ++++++ templates/emails/sudo-code-subject.html | 1 + 6 files changed, 130 insertions(+) create mode 100644 hc/accounts/decorators.py create mode 100644 templates/accounts/sudo.html create mode 100644 templates/emails/sudo-code-body-html.html create mode 100644 templates/emails/sudo-code-body-text.html create mode 100644 templates/emails/sudo-code-subject.html 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 %} +
+
+
+

Enter a Confirmation Code

+
+

+ We have sent a confirmation code to your email + address. Please enter it below to continue: +

+ +
+ {% csrf_token %} + +
+ + + {% if wrong_code %} +
The entered code was not correct.
+ {% endif %} +
+ + + +
+ +
+
+
+ +{% endblock %} diff --git a/templates/emails/sudo-code-body-html.html b/templates/emails/sudo-code-body-html.html new file mode 100644 index 00000000..86e02667 --- /dev/null +++ b/templates/emails/sudo-code-body-html.html @@ -0,0 +1,27 @@ +{% extends "emails/base.html" %} +{% load hc_extras %} + +{% block content %} +Hello,
+ +
+ +We send a confirmation code before performing sensitive actions on {% site_name%}.
+Your confirmation code is:
+
+
+ {{ sudo_code }} +
+
+ +Note: this code is only valid for 15 minutes.
+ +
+{% endblock %} + +{% block content_more %} +All the Best,
+The {% site_name %} Team +

+ +{% endblock %} diff --git a/templates/emails/sudo-code-body-text.html b/templates/emails/sudo-code-body-text.html new file mode 100644 index 00000000..873a55a3 --- /dev/null +++ b/templates/emails/sudo-code-body-text.html @@ -0,0 +1,12 @@ +{% load hc_extras %} +Hello, + +We send a confirmation code before performing sensitive actions on {% site_name%}. + +Your confirmation code is: {{ sudo_code }} + +Note: this code is only valid for 15 minutes. + +-- +All the Best, +The {% site_name %} Team diff --git a/templates/emails/sudo-code-subject.html b/templates/emails/sudo-code-subject.html new file mode 100644 index 00000000..f04c61bc --- /dev/null +++ b/templates/emails/sudo-code-subject.html @@ -0,0 +1 @@ +Confirmation code: {{ sudo_code }}