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