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.pull/456/head
@ -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 |
@ -0,0 +1,39 @@ | |||||
{% extends "base.html" %} | |||||
{% block content %} | |||||
<div class="row"> | |||||
<div class="col-sm-6 col-sm-offset-3"> | |||||
<div class="hc-dialog"> | |||||
<h1>Enter a Confirmation Code</h1> | |||||
<br /> | |||||
<p> | |||||
We have sent a confirmation code to your email | |||||
address. Please enter it below to continue: | |||||
</p> | |||||
<form method="POST"> | |||||
{% csrf_token %} | |||||
<div class="form-group {% if wrong_code %}has-error{% endif %}"> | |||||
<input | |||||
class="form-control input-lg" | |||||
type="text" name="sudo_code" /> | |||||
{% if wrong_code %} | |||||
<div class="help-block">The entered code was not correct.</div> | |||||
{% endif %} | |||||
</div> | |||||
<button | |||||
class="btn btn-lg btn-primary btn-block" | |||||
type="submit"> | |||||
Continue | |||||
</button> | |||||
</form> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
{% endblock %} |
@ -0,0 +1,27 @@ | |||||
{% extends "emails/base.html" %} | |||||
{% load hc_extras %} | |||||
{% block content %} | |||||
Hello,<br /> | |||||
<br /> | |||||
We send a confirmation code before performing sensitive actions on {% site_name%}.<br /> | |||||
Your confirmation code is:<br /> | |||||
<br /> | |||||
<div style="font-family: monospace; font-weight: bold; font-size: 24px"> | |||||
{{ sudo_code }} | |||||
</div> | |||||
<br /> | |||||
Note: this code is only valid for 15 minutes.<br /> | |||||
<br /> | |||||
{% endblock %} | |||||
{% block content_more %} | |||||
All the Best,<br> | |||||
The {% site_name %} Team | |||||
<br /><br /> | |||||
{% endblock %} |
@ -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 |
@ -0,0 +1 @@ | |||||
Confirmation code: {{ sudo_code }} |