bookwyrm/bookwyrm/views/preferences/two_factor_auth.py
Hugh Rundle 54daade9f9 prepare for 2FA
- add and migrate User fields for 2FA
- add views for 2FA
- add new forms for 2FA
- update package list in requirements.txt
- add URLs for 2FA views
2022-10-15 07:47:20 +11:00

94 lines
3.5 KiB
Python

""" class views for 2FA management """
import base64
import io
from pipes import Template
from turtle import fillcolor
import pyotp
import qrcode
import qrcode.image.svg
from django.contrib.auth import login
from django.contrib.auth.decorators import login_required
from django.template.response import TemplateResponse
from django.shortcuts import redirect
from django.utils.decorators import method_decorator
from django.views import View
from django.views.decorators.debug import sensitive_variables, sensitive_post_parameters
from bookwyrm import forms
from bookwyrm.settings import DOMAIN
# pylint: disable= no-self-use
@method_decorator(login_required, name="dispatch")
class Edit2FA(View):
"""change 2FA settings as logged in user"""
def get(self, request):
"""Two Factor auth page"""
data = {"form": forms.ConfirmPasswordForm()}
return TemplateResponse(request, "preferences/2fa.html", data)
@method_decorator(sensitive_post_parameters("password"))
def post(self, request):
"""check the user's password"""
form = forms.ConfirmPasswordForm(request.POST, instance=request.user)
# TODO: display an error
if not form.is_valid():
data = {"form": form}
return TemplateResponse(request, "preferences/2fa.html", data)
qr_form = forms.Confirm2FAForm(request.POST, instance=request.user)
data = {
"password_confirmed": True,
"qrcode": self.create_qr_code(request.user),
"form": qr_form,
}
return TemplateResponse(request, "preferences/2fa.html", data)
def create_qr_code(self, user):
"""generate and save a qr code for 2FA"""
otp_secret = pyotp.random_base32()
# save the secret to the user record - we'll need it to check codes in future
user.otp_secret = otp_secret
user.save(broadcast=False, update_fields=["otp_secret"])
# now we create the qr code
provisioning_url = pyotp.totp.TOTP(otp_secret).provisioning_uri(
name=user.localname, issuer_name=DOMAIN
)
qr = qrcode.QRCode(image_factory=qrcode.image.svg.SvgPathImage)
qr.add_data(provisioning_url)
qr.make(fit=True)
img = qr.make_image(attrib={"fill": "black", "background": "white"})
return img.to_string()
class Confirm2FA(View):
"""confirm user's 2FA settings"""
def post(self, request):
"""confirm the 2FA works before requiring it"""
form = forms.Confirm2FAForm(request.POST, instance=request.user)
# TODO: show an error here
if not form.is_valid():
data = {"form": form}
return redirect("prefs-2fa")
# set the user's 2FA setting on
request.user.two_factor_auth = True
request.user.save(broadcast=False, update_fields=["two_factor_auth"])
data = {"form": form, "success": True}
return TemplateResponse(request, "preferences/2fa.html", data)
class Disable2FA(View):
"""Turn off 2FA on this user account"""
def get(self, request):
"""Confirmation page to turn off 2FA"""
return TemplateResponse(request, "preferences/disable-2fa.html")
def post(self, request):
"""Turn off 2FA on this user account"""
request.user.two_factor_auth = False
request.user.save(broadcast=False, update_fields=["two_factor_auth"])
data = {"form": forms.ConfirmPasswordForm(), "success": True}
return TemplateResponse(request, "preferences/2fa.html", data)