From 3871bb6cd294aac58f3e938221f0e3e4a76b360b Mon Sep 17 00:00:00 2001 From: alexandrump Date: Thu, 29 Oct 2026 21:14:24 +0000 Subject: [PATCH] fix: auth server-side para nuevo-invitado; sin hash en HTML --- server.py | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/server.py b/server.py index 6c39c09..a9564dc 100644 --- a/server.py +++ b/server.py @@ -7,6 +7,7 @@ No guarda los mensajes en disco: solo los reenvía por correo. """ import datetime from zoneinfo import ZoneInfo +import hmac as _hmac import json import os from urllib.parse import urlparse, parse_qs, quote @@ -142,6 +143,15 @@ def invitado(n, f): return (n, ts) if -10 <= delta <= 14 else None +def _gen_token(offset=0): + """Token HMAC diario para la página generadora. Válido 48h (slot actual + anterior).""" + key = (os.environ.get('LINK_PASS') or '').encode() + if not key: + return None + slot = str(int(time.time()) // 86400 - offset).encode() + return _hmac.new(key, slot, 'sha256').hexdigest() + + def parse_dias(d_str): """Valida una lista de timestamps separados por comas. Devuelve lista de ints o None.""" if not d_str: @@ -182,7 +192,12 @@ class H(BaseHTTPRequestHandler): return (self.headers.get('X-Forwarded-For') or self.client_address[0]).split(',')[0].strip() def do_GET(self): - if urlparse(self.path).path != '/api/invitados/check': + path = urlparse(self.path).path + if path == '/api/gen-verify': + tok = parse_qs(urlparse(self.path).query).get('t', [''])[0] + valid = tok and tok in filter(None, [_gen_token(0), _gen_token(1)]) + return self.reply(200, {'ok': bool(valid)}) + if path != '/api/invitados/check': return self.reply(404, {'ok': False}) if limited(check_hits, self.client_ip(), 40): return self.reply(429, {'ok': False}) @@ -286,7 +301,20 @@ class H(BaseHTTPRequestHandler): self.reply(200, {'ok': True}) def do_POST(self): - if self.path.split('?')[0] == '/api/invitados': + p = self.path.split('?')[0] + if p == '/api/gen-auth': + try: + n = int(self.headers.get('Content-Length', '0')) + d = json.loads(self.rfile.read(n)) if 0 < n <= 1024 else {} + except Exception: + return self.reply(400, {'ok': False}) + link_pass = os.environ.get('LINK_PASS', '') + if not link_pass: + return self.reply(503, {'ok': False, 'error': 'No configurado.'}) + if _hmac.compare_digest(str(d.get('pass', '')), link_pass): + return self.reply(200, {'ok': True, 'token': _gen_token(0)}) + return self.reply(401, {'ok': False, 'error': 'Contraseña incorrecta.'}) + if p == '/api/invitados': return self.invitados() if self.path not in ('/api/contacto', '/api/patrocinio'): return self.reply(404, {'ok': False})