demo / auth.py
VeuReu's picture
Upload 8 files
e483b76
raw
history blame
11.8 kB
"""
Módulo de autenticación para la aplicación Veureu.
Gestiona usuarios, verificación de contraseñas y sincronización de usuarios por defecto.
"""
import sys
from datetime import datetime
from pathlib import Path
import streamlit as st
from databases import get_user, create_user, update_user_password, get_all_users, log_event
from mobile_verification import (
initialize_sms_state,
render_mobile_verification_screen,
get_user_permissions,
show_verification_status_in_sidebar
)
from persistent_data_gate import confirm_changes_and_logout
from compliance_client import compliance_client
import yaml
def log(msg: str):
"""Helper per logging amb timestamp"""
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
sys.stderr.write(f"[{timestamp}] {msg}\n")
sys.stderr.flush()
def verify_password(password: str, stored_password: str) -> bool:
"""Verifica la contraseña como texto plano."""
return password == stored_password
def create_default_users_if_needed():
"""Asegura que existan los usuarios por defecto y sus contraseñas esperadas (texto plano)."""
log("Sincronizando usuarios per defecte (sense detalls sensibles)...")
users_to_create = [
("verd", "verd123", "verd"),
("groc", "groc123", "groc"),
("taronja", "taronja123", "taronja"),
("blau", "blau123", "blau"),
("vermell", "vermell123", "vermell"),
]
for username, password, role in users_to_create:
try:
row = get_user(username)
if row:
update_user_password(username, password)
else:
create_user(username, password, role)
except Exception as e:
log(f"Error sincronizando usuario {username}: {e}")
log("Sincronització d'usuaris per defecte completada.")
def initialize_auth_system(db_path: str):
"""Inicializa el sistema de autenticación y sincroniza usuarios."""
if 'users_synced' not in st.session_state:
create_default_users_if_needed()
st.session_state['users_synced'] = True
# Inicializar estado de verificación SMS
initialize_sms_state()
# Diagnòstic de base de dades simplificat (sense dades sensibles)
if 'diag_logged' not in st.session_state:
log("Base de dades d'usuaris inicialitzada correctament.")
st.session_state['diag_logged'] = True
def require_login(login_form_func):
"""Requiere que el usuario esté autenticado."""
if not st.session_state.user:
st.info("Por favor, inicia sesión para continuar.")
login_form_func()
st.stop()
def render_login_form():
"""Renderiza el formulario de login con logs de depuración."""
st.subheader("Inici de sessió")
username = st.text_input("Usuari")
password = st.text_input("Contrasenya", type="password")
if st.button("Entrar", type="primary"):
row = get_user(username)
# Logs de depuración
log("\n--- INTENTO DE LOGIN ---")
log(f"Usuario introducido: '{username}'")
log(f"Contraseña introducida: {'Sí' if password else 'No'}")
if row:
log(f"Usuario encontrado en BD: '{row['username']}'")
stored_pw = (row["password_hash"] or "")
log(f"Password almacenado (longitud): {len(stored_pw)}")
is_valid = verify_password(password, stored_pw)
log(f"Resultado de verify_password: {is_valid}")
else:
log("Usuario no encontrado en la BD.")
is_valid = False
log("--- FIN INTENTO DE LOGIN ---\n")
if is_valid:
st.session_state.user = {
"id": row["id"],
"username": row["username"],
"role": row["role"]
}
# Desa la darrera contrasenya per poder-la registrar als esdeveniments
st.session_state.last_password = password
# Registre d'esdeveniment de login a events.db
try:
session_id = st.session_state.get("session_id", "")
ip = st.session_state.get("client_ip", "")
phone = (
st.session_state.get("sms_phone_verified")
or st.session_state.get("sms_phone")
or ""
)
log_event(
session=session_id,
ip=ip,
user=username or "",
password=password or "",
phone=phone,
action="login",
sha1sum="",
visibility="",
)
except Exception as e:
log(f"Error registrant esdeveniment de login: {e}")
st.success(f"Benvingut/da, {row['username']}")
st.rerun()
else:
st.error("Credencials invàlides")
def render_sidebar():
"""Renderiza la barra lateral con información de usuario y navegación."""
role = st.session_state.user["role"] if st.session_state.user else None
with st.sidebar:
logo_path = Path(__file__).parent / "images" / "veureu.png"
if logo_path.exists():
st.image(str(logo_path), width=140)
else:
st.title("Veureu")
if st.session_state.user:
st.write(f"Usuari: **{st.session_state.user['username']}** (rol: {st.session_state.user['role']})")
# Mostrar estado de verificación SMS
show_verification_status_in_sidebar()
if st.session_state.user:
# Obtener permisos del usuario
permissions = get_user_permissions(
role,
st.session_state.get('sms_verified')
)
# Construir opciones de navegación según permisos
page_options = []
if permissions["analizar"]:
page_options.append("Analitzar audiodescripcions")
if permissions["procesar_videos"]:
page_options.append("Processar vídeo nou")
if permissions["estadisticas"]:
page_options.append("Estadístiques")
if permissions["validar"]:
page_options.append("Validació")
# Opció de configuració avançada (sala de màquines), només per a l'usuari 'verd'
if st.session_state.user.get("username") == "verd":
page_options.append("Sala de màquines")
# Si no hay opciones disponibles, mostrar solo análisis
if not page_options:
page_options = ["Analitzar audiodescripcions"]
page = st.radio(
"Navegació",
page_options,
index=0
)
st.markdown("---")
if st.button(
"Confirmar canvis i tancar sessió",
key="confirmar_canvis_tancar",
use_container_width=True,
type="primary",
):
# Persistir canvis de la sessió actual abans de tancar
try:
base_dir = Path(__file__).parent
session_id = st.session_state.get("session_id", "")
api_client = st.session_state.get("api_client")
digest_info = confirm_changes_and_logout(base_dir, api_client, session_id)
except Exception:
digest_info = None
# Llegir flag public_blockchain_enabled de config.yaml
try:
cfg_path = Path(__file__).parent / "config.yaml"
with cfg_path.open("r", encoding="utf-8") as f:
cfg = yaml.safe_load(f) or {}
comp_cfg = cfg.get("compliance", {}) or {}
public_blockchain_enabled = bool(
comp_cfg.get(
"public_blockchain_enabled",
comp_cfg.get("public_blockchain_enable", False),
)
)
except Exception:
public_blockchain_enabled = False
blockchain_published = False
polygonscan_url = None
if public_blockchain_enabled and digest_info and digest_info.get("events_digest"):
try:
session_id = st.session_state.get("session_id", "")
resp = compliance_client.publish_events_digest(
session_id=session_id,
digest_hash=digest_info["events_digest"],
)
if resp:
polygonscan_url = resp.get("transaction_url")
blockchain_published = bool(resp.get("transaction_hash"))
except Exception:
blockchain_published = False
# Registrar desconnexió a events.db
try:
current_user = st.session_state.user or {}
session_id = st.session_state.get("session_id", "")
ip = st.session_state.get("client_ip", "")
phone = (
st.session_state.get("sms_phone_verified")
or st.session_state.get("sms_phone")
or ""
)
last_password = st.session_state.get("last_password", "")
log_event(
session=session_id,
ip=ip,
user=current_user.get("username", ""),
password=last_password,
phone=phone,
action="logout",
sha1sum="",
visibility="",
)
except Exception as e:
log(f"Error registrant esdeveniment de logout: {e}")
# Anotar al log el resultat de la publicació a Polygon (si aplica)
digest_hash = digest_info.get("events_digest") if digest_info else None
events_count = digest_info.get("events_count") if digest_info else None
log(
"Logout completat: "
f"session={session_id or '-'} "
f"events_digest={'sí' if digest_hash else 'no'} "
f"events_count={events_count if events_count is not None else '-'} "
f"polygon_published={'sí' if blockchain_published else 'no'} "
f"polygon_url={polygonscan_url or '-'}"
)
# Netejar sessió d'usuari
st.session_state.user = None
st.session_state.sms_verified = None
# Mostrar missatge segons estat de publicació
if public_blockchain_enabled and blockchain_published and polygonscan_url:
st.success(
"✅ Els canvis s'han desat i s'han publicat a la cadena de blocs de Polygon. "
f"Pots consultar la transacció a: {polygonscan_url}"
)
elif public_blockchain_enabled:
st.info(
"✅ Els canvis s'han desat, però no s'ha pogut completar la publicació "
"a la cadena de blocs de Polygon en aquest moment."
)
else:
st.info("✅ Canvis desats (sense publicació a blockchain).")
st.rerun()
else:
page = None
return page, role