from __future__ import annotations import secrets import string import uuid from django.conf import settings from django.db import models from django.utils import timezone from django_prometheus.models import ExportModelOperationsMixin from desecapi import metrics def captcha_default_content(kind: str) -> str: if kind == Captcha.Kind.IMAGE: alphabet = (string.ascii_uppercase + string.digits).translate( {ord(c): None for c in "IO0"} ) length = 5 elif kind == Captcha.Kind.AUDIO: alphabet = string.digits length = 8 else: raise ValueError(f"Unknown Captcha kind: {kind}") content = "".join([secrets.choice(alphabet) for _ in range(length)]) metrics.get("desecapi_captcha_content_created").labels(kind).inc() return content class Captcha(ExportModelOperationsMixin("Captcha"), models.Model): class Kind(models.TextChoices): IMAGE = "image" AUDIO = "audio" id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) created = models.DateTimeField(auto_now_add=True) content = models.CharField(max_length=24, default="") kind = models.CharField(choices=Kind.choices, default=Kind.IMAGE, max_length=24) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) if not self.content: self.content = captcha_default_content(self.kind) def verify(self, solution: str): age = timezone.now() - self.created self.delete() return ( str(solution).upper().strip() == self.content # solution correct and age <= settings.CAPTCHA_VALIDITY_PERIOD # not expired )