Giter Site home page Giter Site logo

aaronn / django-rest-framework-passwordless Goto Github PK

View Code? Open in Web Editor NEW
692.0 13.0 152.0 302 KB

Passwordless Auth for Django REST Framework

License: MIT License

Python 99.56% HTML 0.44%
django-rest-framework django python3 authentication authorization passwordless

django-rest-framework-passwordless's Introduction

splash-image ci-image

drfpasswordless is a quick way to integrate ‘passwordless’ auth into your Django Rest Framework project using a user’s email address or mobile number only (herein referred to as an alias).

Built to work with DRF’s own TokenAuthentication system, it sends the user a 6-digit callback token to a given email address or a mobile number. The user sends it back correctly and they’re given an authentication token (again, provided by Django Rest Framework’s TokenAuthentication system).

Callback tokens by default expire after 15 minutes.

Example Usage:

curl -X POST -d “[email protected]” localhost:8000/auth/email/

Email to [email protected]:

...
<h1>Your login token is 815381.</h1>
...

Return Stage

curl -X POST -d "[email protected]&token=815381" localhost:8000/auth/token/

> HTTP/1.0 200 OK
> {"token":"76be2d9ecfaf5fa4226d722bzdd8a4fff207ed0e”}

Requirements

  • Python (3.7+)
  • Django (2.2+)
  • Django Rest Framework + AuthToken (3.10+)
  • Python-Twilio (Optional, for mobile.)

Install

  1. Install drfpasswordless

    pipenv install drfpasswordless
    
  2. Add Django Rest Framework’s Token Authentication to your Django Rest Framework project.

 REST_FRAMEWORK = {
     'DEFAULT_AUTHENTICATION_CLASSES':
    ('rest_framework.authentication.TokenAuthentication',
 )}

 INSTALLED_APPS = [
     ...
     'rest_framework',
     'rest_framework.authtoken',
     'drfpasswordless',
      ...
 ]

And run

python manage.py migrate
  1. Set which types of contact points are allowed for auth in your Settings.py. The available options are EMAIL and MOBILE.
PASSWORDLESS_AUTH = {
   ..
   'PASSWORDLESS_AUTH_TYPES': ['EMAIL', 'MOBILE'],
   ..
}

By default drfpasswordless looks for fields named email or mobile on the User model. If an alias provided doesn’t belong to any given user, a new user is created.

3a. If you’re using email, see the Configuring Email section below.

3b. If you’re using mobile, see the Configuring Mobile section below.

  1. Add drfpasswordless.urls to your urls.py
 from django.urls import path, include

 urlpatterns = [
     ..
     path('', include('drfpasswordless.urls')),
     ..
 ]
  1. You can now POST to either of the endpoints:
curl -X POST -d "[email protected]" localhost:8000/auth/email/

// OR

curl -X POST -d "mobile=+15552143912" localhost:8000/auth/mobile/

A 6 digit callback token will be sent to the contact point.

  1. The client has 15 minutes to use the 6 digit callback token correctly. If successful, they get an authorization token in exchange which the client can then use with Django Rest Framework’s TokenAuthentication scheme.
curl -X POST -d "[email protected]&token=815381" localhost:8000/auth/token/

> HTTP/1.0 200 OK
> {"token":"76be2d9ecfaf5fa4226d722bzdd8a4fff207ed0e”}

Configuring Emails

Specify the email address you’d like to send the callback token from with the PASSWORDLESS_EMAIL_NOREPLY_ADDRESS setting.

PASSWORDLESS_AUTH = {
   ..
   'PASSWORDLESS_AUTH_TYPES': ['EMAIL',],
   'PASSWORDLESS_EMAIL_NOREPLY_ADDRESS': 'noreply@example.com',
   ..
}

You’ll also need to set up an SMTP server to send emails but for development you can set up a dummy development smtp server to test emails. Sent emails will print to the console. Read more here. <https://docs.djangoproject.com/en/3.0/topics/email/#console-backend>__

# Settings.py
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'

Configuring Mobile

You’ll need to have the python twilio module installed

pipenv install twilio

and set the TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN environment variables. These are read from os.environ, so make sure you don't put them in your settings file accidentally.

You’ll also need to specify the number you send the token from with the PASSWORDLESS_MOBILE_NOREPLY_NUMBER setting.

Templates

If you’d like to use a custom email template for your email callback token, specify your template name with this setting:

PASSWORDLESS_AUTH = {
   ...
  'PASSWORDLESS_EMAIL_TOKEN_HTML_TEMPLATE_NAME': "mytemplate.html"
}

The template renders a single variable {{ callback_token }} which is the 6 digit callback token being sent.

Contact Point Validation

Endpoints can automatically mark themselves as validated when a user logs in with a token sent to a specific endpoint. They can also automatically mark themselves as invalid when a user changes a contact point.

This is off by default but can be turned on with PASSWORDLESS_USER_MARK_EMAIL_VERIFIED or PASSWORDLESS_USER_MARK_MOBILE_VERIFIED. By default when these are enabled they look for the User model fields email_verified or mobile_verified.

You can also use auth/verify/email/ or /auth/verify/mobile/ which will automatically send a token to the endpoint attached to the current request.user's email or mobile if available.

You can then send that token to /auth/verify/ which will double-check that the endpoint belongs to the request.user and mark the alias as verified.

Registration

All unrecognized emails and mobile numbers create new accounts by default. New accounts are automatically set with set_unusable_password() but it’s recommended that admins have some type of password.

This can be turned off with the PASSWORDLESS_REGISTER_NEW_USERS setting.

Other Settings

Here’s a full list of the configurable defaults.

DEFAULTS = {

    # Allowed auth types, can be EMAIL, MOBILE, or both.
    'PASSWORDLESS_AUTH_TYPES': ['EMAIL'],

    # URL Prefix for Authentication Endpoints
    'PASSWORDLESS_AUTH_PREFIX': 'auth/',
    
    #  URL Prefix for Verification Endpoints
    'PASSWORDLESS_VERIFY_PREFIX': 'auth/verify/',

    # Amount of time that tokens last, in seconds
    'PASSWORDLESS_TOKEN_EXPIRE_TIME': 15 * 60,

    # The user's email field name
    'PASSWORDLESS_USER_EMAIL_FIELD_NAME': 'email',

    # The user's mobile field name
    'PASSWORDLESS_USER_MOBILE_FIELD_NAME': 'mobile',

    # Marks itself as verified the first time a user completes auth via token.
    # Automatically unmarks itself if email is changed.
    'PASSWORDLESS_USER_MARK_EMAIL_VERIFIED': False,
    'PASSWORDLESS_USER_EMAIL_VERIFIED_FIELD_NAME': 'email_verified',

    # Marks itself as verified the first time a user completes auth via token.
    # Automatically unmarks itself if mobile number is changed.
    'PASSWORDLESS_USER_MARK_MOBILE_VERIFIED': False,
    'PASSWORDLESS_USER_MOBILE_VERIFIED_FIELD_NAME': 'mobile_verified',

    # The email the callback token is sent from
    'PASSWORDLESS_EMAIL_NOREPLY_ADDRESS': None,

    # The email subject
    'PASSWORDLESS_EMAIL_SUBJECT': "Your Login Token",

    # A plaintext email message overridden by the html message. Takes one string.
    'PASSWORDLESS_EMAIL_PLAINTEXT_MESSAGE': "Enter this token to sign in: %s",

    # The email template name.
    'PASSWORDLESS_EMAIL_TOKEN_HTML_TEMPLATE_NAME': "passwordless_default_token_email.html",

    # Your twilio number that sends the callback tokens.
    'PASSWORDLESS_MOBILE_NOREPLY_NUMBER': None,

    # The message sent to mobile users logging in. Takes one string.
    'PASSWORDLESS_MOBILE_MESSAGE': "Use this code to log in: %s",

    # Registers previously unseen aliases as new users.
    'PASSWORDLESS_REGISTER_NEW_USERS': True,

    # Suppresses actual SMS for testing
    'PASSWORDLESS_TEST_SUPPRESSION': False,

    # Context Processors for Email Template
    'PASSWORDLESS_CONTEXT_PROCESSORS': [],

    # The verification email subject
    'PASSWORDLESS_EMAIL_VERIFICATION_SUBJECT': "Your Verification Token",

    # A plaintext verification email message overridden by the html message. Takes one string.
    'PASSWORDLESS_EMAIL_VERIFICATION_PLAINTEXT_MESSAGE': "Enter this verification code: %s",

    # The verification email template name.
    'PASSWORDLESS_EMAIL_VERIFICATION_TOKEN_HTML_TEMPLATE_NAME': "passwordless_default_verification_token_email.html",

    # The message sent to mobile users logging in. Takes one string.
    'PASSWORDLESS_MOBILE_VERIFICATION_MESSAGE': "Enter this verification code: %s",

    # 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',
    
    # 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',

    # A dictionary of demo user's primary key mapped to their static pin
    'PASSWORDLESS_DEMO_USERS': {},

    # configurable function for sending email
    'PASSWORDLESS_EMAIL_CALLBACK': 'drfpasswordless.utils.send_email_with_callback_token',
    
    # configurable function for sending sms
    'PASSWORDLESS_SMS_CALLBACK': 'drfpasswordless.utils.send_sms_with_callback_token',

    # Token Generation Retry Count
    'PASSWORDLESS_TOKEN_GENERATION_ATTEMPTS': 3


}

To Do

  • github.io project page
  • Add MkDocs - http://www.mkdocs.org/
  • Support non-US mobile numbers
  • Custom URLs
  • Change bad settings to 500's

Pull requests are encouraged!

Donations & Support

If you found drfpasswordless useful, consider giving me a follow @localghost on Twitter and @hi.aaron on Instagram.

If you'd like to go a step further and are using drfpasswordless in your startup or business, consider a donation:

  • BTC: 3FzSFeKVABL5Adh9Egoxh77gHbtg2kcTPk
  • ETH: 0x13412a79F06A83B107A8833dB209BECbcb700f24
  • Square Cash: $aaron

License

The MIT License (MIT)

Copyright (c) 2020 Aaron Ng

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

django-rest-framework-passwordless's People

Contributors

aaronn avatar aleffert avatar budlight avatar bwesen avatar charleshan avatar dependabot[bot] avatar ebram96 avatar fritzvd avatar helsont avatar jws avatar kevin-brown avatar null-none avatar realorangeone avatar sslash avatar supernascher avatar tghw avatar thetarby avatar ysinjab avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

django-rest-framework-passwordless's Issues

Allow authorization by clicking on a link

Instead of having the user manually write the token, is it possible to send a one time token accessible via URL? When the user clicks the link, it automatically follows the URL to the website passing the authentication token, and the user is logged in (and registered if that is the case)

Logout feature

Hi. Thank you for this great repository. It helped me a lot.

I think adding a logout API would be useful. I can create a pull request for that if you like this feature.

PASSWORDLESS_USER_MOBILE_FIELD_NAME on CustomUser

If I understand everything correctly, the property "PASSWORDLESS_USER_MOBILE_FIELD_NAME", helps to specify the name of your field

Cannot resolve keyword 'mobile' into field

settings.py:

PASSWORDLESS_AUTH = {
    'PASSWORDLESS_REGISTER_NEW_USERS': False,
    'PASSWORDLESS_USER_MOBILE_FIELD_NAME': 'phone_number',
    'PASSWORDLESS_AUTH_TYPES': ['MOBILE'],
}

models.py:

class CustomUser():
    uuid = models.UUIDField()
    phone_number = models.CharField()

    USERNAME_FIELD = 'phone_number'

Unable to send you a login code. Try again later.

Hello, everyone! I have a problem with sms auth using Twilio. That's my settings:

PASSWORDLESS_AUTH = {
    'PASSWORDLESS_AUTH_TYPES': ['MOBILE'],
    'PASSWORDLESS_MOBILE_NOREPLY_NUMBER': 'secret',
    'PASSWORDLESS_USER_MARK_MOBILE_VERIFIED': True,
}

And .env:

TWILIO_ACCOUNT_SID=secret
TWILIO_AUTH_TOKEN=secret

The problem is this error message (requesting /auth/mobile/):

{
    "detail": "Unable to send you a login code. Try again later."
}

Can you help me? I really upset about it. I don't think there is a problem with my Twilio credentials, because I have ho problem with this creds in console.

What about logging in again?

I have registered and the user logs out from client app, now how to login with same emailid and create new token? Is there a way to do so?

Support other authentication backends

I'd like to use https://github.com/James1345/django-rest-knox instead of the built in TokenAuthentication backend. I'm fine using the existing system for the initial 6-digit codes, this change would only be for the final token generated after a successful callback.

I have an idea about what this PR would look like and I think it's pretty small, but wanted to talk it out before spending time on implementation.

In particular, it looks like https://github.com/aaronn/django-rest-framework-passwordless/blob/master/drfpasswordless/views.py#L131 is the only place a 6-digit code gets turned into a token, so I'd like to abstract that out.

This would entail:

  1. Adding a new setting PASSWORDLESS_AUTH_TOKEN_CREATOR which is a string that will get mapped to a string by the settings infrastructure. This function would take a user and return (string, bool) matching the existing call. This would default to a function containing the current code.

  2. Update the call linked above to invoke whatever function is bound to PASSWORDLESS_AUTH_TOKEN_CREATOR .

Does this sound good?

Thanks for a great project!

invalidate_previous_tokens should be post_save to avoid infinite recursion

In https://github.com/aaronn/django-rest-framework-passwordless/blob/master/drfpasswordless/signals.py#L13 your doc string says post_save, which I believe is a correct implementation, but the actual code uses pre_save.

With pre_save I am seeing infinite loops whenever the user has more than 1 active tokens already. The pre_save calls save, which in turn invokes pre_save, but active_tokens = CallbackToken.objects.active().filter(user=instance.user).exclude(id=instance.id) grabs the same tokens from the database (as we are pre-save so the is_active=False hasn't yet been comitted). This once again invokes another save->pre_save->save until maximum recursion limit is hit....

cannot create more than one account

I got an unusual issue that I've stumbled upon.
After integrating the module in my Django app when I make the first API request it gets processed, the user gets created and everything works flawlessly, but when attempting to create another account it crashes.

IntegrityError at /passwordless/auth/email/
UNIQUE constraint failed: auth_user.username

Request Method: POST
Request URL: http://127.0.0.1:8000/passwordless/auth/email/
Django Version: 2.2.10
Python Executable: /home/terkea/django_react_template/src/venv/bin/python
Python Version: 3.6.9
Python Path: ['/home/terkea/django_react_template/src', '/usr/lib/python36.zip', '/usr/lib/python3.6', '/usr/lib/python3.6/lib-dynload', '/home/terkea/django_react_template/src/venv/lib/python3.6/site-packages']
Server time: Tue, 14 Apr 2020 02:04:41 +0000
Installed Applications:
['django.contrib.admin',
 'django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.messages',
 'django.contrib.staticfiles',
 'django.contrib.sites',
 'rest_framework',
 'corsheaders',
 'rest_auth',
 'rest_framework.authtoken',
 'drfpasswordless',
 'rest_auth.registration',
 'allauth',
 'allauth.account',
 'allauth.socialaccount',
 'allauth.socialaccount.providers.facebook',
 'api']
Installed Middleware:
['corsheaders.middleware.CorsMiddleware',
 'django.middleware.security.SecurityMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.common.CommonMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware',
 'django.middleware.clickjacking.XFrameOptionsMiddleware']

As it says in the django.allauth docs I've disabled the username and made the email mandatory but for some reason, it doesn't wanna pick up on those changes.

ACCOUNT_USER_MODEL_USERNAME_FIELD = None
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_USERNAME_REQUIRED = False
ACCOUNT_AUTHENTICATION_METHOD = 'email'

DataError on to_alias field name (254 > 40)

drfpasswordless can't be used with contrib.auth.User model, because AbstractBaseCallbackToken.to_alias field, which in theory should contain user email, is just 40 symbols length, while default value for user email max_length = 254.

  File "/root/code/EquiTrack/email_auth/utils.py", line 24, in get_token_auth_link
    token = create_callback_token_for_user(user, 'email')
  File "/root/code/env1/lib/python2.7/site-packages/drfpasswordless/utils.py", line 45, in create_callback_token_for_user
    to_alias=getattr(user, api_settings.PASSWORDLESS_USER_EMAIL_FIELD_NAME))
  File "/root/code/env1/lib/python2.7/site-packages/django/db/models/manager.py", line 122, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/root/code/env1/lib/python2.7/site-packages/django/db/models/query.py", line 401, in create
    obj.save(force_insert=True, using=self.db)
  File "/root/code/env1/lib/python2.7/site-packages/django/db/models/base.py", line 708, in save
    force_update=force_update, update_fields=update_fields)
  File "/root/code/env1/lib/python2.7/site-packages/django/db/models/base.py", line 736, in save_base
    updated = self._save_table(raw, cls, force_insert, force_update, using, update_fields)
  File "/root/code/env1/lib/python2.7/site-packages/django/db/models/base.py", line 820, in _save_table
    result = self._do_insert(cls._base_manager, using, fields, update_pk, raw)
  File "/root/code/env1/lib/python2.7/site-packages/django/db/models/base.py", line 859, in _do_insert
    using=using, raw=raw)
  File "/root/code/env1/lib/python2.7/site-packages/django/db/models/manager.py", line 122, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/root/code/env1/lib/python2.7/site-packages/django/db/models/query.py", line 1039, in _insert
    return query.get_compiler(using=using).execute_sql(return_id)
  File "/root/code/env1/lib/python2.7/site-packages/django/db/models/sql/compiler.py", line 1060, in execute_sql
    cursor.execute(sql, params)
  File "/root/code/env1/lib/python2.7/site-packages/django/db/backends/utils.py", line 64, in execute
    return self.cursor.execute(sql, params)
  File "/root/code/env1/lib/python2.7/site-packages/django/db/utils.py", line 95, in __exit__
    six.reraise(dj_exc_type, dj_exc_value, traceback)
  File "/root/code/env1/lib/python2.7/site-packages/django/db/backends/utils.py", line 64, in execute
    return self.cursor.execute(sql, params)
DataError: value too long for type character varying(40)

i guess, field should be expanded, or at least cropped before saving (if custom user model will be used with even longer values).

Cannot resolve keyword 'mobile' into field

Hi,

when i try to test the mobile authentication i get this error:

FieldError at /auth/mobile/ Cannot resolve keyword 'mobile' into field. Choices are: auth_token, callbacktoken, date_joined, email, first_name, groups, id, is_active, is_staff, is_superuser, last_login, last_name, logentry, password, user_permissions, username Request Method: POST Request URL: http://localhost:8000/auth/mobile/

And when i try to post without the key mobile i get a error says: mobile is a required field!

How can i solve thi problem?

Thank you.

Override `EmailAuthSerializer` to add custom fields.

Is it possible to override this class to allow adding other custom fields?
e.g. In my case is an administrator who register the user email. I already modified the send_email_with_callback_token function to include the token into a link but I also want to be able to add other custom codes to the link so that the user will be redirected appropriately based on the administrator wishes.

How do I override this class to add some more serialisers fields? Thanks.

class EmailAuthSerializer(AbstractBaseAliasAuthenticationSerializer):
    @property
    def alias_type(self):
        return 'email'

    email = serializers.EmailField()

Custom email template

I have added a custom email template for token email.
inside template folder and I have mentioned it in the settings but it is not sending the email with that template

Cannot generate a token for the new user.

I get the otp in my email but when i try to verify instead of getting the token I get this error {"non_field_errors":["Invalid user alias parameters provided."]}. But a user has been created as I can see in the admin but there is no token generated

Multiple User Types and Custom Fields

This is a great Library that solves one of my problems when it come to multiple user types. I am interested in knowing if I can override the serializers for user creation and have custom views and still have all other features. For I have a multiple user types system which uses proxy models to create users based on their types and would like to merge with this beautiful work you put forward. Thanks in adavnce

ObtainAuthTokenFromCallbackToken lacks AllowAny permissions

I'm not sure how this module could work at all without AllowAny on the callback token post. We had to override this class and add

permission_classes = (AllowAny,)

for it to work, as otherwise you can't post the 6-digit token as you're not logged in yet :)

The AllowAny is on the user registration endpoints (/account/auth/email etc.) but not on this...

Default Django user model doesn't work

I'm using like user model the default user model provided by Django (django.contrib.auth.models.User), but If I try to use more than one email I get this error UNIQUE constraint failed: auth_user.username.

Is it mandatory to create a custom user model?

Custom token length?

I think a token length of 6 is not secure. It would be nice to have the ability to change the length of the token (and maybe completely change the token? I think alphanumeric would be better).

If my calculations are correct, a hacker has a pretty good chance of getting the right token.

You can calculate the entropy using the formula:
log(uniqueCharactersAmount^Length) / log(2)

The entropy of the password is:
log(10^6) / log(2) = 19.9315
So about 20 bits.

With the formula:
2^(entropy - 1) / requestsPerSecond

you can calculate the duration it takes to crack the token.
According to this site, django can handle about 15,000 requests per second, but a production server has middlewares, logging, etc. Let's just work with 500 request per second.

2^(20 - 1) / 500 = 1,048.576 ~ 17.5 Minutes

15 / 17.5 is about 0.85 - so a hacker has a 85% chance of finding the right token.

If we'd use alphanumeric (62 characters), it would take about 397 days to bruteforce all of them!

But anyway, just my two cents and I probably screwed the maths up :D

Twilio bug

hey, thanks for package @aaronn

bug in send_sms_with_callback_token()

 twilio_client.messages.create(
                body=base_string % mobile_token.key,
                to=to_number,
                from_=api_settings.PASSWORDLESS_MOBILE_NOREPLY_NUMBER
            )

to_number should have "+"
otherwise twilio client doesn't work
and it was hard to catch the error, since passwordless doesn't pass Twilio's error

cheers!

Major security concern

I know that the 6-digit login code length has been brought up before but the real problem is that the api checks the login code at a global level. This means that the larger the user base, the easier it becomes to get access to random accounts.

We should check the login code combined with the email.

Proposed change (pseudocode):

# serializers.py

callback_token = attrs.get('token', None)
token = CallbackToken.objects.get(key=callback_token, is_active=True)

to:

callback_token = attrs.get('token', None)
email = attrs.get('email', None)

user = User.objects.get(email=email)
token = CallbackToken.objects.get(key=callback_token, user=user, is_active=True)

Unique constraint violation on CallbackToken

Trace back:

[ERROR   ] Internal Server Error: /auth/mobile/
Traceback (most recent call last):
  File "/home/ubuntu/.virtualenvs/project/lib/python3.6/site-packages/django/db/backends/utils.py", line 86, in _execute
    return self.cursor.execute(sql, params)
  File "/home/ubuntu/.virtualenvs/project/lib/python3.6/site-packages/newrelic/hooks/database_psycopg2.py", line 65, in execute
    **kwargs)
  File "/home/ubuntu/.virtualenvs/project/lib/python3.6/site-packages/newrelic/hooks/database_dbapi2.py", line 39, in execute
    *args, **kwargs)
psycopg2.errors.UniqueViolation: duplicate key value violates unique constraint "drfpasswordless_callbacktoken_is_active_key_type_78906b19_uniq"
DETAIL:  Key (is_active, key, type)=(f, 585258, AUTH) already exists.

There is about 14,000 Callback token in the system.
Using drfpasswordless==1.5.6

Here is my understanding of the problem:
In this scenario, there is two callback token with key '585258'. For one active is true, for others it is false.
When passwordless try to create another token for the user with active true, drfpasswordless.signals.invalidate_previous_tokens runs and creates this error.

@receiver(signals.post_save, sender=CallbackToken)
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 instance.user.pk in api_settings.PASSWORDLESS_DEMO_USERS.keys():
        return

    if isinstance(instance, CallbackToken):
        CallbackToken.objects.active().filter(user=instance.user, type=instance.type).exclude(id=instance.id).update(is_active=False)

No way to handle failed sending of callback tokens

When /auth/mobile/ is invoked, there are two types of failure cases:

  1. SMS sending fails because the user has entered an invalid or non-supported phone number (e.g. country codes that are not enabled by Twilio)
  2. SMS sending fails because Twilio is not configured correctly (e.g. the account has become inactive due to insufficient funds)

In the second case it is crucial that the server admins are notified of this error, whereas in the first case not so much. Right now the only way to achieve this is to try to pattern-match the debug-level log errors sent out by lines such as this, which is very brittle.

Proposed solution(s):
Make the error-catching in send_sms_with_callback_token more robust. Then when an exception is encountered that indicates a server-side fault do one of the following:
a) Throw a 500 error in all cases where it is the fault of the server that the SMS is not being sent.
b) Allow the user to set the logging level of server-side faults in settings.py
c) Optionally invoke a callback of some kind specified in settings.py

I'd be happy with any of these solutions. I think a) is the most elegant, but is not necessarily backwards-compatible, and may tie this package to specific versions of the Twilio API. b) has the drawback that the actual exception itself is not passed to the application level, and that further recovery action would have to be triggered through the logging system. Because a) and b) both have drawbacks I was thinking of submitting a PR with c), but wanted to check here first.

Please let me know the way of solving this that would be best, and I will submit a PR, although I do not use the email functionality and may need guidance on which email-related exceptions to look out for.

different token auth

could someone provide some actual examples where they using different token backends such as jwt or knox in the docs, please?

Add email as a template context variable

I'm using this with a SPA in a browser context and need to build a url that contains all the information that will be needed to make the token obtain request (e.g: https://someapp.com/login?token=123456&[email protected])

Essentially, I want the user to be able to click on the link from the email instead of having to type the code manually.

Use twilio verify instead of twilio messages?

Thanks for creating this amazing library!

I just noticed that when I was trying to use this library. I was getting an error which said that I cannot send messages to unverified numbers using a trial account. I read the twilio documentation and figured there was a Verify service present.

My question is why is messages service implemented here? What if the user has registered for the first time and hasn't verified his number?

Invalid field name(s) for model User: 'mobile'.

Hello, i am following the docs and install these (pip freeze output):

certifi==2018.8.24
chardet==3.0.4
Django==2.1.1
djangorestframework==3.8.2
drfpasswordless==1.2.0
idna==2.7
oauthlib==2.1.0
pkg-resources==0.0.0
PyJWT==1.6.4
PySocks==1.6.8
pytz==2018.5
requests==2.19.1
six==1.11.0
twilio==6.16.4
urllib3==1.23

and when i am doing curl i am getting error.

curl -X POST -d "mobile=+918448876298" localhost:9001/auth/mobile/

FieldError at /auth/mobile/
Invalid field name(s) for model User: 'mobile'.

Request Method: POST
Request URL: http://localhost:9001/auth/mobile/
Django Version: 2.1.1
Python Executable: /home/bandar/restrobook/bin/python3.6
Python Version: 3.6.6

Please help me for resolving this issue. Thanks for your precious time.

No support for i18n

The messages that are sent should be in the user's language, which can be derived from for instance the HTTP request headers.

There is no demo credentials to test the login

I previously integrated ios app with django passwordless. On submiting the app for review they are asking a demo account from which they can login without having to enter the six digit code. is there any way to create demo credentials for mobile login with phone number and a fixed 6 digit code?

Why save instead of delete tokens?

Hi there, i'm using this awesome package for my project and i was investigate through the code and i saw that every expired tokens (6-digits) is saved in db. But for what reason?
I mean, can't we just delete the row of the token after it expire (15 min default)?

Feature Request: Resend code feature?

I think this is important feature.

Consider a scenario where the user wants to verify his number but he didn't receive code over SMS. Shouldn't there be a resend code option for the user.

Would be more than happy to contribute :)

Settings Option for more Secure Login URL over 6 Digit Token

I think this project would benefit from the ability to send in the 6 digit token OR to send in a URL to the email or phone number provided to provide login access.

The URL would be generated with to send the user to a correct URL hosted on a production domain's frontend application. The URL would be generated by salting + hashing the six digit token. This hashed value (primitive example below) could then be taken in by the login endpoint. The login view would unhash the token and if correct, return the correct token to be added to the user's cookies.

The unhashing could also be completed in the front end application and no endpoint would need to be changed, however I think this is less than ideal.

# This is incomplete. It would be required to also add a salt (setting's password?)
import hashlib
hashlib.sha256(B"123456").hexdigest()
'8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92'

PASSWORDLESS_USER_MOBILE_FIELD_NAME doesn't work for anything other than 'mobile'

If you override PASSWORDLESS_USER_MOBILE_FIELD_NAME from the mobile default, the API serializer is not aware and will fail on validate.

user = User.objects.get(**{self.alias_type: alias})

The serializer should use PASSWORDLESS_USER_MOBILE_FIELD_NAME for:

  • querying against User model
  • request payload
  • (possibly) even the registered urls

'dict' object has no attribute '__dict__'

Hey,
I am getting this error message 'dict' object has no attribute '__dict__' when generating token from simple-jwt package manually.

Here is the code for the function.

def create_authentication_token(user): """ Creates simple-jswt tokens""" refresh_token = RefreshToken.for_user(user) token = {'refresh': refresh_token, 'access': refresh_token.access_token} return (token, True)
This is happening when it hit this line:
token_serializer = TokenSerializer(data=token.__dict__, partial=True)

Any idea what I am doing wrong? Thanks

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.