captcha.py 1.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
  1. from __future__ import annotations
  2. import secrets
  3. import string
  4. import uuid
  5. from django.conf import settings
  6. from django.db import models
  7. from django.utils import timezone
  8. from django_prometheus.models import ExportModelOperationsMixin
  9. from desecapi import metrics
  10. def captcha_default_content(kind: str) -> str:
  11. if kind == Captcha.Kind.IMAGE:
  12. alphabet = (string.ascii_uppercase + string.digits).translate({ord(c): None for c in 'IO0'})
  13. length = 5
  14. elif kind == Captcha.Kind.AUDIO:
  15. alphabet = string.digits
  16. length = 8
  17. else:
  18. raise ValueError(f'Unknown Captcha kind: {kind}')
  19. content = ''.join([secrets.choice(alphabet) for _ in range(length)])
  20. metrics.get('desecapi_captcha_content_created').labels(kind).inc()
  21. return content
  22. class Captcha(ExportModelOperationsMixin('Captcha'), models.Model):
  23. class Kind(models.TextChoices):
  24. IMAGE = 'image'
  25. AUDIO = 'audio'
  26. id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
  27. created = models.DateTimeField(auto_now_add=True)
  28. content = models.CharField(max_length=24, default="")
  29. kind = models.CharField(choices=Kind.choices, default=Kind.IMAGE, max_length=24)
  30. def __init__(self, *args, **kwargs):
  31. super().__init__(*args, **kwargs)
  32. if not self.content:
  33. self.content = captcha_default_content(self.kind)
  34. def verify(self, solution: str):
  35. age = timezone.now() - self.created
  36. self.delete()
  37. return (
  38. str(solution).upper().strip() == self.content # solution correct
  39. and
  40. age <= settings.CAPTCHA_VALIDITY_PERIOD # not expired
  41. )