fix: auth server-side para nuevo-invitado; sin hash en HTML

This commit is contained in:
2026-10-29 21:14:24 +00:00
parent f8711dd464
commit 3871bb6cd2
+30 -2
View File
@@ -7,6 +7,7 @@ No guarda los mensajes en disco: solo los reenvía por correo.
""" """
import datetime import datetime
from zoneinfo import ZoneInfo from zoneinfo import ZoneInfo
import hmac as _hmac
import json import json
import os import os
from urllib.parse import urlparse, parse_qs, quote 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 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): def parse_dias(d_str):
"""Valida una lista de timestamps separados por comas. Devuelve lista de ints o None.""" """Valida una lista de timestamps separados por comas. Devuelve lista de ints o None."""
if not d_str: 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() return (self.headers.get('X-Forwarded-For') or self.client_address[0]).split(',')[0].strip()
def do_GET(self): 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}) return self.reply(404, {'ok': False})
if limited(check_hits, self.client_ip(), 40): if limited(check_hits, self.client_ip(), 40):
return self.reply(429, {'ok': False}) return self.reply(429, {'ok': False})
@@ -286,7 +301,20 @@ class H(BaseHTTPRequestHandler):
self.reply(200, {'ok': True}) self.reply(200, {'ok': True})
def do_POST(self): 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() return self.invitados()
if self.path not in ('/api/contacto', '/api/patrocinio'): if self.path not in ('/api/contacto', '/api/patrocinio'):
return self.reply(404, {'ok': False}) return self.reply(404, {'ok': False})