hash otp
This commit is contained in:
parent
0d59a34e06
commit
2fe322241a
6 changed files with 127 additions and 27 deletions
|
|
@ -0,0 +1,99 @@
|
||||||
|
# Generated by Django 5.1.4 on 2025-11-20 09:05
|
||||||
|
|
||||||
|
import django.utils.timezone
|
||||||
|
import uuid
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('gooyal_oauth2', '0004_application_avatar'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='refreshtoken',
|
||||||
|
unique_together=set(),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='application',
|
||||||
|
name='created_at',
|
||||||
|
field=models.DateTimeField(auto_now_add=True, db_index=True, default=django.utils.timezone.now),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='application',
|
||||||
|
name='updated_at',
|
||||||
|
field=models.DateTimeField(auto_now=True, db_index=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='grant',
|
||||||
|
name='created_at',
|
||||||
|
field=models.DateTimeField(auto_now_add=True, db_index=True, default=django.utils.timezone.now),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='grant',
|
||||||
|
name='updated_at',
|
||||||
|
field=models.DateTimeField(auto_now=True, db_index=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='refreshtoken',
|
||||||
|
name='created_at',
|
||||||
|
field=models.DateTimeField(auto_now_add=True, db_index=True, default=django.utils.timezone.now),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='refreshtoken',
|
||||||
|
name='updated_at',
|
||||||
|
field=models.DateTimeField(auto_now=True, db_index=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='resource',
|
||||||
|
name='created_at',
|
||||||
|
field=models.DateTimeField(auto_now_add=True, db_index=True, default=django.utils.timezone.now),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='resource',
|
||||||
|
name='updated_at',
|
||||||
|
field=models.DateTimeField(auto_now=True, db_index=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='scope',
|
||||||
|
name='created_at',
|
||||||
|
field=models.DateTimeField(auto_now_add=True, db_index=True, default=django.utils.timezone.now),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='scope',
|
||||||
|
name='updated_at',
|
||||||
|
field=models.DateTimeField(auto_now=True, db_index=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='application',
|
||||||
|
name='uuid',
|
||||||
|
field=models.UUIDField(db_index=True, default=uuid.uuid4, primary_key=True, serialize=False, unique=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='grant',
|
||||||
|
name='uuid',
|
||||||
|
field=models.UUIDField(db_index=True, default=uuid.uuid4, primary_key=True, serialize=False, unique=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='refreshtoken',
|
||||||
|
name='uuid',
|
||||||
|
field=models.UUIDField(db_index=True, default=uuid.uuid4, primary_key=True, serialize=False, unique=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='resource',
|
||||||
|
name='uuid',
|
||||||
|
field=models.UUIDField(db_index=True, default=uuid.uuid4, primary_key=True, serialize=False, unique=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='scope',
|
||||||
|
name='uuid',
|
||||||
|
field=models.UUIDField(db_index=True, default=uuid.uuid4, primary_key=True, serialize=False, unique=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -16,14 +16,14 @@ from django.conf import settings
|
||||||
from django.db import models, router, transaction
|
from django.db import models, router, transaction
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from utils.models import BaseModel
|
||||||
from .settings import oauth2_settings
|
from .settings import oauth2_settings
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Resource(models.Model):
|
class Resource(BaseModel):
|
||||||
uuid = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4, unique=True, db_index=True)
|
|
||||||
name = models.CharField(max_length=255)
|
name = models.CharField(max_length=255)
|
||||||
|
|
||||||
user = models.ForeignKey(
|
user = models.ForeignKey(
|
||||||
|
|
@ -36,13 +36,12 @@ class Resource(models.Model):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
class Application(AbstractApplication):
|
class Application(AbstractApplication, BaseModel):
|
||||||
"""
|
"""
|
||||||
Application model for use with Django OAuth Toolkit that allows the scopes
|
Application model for use with Django OAuth Toolkit that allows the scopes
|
||||||
available to an application to be restricted on a per-application basis.
|
available to an application to be restricted on a per-application basis.
|
||||||
"""
|
"""
|
||||||
id=None
|
id = None
|
||||||
uuid = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4, unique=True, db_index=True)
|
|
||||||
name = models.CharField(max_length=255, blank=False, unique=True)
|
name = models.CharField(max_length=255, blank=False, unique=True)
|
||||||
user = models.ForeignKey(
|
user = models.ForeignKey(
|
||||||
settings.AUTH_USER_MODEL,
|
settings.AUTH_USER_MODEL,
|
||||||
|
|
@ -74,14 +73,12 @@ class Application(AbstractApplication):
|
||||||
allowed_scopes = self.allowed_scopes
|
allowed_scopes = self.allowed_scopes
|
||||||
return Scope.objects.filter(name__in=allowed_scopes).order_by('name')
|
return Scope.objects.filter(name__in=allowed_scopes).order_by('name')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def allows_grant_type(self, *grant_types):
|
def allows_grant_type(self, *grant_types):
|
||||||
# Assume, for this example, that self.authorization_grant_type is set to self.GRANT_AUTHORIZATION_CODE
|
# Assume, for this example, that self.authorization_grant_type is set to self.GRANT_AUTHORIZATION_CODE
|
||||||
return bool(set([self.authorization_grant_type, self.GRANT_CLIENT_CREDENTIALS]) & set(grant_types))
|
return bool(set([self.authorization_grant_type, self.GRANT_CLIENT_CREDENTIALS]) & set(grant_types))
|
||||||
|
|
||||||
|
|
||||||
class Scope(models.Model):
|
class Scope(BaseModel):
|
||||||
"""
|
"""
|
||||||
Django model for an OAuth scope.
|
Django model for an OAuth scope.
|
||||||
"""
|
"""
|
||||||
|
|
@ -89,8 +86,6 @@ class Scope(models.Model):
|
||||||
# NOTE: This is not used to limit access to the scope in any way - we want the
|
# NOTE: This is not used to limit access to the scope in any way - we want the
|
||||||
# scope to be available to other applications in order to request access
|
# scope to be available to other applications in order to request access
|
||||||
# to the resource it protects!
|
# to the resource it protects!
|
||||||
id = None
|
|
||||||
uuid = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4, unique=True, db_index=True)
|
|
||||||
application = models.ForeignKey(
|
application = models.ForeignKey(
|
||||||
oauth2_settings.APPLICATION_MODEL,
|
oauth2_settings.APPLICATION_MODEL,
|
||||||
models.CASCADE,
|
models.CASCADE,
|
||||||
|
|
@ -177,17 +172,15 @@ class Scope(models.Model):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
class Grant(AbstractGrant):
|
class Grant(AbstractGrant, BaseModel):
|
||||||
id = None
|
id = None
|
||||||
uuid = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4, unique=True, db_index=True)
|
|
||||||
application = models.ForeignKey(
|
application = models.ForeignKey(
|
||||||
oauth2_settings.APPLICATION_MODEL, on_delete=models.CASCADE, related_name='grants'
|
oauth2_settings.APPLICATION_MODEL, on_delete=models.CASCADE, related_name='grants'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class RefreshToken(AbstractRefreshToken):
|
class RefreshToken(BaseModel, AbstractRefreshToken):
|
||||||
id = None
|
id = None
|
||||||
uuid = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4, unique=True, db_index=True)
|
|
||||||
application = models.ForeignKey(
|
application = models.ForeignKey(
|
||||||
oauth2_settings.APPLICATION_MODEL, on_delete=models.CASCADE, related_name='refresh_tokens')
|
oauth2_settings.APPLICATION_MODEL, on_delete=models.CASCADE, related_name='refresh_tokens')
|
||||||
|
|
||||||
|
|
@ -215,7 +208,6 @@ class RefreshToken(AbstractRefreshToken):
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class AccessToken(AbstractAccessToken):
|
class AccessToken(AbstractAccessToken):
|
||||||
id = None
|
id = None
|
||||||
uuid = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4, unique=True, db_index=True)
|
uuid = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4, unique=True, db_index=True)
|
||||||
|
|
@ -240,7 +232,7 @@ def clear_expired():
|
||||||
flat_queryset = queryset.values_list("pk", flat=True)[:CLEAR_EXPIRED_TOKENS_BATCH_SIZE]
|
flat_queryset = queryset.values_list("pk", flat=True)[:CLEAR_EXPIRED_TOKENS_BATCH_SIZE]
|
||||||
batch_length = flat_queryset.count()
|
batch_length = flat_queryset.count()
|
||||||
queryset.model.objects.filter(pk__in=list(flat_queryset)).delete()
|
queryset.model.objects.filter(pk__in=list(flat_queryset)).delete()
|
||||||
logger.debug(f"{batch_length} tokens deleted, {current_no-batch_length} left")
|
logger.debug(f"{batch_length} tokens deleted, {current_no - batch_length} left")
|
||||||
queryset = queryset.model.objects.filter(query)
|
queryset = queryset.model.objects.filter(query)
|
||||||
time.sleep(CLEAR_EXPIRED_TOKENS_BATCH_INTERVAL)
|
time.sleep(CLEAR_EXPIRED_TOKENS_BATCH_INTERVAL)
|
||||||
current_no = queryset.count()
|
current_no = queryset.count()
|
||||||
|
|
@ -298,4 +290,3 @@ def clear_expired():
|
||||||
|
|
||||||
grants_deleted_no = batch_delete(grants, grants_query)
|
grants_deleted_no = batch_delete(grants, grants_query)
|
||||||
logger.info("%s Expired grant tokens deleted", grants_deleted_no)
|
logger.info("%s Expired grant tokens deleted", grants_deleted_no)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,3 +31,9 @@ class ApplicationViewSet(ReadOnlyModelViewSet):
|
||||||
else:
|
else:
|
||||||
raise Http404()
|
raise Http404()
|
||||||
|
|
||||||
|
# @action(detail=True, permission_classes=[AllowAny])
|
||||||
|
# def tokens(self, request, pk=None):
|
||||||
|
# app: Application = self.get_object()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ from django.db import models
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
MAX_OTP_TRY = 3
|
MAX_OTP_TRY = 3
|
||||||
DEVELOPMENT_PHONE_NUMBERS = ['+989999999999', '+989999999998']
|
DEVELOPMENT_PHONE_NUMBERS = []
|
||||||
MAX_OTP_VALID_DURATION = 120
|
MAX_OTP_VALID_DURATION = 120
|
||||||
|
|
||||||
class GenderChoices(models.TextChoices):
|
class GenderChoices(models.TextChoices):
|
||||||
|
|
|
||||||
|
|
@ -166,6 +166,7 @@ class User(AbstractUser):
|
||||||
|
|
||||||
self.otp_expire = timezone.now() + timedelta(seconds=MAX_OTP_VALID_DURATION)
|
self.otp_expire = timezone.now() + timedelta(seconds=MAX_OTP_VALID_DURATION)
|
||||||
self.otp_try = 0
|
self.otp_try = 0
|
||||||
|
self.save()
|
||||||
|
|
||||||
def set_ott(self):
|
def set_ott(self):
|
||||||
self.ott = ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(32))
|
self.ott = ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(32))
|
||||||
|
|
@ -180,18 +181,22 @@ class User(AbstractUser):
|
||||||
|
|
||||||
def check_otp(self, otp):
|
def check_otp(self, otp):
|
||||||
if self.otp_try <= MAX_OTP_TRY:
|
if self.otp_try <= MAX_OTP_TRY:
|
||||||
result = otp and check_password(self.otp, otp) and self.otp_is_valid()
|
try:
|
||||||
|
result = otp and check_password(otp, self.otp) and self.otp_is_valid()
|
||||||
|
except Exception as e:
|
||||||
|
result = False
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
self.otp = None
|
self.otp = None
|
||||||
self.date_joined = timezone.now()
|
if not self.date_joined:
|
||||||
|
self.date_joined = timezone.now()
|
||||||
else:
|
else:
|
||||||
self.otp_try += 1
|
self.otp_try += 1
|
||||||
|
self.save()
|
||||||
else:
|
else:
|
||||||
self.otp = None
|
|
||||||
result = False
|
result = False
|
||||||
|
|
||||||
self.save()
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def check_ott(self, ott: str):
|
def check_ott(self, ott: str):
|
||||||
|
|
|
||||||
|
|
@ -73,13 +73,12 @@ class RequestOTPSerializer(serializers.ModelSerializer):
|
||||||
try:
|
try:
|
||||||
user.set_otp()
|
user.set_otp()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise UnprocessableEntity()
|
raise UnprocessableEntity('otp expire time not reached')
|
||||||
|
|
||||||
user.save()
|
|
||||||
user.send_otp()
|
user.send_otp()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise UnprocessableEntity()
|
raise UnprocessableEntity('otp expire time not reached yet')
|
||||||
|
|
||||||
self.instance = user
|
self.instance = user
|
||||||
return user
|
return user
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue