fix: auth server-side para nuevo-invitado; sin hash en HTML
This commit is contained in:
@@ -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})
|
||||||
|
|||||||
Reference in New Issue
Block a user