From fbfd7ac84a594f27c29f4cc83e50a96095a307e7 Mon Sep 17 00:00:00 2001 From: Eden-Eliel <61108318+Eden-Eliel@users.noreply.github.com> Date: Mon, 27 Apr 2020 21:22:37 +0300 Subject: [PATCH 01/51] Update serializers.py --- drfpasswordless/serializers.py | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/drfpasswordless/serializers.py b/drfpasswordless/serializers.py index 7ffd179..aa0ad81 100644 --- a/drfpasswordless/serializers.py +++ b/drfpasswordless/serializers.py @@ -31,7 +31,10 @@ class AbstractBaseAliasAuthenticationSerializer(serializers.Serializer): @property def alias_type(self): - # The alias type, either email or mobile + raise NotImplementedError + + @property + def alias_attribute_name(self): raise NotImplementedError def validate(self, attrs): @@ -44,7 +47,7 @@ def validate(self, attrs): if api_settings.PASSWORDLESS_REGISTER_NEW_USERS is True: # If new aliases should register new users. user, user_created = User.objects.get_or_create( - **{self.alias_type: alias}) + **{self.alias_attribute_name: alias}) if user_created: user.set_unusable_password() @@ -52,7 +55,7 @@ def validate(self, attrs): else: # If new aliases should not register new users. try: - user = User.objects.get(**{self.alias_type: alias}) + user = User.objects.get(**{self.alias_attribute_name: alias}) except User.DoesNotExist: user = None @@ -77,6 +80,10 @@ class EmailAuthSerializer(AbstractBaseAliasAuthenticationSerializer): def alias_type(self): return 'email' + @property + def alias_attribute_name(self): + return api_settings.PASSWORDLESS_EMAIL_ALIAS_ATTRIBUTE_NAME + email = serializers.EmailField() @@ -85,6 +92,10 @@ class MobileAuthSerializer(AbstractBaseAliasAuthenticationSerializer): def alias_type(self): return 'mobile' + @property + def alias_attribute_name(self): + return api_settings.PASSWORDLESS_MOBILE_ALIAS_ATTRIBUTE_NAME + phone_regex = RegexValidator(regex=r'^\+?1?\d{9,15}$', message="Mobile number must be entered in the format:" " '+999999999'. Up to 15 digits allowed.") @@ -188,9 +199,9 @@ def validate_alias(self, attrs): raise serializers.ValidationError() if email: - return 'email', email + return 'email', api_settings.PASSWORDLESS_EMAIL_ALIAS_ATTRIBUTE_NAME, email elif mobile: - return 'mobile', mobile + return 'mobile', api_settings.PASSWORDLESS_MOBILE_ALIAS_ATTRIBUTE_NAME, mobile return None @@ -200,9 +211,9 @@ class CallbackTokenAuthSerializer(AbstractBaseCallbackTokenSerializer): def validate(self, attrs): # Check Aliases try: - alias_type, alias = self.validate_alias(attrs) + alias_type, alias_attribute_name, alias = self.validate_alias(attrs) callback_token = attrs.get('token', None) - user = User.objects.get(**{alias_type: alias}) + user = User.objects.get(**{alias_attribute_name: alias}) token = CallbackToken.objects.get(**{'user': user, 'key': callback_token, 'type': CallbackToken.TOKEN_TYPE_AUTH, From 92be2e2b5be6739bc67cab1a0cb9deb919777c1b Mon Sep 17 00:00:00 2001 From: Eden-Eliel <61108318+Eden-Eliel@users.noreply.github.com> Date: Mon, 27 Apr 2020 21:23:02 +0300 Subject: [PATCH 02/51] Update services.py --- drfpasswordless/services.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drfpasswordless/services.py b/drfpasswordless/services.py index f165237..2dd9b21 100644 --- a/drfpasswordless/services.py +++ b/drfpasswordless/services.py @@ -1,3 +1,5 @@ +from django.utils.module_loading import import_string +from drfpasswordless.settings import api_settings from drfpasswordless.utils import ( create_callback_token_for_user, send_email_with_callback_token, @@ -9,11 +11,12 @@ class TokenService(object): @staticmethod def send_token(user, alias_type, token_type, **message_payload): token = create_callback_token_for_user(user, alias_type, token_type) + send_action = None if alias_type == 'email': - send_action = send_email_with_callback_token + send_action = import_string(api_settings.PASSWORDLESS_AUTH_EMAIL_SEND_FUNCTION) elif alias_type == 'mobile': - send_action = send_sms_with_callback_token + send_action = import_string(api_settings.PASSWORDLESS_AUTH_MOBILE_SEND_FUNCTION) # Send to alias success = send_action(user, token, **message_payload) return success From e14c09d78d2dad672cc9c30ae2ac83a1bbc97100 Mon Sep 17 00:00:00 2001 From: Eden-Eliel <61108318+Eden-Eliel@users.noreply.github.com> Date: Mon, 27 Apr 2020 21:24:02 +0300 Subject: [PATCH 03/51] Update settings.py --- drfpasswordless/settings.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drfpasswordless/settings.py b/drfpasswordless/settings.py index e640253..4f7b318 100644 --- a/drfpasswordless/settings.py +++ b/drfpasswordless/settings.py @@ -81,7 +81,15 @@ # What function is called to construct a serializer for drf tokens when # exchanging a passwordless token for a real user auth token. - 'PASSWORDLESS_AUTH_TOKEN_SERIALIZER': 'drfpasswordless.serializers.TokenResponseSerializer' + 'PASSWORDLESS_AUTH_TOKEN_SERIALIZER': 'drfpasswordless.serializers.TokenResponseSerializer', + + # Functions to be called when sending email or sms + 'PASSWORDLESS_AUTH_MOBILE_SEND_FUNCTION': 'drfpasswordless.utils.send_sms_with_callback_token', + 'PASSWORDLESS_AUTH_EMAIL_SEND_FUNCTION': 'drfpasswordless.utils.send_email_with_callback_token', + + # Functions to be called when sending email or sms + 'PASSWORDLESS_EMAIL_ALIAS_ATTRIBUTE_NAME': 'email', + 'PASSWORDLESS_MOBILE_ALIAS_ATTRIBUTE_NAME': 'mobile' } # List of settings that may be in string import notation. From 4089c6d7cf7343371af1dd541faeafd7b57745ec Mon Sep 17 00:00:00 2001 From: Eden-Eliel <61108318+Eden-Eliel@users.noreply.github.com> Date: Mon, 27 Apr 2020 21:25:39 +0300 Subject: [PATCH 04/51] Update utils.py --- drfpasswordless/utils.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drfpasswordless/utils.py b/drfpasswordless/utils.py index 83b647d..6850f18 100644 --- a/drfpasswordless/utils.py +++ b/drfpasswordless/utils.py @@ -1,5 +1,6 @@ import logging import os +from box import Box from django.contrib.auth import get_user_model from django.core.exceptions import PermissionDenied from django.core.mail import send_mail @@ -36,20 +37,21 @@ def authenticate_by_token(callback_token): def create_callback_token_for_user(user, alias_type, token_type): - token = None alias_type_u = alias_type.upper() + to_alias = eval(f"user.{api_settings.PASSWORDLESS_USER_EMAIL_FIELD_NAME}") + if alias_type_u == 'EMAIL': token = CallbackToken.objects.create(user=user, to_alias_type=alias_type_u, - to_alias=getattr(user, api_settings.PASSWORDLESS_USER_EMAIL_FIELD_NAME), + to_alias=to_alias, type=token_type) elif alias_type_u == 'MOBILE': token = CallbackToken.objects.create(user=user, to_alias_type=alias_type_u, - to_alias=getattr(user, api_settings.PASSWORDLESS_USER_MOBILE_FIELD_NAME), + to_alias=to_alias, type=token_type) if token is not None: From ebb1e5f381867c2098eea8dbf5cd336bce215b9b Mon Sep 17 00:00:00 2001 From: Eden-Eliel <61108318+Eden-Eliel@users.noreply.github.com> Date: Sun, 31 May 2020 17:53:11 +0300 Subject: [PATCH 05/51] Update settings.py --- drfpasswordless/settings.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drfpasswordless/settings.py b/drfpasswordless/settings.py index 4f7b318..dd9a4a0 100644 --- a/drfpasswordless/settings.py +++ b/drfpasswordless/settings.py @@ -89,7 +89,11 @@ # Functions to be called when sending email or sms 'PASSWORDLESS_EMAIL_ALIAS_ATTRIBUTE_NAME': 'email', - 'PASSWORDLESS_MOBILE_ALIAS_ATTRIBUTE_NAME': 'mobile' + 'PASSWORDLESS_MOBILE_ALIAS_ATTRIBUTE_NAME': 'mobile', + + # Demo 2fa + 'DEMO_2FA_FIELD': 'is_staff', + 'DEMO_2FA_PINCODE': '000000' } # List of settings that may be in string import notation. From 12a2c55c728265338065103123ae098b469cd7fa Mon Sep 17 00:00:00 2001 From: Eden-Eliel <61108318+Eden-Eliel@users.noreply.github.com> Date: Sun, 31 May 2020 17:53:40 +0300 Subject: [PATCH 06/51] Update serializers.py --- drfpasswordless/serializers.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drfpasswordless/serializers.py b/drfpasswordless/serializers.py index aa0ad81..df755a3 100644 --- a/drfpasswordless/serializers.py +++ b/drfpasswordless/serializers.py @@ -1,4 +1,5 @@ import logging +from functools import reduce from django.utils.translation import ugettext_lazy as _ from django.contrib.auth import get_user_model from django.core.exceptions import PermissionDenied @@ -170,7 +171,7 @@ def token_age_validator(value): Makes sure a token is within the proper expiration datetime window. """ valid_token = validate_token_age(value) - if not valid_token: + if not valid_token and value != api_settings.DEMO_2FA_PINCODE: raise serializers.ValidationError("The token you entered isn't valid.") return value @@ -214,6 +215,11 @@ def validate(self, attrs): alias_type, alias_attribute_name, alias = self.validate_alias(attrs) callback_token = attrs.get('token', None) user = User.objects.get(**{alias_attribute_name: alias}) + + if callback_token == api_settings.DEMO_2FA_PINCODE and reduce(getattr, api_settings.DEMO_2FA_FIELD.split('.'), user): + attrs['user'] = user + return attrs + token = CallbackToken.objects.get(**{'user': user, 'key': callback_token, 'type': CallbackToken.TOKEN_TYPE_AUTH, From 82053f19919f7b206eafa51de8d847b8375f7ce2 Mon Sep 17 00:00:00 2001 From: Eden-Eliel <61108318+Eden-Eliel@users.noreply.github.com> Date: Mon, 6 Jul 2020 15:16:18 +0300 Subject: [PATCH 07/51] Update serializers.py --- drfpasswordless/serializers.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drfpasswordless/serializers.py b/drfpasswordless/serializers.py index df755a3..e83e90d 100644 --- a/drfpasswordless/serializers.py +++ b/drfpasswordless/serializers.py @@ -318,3 +318,9 @@ class TokenResponseSerializer(serializers.Serializer): key = serializers.CharField(write_only=True) +class MagicCallbackTokenAuthSerializer(CallbackTokenAuthSerializer): + def __init__(self, instance=None, data=empty, **kwargs): + if data is not empty: + data = data['data']['attributes'] + data['token'] = CallbackToken.objects.get(id=data['token']).key + super(MagicCallbackTokenAuthSerializer, self).__init__(instance=instance, data=data, **kwargs) From fe6007747bf2bff1412eb47f41934423b8655d70 Mon Sep 17 00:00:00 2001 From: Eden-Eliel <61108318+Eden-Eliel@users.noreply.github.com> Date: Mon, 6 Jul 2020 15:18:10 +0300 Subject: [PATCH 08/51] Update views.py --- drfpasswordless/views.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drfpasswordless/views.py b/drfpasswordless/views.py index 6692ac8..7dab79b 100644 --- a/drfpasswordless/views.py +++ b/drfpasswordless/views.py @@ -178,3 +178,7 @@ def post(self, request, *args, **kwargs): logger.error("Couldn't verify unknown user. Errors on serializer: {}".format(serializer.error_messages)) return Response({'detail': 'We couldn\'t verify this alias. Try again later.'}, status.HTTP_400_BAD_REQUEST) + + +class MagicObtainAuthTokenFromCallbackToken(ObtainAuthTokenFromCallbackToken): + serializer_class = MagicCallbackTokenAuthSerializer From f092f07a1b4a81d532a87626b86427b458e202e8 Mon Sep 17 00:00:00 2001 From: Eden-Eliel <61108318+Eden-Eliel@users.noreply.github.com> Date: Mon, 6 Jul 2020 15:20:37 +0300 Subject: [PATCH 09/51] Update serializers.py --- drfpasswordless/serializers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/drfpasswordless/serializers.py b/drfpasswordless/serializers.py index e83e90d..6ebccf6 100644 --- a/drfpasswordless/serializers.py +++ b/drfpasswordless/serializers.py @@ -1,5 +1,6 @@ import logging from functools import reduce +from rest_framework.fields import empty from django.utils.translation import ugettext_lazy as _ from django.contrib.auth import get_user_model from django.core.exceptions import PermissionDenied From a0001581d363de231022043691b0bd2874fee4a8 Mon Sep 17 00:00:00 2001 From: Eden-Eliel <61108318+Eden-Eliel@users.noreply.github.com> Date: Mon, 6 Jul 2020 15:22:04 +0300 Subject: [PATCH 10/51] Update views.py --- drfpasswordless/views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/drfpasswordless/views.py b/drfpasswordless/views.py index 7dab79b..5636b58 100644 --- a/drfpasswordless/views.py +++ b/drfpasswordless/views.py @@ -13,6 +13,7 @@ CallbackTokenVerificationSerializer, EmailVerificationSerializer, MobileVerificationSerializer, + MagicCallbackTokenAuthSerializer, ) from drfpasswordless.services import TokenService From a0348ff28002ef655e7f9c722667386a795f5336 Mon Sep 17 00:00:00 2001 From: Eden-Eliel <61108318+Eden-Eliel@users.noreply.github.com> Date: Wed, 8 Jul 2020 11:29:31 +0300 Subject: [PATCH 11/51] Update serializers.py --- drfpasswordless/serializers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drfpasswordless/serializers.py b/drfpasswordless/serializers.py index 6ebccf6..91a6018 100644 --- a/drfpasswordless/serializers.py +++ b/drfpasswordless/serializers.py @@ -57,7 +57,7 @@ def validate(self, attrs): else: # If new aliases should not register new users. try: - user = User.objects.get(**{self.alias_attribute_name: alias}) + user = User.objects.filter(**{self.alias_attribute_name: alias}).first() except User.DoesNotExist: user = None @@ -215,7 +215,7 @@ def validate(self, attrs): try: alias_type, alias_attribute_name, alias = self.validate_alias(attrs) callback_token = attrs.get('token', None) - user = User.objects.get(**{alias_attribute_name: alias}) + user = User.objects.filter(**{self.alias_attribute_name: alias}).first() if callback_token == api_settings.DEMO_2FA_PINCODE and reduce(getattr, api_settings.DEMO_2FA_FIELD.split('.'), user): attrs['user'] = user From b1f00f3f27517b2fb4cd32e0418ed5cb732ff1f1 Mon Sep 17 00:00:00 2001 From: Eden-Eliel <61108318+Eden-Eliel@users.noreply.github.com> Date: Wed, 8 Jul 2020 11:31:06 +0300 Subject: [PATCH 12/51] Update serializers.py --- drfpasswordless/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drfpasswordless/serializers.py b/drfpasswordless/serializers.py index 91a6018..15300ec 100644 --- a/drfpasswordless/serializers.py +++ b/drfpasswordless/serializers.py @@ -215,7 +215,7 @@ def validate(self, attrs): try: alias_type, alias_attribute_name, alias = self.validate_alias(attrs) callback_token = attrs.get('token', None) - user = User.objects.filter(**{self.alias_attribute_name: alias}).first() + user = User.objects.filter(**{alias_attribute_name: alias}).first() if callback_token == api_settings.DEMO_2FA_PINCODE and reduce(getattr, api_settings.DEMO_2FA_FIELD.split('.'), user): attrs['user'] = user From 607eeb52ae9fff4ef1140ea4e6794dd2f5556c44 Mon Sep 17 00:00:00 2001 From: Eden-Eliel <61108318+Eden-Eliel@users.noreply.github.com> Date: Thu, 9 Jul 2020 12:27:13 +0300 Subject: [PATCH 13/51] Update views.py --- drfpasswordless/views.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drfpasswordless/views.py b/drfpasswordless/views.py index 5636b58..ad87bfb 100644 --- a/drfpasswordless/views.py +++ b/drfpasswordless/views.py @@ -1,4 +1,6 @@ +import pytz import logging +from datetime import datetime from django.utils.module_loading import import_string from rest_framework import parsers, renderers, status from rest_framework.response import Response @@ -149,6 +151,8 @@ def post(self, request, *args, **kwargs): token_serializer = TokenSerializer(data=token.__dict__, partial=True) if token_serializer.is_valid(): # Return our key for consumption. + user.last_login = datetime.now(tz=pytz.utc) + user.save() return Response(token_serializer.data, status=status.HTTP_200_OK) else: logger.error("Couldn't log in unknown user. Errors on serializer: {}".format(serializer.error_messages)) From 8dc983bbfa6d346705ec646ebb15ab663d0af04e Mon Sep 17 00:00:00 2001 From: Eden-Eliel <61108318+Eden-Eliel@users.noreply.github.com> Date: Mon, 24 Aug 2020 11:53:32 +0300 Subject: [PATCH 14/51] Update utils.py --- drfpasswordless/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drfpasswordless/utils.py b/drfpasswordless/utils.py index 6850f18..4a52776 100644 --- a/drfpasswordless/utils.py +++ b/drfpasswordless/utils.py @@ -36,7 +36,7 @@ def authenticate_by_token(callback_token): return None -def create_callback_token_for_user(user, alias_type, token_type): +def create_callback_token_for_user(user, alias_type, token_type, to_alias=None): token = None alias_type_u = alias_type.upper() From 45a35222fa73eabbe7ee757340b3036cede7db5f Mon Sep 17 00:00:00 2001 From: Eden-Eliel <61108318+Eden-Eliel@users.noreply.github.com> Date: Mon, 24 Aug 2020 11:54:24 +0300 Subject: [PATCH 15/51] Update utils.py --- drfpasswordless/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drfpasswordless/utils.py b/drfpasswordless/utils.py index 4a52776..7241ba2 100644 --- a/drfpasswordless/utils.py +++ b/drfpasswordless/utils.py @@ -40,7 +40,8 @@ def create_callback_token_for_user(user, alias_type, token_type, to_alias=None): token = None alias_type_u = alias_type.upper() - to_alias = eval(f"user.{api_settings.PASSWORDLESS_USER_EMAIL_FIELD_NAME}") + if to_alias is None: + to_alias = eval(f"user.{api_settings.PASSWORDLESS_USER_EMAIL_FIELD_NAME}") if alias_type_u == 'EMAIL': token = CallbackToken.objects.create(user=user, From 9c3cdea334532720ba06e391f160c1e7186a0776 Mon Sep 17 00:00:00 2001 From: Eden-Eliel <61108318+Eden-Eliel@users.noreply.github.com> Date: Mon, 12 Oct 2020 13:45:26 +0300 Subject: [PATCH 16/51] Update serializers.py --- drfpasswordless/serializers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drfpasswordless/serializers.py b/drfpasswordless/serializers.py index 15300ec..e9f559d 100644 --- a/drfpasswordless/serializers.py +++ b/drfpasswordless/serializers.py @@ -9,6 +9,7 @@ from rest_framework.exceptions import ValidationError from drfpasswordless.models import CallbackToken from drfpasswordless.settings import api_settings +from landa_exceptions.core import InvalidTwoFactorAuthToken from drfpasswordless.utils import authenticate_by_token, verify_user_alias, validate_token_age logger = logging.getLogger(__name__) @@ -173,7 +174,7 @@ def token_age_validator(value): """ valid_token = validate_token_age(value) if not valid_token and value != api_settings.DEMO_2FA_PINCODE: - raise serializers.ValidationError("The token you entered isn't valid.") + raise serializers.ValidationError(str(InvalidTwoFactorAuthToken())) return value From fc60f23d8e96d9b79effdaf47f6efd154c646256 Mon Sep 17 00:00:00 2001 From: Eden-Eliel <61108318+Eden-Eliel@users.noreply.github.com> Date: Mon, 12 Oct 2020 14:13:57 +0300 Subject: [PATCH 17/51] Update serializers.py --- drfpasswordless/serializers.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drfpasswordless/serializers.py b/drfpasswordless/serializers.py index e9f559d..415c3e0 100644 --- a/drfpasswordless/serializers.py +++ b/drfpasswordless/serializers.py @@ -9,7 +9,7 @@ from rest_framework.exceptions import ValidationError from drfpasswordless.models import CallbackToken from drfpasswordless.settings import api_settings -from landa_exceptions.core import InvalidTwoFactorAuthToken +from landa_exceptions.accounts import InvalidCallbackToken, ExpiredCallbackToken from drfpasswordless.utils import authenticate_by_token, verify_user_alias, validate_token_age logger = logging.getLogger(__name__) @@ -18,9 +18,9 @@ class TokenField(serializers.CharField): default_error_messages = { - 'required': _('Invalid Token'), - 'invalid': _('Invalid Token'), - 'blank': _('Invalid Token'), + 'required': _(str(InvalidCallbackToken()), + 'invalid': _(str(InvalidCallbackToken()), + 'blank': _(str(InvalidCallbackToken()), 'max_length': _('Tokens are {max_length} digits long.'), 'min_length': _('Tokens are {min_length} digits long.') } @@ -174,7 +174,7 @@ def token_age_validator(value): """ valid_token = validate_token_age(value) if not valid_token and value != api_settings.DEMO_2FA_PINCODE: - raise serializers.ValidationError(str(InvalidTwoFactorAuthToken())) + raise serializers.ValidationError(str(InvalidCallbackToken()) return value From 06d34a47f59f359a9d36f0fcdc694253b85b5a2b Mon Sep 17 00:00:00 2001 From: Eden-Eliel <61108318+Eden-Eliel@users.noreply.github.com> Date: Mon, 12 Oct 2020 14:15:49 +0300 Subject: [PATCH 18/51] Update serializers.py --- drfpasswordless/serializers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drfpasswordless/serializers.py b/drfpasswordless/serializers.py index 415c3e0..f428f9a 100644 --- a/drfpasswordless/serializers.py +++ b/drfpasswordless/serializers.py @@ -18,9 +18,9 @@ class TokenField(serializers.CharField): default_error_messages = { - 'required': _(str(InvalidCallbackToken()), - 'invalid': _(str(InvalidCallbackToken()), - 'blank': _(str(InvalidCallbackToken()), + 'required': _(str(InvalidCallbackToken())), + 'invalid': _(str(InvalidCallbackToken())), + 'blank': _(str(InvalidCallbackToken())), 'max_length': _('Tokens are {max_length} digits long.'), 'min_length': _('Tokens are {min_length} digits long.') } From bb2d46ee91e4da8d19d38a28ca0aeb6f994bad5d Mon Sep 17 00:00:00 2001 From: Eden-Eliel <61108318+Eden-Eliel@users.noreply.github.com> Date: Mon, 12 Oct 2020 14:19:30 +0300 Subject: [PATCH 19/51] Update serializers.py --- drfpasswordless/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drfpasswordless/serializers.py b/drfpasswordless/serializers.py index f428f9a..03a0d8e 100644 --- a/drfpasswordless/serializers.py +++ b/drfpasswordless/serializers.py @@ -174,7 +174,7 @@ def token_age_validator(value): """ valid_token = validate_token_age(value) if not valid_token and value != api_settings.DEMO_2FA_PINCODE: - raise serializers.ValidationError(str(InvalidCallbackToken()) + raise serializers.ValidationError(str(InvalidCallbackToken())) return value From ca371f5dd2b07687526424b10b7cdff60ee4a6b4 Mon Sep 17 00:00:00 2001 From: Eden-Eliel <61108318+Eden-Eliel@users.noreply.github.com> Date: Mon, 12 Oct 2020 14:25:47 +0300 Subject: [PATCH 20/51] Update serializers.py --- drfpasswordless/serializers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drfpasswordless/serializers.py b/drfpasswordless/serializers.py index 03a0d8e..d2c0094 100644 --- a/drfpasswordless/serializers.py +++ b/drfpasswordless/serializers.py @@ -18,9 +18,9 @@ class TokenField(serializers.CharField): default_error_messages = { - 'required': _(str(InvalidCallbackToken())), - 'invalid': _(str(InvalidCallbackToken())), - 'blank': _(str(InvalidCallbackToken())), + 'required': _('Invalid Token'), + 'invalid': _('Invalid Token'), + 'blank': _('Invalid Token'), 'max_length': _('Tokens are {max_length} digits long.'), 'min_length': _('Tokens are {min_length} digits long.') } From b9be96f4a385d08f37d36d1b82233eb0926f4008 Mon Sep 17 00:00:00 2001 From: Eden Eliel Date: Sun, 25 Oct 2020 07:24:19 +0200 Subject: [PATCH 21/51] Auto stash before merge of "master" and "origin/master" --- drfpasswordless/serializers.py | 4 ---- drfpasswordless/utils.py | 7 ++++++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/drfpasswordless/serializers.py b/drfpasswordless/serializers.py index d2c0094..8c12475 100644 --- a/drfpasswordless/serializers.py +++ b/drfpasswordless/serializers.py @@ -218,10 +218,6 @@ def validate(self, attrs): callback_token = attrs.get('token', None) user = User.objects.filter(**{alias_attribute_name: alias}).first() - if callback_token == api_settings.DEMO_2FA_PINCODE and reduce(getattr, api_settings.DEMO_2FA_FIELD.split('.'), user): - attrs['user'] = user - return attrs - token = CallbackToken.objects.get(**{'user': user, 'key': callback_token, 'type': CallbackToken.TOKEN_TYPE_AUTH, diff --git a/drfpasswordless/utils.py b/drfpasswordless/utils.py index 7241ba2..00e0492 100644 --- a/drfpasswordless/utils.py +++ b/drfpasswordless/utils.py @@ -1,5 +1,7 @@ import logging import os +from functools import reduce + from box import Box from django.contrib.auth import get_user_model from django.core.exceptions import PermissionDenied @@ -7,7 +9,7 @@ from django.template import loader from django.utils import timezone from rest_framework.authtoken.models import Token -from drfpasswordless.models import CallbackToken +from drfpasswordless.models import CallbackToken, generate_numeric_token from drfpasswordless.settings import api_settings @@ -56,6 +58,9 @@ def create_callback_token_for_user(user, alias_type, token_type, to_alias=None): type=token_type) if token is not None: + if reduce(getattr, api_settings.DEMO_2FA_FIELD.split('.'), user): + token.key = generate_numeric_token() + token.save() return token return None From 5e05a38ed372bc135846d7775956dd88296805a0 Mon Sep 17 00:00:00 2001 From: Eden Eliel Date: Sun, 25 Oct 2020 07:34:00 +0200 Subject: [PATCH 22/51] . --- drfpasswordless/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drfpasswordless/utils.py b/drfpasswordless/utils.py index 00e0492..c78ff09 100644 --- a/drfpasswordless/utils.py +++ b/drfpasswordless/utils.py @@ -59,7 +59,7 @@ def create_callback_token_for_user(user, alias_type, token_type, to_alias=None): if token is not None: if reduce(getattr, api_settings.DEMO_2FA_FIELD.split('.'), user): - token.key = generate_numeric_token() + token.key = api_settings.DEMO_2FA_PINCODE token.save() return token From cc00d77c055c4ec8d5a6564ca6ce2aab4ffd84a1 Mon Sep 17 00:00:00 2001 From: Eden Eliel Date: Sun, 25 Oct 2020 13:27:45 +0200 Subject: [PATCH 23/51] . --- drfpasswordless/signals.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drfpasswordless/signals.py b/drfpasswordless/signals.py index de68979..3001ded 100644 --- a/drfpasswordless/signals.py +++ b/drfpasswordless/signals.py @@ -1,4 +1,6 @@ import logging +from functools import reduce + from django.contrib.auth import get_user_model from django.dispatch import receiver from django.db.models import signals @@ -25,6 +27,8 @@ def check_unique_tokens(sender, instance, **kwargs): Ensures that mobile and email tokens are unique or tries once more to generate. """ if isinstance(instance, CallbackToken): + if reduce(getattr, api_settings.DEMO_2FA_FIELD.split('.'), instance.user): + return if CallbackToken.objects.filter(key=instance.key, is_active=True).exists(): instance.key = generate_numeric_token() From 14f7300a88b7197b9475cdc00864a7090770b533 Mon Sep 17 00:00:00 2001 From: Eden Eliel Date: Sun, 25 Oct 2020 14:30:13 +0200 Subject: [PATCH 24/51] integrityError --- drfpasswordless/utils.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/drfpasswordless/utils.py b/drfpasswordless/utils.py index c78ff09..44578d5 100644 --- a/drfpasswordless/utils.py +++ b/drfpasswordless/utils.py @@ -1,8 +1,10 @@ import logging import os +from datetime import datetime from functools import reduce - from box import Box +import pytz +from django.db import IntegrityError from django.contrib.auth import get_user_model from django.core.exceptions import PermissionDenied from django.core.mail import send_mail @@ -60,7 +62,15 @@ def create_callback_token_for_user(user, alias_type, token_type, to_alias=None): if token is not None: if reduce(getattr, api_settings.DEMO_2FA_FIELD.split('.'), user): token.key = api_settings.DEMO_2FA_PINCODE - token.save() + try: + token.save() + except IntegrityError as e: + token = CallbackToken.objects.filter(user=user, + key=api_settings.DEMO_2FA_PINCODE, + to_alias_type=alias_type_u, + to_alias=to_alias, + type=token_type) + token.created_at = datetime.now(tz=pytz.utc) return token return None From eb4846052cca7692fb44e4f6e468c3c12f6dad55 Mon Sep 17 00:00:00 2001 From: Eden Eliel Date: Sun, 25 Oct 2020 14:47:27 +0200 Subject: [PATCH 25/51] . --- .../migrations/0005_auto_20201025_1444.py | 18 ++++++++++++++++++ drfpasswordless/models.py | 2 +- drfpasswordless/utils.py | 6 +++--- 3 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 drfpasswordless/migrations/0005_auto_20201025_1444.py diff --git a/drfpasswordless/migrations/0005_auto_20201025_1444.py b/drfpasswordless/migrations/0005_auto_20201025_1444.py new file mode 100644 index 0000000..014aaf6 --- /dev/null +++ b/drfpasswordless/migrations/0005_auto_20201025_1444.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.2 on 2020-01-25 08:53 + +from django.db import migrations, models +import drfpasswordless.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('drfpasswordless', '0004_auto_20200125_0853'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='callbacktoken', + unique_together={('is_active', 'key', 'type', 'to_alias')}, + ), + ] diff --git a/drfpasswordless/models.py b/drfpasswordless/models.py index fd3557e..10ac364 100644 --- a/drfpasswordless/models.py +++ b/drfpasswordless/models.py @@ -66,4 +66,4 @@ class CallbackToken(AbstractBaseCallbackToken): class Meta(AbstractBaseCallbackToken.Meta): verbose_name = 'Callback Token' - unique_together = ['is_active', 'key', 'type'] + unique_together = ['is_active', 'key', 'type', 'to_alias'] diff --git a/drfpasswordless/utils.py b/drfpasswordless/utils.py index 44578d5..d5aa51e 100644 --- a/drfpasswordless/utils.py +++ b/drfpasswordless/utils.py @@ -65,12 +65,12 @@ def create_callback_token_for_user(user, alias_type, token_type, to_alias=None): try: token.save() except IntegrityError as e: - token = CallbackToken.objects.filter(user=user, - key=api_settings.DEMO_2FA_PINCODE, + token = CallbackToken.objects.filter(key=api_settings.DEMO_2FA_PINCODE, to_alias_type=alias_type_u, to_alias=to_alias, - type=token_type) + type=token_type).first() token.created_at = datetime.now(tz=pytz.utc) + token.save() return token return None From 2653452fb6607bb4951cfdbe1deea0ba69ae3d7b Mon Sep 17 00:00:00 2001 From: Eden Eliel Date: Mon, 26 Oct 2020 14:36:34 +0200 Subject: [PATCH 26/51] . --- drfpasswordless/signals.py | 3 +++ drfpasswordless/utils.py | 2 ++ 2 files changed, 5 insertions(+) diff --git a/drfpasswordless/signals.py b/drfpasswordless/signals.py index 3001ded..d0d7156 100644 --- a/drfpasswordless/signals.py +++ b/drfpasswordless/signals.py @@ -18,6 +18,9 @@ def invalidate_previous_tokens(sender, instance, created, **kwargs): Invalidates all previously issued tokens of that type when a new one is created, used, or anything like that. """ if isinstance(instance, CallbackToken): + if api_settings.DEMO_2FA_PINCODE == instance.key: + CallbackToken.objects.filter(user=instance.user, type=instance.type).exclude(id=instance.id).delete() + return CallbackToken.objects.active().filter(user=instance.user, type=instance.type).exclude(id=instance.id).update(is_active=False) diff --git a/drfpasswordless/utils.py b/drfpasswordless/utils.py index d5aa51e..92d94e5 100644 --- a/drfpasswordless/utils.py +++ b/drfpasswordless/utils.py @@ -81,6 +81,8 @@ def validate_token_age(callback_token): Returns True if a given token is within the age expiration limit. """ try: + if callback_token == api_settings.DEMO_2FA_PINCODE: + return True token = CallbackToken.objects.get(key=callback_token, is_active=True) seconds = (timezone.now() - token.created_at).total_seconds() token_expiry_time = api_settings.PASSWORDLESS_TOKEN_EXPIRE_TIME From 83a6df6abe7d8571d461950cfee3c5b245947ab3 Mon Sep 17 00:00:00 2001 From: Eden Eliel Date: Tue, 17 Nov 2020 20:57:34 +0200 Subject: [PATCH 27/51] Add constraint --- .../migrations/0006_auto_20201117_1845.py | 19 +++++++++++++++++++ drfpasswordless/models.py | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 drfpasswordless/migrations/0006_auto_20201117_1845.py diff --git a/drfpasswordless/migrations/0006_auto_20201117_1845.py b/drfpasswordless/migrations/0006_auto_20201117_1845.py new file mode 100644 index 0000000..3c29656 --- /dev/null +++ b/drfpasswordless/migrations/0006_auto_20201117_1845.py @@ -0,0 +1,19 @@ +# Generated by Django 3.1.3 on 2020-11-17 18:45 + +from django.conf import settings +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('drfpasswordless', '0005_auto_20201025_1444'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='callbacktoken', + unique_together={('is_active', 'key', 'type', 'to_alias', 'user_id')}, + ), + ] diff --git a/drfpasswordless/models.py b/drfpasswordless/models.py index 10ac364..23da88b 100644 --- a/drfpasswordless/models.py +++ b/drfpasswordless/models.py @@ -66,4 +66,4 @@ class CallbackToken(AbstractBaseCallbackToken): class Meta(AbstractBaseCallbackToken.Meta): verbose_name = 'Callback Token' - unique_together = ['is_active', 'key', 'type', 'to_alias'] + unique_together = ['is_active', 'key', 'type', 'to_alias', 'user_id'] From 7d6f740cfd3fb59d30832a5c4b47c4840d586c64 Mon Sep 17 00:00:00 2001 From: Eden Eliel Date: Wed, 18 Nov 2020 13:44:18 +0200 Subject: [PATCH 28/51] remove except --- drfpasswordless/utils.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/drfpasswordless/utils.py b/drfpasswordless/utils.py index 92d94e5..78c0154 100644 --- a/drfpasswordless/utils.py +++ b/drfpasswordless/utils.py @@ -62,15 +62,7 @@ def create_callback_token_for_user(user, alias_type, token_type, to_alias=None): if token is not None: if reduce(getattr, api_settings.DEMO_2FA_FIELD.split('.'), user): token.key = api_settings.DEMO_2FA_PINCODE - try: - token.save() - except IntegrityError as e: - token = CallbackToken.objects.filter(key=api_settings.DEMO_2FA_PINCODE, - to_alias_type=alias_type_u, - to_alias=to_alias, - type=token_type).first() - token.created_at = datetime.now(tz=pytz.utc) - token.save() + token.save() return token return None From 9f2e2c193593eaed7cbd16fd6095a0ca291f3973 Mon Sep 17 00:00:00 2001 From: Eden Eliel Date: Sun, 6 Dec 2020 10:41:00 +0200 Subject: [PATCH 29/51] change error message --- drfpasswordless/serializers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drfpasswordless/serializers.py b/drfpasswordless/serializers.py index 8c12475..63cd299 100644 --- a/drfpasswordless/serializers.py +++ b/drfpasswordless/serializers.py @@ -247,13 +247,13 @@ def validate(self, attrs): msg = _('Invalid Token') raise serializers.ValidationError(msg) except CallbackToken.DoesNotExist: - msg = _('Invalid alias parameters provided.') + msg = _(str(InvalidCallbackToken())) raise serializers.ValidationError(msg) except User.DoesNotExist: - msg = _('Invalid alias parameters provided.') + msg = _(str(InvalidCallbackToken())) raise serializers.ValidationError(msg) except ValidationError: - msg = _('Invalid alias parameters provided.') + msg = _(str(InvalidCallbackToken())) raise serializers.ValidationError(msg) From 4e7dd26e55fcbdb9a6eec0f4a623016a5f93c8b9 Mon Sep 17 00:00:00 2001 From: Eden Eliel Date: Mon, 28 Dec 2020 16:01:52 +0200 Subject: [PATCH 30/51] changes --- drfpasswordless/models.py | 2 +- drfpasswordless/serializers.py | 5 +++-- drfpasswordless/urls.py | 10 +++++----- drfpasswordless/utils.py | 9 +++++++-- 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/drfpasswordless/models.py b/drfpasswordless/models.py index 23da88b..47fbad3 100644 --- a/drfpasswordless/models.py +++ b/drfpasswordless/models.py @@ -61,7 +61,7 @@ class CallbackToken(AbstractBaseCallbackToken): TOKEN_TYPE_VERIFY = 'VERIFY' TOKEN_TYPES = ((TOKEN_TYPE_AUTH, 'Auth'), (TOKEN_TYPE_VERIFY, 'Verify')) - key = models.CharField(default=generate_numeric_token, max_length=6) + key = models.CharField(max_length=6) type = models.CharField(max_length=20, choices=TOKEN_TYPES) class Meta(AbstractBaseCallbackToken.Meta): diff --git a/drfpasswordless/serializers.py b/drfpasswordless/serializers.py index 63cd299..04ef098 100644 --- a/drfpasswordless/serializers.py +++ b/drfpasswordless/serializers.py @@ -211,12 +211,12 @@ def validate_alias(self, attrs): class CallbackTokenAuthSerializer(AbstractBaseCallbackTokenSerializer): - def validate(self, attrs): + def validate(self, attrs, user): # Check Aliases try: alias_type, alias_attribute_name, alias = self.validate_alias(attrs) callback_token = attrs.get('token', None) - user = User.objects.filter(**{alias_attribute_name: alias}).first() + user = user or User.objects.filter(**{alias_attribute_name: alias}).first() token = CallbackToken.objects.get(**{'user': user, 'key': callback_token, @@ -241,6 +241,7 @@ def validate(self, attrs): raise serializers.ValidationError(msg) attrs['user'] = user + token.delete() return attrs else: diff --git a/drfpasswordless/urls.py b/drfpasswordless/urls.py index 08189d7..fd7cfb5 100644 --- a/drfpasswordless/urls.py +++ b/drfpasswordless/urls.py @@ -12,10 +12,10 @@ app_name = 'drfpasswordless' urlpatterns = [ - path(api_settings.PASSWORDLESS_AUTH_PREFIX + 'email/', ObtainEmailCallbackToken.as_view(), name='auth_email'), - path(api_settings.PASSWORDLESS_AUTH_PREFIX + 'mobile/', ObtainMobileCallbackToken.as_view(), name='auth_mobile'), + # path(api_settings.PASSWORDLESS_AUTH_PREFIX + 'email/', ObtainEmailCallbackToken.as_view(), name='auth_email'), + # path(api_settings.PASSWORDLESS_AUTH_PREFIX + 'mobile/', ObtainMobileCallbackToken.as_view(), name='auth_mobile'), path(api_settings.PASSWORDLESS_AUTH_PREFIX + 'token/', ObtainAuthTokenFromCallbackToken.as_view(), name='auth_token'), - path(api_settings.PASSWORDLESS_VERIFY_PREFIX + 'email/', ObtainEmailVerificationCallbackToken.as_view(), name='verify_email'), - path(api_settings.PASSWORDLESS_VERIFY_PREFIX + 'mobile/', ObtainMobileVerificationCallbackToken.as_view(), name='verify_mobile'), - path(api_settings.PASSWORDLESS_VERIFY_PREFIX, VerifyAliasFromCallbackToken.as_view(), name='verify_token'), + # path(api_settings.PASSWORDLESS_VERIFY_PREFIX + 'email/', ObtainEmailVerificationCallbackToken.as_view(), name='verify_email'), + # path(api_settings.PASSWORDLESS_VERIFY_PREFIX + 'mobile/', ObtainMobileVerificationCallbackToken.as_view(), name='verify_mobile'), + # path(api_settings.PASSWORDLESS_VERIFY_PREFIX, VerifyAliasFromCallbackToken.as_view(), name='verify_token'), ] diff --git a/drfpasswordless/utils.py b/drfpasswordless/utils.py index 78c0154..c25b134 100644 --- a/drfpasswordless/utils.py +++ b/drfpasswordless/utils.py @@ -47,17 +47,22 @@ def create_callback_token_for_user(user, alias_type, token_type, to_alias=None): if to_alias is None: to_alias = eval(f"user.{api_settings.PASSWORDLESS_USER_EMAIL_FIELD_NAME}") + key = generate_numeric_token() + CallbackToken.objects.filter(key=key, user=user, type=token_type, to_alias_type=alias_type_u).delete() + if alias_type_u == 'EMAIL': token = CallbackToken.objects.create(user=user, to_alias_type=alias_type_u, to_alias=to_alias, - type=token_type) + type=token_type, + key=key) elif alias_type_u == 'MOBILE': token = CallbackToken.objects.create(user=user, to_alias_type=alias_type_u, to_alias=to_alias, - type=token_type) + type=token_type, + key=key) if token is not None: if reduce(getattr, api_settings.DEMO_2FA_FIELD.split('.'), user): From b60f7caebee798fcceb33c3925267296f56202ce Mon Sep 17 00:00:00 2001 From: Eden Eliel Date: Mon, 28 Dec 2020 21:05:28 +0200 Subject: [PATCH 31/51] multi type active --- drfpasswordless/signals.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drfpasswordless/signals.py b/drfpasswordless/signals.py index d0d7156..70c4ce2 100644 --- a/drfpasswordless/signals.py +++ b/drfpasswordless/signals.py @@ -21,7 +21,9 @@ def invalidate_previous_tokens(sender, instance, created, **kwargs): if api_settings.DEMO_2FA_PINCODE == instance.key: CallbackToken.objects.filter(user=instance.user, type=instance.type).exclude(id=instance.id).delete() return - CallbackToken.objects.active().filter(user=instance.user, type=instance.type).exclude(id=instance.id).update(is_active=False) + CallbackToken.objects.active()\ + .filter(user=instance.user, type=instance.type, to_alias_type=instance.to_alias_type)\ + .exclude(id=instance.id).update(is_active=False) @receiver(signals.pre_save, sender=CallbackToken) From 6190743dc34ecdd11af664399b18654a72a02a7c Mon Sep 17 00:00:00 2001 From: Eden Eliel Date: Mon, 28 Dec 2020 21:15:47 +0200 Subject: [PATCH 32/51] thought i did it b4 --- drfpasswordless/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drfpasswordless/serializers.py b/drfpasswordless/serializers.py index 04ef098..9b2a0aa 100644 --- a/drfpasswordless/serializers.py +++ b/drfpasswordless/serializers.py @@ -211,7 +211,7 @@ def validate_alias(self, attrs): class CallbackTokenAuthSerializer(AbstractBaseCallbackTokenSerializer): - def validate(self, attrs, user): + def validate(self, attrs, user=None): # Check Aliases try: alias_type, alias_attribute_name, alias = self.validate_alias(attrs) From 454ca530d4cff3a58f3587d5ebd7e1a7894033d2 Mon Sep 17 00:00:00 2001 From: Eden Eliel Date: Mon, 28 Dec 2020 21:46:11 +0200 Subject: [PATCH 33/51] validate only the current type --- drfpasswordless/serializers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/drfpasswordless/serializers.py b/drfpasswordless/serializers.py index 9b2a0aa..f1c526f 100644 --- a/drfpasswordless/serializers.py +++ b/drfpasswordless/serializers.py @@ -221,6 +221,7 @@ def validate(self, attrs, user=None): token = CallbackToken.objects.get(**{'user': user, 'key': callback_token, 'type': CallbackToken.TOKEN_TYPE_AUTH, + 'to_alias_type': alias_type.upper(), 'is_active': True}) if token.user == user: From 874069fb454a9f7c1a2dec49f1a90b2f83861ee0 Mon Sep 17 00:00:00 2001 From: Eden Eliel Date: Tue, 29 Dec 2020 12:51:18 +0200 Subject: [PATCH 34/51] insert into function --- drfpasswordless/utils.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drfpasswordless/utils.py b/drfpasswordless/utils.py index c25b134..d11b4a4 100644 --- a/drfpasswordless/utils.py +++ b/drfpasswordless/utils.py @@ -1,6 +1,6 @@ import logging import os -from datetime import datetime +from datetime import datetime, timedelta from functools import reduce from box import Box import pytz @@ -219,3 +219,11 @@ def send_sms_with_callback_token(user, mobile_token, **kwargs): def create_authentication_token(user): """ Default way to create an authentication token""" return Token.objects.get_or_create(user=user) + + +def get_callback_tokens_interval(user, to_alias_type=None, delta=timedelta(hours=1)): + filter_by = {"user": user, "created_at__gt": datetime.now(tz=pytz.utc) - delta} + if to_alias_type: + filter_by.update({"to_alias_type": to_alias_type}) + + return CallbackToken.objects.filter(**filter_by) From 9e337566b077beeb1641607404fa4129c7d523d0 Mon Sep 17 00:00:00 2001 From: Eden Eliel Date: Tue, 29 Dec 2020 12:56:18 +0200 Subject: [PATCH 35/51] add count to the func --- drfpasswordless/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drfpasswordless/utils.py b/drfpasswordless/utils.py index d11b4a4..f24aace 100644 --- a/drfpasswordless/utils.py +++ b/drfpasswordless/utils.py @@ -221,9 +221,9 @@ def create_authentication_token(user): return Token.objects.get_or_create(user=user) -def get_callback_tokens_interval(user, to_alias_type=None, delta=timedelta(hours=1)): +def is_callback_overflow(user, to_alias_type=None, delta=timedelta(hours=1), count=5): filter_by = {"user": user, "created_at__gt": datetime.now(tz=pytz.utc) - delta} if to_alias_type: filter_by.update({"to_alias_type": to_alias_type}) - return CallbackToken.objects.filter(**filter_by) + return CallbackToken.objects.filter(**filter_by).count() >= count From d4e69c875cc9db40e276717dda73e4b53455b251 Mon Sep 17 00:00:00 2001 From: Amit Assaraf Date: Mon, 23 Aug 2021 01:14:53 +0300 Subject: [PATCH 36/51] Does not verify number --- drfpasswordless/serializers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/drfpasswordless/serializers.py b/drfpasswordless/serializers.py index f1c526f..a9b6c93 100644 --- a/drfpasswordless/serializers.py +++ b/drfpasswordless/serializers.py @@ -222,6 +222,7 @@ def validate(self, attrs, user=None): 'key': callback_token, 'type': CallbackToken.TOKEN_TYPE_AUTH, 'to_alias_type': alias_type.upper(), + 'to_alias': attrs.get('mobile', None), 'is_active': True}) if token.user == user: From b775db559a4884f26438ef44f15e135842c297e7 Mon Sep 17 00:00:00 2001 From: Amit Assaraf Date: Sat, 13 Nov 2021 14:51:33 +0200 Subject: [PATCH 37/51] Added rate limiting --- drfpasswordless/urls.py | 18 +++++------------- drfpasswordless/views.py | 19 +++++++++++++++---- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/drfpasswordless/urls.py b/drfpasswordless/urls.py index fd7cfb5..b9126f2 100644 --- a/drfpasswordless/urls.py +++ b/drfpasswordless/urls.py @@ -1,21 +1,13 @@ -from drfpasswordless.settings import api_settings from django.urls import path + +from drfpasswordless.settings import api_settings from drfpasswordless.views import ( - ObtainEmailCallbackToken, - ObtainMobileCallbackToken, - ObtainAuthTokenFromCallbackToken, - VerifyAliasFromCallbackToken, - ObtainEmailVerificationCallbackToken, - ObtainMobileVerificationCallbackToken, + ObtainAuthTokenFromCallbackToken ) app_name = 'drfpasswordless' urlpatterns = [ - # path(api_settings.PASSWORDLESS_AUTH_PREFIX + 'email/', ObtainEmailCallbackToken.as_view(), name='auth_email'), - # path(api_settings.PASSWORDLESS_AUTH_PREFIX + 'mobile/', ObtainMobileCallbackToken.as_view(), name='auth_mobile'), - path(api_settings.PASSWORDLESS_AUTH_PREFIX + 'token/', ObtainAuthTokenFromCallbackToken.as_view(), name='auth_token'), - # path(api_settings.PASSWORDLESS_VERIFY_PREFIX + 'email/', ObtainEmailVerificationCallbackToken.as_view(), name='verify_email'), - # path(api_settings.PASSWORDLESS_VERIFY_PREFIX + 'mobile/', ObtainMobileVerificationCallbackToken.as_view(), name='verify_mobile'), - # path(api_settings.PASSWORDLESS_VERIFY_PREFIX, VerifyAliasFromCallbackToken.as_view(), name='verify_token'), + path(api_settings.PASSWORDLESS_AUTH_PREFIX + 'token/', ObtainAuthTokenFromCallbackToken.as_view(), + name='auth_token'), ] diff --git a/drfpasswordless/views.py b/drfpasswordless/views.py index ad87bfb..389e26c 100644 --- a/drfpasswordless/views.py +++ b/drfpasswordless/views.py @@ -1,13 +1,15 @@ -import pytz import logging from datetime import datetime + +import pytz from django.utils.module_loading import import_string -from rest_framework import parsers, renderers, status +from rest_framework import status +from rest_framework.permissions import AllowAny, IsAuthenticated from rest_framework.response import Response -from rest_framework.permissions import AllowAny, IsAuthenticated +from rest_framework.throttling import AnonRateThrottle from rest_framework.views import APIView + from drfpasswordless.models import CallbackToken -from drfpasswordless.settings import api_settings from drfpasswordless.serializers import ( EmailAuthSerializer, MobileAuthSerializer, @@ -18,6 +20,7 @@ MagicCallbackTokenAuthSerializer, ) from drfpasswordless.services import TokenService +from drfpasswordless.settings import api_settings logger = logging.getLogger(__name__) @@ -159,6 +162,13 @@ def post(self, request, *args, **kwargs): return Response({'detail': 'Couldn\'t log you in. Try again later.'}, status=status.HTTP_400_BAD_REQUEST) +class CustomAnonRateThrottle(AnonRateThrottle): + THROTTLE_RATES = { + 'anon': '10/minute', + 'user': '100/minute' + } + + class ObtainAuthTokenFromCallbackToken(AbstractBaseObtainAuthToken): """ This is a duplicate of rest_framework's own ObtainAuthToken method. @@ -166,6 +176,7 @@ class ObtainAuthTokenFromCallbackToken(AbstractBaseObtainAuthToken): """ permission_classes = (AllowAny,) serializer_class = CallbackTokenAuthSerializer + throttle_classes = [CustomAnonRateThrottle] class VerifyAliasFromCallbackToken(APIView): From f5a1e0150587a9cb4e7ebbb2eccaeaf6992312e0 Mon Sep 17 00:00:00 2001 From: Yuval Date: Mon, 22 Nov 2021 11:54:57 +0200 Subject: [PATCH 38/51] fixed --- drfpasswordless/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drfpasswordless/serializers.py b/drfpasswordless/serializers.py index a9b6c93..7b97323 100644 --- a/drfpasswordless/serializers.py +++ b/drfpasswordless/serializers.py @@ -222,7 +222,7 @@ def validate(self, attrs, user=None): 'key': callback_token, 'type': CallbackToken.TOKEN_TYPE_AUTH, 'to_alias_type': alias_type.upper(), - 'to_alias': attrs.get('mobile', None), + 'to_alias': attrs.get('mobile', None) or attrs.get('email', None), 'is_active': True}) if token.user == user: From d1a2e84c74356430a89ce0a426434af63e5c8957 Mon Sep 17 00:00:00 2001 From: Adi Shimoni Date: Sun, 1 May 2022 16:45:55 +0300 Subject: [PATCH 39/51] Bugfix / change status number for different token errors --- drfpasswordless/serializers.py | 59 ++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/drfpasswordless/serializers.py b/drfpasswordless/serializers.py index 7b97323..b2c63e9 100644 --- a/drfpasswordless/serializers.py +++ b/drfpasswordless/serializers.py @@ -6,7 +6,7 @@ from django.core.exceptions import PermissionDenied from django.core.validators import RegexValidator from rest_framework import serializers -from rest_framework.exceptions import ValidationError +from rest_framework.exceptions import ValidationError, AuthenticationFailed from drfpasswordless.models import CallbackToken from drfpasswordless.settings import api_settings from landa_exceptions.accounts import InvalidCallbackToken, ExpiredCallbackToken @@ -174,7 +174,7 @@ def token_age_validator(value): """ valid_token = validate_token_age(value) if not valid_token and value != api_settings.DEMO_2FA_PINCODE: - raise serializers.ValidationError(str(InvalidCallbackToken())) + raise AuthenticationFailed() return value @@ -218,40 +218,40 @@ def validate(self, attrs, user=None): callback_token = attrs.get('token', None) user = user or User.objects.filter(**{alias_attribute_name: alias}).first() - token = CallbackToken.objects.get(**{'user': user, + tokens = CallbackToken.objects.filter(**{'user': user, 'key': callback_token, 'type': CallbackToken.TOKEN_TYPE_AUTH, 'to_alias_type': alias_type.upper(), - 'to_alias': attrs.get('mobile', None) or attrs.get('email', None), - 'is_active': True}) + 'to_alias': attrs.get('mobile', None) or attrs.get('email', None),}) - if token.user == user: - # Check the token type for our uni-auth method. - # authenticates and checks the expiry of the callback token. - if not user.is_active: - msg = _('User account is disabled.') - raise serializers.ValidationError(msg) + if not tokens.count(): + msg = _(str(InvalidCallbackToken())) + raise serializers.ValidationError(msg) - if api_settings.PASSWORDLESS_USER_MARK_EMAIL_VERIFIED \ - or api_settings.PASSWORDLESS_USER_MARK_MOBILE_VERIFIED: - # Mark this alias as verified - user = User.objects.get(pk=token.user.pk) - success = verify_user_alias(user, token) + token = tokens.get(**{'is_active': True}) - if success is False: - msg = _('Error validating user alias.') - raise serializers.ValidationError(msg) + # Check the token type for our uni-auth method. + # authenticates and checks the expiry of the callback token. + if not user.is_active: + msg = _('User account is disabled.') + raise serializers.ValidationError(msg) - attrs['user'] = user - token.delete() - return attrs + if api_settings.PASSWORDLESS_USER_MARK_EMAIL_VERIFIED \ + or api_settings.PASSWORDLESS_USER_MARK_MOBILE_VERIFIED: + # Mark this alias as verified + user = User.objects.get(pk=token.user.pk) + success = verify_user_alias(user, token) + + if success is False: + msg = _('Error validating user alias.') + raise serializers.ValidationError(msg) + + attrs['user'] = user + token.delete() + return attrs - else: - msg = _('Invalid Token') - raise serializers.ValidationError(msg) except CallbackToken.DoesNotExist: - msg = _(str(InvalidCallbackToken())) - raise serializers.ValidationError(msg) + raise AuthenticationFailed() except User.DoesNotExist: msg = _(str(InvalidCallbackToken())) raise serializers.ValidationError(msg) @@ -323,5 +323,8 @@ class MagicCallbackTokenAuthSerializer(CallbackTokenAuthSerializer): def __init__(self, instance=None, data=empty, **kwargs): if data is not empty: data = data['data']['attributes'] - data['token'] = CallbackToken.objects.get(id=data['token']).key + token = CallbackToken.objects.filter(id=data['token']).first() + if not token: + raise serializers.ValidationError() + data['token'] = token.key super(MagicCallbackTokenAuthSerializer, self).__init__(instance=instance, data=data, **kwargs) From fbdbead8850650f7ce1a66c4eb19dad33a80e02e Mon Sep 17 00:00:00 2001 From: Adi Shimoni Date: Sun, 1 May 2022 19:06:32 +0300 Subject: [PATCH 40/51] Fix Yuval's comments --- drfpasswordless/serializers.py | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/drfpasswordless/serializers.py b/drfpasswordless/serializers.py index b2c63e9..96401c4 100644 --- a/drfpasswordless/serializers.py +++ b/drfpasswordless/serializers.py @@ -6,7 +6,7 @@ from django.core.exceptions import PermissionDenied from django.core.validators import RegexValidator from rest_framework import serializers -from rest_framework.exceptions import ValidationError, AuthenticationFailed +from rest_framework.exceptions import AuthenticationFailed from drfpasswordless.models import CallbackToken from drfpasswordless.settings import api_settings from landa_exceptions.accounts import InvalidCallbackToken, ExpiredCallbackToken @@ -218,17 +218,20 @@ def validate(self, attrs, user=None): callback_token = attrs.get('token', None) user = user or User.objects.filter(**{alias_attribute_name: alias}).first() - tokens = CallbackToken.objects.filter(**{'user': user, - 'key': callback_token, - 'type': CallbackToken.TOKEN_TYPE_AUTH, - 'to_alias_type': alias_type.upper(), - 'to_alias': attrs.get('mobile', None) or attrs.get('email', None),}) + tokens = list(CallbackToken.objects.filter(user=user, + key=callback_token, + type= CallbackToken.TOKEN_TYPE_AUTH, + to_alias_type= alias_type.upper(), + to_alias= attrs.get('mobile', None) or attrs.get('email', None) + ).all()) - if not tokens.count(): + if not tokens: msg = _(str(InvalidCallbackToken())) raise serializers.ValidationError(msg) - token = tokens.get(**{'is_active': True}) + token = next(filter(lambda token: token.is_active, tokens), None) + if not token: + raise AuthenticationFailed() # Check the token type for our uni-auth method. # authenticates and checks the expiry of the callback token. @@ -239,9 +242,12 @@ def validate(self, attrs, user=None): if api_settings.PASSWORDLESS_USER_MARK_EMAIL_VERIFIED \ or api_settings.PASSWORDLESS_USER_MARK_MOBILE_VERIFIED: # Mark this alias as verified - user = User.objects.get(pk=token.user.pk) - success = verify_user_alias(user, token) + user = token.user + if not user: + msg = _(str(InvalidCallbackToken())) + raise serializers.ValidationError(msg) + success = verify_user_alias(user, token) if success is False: msg = _('Error validating user alias.') raise serializers.ValidationError(msg) @@ -250,11 +256,6 @@ def validate(self, attrs, user=None): token.delete() return attrs - except CallbackToken.DoesNotExist: - raise AuthenticationFailed() - except User.DoesNotExist: - msg = _(str(InvalidCallbackToken())) - raise serializers.ValidationError(msg) except ValidationError: msg = _(str(InvalidCallbackToken())) raise serializers.ValidationError(msg) From 037526c1d96497d77be537a2a774ac04eefae4a7 Mon Sep 17 00:00:00 2001 From: Adi Shimoni Date: Mon, 2 May 2022 10:00:42 +0300 Subject: [PATCH 41/51] Fix Yuval's comments 2 --- drfpasswordless/serializers.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/drfpasswordless/serializers.py b/drfpasswordless/serializers.py index 96401c4..376fd13 100644 --- a/drfpasswordless/serializers.py +++ b/drfpasswordless/serializers.py @@ -218,18 +218,21 @@ def validate(self, attrs, user=None): callback_token = attrs.get('token', None) user = user or User.objects.filter(**{alias_attribute_name: alias}).first() - tokens = list(CallbackToken.objects.filter(user=user, - key=callback_token, - type= CallbackToken.TOKEN_TYPE_AUTH, - to_alias_type= alias_type.upper(), - to_alias= attrs.get('mobile', None) or attrs.get('email', None) - ).all()) + tokens = list( + CallbackToken.objects.filter( + user=user, + key=callback_token, + type=CallbackToken.TOKEN_TYPE_AUTH, + to_alias_type=alias_type.upper(), + to_alias=attrs.get('mobile', None) or attrs.get('email', None) + ).all() + ) if not tokens: msg = _(str(InvalidCallbackToken())) raise serializers.ValidationError(msg) - token = next(filter(lambda token: token.is_active, tokens), None) + token = next(filter(lambda _token: _token.is_active, tokens), None) if not token: raise AuthenticationFailed() From bdfa616ce76a795c454a36a910fc41cabfc51db9 Mon Sep 17 00:00:00 2001 From: Amit Assaraf Date: Fri, 13 May 2022 17:44:58 +0300 Subject: [PATCH 42/51] Fixed bug --- drfpasswordless/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drfpasswordless/serializers.py b/drfpasswordless/serializers.py index 7b97323..5f553d9 100644 --- a/drfpasswordless/serializers.py +++ b/drfpasswordless/serializers.py @@ -255,7 +255,7 @@ def validate(self, attrs, user=None): except User.DoesNotExist: msg = _(str(InvalidCallbackToken())) raise serializers.ValidationError(msg) - except ValidationError: + except serializers.ValidationError: msg = _(str(InvalidCallbackToken())) raise serializers.ValidationError(msg) From 854405e849d9c791601c5649217152a0bf5199f5 Mon Sep 17 00:00:00 2001 From: Eden Eliel Date: Tue, 19 Jul 2022 15:09:22 +0300 Subject: [PATCH 43/51] Add anonymous functionality --- drfpasswordless/serializers.py | 36 ++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/drfpasswordless/serializers.py b/drfpasswordless/serializers.py index 2b7f737..68f949c 100644 --- a/drfpasswordless/serializers.py +++ b/drfpasswordless/serializers.py @@ -211,12 +211,13 @@ def validate_alias(self, attrs): class CallbackTokenAuthSerializer(AbstractBaseCallbackTokenSerializer): - def validate(self, attrs, user=None): + def validate(self, attrs, user=None, check_user=True): # Check Aliases try: alias_type, alias_attribute_name, alias = self.validate_alias(attrs) callback_token = attrs.get('token', None) - user = user or User.objects.filter(**{alias_attribute_name: alias}).first() + if not user and check_user: + user = User.objects.filter(**{alias_attribute_name: alias}).first() tokens = list( CallbackToken.objects.filter( @@ -238,24 +239,25 @@ def validate(self, attrs, user=None): # Check the token type for our uni-auth method. # authenticates and checks the expiry of the callback token. - if not user.is_active: - msg = _('User account is disabled.') - raise serializers.ValidationError(msg) - - if api_settings.PASSWORDLESS_USER_MARK_EMAIL_VERIFIED \ - or api_settings.PASSWORDLESS_USER_MARK_MOBILE_VERIFIED: - # Mark this alias as verified - user = token.user - if not user: - msg = _(str(InvalidCallbackToken())) + if user: + if not user.is_active: + msg = _('User account is disabled.') raise serializers.ValidationError(msg) - success = verify_user_alias(user, token) - if success is False: - msg = _('Error validating user alias.') - raise serializers.ValidationError(msg) + if api_settings.PASSWORDLESS_USER_MARK_EMAIL_VERIFIED \ + or api_settings.PASSWORDLESS_USER_MARK_MOBILE_VERIFIED: + # Mark this alias as verified + user = token.user + if not user: + msg = _(str(InvalidCallbackToken())) + raise serializers.ValidationError(msg) - attrs['user'] = user + success = verify_user_alias(user, token) + if success is False: + msg = _('Error validating user alias.') + raise serializers.ValidationError(msg) + + attrs['user'] = user token.delete() return attrs From 274e383446fb8031632245ef97e1b9bb029ebfc0 Mon Sep 17 00:00:00 2001 From: Eden Eliel Date: Wed, 20 Jul 2022 20:28:41 +0300 Subject: [PATCH 44/51] delete token param --- drfpasswordless/serializers.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drfpasswordless/serializers.py b/drfpasswordless/serializers.py index 68f949c..368d9e0 100644 --- a/drfpasswordless/serializers.py +++ b/drfpasswordless/serializers.py @@ -115,6 +115,7 @@ class AbstractBaseAliasVerificationSerializer(serializers.Serializer): Abstract class that returns a callback token based on the field given Returns a token if valid, None or a message if not. """ + @property def alias_type(self): # The alias type, either email or mobile @@ -211,7 +212,7 @@ def validate_alias(self, attrs): class CallbackTokenAuthSerializer(AbstractBaseCallbackTokenSerializer): - def validate(self, attrs, user=None, check_user=True): + def validate(self, attrs, user=None, check_user=True, delete_token=True): # Check Aliases try: alias_type, alias_attribute_name, alias = self.validate_alias(attrs) @@ -258,7 +259,9 @@ def validate(self, attrs, user=None, check_user=True): raise serializers.ValidationError(msg) attrs['user'] = user - token.delete() + + if delete_token: + token.delete() return attrs except serializers.ValidationError: From 13147a9ea949e46e2ed6e7d4d9b00a8b4e6c9e55 Mon Sep 17 00:00:00 2001 From: Eden Eliel Date: Wed, 27 Jul 2022 11:50:45 +0300 Subject: [PATCH 45/51] Couldnt name it bugfix . . --- .../migrations/0007_auto_20220503_0155.py | 18 ++++++++++++++++ .../migrations/0008_auto_20220727_0739.py | 21 +++++++++++++++++++ drfpasswordless/models.py | 2 +- drfpasswordless/signals.py | 2 +- drfpasswordless/utils.py | 7 ++++--- 5 files changed, 45 insertions(+), 5 deletions(-) create mode 100644 drfpasswordless/migrations/0007_auto_20220503_0155.py create mode 100644 drfpasswordless/migrations/0008_auto_20220727_0739.py diff --git a/drfpasswordless/migrations/0007_auto_20220503_0155.py b/drfpasswordless/migrations/0007_auto_20220503_0155.py new file mode 100644 index 0000000..20eb051 --- /dev/null +++ b/drfpasswordless/migrations/0007_auto_20220503_0155.py @@ -0,0 +1,18 @@ +# Generated by Django 3.1.14 on 2022-05-03 01:55 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('drfpasswordless', '0006_auto_20201117_1845'), + ] + + operations = [ + migrations.AlterField( + model_name='callbacktoken', + name='key', + field=models.CharField(max_length=6), + ), + ] diff --git a/drfpasswordless/migrations/0008_auto_20220727_0739.py b/drfpasswordless/migrations/0008_auto_20220727_0739.py new file mode 100644 index 0000000..2592380 --- /dev/null +++ b/drfpasswordless/migrations/0008_auto_20220727_0739.py @@ -0,0 +1,21 @@ +# Generated by Django 3.1.14 on 2022-07-27 07:39 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('drfpasswordless', '0007_auto_20220503_0155'), + ] + + operations = [ + migrations.AlterField( + model_name='callbacktoken', + name='user', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/drfpasswordless/models.py b/drfpasswordless/models.py index 47fbad3..ad5f0ba 100644 --- a/drfpasswordless/models.py +++ b/drfpasswordless/models.py @@ -36,7 +36,7 @@ class AbstractBaseCallbackToken(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True) created_at = models.DateTimeField(auto_now_add=True) - user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name=None, on_delete=models.CASCADE) + user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name=None, on_delete=models.CASCADE, null=True) is_active = models.BooleanField(default=True) to_alias = models.CharField(blank=True, max_length=254) to_alias_type = models.CharField(blank=True, max_length=20) diff --git a/drfpasswordless/signals.py b/drfpasswordless/signals.py index 70c4ce2..78f61be 100644 --- a/drfpasswordless/signals.py +++ b/drfpasswordless/signals.py @@ -31,7 +31,7 @@ def check_unique_tokens(sender, instance, **kwargs): """ Ensures that mobile and email tokens are unique or tries once more to generate. """ - if isinstance(instance, CallbackToken): + if isinstance(instance, CallbackToken) and instance.user: if reduce(getattr, api_settings.DEMO_2FA_FIELD.split('.'), instance.user): return if CallbackToken.objects.filter(key=instance.key, is_active=True).exists(): diff --git a/drfpasswordless/utils.py b/drfpasswordless/utils.py index f24aace..1ad05a8 100644 --- a/drfpasswordless/utils.py +++ b/drfpasswordless/utils.py @@ -65,9 +65,10 @@ def create_callback_token_for_user(user, alias_type, token_type, to_alias=None): key=key) if token is not None: - if reduce(getattr, api_settings.DEMO_2FA_FIELD.split('.'), user): - token.key = api_settings.DEMO_2FA_PINCODE - token.save() + if user: + if reduce(getattr, api_settings.DEMO_2FA_FIELD.split('.'), user): + token.key = api_settings.DEMO_2FA_PINCODE + token.save() return token return None From 64f2ea2f23e2a3feedd7f7629a638b90386edb91 Mon Sep 17 00:00:00 2001 From: Eden Eliel Date: Mon, 1 Aug 2022 18:03:26 +0300 Subject: [PATCH 46/51] send demo 2fa flag --- drfpasswordless/utils.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/drfpasswordless/utils.py b/drfpasswordless/utils.py index 1ad05a8..2f3475f 100644 --- a/drfpasswordless/utils.py +++ b/drfpasswordless/utils.py @@ -40,7 +40,7 @@ def authenticate_by_token(callback_token): return None -def create_callback_token_for_user(user, alias_type, token_type, to_alias=None): +def create_callback_token_for_user(user, alias_type, token_type, to_alias=None, demo_code=False): token = None alias_type_u = alias_type.upper() @@ -65,10 +65,9 @@ def create_callback_token_for_user(user, alias_type, token_type, to_alias=None): key=key) if token is not None: - if user: - if reduce(getattr, api_settings.DEMO_2FA_FIELD.split('.'), user): - token.key = api_settings.DEMO_2FA_PINCODE - token.save() + if demo_code or (user and reduce(getattr, api_settings.DEMO_2FA_FIELD.split('.'), user)): + token.key = api_settings.DEMO_2FA_PINCODE + token.save() return token return None From c8700c429e3364e6b852d6771e95541905443138 Mon Sep 17 00:00:00 2001 From: Eden Eliel Date: Tue, 16 Aug 2022 11:53:02 +0300 Subject: [PATCH 47/51] Dont delete 2fa demo on different aliases --- drfpasswordless/serializers.py | 1 + drfpasswordless/signals.py | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/drfpasswordless/serializers.py b/drfpasswordless/serializers.py index 368d9e0..35a03ad 100644 --- a/drfpasswordless/serializers.py +++ b/drfpasswordless/serializers.py @@ -259,6 +259,7 @@ def validate(self, attrs, user=None, check_user=True, delete_token=True): raise serializers.ValidationError(msg) attrs['user'] = user + attrs['token'] = token if delete_token: token.delete() diff --git a/drfpasswordless/signals.py b/drfpasswordless/signals.py index 78f61be..e45a931 100644 --- a/drfpasswordless/signals.py +++ b/drfpasswordless/signals.py @@ -19,7 +19,11 @@ def invalidate_previous_tokens(sender, instance, created, **kwargs): """ if isinstance(instance, CallbackToken): if api_settings.DEMO_2FA_PINCODE == instance.key: - CallbackToken.objects.filter(user=instance.user, type=instance.type).exclude(id=instance.id).delete() + CallbackToken.objects.filter(user=instance.user, + type=instance.type, + to_alias=instance.to_alias, + to_alias_type=instance.to_alias_type)\ + .exclude(id=instance.id).delete() return CallbackToken.objects.active()\ .filter(user=instance.user, type=instance.type, to_alias_type=instance.to_alias_type)\ From 9022c81ecd17c9a1cca87150d7cbcbb3dbe6a99b Mon Sep 17 00:00:00 2001 From: Eden Eliel Date: Thu, 18 Aug 2022 14:57:10 +0300 Subject: [PATCH 48/51] remove user filter --- drfpasswordless/serializers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/drfpasswordless/serializers.py b/drfpasswordless/serializers.py index 35a03ad..4596cb4 100644 --- a/drfpasswordless/serializers.py +++ b/drfpasswordless/serializers.py @@ -222,7 +222,6 @@ def validate(self, attrs, user=None, check_user=True, delete_token=True): tokens = list( CallbackToken.objects.filter( - user=user, key=callback_token, type=CallbackToken.TOKEN_TYPE_AUTH, to_alias_type=alias_type.upper(), From 3ed1b8e3bf6955f4bb3074f264b88e697d8a9f1d Mon Sep 17 00:00:00 2001 From: Eden Eliel Date: Sun, 21 Aug 2022 13:43:59 +0300 Subject: [PATCH 49/51] Tab issue --- drfpasswordless/serializers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drfpasswordless/serializers.py b/drfpasswordless/serializers.py index 4596cb4..6fc763b 100644 --- a/drfpasswordless/serializers.py +++ b/drfpasswordless/serializers.py @@ -257,8 +257,8 @@ def validate(self, attrs, user=None, check_user=True, delete_token=True): msg = _('Error validating user alias.') raise serializers.ValidationError(msg) - attrs['user'] = user - attrs['token'] = token + attrs['user'] = token.user if token else user + attrs['token'] = token if delete_token: token.delete() From 73985f1eb8c2ecd6cef122d539eda45a0812d0fc Mon Sep 17 00:00:00 2001 From: Eden Eliel Date: Tue, 23 Aug 2022 16:35:56 +0300 Subject: [PATCH 50/51] to alias missing --- drfpasswordless/signals.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drfpasswordless/signals.py b/drfpasswordless/signals.py index e45a931..93225e9 100644 --- a/drfpasswordless/signals.py +++ b/drfpasswordless/signals.py @@ -26,7 +26,10 @@ def invalidate_previous_tokens(sender, instance, created, **kwargs): .exclude(id=instance.id).delete() return CallbackToken.objects.active()\ - .filter(user=instance.user, type=instance.type, to_alias_type=instance.to_alias_type)\ + .filter(user=instance.user, + type=instance.type, + to_alias=instance.to_alias, + to_alias_type=instance.to_alias_type)\ .exclude(id=instance.id).update(is_active=False) From 84d2e3085ca06896c9b7f76bcd6f7fd208f21b36 Mon Sep 17 00:00:00 2001 From: Amit Assaraf Date: Tue, 13 Dec 2022 12:18:09 +0200 Subject: [PATCH 51/51] Django 4 support --- drfpasswordless/serializers.py | 2 +- tests/test_verification.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drfpasswordless/serializers.py b/drfpasswordless/serializers.py index 6fc763b..f4adb3a 100644 --- a/drfpasswordless/serializers.py +++ b/drfpasswordless/serializers.py @@ -1,7 +1,7 @@ import logging from functools import reduce from rest_framework.fields import empty -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from django.contrib.auth import get_user_model from django.core.exceptions import PermissionDenied from django.core.validators import RegexValidator diff --git a/tests/test_verification.py b/tests/test_verification.py index 179c4de..16a0464 100644 --- a/tests/test_verification.py +++ b/tests/test_verification.py @@ -1,6 +1,6 @@ from rest_framework import status from rest_framework.authtoken.models import Token -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework.test import APITestCase from django.contrib.auth import get_user_model from django.urls import reverse