def add_totp(request): if request.profile.totp: # TOTP is already configured, refuse to continue return HttpResponseBadRequest() if "totp_secret" not in request.session: request.session["totp_secret"] = pyotp.random_base32() totp = pyotp.totp.TOTP(request.session["totp_secret"]) if request.method == "POST": form = forms.TotpForm(totp, request.POST) if form.is_valid(): request.profile.totp = request.session["totp_secret"] request.profile.totp_created = now() request.profile.save() request.session["enabled_totp"] = True request.session.pop("totp_secret") return redirect("hc-profile") else: form = forms.TotpForm(totp) uri = totp.provisioning_uri(name=request.user.email, issuer_name=settings.SITE_NAME) qr_data_uri = segno.make(uri).png_data_uri(scale=8) ctx = { "form": form, "qr_data_uri": qr_data_uri, "secret": request.session["totp_secret"], } return render(request, "accounts/add_totp.html", ctx)
def login_totp(request): # Expect an unauthenticated user if request.user.is_authenticated: return HttpResponseBadRequest() if "2fa_user" not in request.session: return HttpResponseBadRequest() user_id, email, timestamp = request.session["2fa_user"] if timestamp + 300 < time.time(): return redirect("hc-login") try: user = User.objects.get(id=user_id, email=email) except User.DoesNotExist: return HttpResponseBadRequest() if not user.profile.totp: return HttpResponseBadRequest() totp = pyotp.totp.TOTP(user.profile.totp) if request.method == "POST": # To guard against brute-forcing TOTP codes, we allow # 96 attempts per user per 24h. if not TokenBucket.authorize_totp_attempt(user): return render(request, "try_later.html") form = forms.TotpForm(totp, request.POST) if form.is_valid(): # We blacklist an used TOTP code for 90 seconds, # so an attacker cannot reuse a stolen code. if not TokenBucket.authorize_totp_code(user, form.cleaned_data["code"]): return render(request, "try_later.html") request.session.pop("2fa_user") auth_login(request, user, "hc.accounts.backends.EmailBackend") return _redirect_after_login(request) else: form = forms.TotpForm(totp) return render(request, "accounts/login_totp.html", {"form": form})