VeuReu commited on
Commit
c7c9904
·
1 Parent(s): 2109071

Upload 10 files

Browse files
Files changed (5) hide show
  1. app.py +20 -3
  2. compliance_client.py +48 -1
  3. config.yaml +7 -3
  4. mobile_verification.py +32 -2
  5. persistent_data_gate.py +76 -1
app.py CHANGED
@@ -3,6 +3,7 @@ import yaml
3
  import shutil
4
  from pathlib import Path
5
  import uuid
 
6
  try:
7
  import tomllib
8
  except ModuleNotFoundError: # Py<3.11
@@ -73,7 +74,7 @@ def _load_yaml(path="config.yaml") -> dict:
73
  CFG = _load_yaml("config.yaml")
74
 
75
  # Ajuste de variables según tu esquema YAML
76
- # Para la interfaz demo, usaremos siempre la carpeta "temp" local
77
  DATA_DIR = "temp"
78
  BACKEND_BASE_URL = CFG.get("api", {}).get("base_url", "http://localhost:8000")
79
  TTS_URL = "https://veureu-tts.hf.space" # Forzar URL correcta
@@ -82,7 +83,11 @@ print(f"🔧 Tipo de TTS_URL: {type(TTS_URL)}")
82
  print(f"🔧 Longitud de TTS_URL: {len(TTS_URL)}")
83
  print(f"🔧 TTS_URL repr: {repr(TTS_URL)}")
84
  USE_MOCK = bool(CFG.get("app", {}).get("use_mock", False)) # si no la tienes en el yaml, queda False
85
- BLOCKCHAIN_ENABLED = bool(CFG.get("blockchain", {}).get("enabled", False))
 
 
 
 
86
  API_TOKEN = CFG.get("api", {}).get("token") or os.getenv("API_SHARED_TOKEN")
87
 
88
  # Cliente del backend (engine)
@@ -98,7 +103,19 @@ DB_PATH = os.path.join(DATA_DIR, "users.db")
98
  set_db_path(DB_PATH)
99
 
100
  # Configurar si els esdeveniments s'han de registrar a SQLite o a AWS QLDB
101
- set_blockchain_enabled(BLOCKCHAIN_ENABLED)
 
 
 
 
 
 
 
 
 
 
 
 
102
 
103
  init_schema()
104
 
 
3
  import shutil
4
  from pathlib import Path
5
  import uuid
6
+ from datetime import datetime
7
  try:
8
  import tomllib
9
  except ModuleNotFoundError: # Py<3.11
 
74
  CFG = _load_yaml("config.yaml")
75
 
76
  # Ajuste de variables según tu esquema YAML
77
+ # Para la interfaz demo, usaremos sempre la carpeta "temp" local
78
  DATA_DIR = "temp"
79
  BACKEND_BASE_URL = CFG.get("api", {}).get("base_url", "http://localhost:8000")
80
  TTS_URL = "https://veureu-tts.hf.space" # Forzar URL correcta
 
83
  print(f"🔧 Longitud de TTS_URL: {len(TTS_URL)}")
84
  print(f"🔧 TTS_URL repr: {repr(TTS_URL)}")
85
  USE_MOCK = bool(CFG.get("app", {}).get("use_mock", False)) # si no la tienes en el yaml, queda False
86
+
87
+ COMPLIANCE_CFG = CFG.get("compliance", {}) or {}
88
+ PRIVATE_BLOCKCHAIN_ENABLE = bool(COMPLIANCE_CFG.get("private_blockchain_enable", False))
89
+ PUBLIC_BLOCKCHAIN_ENABLE = bool(COMPLIANCE_CFG.get("public_blockchain_enable", False))
90
+ MONTHLY_DIGEST_ENABLED = bool(COMPLIANCE_CFG.get("monthly_digest_enabled", False))
91
  API_TOKEN = CFG.get("api", {}).get("token") or os.getenv("API_SHARED_TOKEN")
92
 
93
  # Cliente del backend (engine)
 
103
  set_db_path(DB_PATH)
104
 
105
  # Configurar si els esdeveniments s'han de registrar a SQLite o a AWS QLDB
106
+ set_blockchain_enabled(PRIVATE_BLOCKCHAIN_ENABLE)
107
+
108
+ # Si està habilitat el registre públic i el digest mensual, publicar al
109
+ # principi de cada mes un digest global via servei de compliance.
110
+ if PUBLIC_BLOCKCHAIN_ENABLE and MONTHLY_DIGEST_ENABLED:
111
+ try:
112
+ today = datetime.utcnow().date()
113
+ if today.day == 1:
114
+ period = today.strftime("%Y-%m")
115
+ compliance_client.publish_monthly_digest(period)
116
+ except Exception:
117
+ # No bloquejar l'app per errors de blockchain/compliance
118
+ pass
119
 
120
  init_schema()
121
 
compliance_client.py CHANGED
@@ -92,6 +92,20 @@ class ComplianceClient:
92
 
93
  def is_authenticated(self) -> bool:
94
  """Verifica si el usuario actual está autenticado"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  if "auth_token" not in st.session_state:
96
  return False
97
 
@@ -113,6 +127,11 @@ class ComplianceClient:
113
 
114
  def get_current_user(self) -> Optional[str]:
115
  """Obtiene email del usuario actual"""
 
 
 
 
 
116
  if self.is_authenticated():
117
  return st.session_state.get("current_user", {}).get("email")
118
  return None
@@ -122,6 +141,11 @@ class ComplianceClient:
122
  print("[OAuth] show_login_button llamado") # Forzar salida a log del Space
123
  logger.info("[OAuth] show_login_button llamado")
124
 
 
 
 
 
 
125
  # Verificar si OAuth está configurado en el servicio
126
  try:
127
  health_response = self._make_request("GET", "/")
@@ -162,7 +186,7 @@ class ComplianceClient:
162
  st.markdown(f"""
163
  ### 🔐 Iniciar sessió
164
 
165
- Per continuar, necessites iniciar sessió amb el teu compte de Google.
166
 
167
  <a href="{login_url}" target="_top">
168
  <button style="
@@ -229,6 +253,29 @@ class ComplianceClient:
229
  logger.error("[OAuth] login_url és None: comprova COMPLIANCE_SERVICE_URL i l'estat del servei de compliance")
230
 
231
  return False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
232
 
233
  def _show_demo_login(self) -> bool:
234
  """Muestra formulario de login por SMS en ventana desplegable cuando OAuth no está configurado"""
 
92
 
93
  def is_authenticated(self) -> bool:
94
  """Verifica si el usuario actual está autenticado"""
95
+ # Primero verificar si hay autenticación tradicional
96
+ if 'user' in st.session_state and st.session_state.user:
97
+ logger.info(f"Usuario ya autenticado tradicionalmente: {st.session_state.user.get('username', 'unknown')}")
98
+ # Crear token automático para usuarios tradicionales
99
+ if 'auth_token' not in st.session_state:
100
+ st.session_state.auth_token = f"traditional_token_{int(time.time())}"
101
+ st.session_state.current_user = {
102
+ "email": f"{st.session_state.user.get('username', 'unknown')}@veureu.local",
103
+ "name": st.session_state.user.get('username', 'Unknown'),
104
+ "method": "traditional"
105
+ }
106
+ return True
107
+
108
+ # Si no hay autenticación tradicional, verificar por token
109
  if "auth_token" not in st.session_state:
110
  return False
111
 
 
127
 
128
  def get_current_user(self) -> Optional[str]:
129
  """Obtiene email del usuario actual"""
130
+ # Primero verificar autenticación tradicional
131
+ if 'user' in st.session_state and st.session_state.user:
132
+ return f"{st.session_state.user.get('username', 'unknown')}@veureu.local"
133
+
134
+ # Si no, verificar autenticación por token
135
  if self.is_authenticated():
136
  return st.session_state.get("current_user", {}).get("email")
137
  return None
 
141
  print("[OAuth] show_login_button llamado") # Forzar salida a log del Space
142
  logger.info("[OAuth] show_login_button llamado")
143
 
144
+ # Si ya hay usuario autenticado tradicionalmente, no mostrar login
145
+ if 'user' in st.session_state and st.session_state.user:
146
+ logger.info(f"[OAuth] Usuario ya autenticado tradicionalmente: {st.session_state.user.get('username', 'unknown')}")
147
+ return True
148
+
149
  # Verificar si OAuth está configurado en el servicio
150
  try:
151
  health_response = self._make_request("GET", "/")
 
186
  st.markdown(f"""
187
  ### 🔐 Iniciar sessió
188
 
189
+ Per continuar, necessitas iniciar sessió amb el teu compte de Google.
190
 
191
  <a href="{login_url}" target="_top">
192
  <button style="
 
253
  logger.error("[OAuth] login_url és None: comprova COMPLIANCE_SERVICE_URL i l'estat del servei de compliance")
254
 
255
  return False
256
+
257
+ # === VERIFICACIÓ PER TELÈFON / WHATSAPP ===
258
+
259
+ def create_phone_verification_session(
260
+ self,
261
+ *,
262
+ page: str,
263
+ user_id: Optional[str] = None,
264
+ email: Optional[str] = None,
265
+ action: Optional[str] = None,
266
+ ) -> Optional[Dict[str, Any]]:
267
+ """Crea una sessió de verificació de telèfon al backend de compliance.
268
+
269
+ Endpoint: POST /phone_verification/create_session
270
+ """
271
+
272
+ payload: Dict[str, Any] = {
273
+ "page": page,
274
+ "user_id": user_id,
275
+ "email": email,
276
+ "action": action,
277
+ }
278
+ return self._make_request("POST", "/phone_verification/create_session", payload)
279
 
280
  def _show_demo_login(self) -> bool:
281
  """Muestra formulario de login por SMS en ventana desplegable cuando OAuth no está configurado"""
config.yaml CHANGED
@@ -5,6 +5,8 @@ app:
5
  # tamaño máximo de subida en MB
6
  max_upload_mb: 500
7
 
 
 
8
  api:
9
  # URL pública de tu space engine (puedes interpolar la var de entorno)
10
  base_url: "${API_BASE_URL}"
@@ -39,7 +41,9 @@ ui:
39
  page_icon: "🎧"
40
  wide_layout: true
41
 
42
- blockchain:
43
- # Habilita o deshabilita el registre d'esdeveniments a AWS QLDB/Polygon
44
- enabled: false
 
 
45
 
 
5
  # tamaño máximo de subida en MB
6
  max_upload_mb: 500
7
 
8
+ data_origin: "internal"
9
+
10
  api:
11
  # URL pública de tu space engine (puedes interpolar la var de entorno)
12
  base_url: "${API_BASE_URL}"
 
41
  page_icon: "🎧"
42
  wide_layout: true
43
 
44
+ compliance:
45
+ # Controla la integració amb AWS QLDB (registre privat) i Polygon (registre públic)
46
+ private_blockchain_enable: false
47
+ public_blockchain_enable: false
48
+ monthly_digest_enabled: false
49
 
mobile_verification.py CHANGED
@@ -14,6 +14,8 @@ import streamlit as st
14
  from datetime import datetime
15
  from typing import Optional, Tuple
16
 
 
 
17
 
18
  def log(msg: str):
19
  """Helper per logging amb timestamp"""
@@ -150,20 +152,48 @@ def render_mobile_verification_screen(username: str, role: str) -> Optional[bool
150
  key="mobile_accept_terms"
151
  )
152
 
153
- # Botón para enviar código
154
  if st.button(
155
  "📤 Enviar codi de verificació",
156
  type="primary",
157
  disabled=not (phone_number and accept_terms)
158
  ):
159
  full_phone = f"{country_code}{phone_number}"
 
 
160
  if send_sms_code(full_phone):
161
  st.session_state.sms_step = 'verify'
162
  st.session_state.sms_phone = full_phone
163
  st.success(f"✅ Codi enviat a {full_phone}")
164
- st.rerun()
165
  else:
166
  st.error("❌ Error enviant el codi. Torna-ho a intentar.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
 
168
  # Si se envió el SMS, mostrar campo para verificar código
169
  if st.session_state.sms_step == 'verify':
 
14
  from datetime import datetime
15
  from typing import Optional, Tuple
16
 
17
+ from compliance_client import compliance_client
18
+
19
 
20
  def log(msg: str):
21
  """Helper per logging amb timestamp"""
 
152
  key="mobile_accept_terms"
153
  )
154
 
155
+ # Botón para enviar código (SMS demo) i, en paral·lel, registrar sessió per WhatsApp al backend
156
  if st.button(
157
  "📤 Enviar codi de verificació",
158
  type="primary",
159
  disabled=not (phone_number and accept_terms)
160
  ):
161
  full_phone = f"{country_code}{phone_number}"
162
+
163
+ # 1) Flux SMS demo local (no canvia)
164
  if send_sms_code(full_phone):
165
  st.session_state.sms_step = 'verify'
166
  st.session_state.sms_phone = full_phone
167
  st.success(f"✅ Codi enviat a {full_phone}")
 
168
  else:
169
  st.error("❌ Error enviant el codi. Torna-ho a intentar.")
170
+
171
+ # 2) Crear sessió de verificació per WhatsApp al backend de compliance
172
+ try:
173
+ user_obj = st.session_state.get("user") or {}
174
+ user_id = str(user_obj.get("id")) if isinstance(user_obj, dict) and "id" in user_obj else None
175
+ email = None # es pot afegir més endavant si hi ha email disponible
176
+
177
+ resp = compliance_client.create_phone_verification_session(
178
+ page="demo/mobile_verification",
179
+ user_id=user_id,
180
+ email=email,
181
+ action="accept_terms_mobile_verification",
182
+ )
183
+
184
+ if resp and "session_code" in resp:
185
+ session_code = resp["session_code"]
186
+ expires_at = resp.get("expires_at")
187
+ st.info(
188
+ "📱 **Verificació per WhatsApp (registre de firma)**\n\n"
189
+ "Envia un WhatsApp des del teu mòbil al **número oficial del projecte** amb el text:\n\n"
190
+ f"`ACEPTO {session_code}`\n\n"
191
+ "Tens fins a **5 minuts** per enviar-lo."
192
+ )
193
+ else:
194
+ log("[COMPLIANCE] No s'ha pogut crear la sessió de verificació per telèfon")
195
+ except Exception as e:
196
+ log(f"[COMPLIANCE] Error cridant create_phone_verification_session: {e}")
197
 
198
  # Si se envió el SMS, mostrar campo para verificar código
199
  if st.session_state.sms_step == 'verify':
persistent_data_gate.py CHANGED
@@ -22,6 +22,38 @@ def _load_data_origin(base_dir: Path) -> str:
22
  return origin
23
 
24
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  def ensure_temp_databases(base_dir: Path, api_client) -> None:
26
  """Garantiza que las BDs *.db estén presentes en demo/temp antes del login.
27
 
@@ -30,6 +62,8 @@ def ensure_temp_databases(base_dir: Path, api_client) -> None:
30
  """
31
 
32
  data_origin = _load_data_origin(base_dir)
 
 
33
  temp_dir = base_dir / "temp"
34
  temp_dir.mkdir(parents=True, exist_ok=True)
35
 
@@ -198,7 +232,48 @@ def confirm_changes_and_logout(base_dir: Path, api_client, session_id: str) -> N
198
  except Exception:
199
  pass
200
 
201
- # --- 2) Nous vídeos a videos.db associats a la sessió ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202
  videos_db = temp_dir / "videos.db"
203
  new_sha1s: set[str] = set()
204
 
 
22
  return origin
23
 
24
 
25
+ def _load_compliance_flags(base_dir: Path) -> dict:
26
+ """Llegeix flags de la secció 'compliance' de demo/config.yaml.
27
+
28
+ Retorna un dict amb claus:
29
+ - private_blockchain_enable
30
+ - public_blockchain_enable
31
+ - monthly_digest_enabled
32
+ """
33
+
34
+ cfg_path = base_dir / "config.yaml"
35
+ flags = {
36
+ "private_blockchain_enable": False,
37
+ "public_blockchain_enable": False,
38
+ "monthly_digest_enabled": False,
39
+ }
40
+
41
+ try:
42
+ import yaml
43
+
44
+ with cfg_path.open("r", encoding="utf-8") as f:
45
+ cfg = yaml.safe_load(f) or {}
46
+ comp = cfg.get("compliance", {}) or {}
47
+ flags["private_blockchain_enable"] = bool(comp.get("private_blockchain_enable", False))
48
+ flags["public_blockchain_enable"] = bool(comp.get("public_blockchain_enable", False))
49
+ flags["monthly_digest_enabled"] = bool(comp.get("monthly_digest_enabled", False))
50
+ except Exception:
51
+ # Manté valors per defecte en cas d'error
52
+ return flags
53
+
54
+ return flags
55
+
56
+
57
  def ensure_temp_databases(base_dir: Path, api_client) -> None:
58
  """Garantiza que las BDs *.db estén presentes en demo/temp antes del login.
59
 
 
62
  """
63
 
64
  data_origin = _load_data_origin(base_dir)
65
+ compliance_flags = _load_compliance_flags(base_dir)
66
+ public_blockchain_enable = bool(compliance_flags.get("public_blockchain_enable", False))
67
  temp_dir = base_dir / "temp"
68
  temp_dir.mkdir(parents=True, exist_ok=True)
69
 
 
232
  except Exception:
233
  pass
234
 
235
+ # --- 2) Digest d'esdeveniments per a la sessió (public blockchain) ---
236
+ if public_blockchain_enable:
237
+ events_db = temp_dir / "events.db"
238
+ try:
239
+ import sqlite3
240
+ import hashlib
241
+ import json
242
+
243
+ with sqlite3.connect(str(events_db)) as econn:
244
+ econn.row_factory = sqlite3.Row
245
+ cur = econn.cursor()
246
+
247
+ # Comprovar que existeix la taula events
248
+ cur.execute(
249
+ "SELECT name FROM sqlite_master WHERE type='table' AND name='events'"
250
+ )
251
+ if cur.fetchone():
252
+ cur.execute(
253
+ "SELECT * FROM events WHERE session = ?", (session_id,)
254
+ )
255
+ rows = cur.fetchall()
256
+ if rows:
257
+ payload = []
258
+ for r in rows:
259
+ payload.append({k: r[k] for k in r.keys()})
260
+
261
+ serialized = json.dumps(
262
+ payload, sort_keys=True, separators=(",", ":")
263
+ )
264
+ digest_hash = hashlib.sha256(
265
+ serialized.encode("utf-8")
266
+ ).hexdigest()
267
+ # Simular publicació a Polygon: log amb prefix clar
268
+ print(
269
+ f"[POLYGON EVENTS DIGEST] session={session_id} "
270
+ f"events={len(payload)} hash={digest_hash}"
271
+ )
272
+ except Exception:
273
+ # No aturar el procés si hi ha errors en el càlcul del digest
274
+ pass
275
+
276
+ # --- 3) Nous vídeos a videos.db associats a la sessió ---
277
  videos_db = temp_dir / "videos.db"
278
  new_sha1s: set[str] = set()
279