diff --git a/apps/users/serializers.py b/apps/users/serializers.py index aab2120..26ad9e1 100644 --- a/apps/users/serializers.py +++ b/apps/users/serializers.py @@ -1,21 +1,57 @@ +import uuid + from django.conf import settings from django.contrib.auth import authenticate +from django.contrib.auth.hashers import make_password from django.contrib.auth.models import update_last_login -from django.utils.module_loading import import_string +from django.contrib.auth.password_validation import validate_password +from django.core.cache import cache from django.utils.translation import gettext_lazy as _ +from django.utils.module_loading import import_string + +from rest_framework import serializers +from rest_framework import exceptions -from rest_framework import exceptions, serializers from rest_framework_simplejwt.serializers import ( - PasswordField, TokenObtainPairSerializer, + PasswordField, ) +from apps.system.core.classes import Email + from .models import Usuario +REDEFINIR_SENHA_CACHE_KEY = "reset-password-%s" + +CONFIRMAR_EMAIL_CACHE_KEY = "email-confirm-%s" + + +def enviar_email_confirmacao_email(email): + codigo_confirmacao = str(uuid.uuid4())[:8].upper() + cache.set(CONFIRMAR_EMAIL_CACHE_KEY % email, codigo_confirmacao) + + email = Email( + titulo="Confirmação de Email", + corpo="Esse é o código de confirmação: %s" % codigo_confirmacao, + destinatarios=[email], + ) + + email.send() + + class UsuarioSerializer(serializers.ModelSerializer): password = PasswordField() + def validate_password(self, password): + validate_password(password) + return make_password(password) + + def create(self, validated_data): + instance = super().create(validated_data) + enviar_email_confirmacao_email(instance.email) + return instance + class Meta: model = Usuario exclude = ( @@ -37,6 +73,132 @@ class Meta: ) +class ReenviaEmailConfirmacaoSerializer(serializers.Serializer): + usuario = serializers.PrimaryKeyRelatedField(queryset=Usuario.objects.all()) + + def validate_usuario(self, value): + if value.email_verified: + raise serializers.ValidationError( + {"usuario": "Esse usuário já possui o email confirmado"} + ) + return value + + def save(self): + enviar_email_confirmacao_email(self.validated_data["usuario"].email) + + +class ConfirmarEmailSerializer(serializers.Serializer): + usuario = serializers.PrimaryKeyRelatedField(queryset=Usuario.objects.all()) + codigo = serializers.CharField() + + def validate(self, attrs): + validated_data = super().validate(attrs) + + usuario = validated_data["usuario"] + + if usuario.email_verified: + raise serializers.ValidationError( + {"mensagem": _("O usuário já confirmou o email")}, + code="invalid_request", + ) + + codigo = validated_data["codigo"] + + cache_key = CONFIRMAR_EMAIL_CACHE_KEY % usuario.email + codigo_cache = cache.get(cache_key, None) + if codigo_cache is None: + raise serializers.ValidationError( + {"mensagem": _("O código de confirmação expirou")}, code="expired_code" + ) + + if codigo != codigo_cache: + raise serializers.ValidationError( + {"mensagem": _("O código informado não é inválido")} + ) + + cache.delete(cache_key) + + return validated_data + + def save(self): + validated_data = self.validated_data + usuario = validated_data["usuario"] + usuario.email_confirmed = True + usuario.save() + + +class EnviarEmailRedefinicaoSenhaSerializer(serializers.Serializer): + usuario = serializers.SlugRelatedField( + queryset=Usuario.objects.all(), + slug_field="email", + ) + + def save(self): + usuario = self.validated_data["usuario"] + + cache_key = REDEFINIR_SENHA_CACHE_KEY % usuario.email + + codigo = uuid.uuid4().hex[:8].upper() + cache.set(cache_key, codigo, 60 * 10) + + email = Email( + titulo="Redefinição de senha", + corpo="Esse é o código da redefinição de senha: %s" % codigo, + destinatarios=[usuario.email], + ) + + email.send() + + +class ConfirmarCodigoRedefinirSenhaSerializer(serializers.Serializer): + usuario = serializers.SlugRelatedField( + queryset=Usuario.objects.all(), slug_field="email" + ) + codigo = serializers.CharField() + + def validate(self, attrs): + dados = super().validate(attrs) + + codigo = dados["codigo"] + usuario = dados["usuario"] + + cache_key = REDEFINIR_SENHA_CACHE_KEY % usuario.email + codigo_cache = cache.get(cache_key, None) + + if codigo_cache is None: + raise serializers.ValidationError( + {"mensagem": _("A redefinição de senha expirou")} + ) + + if codigo != codigo_cache: + raise serializers.ValidationError( + {"mensagem": _("O código informado é inválido")} + ) + + cache.delete(cache_key) + + return dados + + +class RedefinirSenhaSerializer(serializers.Serializer): + usuario = serializers.SlugRelatedField( + queryset=Usuario.objects.all(), slug_field="email" + ) + nova_senha = serializers.CharField() + + def validate_nova_senha(self, value): + validate_password(value) + return value + + def save(self): + validated_data = self.validated_data + nova_senha = validated_data["nova_senha"] + usuario = validated_data["usuario"] + + usuario.password = make_password(nova_senha) + usuario.save() + + class LoginSerializer(TokenObtainPairSerializer): def validate(self, attrs): authenticate_kwargs = { @@ -52,6 +214,7 @@ def validate(self, attrs): usuario = Usuario.objects.filter( email=authenticate_kwargs[self.username_field] ).first() + if not usuario: raise exceptions.AuthenticationFailed( { @@ -73,15 +236,16 @@ def validate(self, attrs): authentication_rule = import_string( settings.SIMPLE_JWT["USER_AUTHENTICATION_RULE"] ) + if not authentication_rule(self.user): raise exceptions.AuthenticationFailed( {"mensagem": _("A senha informada está incorreta")}, "incorret_password", ) - data = super().validate(attrs) - refresh = self.get_token(self.user) - data["access"] = str(refresh.access_token) + data = {} + token = self.get_token(self.user) + data["access"] = str(token.access_token) if settings.SIMPLE_JWT["UPDATE_LAST_LOGIN"]: update_last_login(None, self.user) @@ -89,6 +253,9 @@ def validate(self, attrs): return data @classmethod - def get_token(cls, user): + def get_token(cls, user): # , assinatura token = super().get_token(user) + token["user_name"] = user.first_name + token["user_last_name"] = user.last_name + token["user_full_name"] = user.get_full_name() return token diff --git a/apps/users/urls.py b/apps/users/urls.py index 75efd4b..62ecc29 100644 --- a/apps/users/urls.py +++ b/apps/users/urls.py @@ -2,14 +2,12 @@ from rest_framework.routers import DefaultRouter -from rest_framework_simplejwt.views import token_obtain_pair as login_view +from .views import LoginViewSet, AuthViewSet -from .views import UsuarioViewSet - -router_v1 = DefaultRouter() -router_v1.register("usuarios", UsuarioViewSet, "usuarios") +router_auth = DefaultRouter() +router_auth.register("", AuthViewSet, "auth") urlpatterns = [ - path("auth/login/", login_view), - path("v1/", include(router_v1.urls)), + path("auth/", include(router_auth.urls)), + path("auth/token/obtain/", LoginViewSet.as_view()), ] diff --git a/apps/users/views.py b/apps/users/views.py index e4b50a5..16f4b04 100644 --- a/apps/users/views.py +++ b/apps/users/views.py @@ -1,55 +1,87 @@ -from django.utils.translation import gettext_lazy as _ - from rest_framework import status from rest_framework.decorators import action +from rest_framework.exceptions import ValidationError +from rest_framework.permissions import AllowAny from rest_framework.response import Response -from rest_framework.viewsets import ModelViewSet -from apps.system.core.classes import Email +from rest_framework_simplejwt.views import TokenObtainPairView + +from apps.system.base.views import BaseViewSet + +from .serializers import ( + Usuario, + UsuarioSerializer, + ReenviaEmailConfirmacaoSerializer, + ConfirmarEmailSerializer, + EnviarEmailRedefinicaoSenhaSerializer, + ConfirmarCodigoRedefinirSenhaSerializer, + RedefinirSenhaSerializer, +) -from .serializers import Usuario, UsuarioSerializer +class LoginViewSet(TokenObtainPairView): + authentication_classes = [] + permission_classes = [AllowAny] -class UsuarioViewSet(ModelViewSet): - queryset = Usuario.objects.all() + +class AuthViewSet(BaseViewSet): serializer_class = UsuarioSerializer + authentication_classes = [] + permission_classes = [AllowAny] + + @action(methods=["get"], detail=False) + def validar_cadastro_email(self, request): + email = request.query_params.get("email", None) + if email is None: + raise ValidationError({"email": "Essa query é obrigatória"}) - def perform_create(self, serializer): - serializer.save(is_active=False) - self.enviar_email_confirmacao(serializer.instance.email) - - def enviar_email_confirmacao(self, email): - email = Email(_("Confirme seu email"), corpo="Clique no link abaixo para confirmar sua conta", destinatarios=[email]) - email.send() - - @action(methods=['get'], detail=False) - def verificar_cadastro_email(self, request, pk): - email_usuario = pk # estou passando o email do usuário no lugar da pk try: - Usuario.objects.get(email=email_usuario) - return Response() + Usuario.objects.get(email=email) + return Response({"cadastrado": True}) except Usuario.DoesNotExist: - return Response(status=status.HTTP_404_NOT_FOUND) - - @action(methods=['post'], detail=True) - def confirmar_email(self, request, pk): - instance = self.get_object() - if instance.is_active: - return Response({ - "mensagem": _("Esse usuário já está ativo") - }, status=status.HTTP_400_BAD_REQUEST) - - instance.is_active = True - instance.save() + return Response({"cadastrado": False}) + + @action(methods=["post"], detail=False) + def cadastro(self, request): + serializer = UsuarioSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + return Response(serializer.data, status=status.HTTP_201_CREATED) + + @action(methods=["post"], detail=False) + def confirmar_email(self, request): + serializer = ConfirmarEmailSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + return Response(status=status.HTTP_204_NO_CONTENT) + + @action(methods=["post"], detail=False) + def reenviar_email_confirmacao(self, request): + serializer = ReenviaEmailConfirmacaoSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + return Response(status=status.HTTP_204_NO_CONTENT) + + @action(methods=["post"], detail=False) + def enviar_email_redefinicao_senha(self, request): + serializer = EnviarEmailRedefinicaoSenhaSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() return Response() - @action(methods=['post'], detail=True) - def reenviar_email(self, request, pk): - instance = self.get_object() - if instance.is_active: - return Response({ - "mensagem": _("Esse usuário já está ativo") - }, status=status.HTTP_400_BAD_REQUEST) + @action(methods=["post"], detail=False) + def confirmar_codigo_redefinir_senha(self, request): + serializer = ConfirmarCodigoRedefinirSenhaSerializer( + data=request.data, context={"request": self.request} + ) + serializer.is_valid(raise_exception=True) + return Response() - self.enviar_email_confirmacao(instance.email) - return Response() \ No newline at end of file + @action(methods=["post"], detail=False) + def redefinir_senha(self, request): + serializer = RedefinirSenhaSerializer( + data=request.data, context={"request": self.request} + ) + serializer.is_valid(raise_exception=True) + serializer.save() + return Response()