[DJANGO] Crie seu próprio two factors authenticator.
Eai pessoal, vamos criar nosso próprio fator de autenticação?
settings.py
SITE_ID = 1
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
EMAIL_HOST_USER = "no-repy@{domain}"
INSTALLED_APPS = [
...
"django.contrib.sites",
...
]
models.py
from django.contrib.auth import get_user_model
from django.core.validators import MaxValueValidator
from django.utils.timezone import datetime, timedelta
Account = get_user_model()
class AuthToken(models.Model):
account = models.OneToOneField(Account, on_delete=models.CASCADE)
attempts = models.PositiveIntegerField(
default=0, validators=[MaxValueValidator(3, "Max attempts.")]
)
token = models.CharField(max_length=6)
date_expire = models.DateTimeField(default=datetime.now() + timedelta(days=5))
def __str__(self) -> str:
return "The %s expire at %s " % (self.token, self.date_expire)
Lembrar de criar seu template de emails, ou utilizar o descrito abaixo.
templates/emails/authentication_factor.html
{% autoescape off %}
{{subject}}
{{token}}
{{support}}
{% endautoescape %}
utils/emails.py
import random
from django.utils import timezone
from django.template.loader import render_to_string
from django.core.mail import send_mail
from django.contrib.sites.models import Site
from django.conf import settings
def mail_authenticate_token(cls):
domain = Site.objects.get_current()
data = {
"subject": "Authenticate token",
"token": random.randint(100000, 999999),
"support": settings.EMAIL_HOST_USER.format(domain=domain),
}
token = AuthToken.objects.filter(
account=cls,
)
if token.exists():
token.first().delete()
AuthToken.objects.create(account=cls, token=data["token"])
body = render_to_string("emails/authenticate_factor.html", data)
send_mail(
subject=data.get("subject"),
message=body,
from_email=settings.EMAIL_HOST_USER.format(domain=domain),
recipient_list=[cls.email],
fail_silently=False,
)
def validate_token(cls, token, form):
get_token = AuthToken.objects.filter(account_id__exact=cls.pk)
if not get_token.exists():
return False
authtoken = get_token.first()
if token != authtoken.token:
authtoken.attempts += 1
authtoken.save(
update_fields=[
"attempts",
]
)
if (authtoken.attempts + 1) >= 4:
authtoken.delete()
form.add_error("token", "We are sent to you a new token, check your email.")
mail_authenticate_token(cls)
else:
form.add_error(
"token", "You have more %s attempts." % (3 - authtoken.attempts)
)
return False
if authtoken.date_expire <= timezone.now():
authtoken.delete()
form.add_error("token", "We are sent to you a new token, check your email.")
mail_authenticate_token(cls)
return False
return True
views.py
from django.urls import reverse_lazy
from django.contrib.auth import login, authenticate
fro django.shortcuts import get_object_or_404
from apps.accounts.utils.emails import mail_authenticate_token
from apps.accounts.forms import LoginForm, AuthenticationTokenForm
def get_or_create_session(request):
session = request.session.session_key
if not session:
return request.session.create()
return session
class LoginView(FormView, AuthenticatedRedirectMixin):
template_name = "login.html"
form_class = LoginForm
success_url = reverse_lazy("authentication_token")
def form_valid(self, form):
email, password = form.cleaned_data.values()
auth = authenticate(self.request, **dict(email=email, password=password))
if not auth:
form.add_error("email", "Cannot authenticate with this credentials.")
return super().form_invalid(form)
mail_authenticate_token(auth)
get_or_create_session(self.request)
self.request.session["auth_id"] = auth.id
return super().form_valid(form)
class AuthenticationTokenView(FormView):
template_name = "authentication_token.html"
form_class = AuthenticationTokenForm
success_url = "/"
def dispatch(self, request, *args, **kwargs):
if not request.session.get("auth_id"):
return redirect("/")
return super().dispatch(request, *args, **kwargs)
def form_valid(self, form):
token = form.cleaned_data.get("token")
auth_id = self.request.session.get("auth_id")
user = get_object_or_404(Account, id=auth_id)
if not validate_token(user, token, form):
return super().form_invalid(form)
login(self.request, user)
return super().form_valid(form)
forms.py
class LoginForm(forms.Form):
"""A form to authenticate an account, increased
all fields necessary.
"""
email = forms.CharField(
widget=forms.EmailInput(
attrs={
"placehoder": "[email protected]",
}
)
)
password = forms.CharField(
widget=forms.PasswordInput(attrs={"placeholder": "*********"})
)
def clean(self):
cleaned_data = super().clean()
email = cleaned_data.get("email")
password = cleaned_data.get("password")
if not email:
self.add_error("email", "Email was required.")
if not password:
self.add_error("password", "Password was required")
acc = Account.objects.filter(email__iexact=email)
if not acc.exists():
self.add_error("email", "Wrong credentials, try again.")
if not acc.exists():
return self.add_error("password", "Credentials mismatch.")
return cleaned_data
class AuthenticationTokenForm(forms.Form):
token = forms.CharField(max_length=6)
def clean(self):
cleaned_data = super().clean()
token = cleaned_data.get("token")
if not token:
self.add_error("token", "Token is wanted to continue")
return cleaned_data