import io import secrets import uuid import base64 from PIL import Image, ImageDraw, ImageFont from django.core.cache import caches default_cache = caches['default'] CAPTCHA_CHARS = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789' def _random_color(low, high): return tuple(secrets.randbelow(high - low + 1) + low for _ in range(3)) def generate_captcha(): captcha_key = str(uuid.uuid4()) code = ''.join(secrets.choice(CAPTCHA_CHARS) for _ in range(4)) width, height = 120, 40 image = Image.new('RGB', (width, height), _random_color(200, 255)) draw = ImageDraw.Draw(image) for _ in range(30): x = secrets.randbelow(width) y = secrets.randbelow(height) draw.point((x, y), fill=_random_color(0, 255)) for _ in range(3): x1 = secrets.randbelow(width) y1 = secrets.randbelow(height) x2 = secrets.randbelow(width) y2 = secrets.randbelow(height) draw.line([(x1, y1), (x2, y2)], fill=_random_color(0, 255), width=1) font = ImageFont.load_default() for i, char in enumerate(code): font_size = secrets.randbelow(5) + 12 try: char_font = ImageFont.load_default(size=font_size) except TypeError: char_font = ImageFont.load_default() char_image = Image.new('RGBA', (30, 30), (0, 0, 0, 0)) char_draw = ImageDraw.Draw(char_image) char_color = _random_color(0, 100) char_draw.text((5, 5), char, font=char_font, fill=char_color) angle = secrets.randbelow(61) - 30 rotated = char_image.rotate(angle, expand=True, resample=Image.BICUBIC) x_offset = i * 25 + secrets.randbelow(6) y_offset = secrets.randbelow(10) image.paste(rotated, (x_offset, y_offset), rotated) buffer = io.BytesIO() image.save(buffer, format='PNG') image_base64 = base64.b64encode(buffer.getvalue()).decode('utf-8') captcha_image = f'data:image/png;base64,{image_base64}' default_cache.set(f'captcha_{captcha_key}', code.upper(), timeout=300) return captcha_key, captcha_image def verify_captcha(captcha_key, captcha_code): cache_key = f'captcha_{captcha_key}' stored_code = default_cache.get(cache_key) if stored_code is None: return 'expired' if stored_code == captcha_code.upper(): default_cache.delete(cache_key) return 'ok' return 'wrong' CAPTCHA_THRESHOLD = 3 CAPTCHA_TRIGGER_TTL = 600 def check_captcha_required(operation, identifier): cache_key = f'captcha_trigger_{operation}_{identifier}' count = default_cache.get(cache_key) if count is not None and count >= CAPTCHA_THRESHOLD: return True return False def record_failure(operation, identifier): cache_key = f'captcha_trigger_{operation}_{identifier}' count = default_cache.get(cache_key) if count is None: default_cache.set(cache_key, 1, timeout=CAPTCHA_TRIGGER_TTL) else: default_cache.set(cache_key, count + 1, timeout=CAPTCHA_TRIGGER_TTL) def reset_failures(operation, identifier): cache_key = f'captcha_trigger_{operation}_{identifier}' default_cache.delete(cache_key)