Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
ee6c8f2
Configure token length, max length 6
nishantonline1 Mar 18, 2021
908005a
Merge branch 'aaronn:master' into master
naveenpanwar Aug 16, 2022
34ac637
fix: Fixing codestyle changes
naveenpanwar Aug 17, 2022
3eba9ae
Added Auth token support
anish5256 Sep 30, 2024
0e2a9f5
Updated version
anish5256 Sep 30, 2024
66bc2eb
Merge pull request #1 from anish5256/master
nishantonline1 Sep 30, 2024
5c4d583
model changes and migration
anish5256 Oct 4, 2024
fb5f77e
Token create change
anish5256 Oct 4, 2024
415ceb7
fix
anish5256 Oct 4, 2024
7c85d31
Merge pull request #2 from anish5256/master
anish5256 Oct 4, 2024
9f084f9
Made device_id unique
anish5256 Oct 4, 2024
b7c1a71
Merge pull request #3 from anish5256/master
anish5256 Oct 4, 2024
a975532
Import Q from models
anish5256 Oct 5, 2024
d527820
Merge pull request #4 from anish5256/master
anish5256 Oct 5, 2024
c5d753d
Remove Unique Constrain
anish5256 Oct 17, 2024
98cb82b
Added unique constrain user and device_id
anish5256 Oct 17, 2024
e3934a8
Merge pull request #5 from anish5256/master
anish5256 Oct 20, 2024
a666829
Update version to 1.6.0
anish5256 Oct 20, 2024
5c5ca32
Merge pull request #6 from anish5256/master
anish5256 Oct 20, 2024
d6e08d1
added user, device_id and device_type to readonly fields.
wick-prat Dec 17, 2024
ea087bb
changed version from 1.6.0 to 1.6.1
wick-prat Dec 17, 2024
d25561f
Merge pull request #8 from wick-prat/token_filter
nishantonline1 Dec 17, 2024
3487c62
Allow deive type to be different
anish5256 Jan 23, 2025
da39087
Merge pull request #9 from anish5256/master
anish5256 Jan 23, 2025
0e5de94
Allow deive type to be different
anish5256 Jan 23, 2025
a575130
Merge branch 'master' into master
anish5256 Jan 23, 2025
7ee9d99
Merge pull request #10 from anish5256/master
anish5256 Jan 23, 2025
0e174bd
changes
anish5256 Mar 19, 2025
3fd778a
version upgrade
anish5256 Mar 21, 2025
c692cca
Merge pull request #11 from anish5256/otp_validation_try
anish5256 Mar 21, 2025
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
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -321,8 +321,10 @@ DEFAULTS = {
'PASSWORDLESS_SMS_CALLBACK': 'drfpasswordless.utils.send_sms_with_callback_token',

# Token Generation Retry Count
'PASSWORDLESS_TOKEN_GENERATION_ATTEMPTS': 3
'PASSWORDLESS_TOKEN_GENERATION_ATTEMPTS': 3,

# The length of the token to send in email or sms, maximum 6
'PASSWORDLESS_TOKEN_LENGTH': 6

}
```
Expand Down
2 changes: 1 addition & 1 deletion drfpasswordless/__version__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
VERSION = (1, 5, 8)
VERSION = (1, 6, 4)

__version__ = '.'.join(map(str, VERSION))
6 changes: 6 additions & 0 deletions drfpasswordless/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from rest_framework.authentication import TokenAuthentication
from drfpasswordless.authtoken.models import Token


class TokenAuthentication(TokenAuthentication):
model = Token
4 changes: 4 additions & 0 deletions drfpasswordless/authtoken/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import django

if django.VERSION < (3, 2):
default_app_config = 'drfpasswordless.authtoken.apps.AuthTokenConfig'
53 changes: 53 additions & 0 deletions drfpasswordless/authtoken/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from django.contrib import admin
from django.contrib.admin.utils import quote
from django.contrib.admin.views.main import ChangeList
from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError
from django.urls import reverse

from drfpasswordless.authtoken.models import Token, TokenProxy

User = get_user_model()


class TokenChangeList(ChangeList):
"""Map to matching User id"""
def url_for_result(self, result):
pk = result.user.pk
return reverse('admin:%s_%s_change' % (self.opts.app_label,
self.opts.model_name),
args=(quote(pk),),
current_app=self.model_admin.admin_site.name)


class TokenAdmin(admin.ModelAdmin):
list_display = ('key', 'user', 'device_type', 'created')
fields = ('user', 'device_id', 'device_type')
ordering = ('-created',)
actions = None # Actions not compatible with mapped IDs.
readonly_fields = ("user", "device_id", "device_type")
list_filter = ("device_type",)

def get_changelist(self, request, **kwargs):
return TokenChangeList

def get_object(self, request, object_id, from_field=None):
"""
Map from User ID to matching Token.
"""
queryset = self.get_queryset(request)
field = User._meta.pk
try:
object_id = field.to_python(object_id)
user = User.objects.filter(**{field.name: object_id}).first()
return queryset.filter(user=user).first()
except (queryset.model.DoesNotExist, User.DoesNotExist, ValidationError, ValueError):
return None

def delete_model(self, request, obj):
# Map back to actual Token, since delete() uses pk.
token = Token.objects.get(key=obj.key)
return super().delete_model(request, token)


admin.site.register(TokenProxy, TokenAdmin)
7 changes: 7 additions & 0 deletions drfpasswordless/authtoken/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _


class AuthTokenConfig(AppConfig):
name = 'drfpasswordless.authtoken'
verbose_name = _("Auth Token")
Empty file.
Empty file.
45 changes: 45 additions & 0 deletions drfpasswordless/authtoken/management/commands/drf_create_token.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from django.contrib.auth import get_user_model
from django.core.management.base import BaseCommand, CommandError

from drfpasswordless.authtoken.models import Token

UserModel = get_user_model()


class Command(BaseCommand):
help = 'Create DRF Token for a given user'

def create_user_token(self, username, reset_token):
user = UserModel._default_manager.get_by_natural_key(username)

if reset_token:
Token.objects.filter(user=user).delete()

token = Token.objects.get_or_create(user=user)
return token[0]

def add_arguments(self, parser):
parser.add_argument('username', type=str)

parser.add_argument(
'-r',
'--reset',
action='store_true',
dest='reset_token',
default=False,
help='Reset existing User token and create a new one',
)

def handle(self, *args, **options):
username = options['username']
reset_token = options['reset_token']

try:
token = self.create_user_token(username, reset_token)
except UserModel.DoesNotExist:
raise CommandError(
'Cannot create the Token: user {} does not exist'.format(
username)
)
self.stdout.write(
'Generated token {} for user {}'.format(token.key, username))
23 changes: 23 additions & 0 deletions drfpasswordless/authtoken/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from django.conf import settings
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.CreateModel(
name='Token',
fields=[
('key', models.CharField(primary_key=True, serialize=False, max_length=40)),
('created', models.DateTimeField(auto_now_add=True)),
('user', models.OneToOneField(to=settings.AUTH_USER_MODEL, related_name='auth_token', on_delete=models.CASCADE)),
],
options={
},
bases=(models.Model,),
),
]
31 changes: 31 additions & 0 deletions drfpasswordless/authtoken/migrations/0002_auto_20160226_1747.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from django.conf import settings
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('authtoken', '0001_initial'),
]

operations = [
migrations.AlterModelOptions(
name='token',
options={'verbose_name_plural': 'Tokens', 'verbose_name': 'Token'},
),
migrations.AlterField(
model_name='token',
name='created',
field=models.DateTimeField(verbose_name='Created', auto_now_add=True),
),
migrations.AlterField(
model_name='token',
name='key',
field=models.CharField(verbose_name='Key', max_length=40, primary_key=True, serialize=False),
),
migrations.AlterField(
model_name='token',
name='user',
field=models.OneToOneField(to=settings.AUTH_USER_MODEL, verbose_name='User', related_name='auth_token', on_delete=models.CASCADE),
),
]
25 changes: 25 additions & 0 deletions drfpasswordless/authtoken/migrations/0003_tokenproxy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Generated by Django 3.1.1 on 2020-09-28 09:34

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('authtoken', '0002_auto_20160226_1747'),
]

operations = [
migrations.CreateModel(
name='TokenProxy',
fields=[
],
options={
'verbose_name': 'token',
'proxy': True,
'indexes': [],
'constraints': [],
},
bases=('authtoken.token',),
),
]
35 changes: 35 additions & 0 deletions drfpasswordless/authtoken/migrations/0004_auto_20240927_1224.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Generated by Django 3.2.18 on 2024-10-04 08:26

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),
('authtoken', '0003_tokenproxy'),
]

operations = [
migrations.AddField(
model_name='token',
name='device_id',
field=models.CharField(blank=True, max_length=64, null=True),
),
migrations.AddField(
model_name='token',
name='device_type',
field=models.CharField(blank=True, choices=[('WEB', 'WEB'), ('SPECTRO_TV', 'SPECTRO_TV'), ('LOG_SHEET', 'LOG_SHEET'), ('MELTING_REPORT', 'MELTING_REPORT'), ('IOT', 'IOT')], max_length=32, null=True),
),
migrations.AlterField(
model_name='token',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='auth_token', to=settings.AUTH_USER_MODEL, verbose_name='User'),
),
migrations.AddConstraint(
model_name='token',
constraint=models.UniqueConstraint(condition=models.Q(('device_id__isnull', False), models.Q(('device_id', ''), _negated=True)), fields=('device_id',), name='unique_device_id_not_null_blank'),
),
]
24 changes: 24 additions & 0 deletions drfpasswordless/authtoken/migrations/0005_auto_20241017_1246.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Generated by Django 3.2.18 on 2024-10-17 07:16

from django.conf import settings
from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('authtoken', '0004_auto_20240927_1224'),
]

operations = [
migrations.RemoveConstraint(
model_name='token',
name='unique_device_id_not_null_blank',
),
migrations.AlterUniqueTogether(
name='token',
unique_together={('user', 'device_id')},
),
]

Empty file.
71 changes: 71 additions & 0 deletions drfpasswordless/authtoken/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import binascii
import os
from django.db.models import Q
from django.conf import settings
from django.db import models
from django.utils.translation import gettext_lazy as _


class Token(models.Model):
"""
The default authorization token model.
"""
WEB = "WEB"
SPECTRO_TV = "SPECTRO_TV"
LOG_SHEET = "LOG_SHEET"
MELTING_REPORT = "MELTING_REPORT"
IOT = "IOT"
DEVICE_TYPES = (
(WEB, "WEB"),
(SPECTRO_TV, "SPECTRO_TV"),
(LOG_SHEET, "LOG_SHEET"),
(MELTING_REPORT, "MELTING_REPORT"),
(IOT, "IOT"),
)
key = models.CharField(_("Key"), max_length=40, primary_key=True)
device_id = models.CharField(max_length=64, blank=True, null=True)
device_type = models.CharField(
choices=DEVICE_TYPES, max_length=32, blank=True, null=True
)
user = models.ForeignKey(
settings.AUTH_USER_MODEL, related_name='auth_token',
on_delete=models.CASCADE, verbose_name=_("User")
)
created = models.DateTimeField(_("Created"), auto_now_add=True)

class Meta:
# Work around for a bug in Django:
# https://code.djangoproject.com/ticket/19422
#
# Also see corresponding ticket:
# https://github.com/encode/django-rest-framework/issues/705
abstract = 'drfpasswordless.authtoken' not in settings.INSTALLED_APPS
verbose_name = _("Token")
verbose_name_plural = _("Tokens")
unique_together = (('user', 'device_id'),)

def save(self, *args, **kwargs):
if not self.key:
self.key = self.generate_key()
return super().save(*args, **kwargs)

@classmethod
def generate_key(cls):
return binascii.hexlify(os.urandom(20)).decode()

def __str__(self):
return self.key


class TokenProxy(Token):
"""
Proxy mapping pk to user pk for use in admin.
"""
@property
def pk(self):
return self.user_id

class Meta:
proxy = 'drfpasswordless.authtoken' in settings.INSTALLED_APPS
abstract = 'drfpasswordless.authtoken' not in settings.INSTALLED_APPS
verbose_name = "token"
42 changes: 42 additions & 0 deletions drfpasswordless/authtoken/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from django.contrib.auth import authenticate
from django.utils.translation import gettext_lazy as _

from rest_framework import serializers


class AuthTokenSerializer(serializers.Serializer):
username = serializers.CharField(
label=_("Username"),
write_only=True
)
password = serializers.CharField(
label=_("Password"),
style={'input_type': 'password'},
trim_whitespace=False,
write_only=True
)
token = serializers.CharField(
label=_("Token"),
read_only=True
)

def validate(self, attrs):
username = attrs.get('username')
password = attrs.get('password')

if username and password:
user = authenticate(request=self.context.get('request'),
username=username, password=password)

# The authenticate call simply returns None for is_active=False
# users. (Assuming the default ModelBackend authentication
# backend.)
if not user:
msg = _('Unable to log in with provided credentials.')
raise serializers.ValidationError(msg, code='authorization')
else:
msg = _('Must include "username" and "password".')
raise serializers.ValidationError(msg, code='authorization')

attrs['user'] = user
return attrs
Loading