Initial certificate system
This commit is contained in:
54
backend/app/services/captcha.py
Normal file
54
backend/app/services/captcha.py
Normal file
@@ -0,0 +1,54 @@
|
||||
import base64
|
||||
import io
|
||||
import random
|
||||
import secrets
|
||||
import string
|
||||
import time
|
||||
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
CAPTCHA_TTL_SECONDS = 300
|
||||
_captchas: dict[str, tuple[str, float]] = {}
|
||||
|
||||
|
||||
def make_captcha() -> dict[str, str]:
|
||||
clean_expired_captchas()
|
||||
captcha_id = secrets.token_urlsafe(16)
|
||||
code = "".join(random.choice(string.ascii_uppercase + string.digits) for _ in range(4))
|
||||
_captchas[captcha_id] = (code, time.time() + CAPTCHA_TTL_SECONDS)
|
||||
return {"captcha_id": captcha_id, "image": draw_captcha_image(code)}
|
||||
|
||||
|
||||
def verify_captcha(captcha_id: str, captcha_code: str) -> bool:
|
||||
clean_expired_captchas()
|
||||
record = _captchas.pop(captcha_id, None)
|
||||
if not record:
|
||||
return False
|
||||
expected, expires_at = record
|
||||
if expires_at < time.time():
|
||||
return False
|
||||
return expected.upper() == captcha_code.strip().upper()
|
||||
|
||||
|
||||
def clean_expired_captchas() -> None:
|
||||
now = time.time()
|
||||
expired = [captcha_id for captcha_id, (_, expires_at) in _captchas.items() if expires_at < now]
|
||||
for captcha_id in expired:
|
||||
_captchas.pop(captcha_id, None)
|
||||
|
||||
|
||||
def draw_captcha_image(code: str) -> str:
|
||||
width, height = 132, 44
|
||||
image = Image.new("RGB", (width, height), "#f8fafc")
|
||||
draw = ImageDraw.Draw(image)
|
||||
font = ImageFont.load_default()
|
||||
for i, char in enumerate(code):
|
||||
draw.text((18 + i * 26, 13 + random.randint(-2, 2)), char, fill="#1f2937", font=font)
|
||||
for _ in range(8):
|
||||
x1, y1 = random.randint(0, width), random.randint(0, height)
|
||||
x2, y2 = random.randint(0, width), random.randint(0, height)
|
||||
draw.line((x1, y1, x2, y2), fill="#cbd5e1", width=1)
|
||||
|
||||
buffer = io.BytesIO()
|
||||
image.save(buffer, format="PNG")
|
||||
return "data:image/png;base64," + base64.b64encode(buffer.getvalue()).decode("ascii")
|
||||
Reference in New Issue
Block a user