Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce a way to override how auth tokens are created #21

Merged
merged 1 commit into from
Jan 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,13 @@ DEFAULTS = {

# Automatically send verification email or sms when a user changes their alias.
'PASSWORDLESS_AUTO_SEND_VERIFICATION_TOKEN': False,

# What function is called to construct an authentication tokens when
# exchanging a passwordless token for a real user auth token. This function
# should take a user and return a tuple of two values. The first value is
# the token itself, the second is a boolean value representating whether
# the token was newly created.
'PASSWORDLESS_AUTH_TOKEN_CREATOR': 'drfpasswordless.utils.create_authentication_token'
}
```

Expand Down
3 changes: 3 additions & 0 deletions drfpasswordless/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@
# Automatically send verification email or sms when a user changes their alias.
'PASSWORDLESS_AUTO_SEND_VERIFICATION_TOKEN': False,

# What function is called to construct an authentication tokens when
# exchanging a passwordless token for a real user auth token.
'PASSWORDLESS_AUTH_TOKEN_CREATOR': 'drfpasswordless.utils.create_authentication_token'
}

# List of settings that may be in string import notation.
Expand Down
6 changes: 6 additions & 0 deletions drfpasswordless/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from django.core.mail import send_mail
from django.template import loader
from django.utils import timezone
from rest_framework.authtoken.models import Token
from drfpasswordless.models import CallbackToken
from drfpasswordless.settings import api_settings

Expand Down Expand Up @@ -189,3 +190,8 @@ def send_sms_with_callback_token(user, mobile_token, **kwargs):
"Number entered was {}".format(user.id, getattr(user, api_settings.PASSWORDLESS_USER_MOBILE_FIELD_NAME)))
logger.debug(e)
return False


def create_authentication_token(user):
""" Default way to create an authentication token"""
return Token.objects.get_or_create(user=user)
5 changes: 3 additions & 2 deletions drfpasswordless/views.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import logging
from django.utils.module_loading import import_string
from rest_framework import parsers, renderers, status
from rest_framework.authtoken.models import Token
from rest_framework.response import Response
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.views import APIView
Expand Down Expand Up @@ -130,7 +130,8 @@ def post(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data)
if serializer.is_valid(raise_exception=True):
user = serializer.validated_data['user']
token = Token.objects.get_or_create(user=user)[0]
token_creator = import_string(api_settings.PASSWORDLESS_AUTH_TOKEN_CREATOR)
(token, _) = token_creator(user)

if token:
# Return our key for consumption.
Expand Down
47 changes: 47 additions & 0 deletions tests/test_authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,53 @@ def tearDown(self):
api_settings.PASSWORDLESS_MOBILE_NOREPLY_NUMBER = DEFAULTS['PASSWORDLESS_MOBILE_NOREPLY_NUMBER']


def dummy_token_creator(user):
token = Token.objects.create(key="dummy", user=user)
return (token, True)


class OverrideTokenCreationTests(APITestCase):
def setUp(self):
super().setUp()

api_settings.PASSWORDLESS_AUTH_TOKEN_CREATOR = 'tests.test_authentication.dummy_token_creator'
api_settings.PASSWORDLESS_AUTH_TYPES = ['EMAIL']
api_settings.PASSWORDLESS_EMAIL_NOREPLY_ADDRESS = 'noreply@example.com'

self.email = 'aaron@example.com'
self.url = '/auth/email/'
self.challenge_url = '/callback/auth/'

self.email_field_name = api_settings.PASSWORDLESS_USER_EMAIL_FIELD_NAME
self.user = User.objects.create(**{self.email_field_name: self.email})

def test_token_creation_gets_overridden(self):
"""Ensure that if we change the token creation function, the overridden one gets called"""
data = {'email': self.email}
response = self.client.post(self.url, data)
self.assertEqual(response.status_code, status.HTTP_200_OK)

# Token sent to alias
callback_token = CallbackToken.objects.filter(user=self.user, is_active=True).first()
challenge_data = {'token': callback_token}

# Try to auth with the callback token
challenge_response = self.client.post(self.challenge_url, challenge_data)
self.assertEqual(challenge_response.status_code, status.HTTP_200_OK)

# Verify Auth Token
auth_token = challenge_response.data['token']
self.assertEqual(auth_token, Token.objects.filter(key=auth_token).first().key)
self.assertEqual('dummy', Token.objects.filter(key=auth_token).first().key)

def tearDown(self):
api_settings.PASSWORDLESS_AUTH_TOKEN_CREATOR = DEFAULTS['PASSWORDLESS_AUTH_TOKEN_CREATOR']
api_settings.PASSWORDLESS_AUTH_TYPES = DEFAULTS['PASSWORDLESS_AUTH_TYPES']
api_settings.PASSWORDLESS_EMAIL_NOREPLY_ADDRESS = DEFAULTS['PASSWORDLESS_EMAIL_NOREPLY_ADDRESS']
self.user.delete()
super().tearDown()


class MobileLoginCallbackTokenTests(APITestCase):

def setUp(self):
Expand Down