diff --git a/server/users/mutations_public.py b/server/users/mutations_public.py index 0c186b4f..dd140a4f 100644 --- a/server/users/mutations_public.py +++ b/server/users/mutations_public.py @@ -9,14 +9,19 @@ # @author: chrigu import graphene from django.contrib.auth import authenticate, login +from django.contrib.auth.forms import PasswordResetForm +from django.contrib.auth.views import PasswordResetView, PasswordResetConfirmView, INTERNAL_RESET_URL_TOKEN from graphene import relay +from core import settings +from users.models import User + class FieldError(graphene.ObjectType): code = graphene.String() -class UpdateError(graphene.ObjectType): +class MutationError(graphene.ObjectType): field = graphene.String() errors = graphene.List(FieldError) @@ -27,7 +32,7 @@ class Login(relay.ClientIDMutation): password_input = graphene.String() success = graphene.Boolean() - errors = graphene.List(UpdateError) # todo: change for consistency + errors = graphene.List(MutationError) # todo: change for consistency @classmethod def mutate_and_get_payload(cls, root, info, **kwargs): @@ -40,7 +45,96 @@ class Login(relay.ClientIDMutation): return cls(success=False, errors=['invalid_credentials']) +class PasswordReset(relay.ClientIDMutation): + class Input: + email_input = graphene.String() + + success = graphene.Boolean() + errors = graphene.List(MutationError) + + @classmethod + def mutate_and_get_payload(cls, root, info, **kwargs): + email = kwargs.get('email_input') + try: + user = User.objects.get(email=email) # todo: make lowercase + except User.DoesNotExist: + return cls(success=False, errors=['invalid_email']) + + try: + password_reset_view = PasswordResetView(request=info.context) + form = password_reset_view.form_class({'email': user.email}) + form.is_valid() + password_reset_view.form_valid(form) + except Exception: + return cls(success=False, errors=['email_error']) + + return cls(success=True, errors=[]) + + +class PasswordConfirm: + @classmethod + def verify_token_and_uidb64(cls, token, uidb64, request): + password_reset_confirm_view = PasswordResetConfirmView(request=request) + return password_reset_confirm_view.dispatch(uidb64=uidb64, token=token, request=request) + + +class PasswordResetVerify(relay.ClientIDMutation, PasswordConfirm): + class Input: + token_input = graphene.String() + uidb64_input = graphene.String() + + success = graphene.Boolean() + errors = graphene.List(MutationError) + + @classmethod + def mutate_and_get_payload(cls, root, info, **kwargs): + uidb64 = kwargs.get('uidb64_input') + token = kwargs.get('token_input') + + http_response = cls.verify_token_and_uidb64(token, uidb64, info.context) + + # PasswordResetConfirmView returns a webpage if either token or uidb64 are invalid (HTTP 200) + # if token and uidb64 are correct a redirect is returned (HTTP302) + + if http_response.status_code == 302: + return cls(success=True, errors=[]) + else: + return cls(success=False, errors=['invalid_challenge']) + + +class PasswordResetSetPassword(relay.ClientIDMutation, PasswordConfirm): + class Input: + new_password_input = graphene.String() + confirm_new_password_input = graphene.String() + uidb64_input = graphene.String() + + success = graphene.Boolean() + errors = graphene.List(MutationError) + + @classmethod + def mutate_and_get_payload(cls, root, info, **kwargs): + uidb64 = kwargs.get('uidb64_input') + new_password = kwargs.get('new_password_input') + confirm_new_password_input = kwargs.get('confirm_new_password_input') + + # fake ordinary POST for view + info.context.POST = { + 'new_password1': new_password, + 'new_password2': confirm_new_password_input + } + + http_response = cls.verify_token_and_uidb64(INTERNAL_RESET_URL_TOKEN, uidb64, info.context) + + if http_response.status_code == 302: + return cls(success=True, errors=[]) + else: + return cls(success=False, errors=['invalid_passwords']) # todo: check error from form + + class UserMutations: login = Login.Field() + password_reset = PasswordReset.Field() + password_reset_verify = PasswordResetVerify.Field() + password_reset_set_password = PasswordResetSetPassword.Field()