v1.5.1: persistir el nombre del rep en un token firmado (?n=b64.hmac). Al fijar el nombre se emite el token; al recargar se rehidrata rep_name antes de dibujar el campo. Fail-open, mismo secreto que ?s=.
Browse files
app.py
CHANGED
|
@@ -3,6 +3,7 @@ import re
|
|
| 3 |
import json
|
| 4 |
import time
|
| 5 |
import hmac
|
|
|
|
| 6 |
import hashlib
|
| 7 |
import threading
|
| 8 |
import unicodedata
|
|
@@ -17,7 +18,7 @@ KB_PATH = BASE_DIR / KB_NAME
|
|
| 17 |
|
| 18 |
# Version del bot. Se INCREMENTA al desplegar una mejora salida del feedback de los reps
|
| 19 |
# (se comunica el numero en el changelog de esa release). Formato X.Y.
|
| 20 |
-
APP_VERSION = "1.5.
|
| 21 |
APP_TITLE = f"Enfoque Proto Chatbot {APP_VERSION}"
|
| 22 |
|
| 23 |
# --- Persistencia remota (fuente de verdad) --------------------------------
|
|
@@ -151,6 +152,63 @@ def _valid_session_token(tok):
|
|
| 151 |
return None
|
| 152 |
|
| 153 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 154 |
def _check_auth():
|
| 155 |
auth_config = os.environ.get("HF_SPACES_AUTH", "")
|
| 156 |
if not auth_config:
|
|
@@ -192,6 +250,7 @@ def _check_auth():
|
|
| 192 |
|
| 193 |
|
| 194 |
_check_auth()
|
|
|
|
| 195 |
|
| 196 |
|
| 197 |
# -- Backend -------------------------------------------------------------------
|
|
@@ -1493,7 +1552,7 @@ with st.sidebar:
|
|
| 1493 |
st.divider()
|
| 1494 |
|
| 1495 |
st.text_input("Tu nombre y apellido (obligatorio)", key="rep_name",
|
| 1496 |
-
placeholder="Ej: María Rodríguez")
|
| 1497 |
if len(st.session_state.get("rep_name", "").split()) < 2:
|
| 1498 |
st.caption("⚠️ Escribe tu **nombre y apellido** para poder usar el asistente.")
|
| 1499 |
|
|
|
|
| 3 |
import json
|
| 4 |
import time
|
| 5 |
import hmac
|
| 6 |
+
import base64
|
| 7 |
import hashlib
|
| 8 |
import threading
|
| 9 |
import unicodedata
|
|
|
|
| 18 |
|
| 19 |
# Version del bot. Se INCREMENTA al desplegar una mejora salida del feedback de los reps
|
| 20 |
# (se comunica el numero en el changelog de esa release). Formato X.Y.
|
| 21 |
+
APP_VERSION = "1.5.1"
|
| 22 |
APP_TITLE = f"Enfoque Proto Chatbot {APP_VERSION}"
|
| 23 |
|
| 24 |
# --- Persistencia remota (fuente de verdad) --------------------------------
|
|
|
|
| 152 |
return None
|
| 153 |
|
| 154 |
|
| 155 |
+
# -- Token del NOMBRE del rep (v1.5.1) --------------------------------------------------------
|
| 156 |
+
# El nombre se teclea en la barra lateral DESPUES del login (no en el login), y una recarga dura
|
| 157 |
+
# borra session_state -> el rep re-teclea su nombre. Para evitarlo: cuando el rep fija su nombre,
|
| 158 |
+
# se emite un token firmado (b64.hmac) en el query-string `?n=`; al recargar, se rehidrata el
|
| 159 |
+
# nombre en session_state ANTES de que se dibuje el campo. Firmado con el mismo secreto para que la
|
| 160 |
+
# URL no se pueda falsear. FAIL-OPEN: si `?n=` falta/está mal/no hay secreto, se pide el nombre como
|
| 161 |
+
# antes. El nombre NO es sensible (el rep lo declara libremente y su email ya viaja en el token `s`).
|
| 162 |
+
def _make_name_token(name):
|
| 163 |
+
b = base64.urlsafe_b64encode(name.encode()).decode().rstrip("=")
|
| 164 |
+
sig = hmac.new(_auth_secret().encode(), b.encode(), hashlib.sha256).hexdigest()[:16]
|
| 165 |
+
return f"{b}.{sig}"
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
def _valid_name_token(tok):
|
| 169 |
+
"""Devuelve el nombre si el token valida; si no, None (fail-open)."""
|
| 170 |
+
try:
|
| 171 |
+
b, sig = tok.rsplit(".", 1)
|
| 172 |
+
good = hmac.new(_auth_secret().encode(), b.encode(), hashlib.sha256).hexdigest()[:16]
|
| 173 |
+
if not hmac.compare_digest(good, sig):
|
| 174 |
+
return None
|
| 175 |
+
pad = "=" * (-len(b) % 4)
|
| 176 |
+
name = base64.urlsafe_b64decode((b + pad).encode()).decode()
|
| 177 |
+
return name or None
|
| 178 |
+
except Exception:
|
| 179 |
+
return None
|
| 180 |
+
|
| 181 |
+
|
| 182 |
+
def _restore_name():
|
| 183 |
+
"""Rehidrata rep_name desde `?n=` si aun no esta seteado (tras una recarga dura). Fail-open;
|
| 184 |
+
corre solo estando ya autenticado (despues de _check_auth)."""
|
| 185 |
+
try:
|
| 186 |
+
if st.session_state.get("rep_name"):
|
| 187 |
+
return
|
| 188 |
+
tok = st.query_params.get("n")
|
| 189 |
+
if tok and _auth_secret():
|
| 190 |
+
name = _valid_name_token(tok)
|
| 191 |
+
if name:
|
| 192 |
+
st.session_state["rep_name"] = name
|
| 193 |
+
except Exception:
|
| 194 |
+
pass
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
def _persist_name():
|
| 198 |
+
"""on_change del campo de nombre: emite/actualiza el token `?n=` cuando el rep fija su nombre
|
| 199 |
+
(>= 2 palabras). Si lo vacia, limpia el param. Fail-open."""
|
| 200 |
+
try:
|
| 201 |
+
if not _auth_secret():
|
| 202 |
+
return
|
| 203 |
+
name = st.session_state.get("rep_name", "").strip()
|
| 204 |
+
if len(name.split()) >= 2:
|
| 205 |
+
st.query_params["n"] = _make_name_token(name)
|
| 206 |
+
elif "n" in st.query_params:
|
| 207 |
+
del st.query_params["n"]
|
| 208 |
+
except Exception:
|
| 209 |
+
pass
|
| 210 |
+
|
| 211 |
+
|
| 212 |
def _check_auth():
|
| 213 |
auth_config = os.environ.get("HF_SPACES_AUTH", "")
|
| 214 |
if not auth_config:
|
|
|
|
| 250 |
|
| 251 |
|
| 252 |
_check_auth()
|
| 253 |
+
_restore_name()
|
| 254 |
|
| 255 |
|
| 256 |
# -- Backend -------------------------------------------------------------------
|
|
|
|
| 1552 |
st.divider()
|
| 1553 |
|
| 1554 |
st.text_input("Tu nombre y apellido (obligatorio)", key="rep_name",
|
| 1555 |
+
placeholder="Ej: María Rodríguez", on_change=_persist_name)
|
| 1556 |
if len(st.session_state.get("rep_name", "").split()) < 2:
|
| 1557 |
st.caption("⚠️ Escribe tu **nombre y apellido** para poder usar el asistente.")
|
| 1558 |
|