File size: 13,855 Bytes
e1e0418
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75eddc6
e1e0418
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75eddc6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e1e0418
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
"""

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_action
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, set_data_origin_and_reload
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ó")
    # Valors per defecte de demo: usuari vermell (visitant)
    username = st.text_input("Usuari", value="vermell")
    password = st.text_input("Contrasenya", value="vermell123", type="password")

    # Checkbox per canviar l'origen de dades (capacitat d'edició)
    base_dir = Path(__file__).parent
    try:
        import yaml

        cfg_path = base_dir / "config.yaml"
        with cfg_path.open("r", encoding="utf-8") as f:
            cfg = yaml.safe_load(f) or {}
        app_cfg = cfg.get("app", {}) or {}
        data_origin = str(app_cfg.get("data_origin", "internal")).lower()
    except Exception:
        data_origin = "internal"

    is_external = data_origin == "external"
    edit_mode = st.checkbox("Capacitat d'edició de dades", value=is_external)

    # Si el checkbox canvia respecte a l'estat actual, actualitzar config.yaml i recarregar dades
    new_origin = "external" if edit_mode else "internal"
    if new_origin != data_origin:
        api_client = st.session_state.get("api_client")
        set_data_origin_and_reload(base_dir, api_client, new_origin)
    
    if st.button("Entrar", type="primary"):
        row = get_user(username) if username else None

        # 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 o nombre de usuario vacío. Se asignará rol 'vermell'.")
            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", "")
                phone = (
                    st.session_state.get("sms_phone_verified")
                    or st.session_state.get("sms_phone")
                    or ""
                )
                log_action(
                    session=session_id,
                    user=username or "",
                    phone=phone,
                    action="login",
                    sha1sum="",
                )
            except Exception as e:
                log(f"Error registrant esdeveniment de login: {e}")

            st.success(f"Benvingut/da, {row['username']}")
            st.rerun()
        else:
            # Si l'usuari està buit o no existeix, entrar com a "vermell" (visitant)
            st.session_state.user = {
                "id": None,
                "username": "vermell",
                "role": "vermell",
            }
            st.session_state.last_password = password

            try:
                session_id = st.session_state.get("session_id", "")
                phone = (
                    st.session_state.get("sms_phone_verified")
                    or st.session_state.get("sms_phone")
                    or ""
                )
                log_action(
                    session=session_id,
                    user="vermell",
                    phone=phone,
                    action="login",
                    sha1sum="",
                )
            except Exception as e:
                log(f"Error registrant esdeveniment de login per usuari 'vermell': {e}")

            st.success("Benvingut/da, vermell")
            st.rerun()


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", "")
                    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_action(
                        session=session_id,
                        user=current_user.get("username", ""),
                        phone=phone,
                        action="logout",
                        sha1sum="",
                    )
                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={digest_hash or '-'} "
                    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