From 2fe322241aa64e659290b3d005074b97b3e19863 Mon Sep 17 00:00:00 2001 From: Sayyid Hamid Mahdavi Date: Thu, 20 Nov 2025 13:57:23 +0330 Subject: [PATCH] hash otp --- ...r_refreshtoken_unique_together_and_more.py | 99 +++++++++++++++++++ apps/gooyal_oauth2/models.py | 25 ++--- apps/gooyal_oauth2/views/apis.py | 6 ++ apps/users/constans.py | 2 +- apps/users/models.py | 15 ++- apps/users/serializers.py | 7 +- 6 files changed, 127 insertions(+), 27 deletions(-) create mode 100644 apps/gooyal_oauth2/migrations/0005_alter_refreshtoken_unique_together_and_more.py diff --git a/apps/gooyal_oauth2/migrations/0005_alter_refreshtoken_unique_together_and_more.py b/apps/gooyal_oauth2/migrations/0005_alter_refreshtoken_unique_together_and_more.py new file mode 100644 index 0000000..cb1a740 --- /dev/null +++ b/apps/gooyal_oauth2/migrations/0005_alter_refreshtoken_unique_together_and_more.py @@ -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), + ), + ] diff --git a/apps/gooyal_oauth2/models.py b/apps/gooyal_oauth2/models.py index 476cdcb..cafa003 100644 --- a/apps/gooyal_oauth2/models.py +++ b/apps/gooyal_oauth2/models.py @@ -16,14 +16,14 @@ from django.conf import settings from django.db import models, router, transaction from django.utils import timezone +from utils.models import BaseModel from .settings import oauth2_settings logger = logging.getLogger(__name__) -class Resource(models.Model): - uuid = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4, unique=True, db_index=True) +class Resource(BaseModel): name = models.CharField(max_length=255) user = models.ForeignKey( @@ -36,13 +36,12 @@ class Resource(models.Model): return self.name -class Application(AbstractApplication): +class Application(AbstractApplication, BaseModel): """ Application model for use with Django OAuth Toolkit that allows the scopes available to an application to be restricted on a per-application basis. """ - id=None - uuid = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4, unique=True, db_index=True) + id = None name = models.CharField(max_length=255, blank=False, unique=True) user = models.ForeignKey( settings.AUTH_USER_MODEL, @@ -74,14 +73,12 @@ class Application(AbstractApplication): allowed_scopes = self.allowed_scopes return Scope.objects.filter(name__in=allowed_scopes).order_by('name') - - def allows_grant_type(self, *grant_types): # 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)) -class Scope(models.Model): +class Scope(BaseModel): """ 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 # scope to be available to other applications in order to request access # 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( oauth2_settings.APPLICATION_MODEL, models.CASCADE, @@ -177,17 +172,15 @@ class Scope(models.Model): return True -class Grant(AbstractGrant): +class Grant(AbstractGrant, BaseModel): id = None - uuid = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4, unique=True, db_index=True) application = models.ForeignKey( oauth2_settings.APPLICATION_MODEL, on_delete=models.CASCADE, related_name='grants' ) -class RefreshToken(AbstractRefreshToken): +class RefreshToken(BaseModel, AbstractRefreshToken): id = None - uuid = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4, unique=True, db_index=True) application = models.ForeignKey( oauth2_settings.APPLICATION_MODEL, on_delete=models.CASCADE, related_name='refresh_tokens') @@ -215,7 +208,6 @@ class RefreshToken(AbstractRefreshToken): self.save() - class AccessToken(AbstractAccessToken): id = None 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] batch_length = flat_queryset.count() 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) time.sleep(CLEAR_EXPIRED_TOKENS_BATCH_INTERVAL) current_no = queryset.count() @@ -298,4 +290,3 @@ def clear_expired(): grants_deleted_no = batch_delete(grants, grants_query) logger.info("%s Expired grant tokens deleted", grants_deleted_no) - diff --git a/apps/gooyal_oauth2/views/apis.py b/apps/gooyal_oauth2/views/apis.py index 05d5484..3f40269 100644 --- a/apps/gooyal_oauth2/views/apis.py +++ b/apps/gooyal_oauth2/views/apis.py @@ -31,3 +31,9 @@ class ApplicationViewSet(ReadOnlyModelViewSet): else: raise Http404() + # @action(detail=True, permission_classes=[AllowAny]) + # def tokens(self, request, pk=None): + # app: Application = self.get_object() + + + diff --git a/apps/users/constans.py b/apps/users/constans.py index 89c0e3c..7026189 100644 --- a/apps/users/constans.py +++ b/apps/users/constans.py @@ -2,7 +2,7 @@ from django.db import models from django.utils.translation import gettext_lazy as _ MAX_OTP_TRY = 3 -DEVELOPMENT_PHONE_NUMBERS = ['+989999999999', '+989999999998'] +DEVELOPMENT_PHONE_NUMBERS = [] MAX_OTP_VALID_DURATION = 120 class GenderChoices(models.TextChoices): diff --git a/apps/users/models.py b/apps/users/models.py index a898202..b7159f0 100644 --- a/apps/users/models.py +++ b/apps/users/models.py @@ -166,6 +166,7 @@ class User(AbstractUser): self.otp_expire = timezone.now() + timedelta(seconds=MAX_OTP_VALID_DURATION) self.otp_try = 0 + self.save() def set_ott(self): 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): 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: self.otp = None - self.date_joined = timezone.now() + if not self.date_joined: + self.date_joined = timezone.now() else: self.otp_try += 1 - + self.save() else: - self.otp = None result = False - self.save() + return result def check_ott(self, ott: str): diff --git a/apps/users/serializers.py b/apps/users/serializers.py index 0a29243..38a0b98 100644 --- a/apps/users/serializers.py +++ b/apps/users/serializers.py @@ -73,13 +73,12 @@ class RequestOTPSerializer(serializers.ModelSerializer): try: user.set_otp() except Exception as e: - raise UnprocessableEntity() - - user.save() + raise UnprocessableEntity('otp expire time not reached') + user.send_otp() else: - raise UnprocessableEntity() + raise UnprocessableEntity('otp expire time not reached yet') self.instance = user return user