From 3f7b1667675053bd6812d85133e8641935000b85 Mon Sep 17 00:00:00 2001 From: icamarillo Date: Tue, 10 Mar 2026 13:46:30 -0600 Subject: [PATCH] Sistema limpio --- backend/auth_backup.ts | 104 +++++++++++++++ backend/check_lines.py | 6 + backend/check_user.py | 14 ++ backend/fix_response.py | 16 +++ backend/fix_syntax.py | 18 +++ backend/fix_users.py | 28 ++++ backend/fix_users2.py | 60 +++++++++ backend/fix_users3.py | 36 ++++++ backend/fix_users4.py | 38 ++++++ backend/show_context.py | 11 ++ check_lines.py | 6 + check_user.py | 14 ++ fix_response.py | 16 +++ fix_syntax.py | 18 +++ fix_users.py | 28 ++++ fix_users2.py | 60 +++++++++ fix_users3.py | 36 ++++++ fix_users4.py | 38 ++++++ frontend-client/src/lib/stores/auth.ts.bak | 104 +++++++++++++++ frontend-internal/src/lib/stores/auth.ts | 142 ++++----------------- show_context.py | 11 ++ 21 files changed, 687 insertions(+), 117 deletions(-) create mode 100644 backend/auth_backup.ts create mode 100644 backend/check_lines.py create mode 100644 backend/check_user.py create mode 100644 backend/fix_response.py create mode 100644 backend/fix_syntax.py create mode 100644 backend/fix_users.py create mode 100644 backend/fix_users2.py create mode 100644 backend/fix_users3.py create mode 100644 backend/fix_users4.py create mode 100644 backend/show_context.py create mode 100644 check_lines.py create mode 100644 check_user.py create mode 100644 fix_response.py create mode 100644 fix_syntax.py create mode 100644 fix_users.py create mode 100644 fix_users2.py create mode 100644 fix_users3.py create mode 100644 fix_users4.py create mode 100644 frontend-client/src/lib/stores/auth.ts.bak create mode 100644 show_context.py diff --git a/backend/auth_backup.ts b/backend/auth_backup.ts new file mode 100644 index 0000000..4acf562 --- /dev/null +++ b/backend/auth_backup.ts @@ -0,0 +1,104 @@ +import type { Writable } from 'svelte/store'; +import { writable } from 'svelte/store'; +export interface User { + id: string; + email: string; + first_name: string; + last_name: string; + tenant_id: string; + role: 'CLIENT_ADMIN' | 'CLIENT_USER'; + is_active: boolean; + is_two_factor_enabled: boolean; + created_at: string; +} +export interface AuthState { + user: User | null; + token: string | null; + isAuthenticated: boolean; + isLoading: boolean; +} +export interface LoginRequest { + email: string; + password: string; + tenant_slug: string; + totp_code?: string; +} +export interface LoginResponse { + access_token: string; + token_type: string; + expires_in: number; + user: User; +} +const initialState: AuthState = { + user: null, + token: null, + isAuthenticated: false, + isLoading: false +}; +function createAuthStore() { + const { subscribe, set, update }: Writable = writable(initialState); + let _state = initialState; + subscribe(s => { _state = s; }); + return { + subscribe, + init: async () => { + if (typeof window !== 'undefined') { + try { + const response = await fetch('/api/v1/auth/me', { + credentials: 'include', + headers: { 'X-App': 'client', 'X-Tenant-Slug': 'aduanasoft' } + }); + if (response.ok) { + const user = await response.json(); + set({ user, token: null, isAuthenticated: true, isLoading: false }); + } + } catch (error) {} + } + }, + login: async (credentials: LoginRequest): Promise => { + update(state => ({ ...state, isLoading: true })); + try { + const response = await fetch('/api/v1/auth/login', { + method: 'POST', + credentials: 'include', + headers: { + 'Content-Type': 'application/json', + 'X-Tenant-Slug': credentials.tenant_slug, + }, + body: JSON.stringify(credentials) + }); + if (!response.ok) { + const error = await response.json(); + throw new Error(error.detail || 'Login failed'); + } + const data: LoginResponse = await response.json(); + set({ user: data.user, token: data.access_token, isAuthenticated: true, isLoading: false }); + } catch (error) { + update(state => ({ ...state, isLoading: false })); + throw error; + } + }, + logout: async () => { + try { + const token = _state.token; + await fetch('/api/v1/auth/logout', { + method: 'POST', + credentials: 'include', + headers: { + 'X-App': 'client', + 'X-Tenant-Slug': 'aduanasoft', + ...(token ? { 'Authorization': `Bearer ${token}` } : {}) + } + }); + } catch {} + set(initialState); + if (typeof window !== 'undefined') { + window.location.href = '/login'; + } + }, + updateUser: (user: User) => { update(state => ({ ...state, user })); }, + setUser: (user: User) => { set({ user, token: null, isAuthenticated: true, isLoading: false }); }, + setLoading: (isLoading: boolean) => { update(state => ({ ...state, isLoading })); } + }; +} +export const auth = createAuthStore(); \ No newline at end of file diff --git a/backend/check_lines.py b/backend/check_lines.py new file mode 100644 index 0000000..378adfc --- /dev/null +++ b/backend/check_lines.py @@ -0,0 +1,6 @@ +with open("/app/app/api/v1/endpoints/users.py", "r") as f: + lines = f.readlines() + +for i, line in enumerate(lines): + if "tenant_id == current_user.tenant_id" in line: + print(f"Linea {i+1}: {line.rstrip()}") diff --git a/backend/check_user.py b/backend/check_user.py new file mode 100644 index 0000000..637289d --- /dev/null +++ b/backend/check_user.py @@ -0,0 +1,14 @@ +import asyncio +from app.core.database import AsyncSessionLocal +from app.models.user import User +from sqlalchemy import select +import uuid + +async def check(): + async with AsyncSessionLocal() as db: + result = await db.execute(select(User)) + users = result.scalars().all() + for u in users: + print(f"ID: {u.id} | Email: {u.email} | Tenant: {u.tenant_id} | Rol: {u.role}") + +asyncio.run(check()) diff --git a/backend/fix_response.py b/backend/fix_response.py new file mode 100644 index 0000000..b5ecf13 --- /dev/null +++ b/backend/fix_response.py @@ -0,0 +1,16 @@ +with open("/app/app/api/v1/endpoints/auth.py", "r") as f: + lines = f.readlines() + +# Linea 286 (0-indexed 285): "tenant_id": str(user.tenant_id), +# Agregar tenant_slug despues de tenant_id +for i, line in enumerate(lines): + if '"tenant_id": str(user.tenant_id),' in line: + indent = " " + new_line = indent + '"tenant_slug": tenant.slug if tenant else str(user.tenant_id),\n' + lines.insert(i + 1, new_line) + print(f"OK: tenant_slug agregado en linea {i+2}") + break + +with open("/app/app/api/v1/endpoints/auth.py", "w") as f: + f.writelines(lines) +print("Listo") diff --git a/backend/fix_syntax.py b/backend/fix_syntax.py new file mode 100644 index 0000000..2d591c9 --- /dev/null +++ b/backend/fix_syntax.py @@ -0,0 +1,18 @@ +with open("/app/app/api/v1/endpoints/users.py") as f: + lines = f.readlines() + +new_block = [ + " if current_user.role.value == 'ADMIN':\n", + " query = select(User).where(User.id == user_id)\n", + " else:\n", + " query = select(User).where(\n", + " User.id == user_id,\n", + " User.tenant_id == current_user.tenant_id\n", + " )\n", +] + +lines[150:161] = new_block + +with open("/app/app/api/v1/endpoints/users.py", "w") as f: + f.writelines(lines) +print("Listo") diff --git a/backend/fix_users.py b/backend/fix_users.py new file mode 100644 index 0000000..c048025 --- /dev/null +++ b/backend/fix_users.py @@ -0,0 +1,28 @@ +with open("/app/app/api/v1/endpoints/users.py", "r") as f: + content = f.read() + +old1 = " User.tenant_id == current_user.tenant_id # " + "\u2705" + " Seguridad multi-tenant" +new1 = """ from app.models.user import UserRole as _UserRole + if current_user.role == _UserRole.ADMIN: + query = select(User).where(User.id == user_id) + else: + query = select(User).where( + User.id == user_id, + User.tenant_id == current_user.tenant_id + )""" + +if old1 in content: + content = content.replace(old1, new1) + print("OK bloque 1") +else: + print("SKIP bloque 1 - buscando alternativa") + old1b = " User.tenant_id == current_user.tenant_id\n )\n result = await db.execute(query)\n if not user:" + new1b = " User.tenant_id == current_user.tenant_id\n )\n result = await db.execute(query)\n if not user:" + print("Lineas con tenant_id encontradas:") + for i, line in enumerate(content.split("\n")): + if "tenant_id == current_user.tenant_id" in line: + print(f" Linea {i}: {line}") + +with open("/app/app/api/v1/endpoints/users.py", "w") as f: + f.write(content) +print("Listo") diff --git a/backend/fix_users2.py b/backend/fix_users2.py new file mode 100644 index 0000000..61ba1b9 --- /dev/null +++ b/backend/fix_users2.py @@ -0,0 +1,60 @@ +with open("/app/app/api/v1/endpoints/users.py", "r") as f: + content = f.read() + +from app.models.user import UserRole as _UserRole + +# Reemplazar el patron comun de query con filtro de tenant +# por una version que permite a ADMIN ver todos los tenants + +old_get_user = """ query = select(User).where( + User.id == user_id, + User.tenant_id == current_user.tenant_id + ) + result = await db.execute(query) + user = result.scalar_one_or_none() + if not user:""" + +new_get_user = """ if current_user.role.value == "ADMIN": + query = select(User).where(User.id == user_id) + else: + query = select(User).where( + User.id == user_id, + User.tenant_id == current_user.tenant_id + ) + result = await db.execute(query) + user = result.scalar_one_or_none() + if not user:""" + +old_update_user = """ query = select(User).where( + User.id == user_id, + User.tenant_id == current_user.tenant_id + ) + result = await db.execute(query) + db_user = result.scalar_one_or_none() + if not db_user:""" + +new_update_user = """ if current_user.role.value == "ADMIN": + query = select(User).where(User.id == user_id) + else: + query = select(User).where( + User.id == user_id, + User.tenant_id == current_user.tenant_id + ) + result = await db.execute(query) + db_user = result.scalar_one_or_none() + if not db_user:""" + +count = 0 +for old, new in [(old_get_user, new_get_user), (old_update_user, new_update_user)]: + occurrences = content.count(old) + if occurrences > 0: + content = content.replace(old, new) + count += occurrences + print(f"OK: {occurrences} ocurrencia(s) reemplazada(s)") + else: + print(f"SKIP: bloque no encontrado") + +with open("/app/app/api/v1/endpoints/users.py", "w") as f: + f.write(content) + +print(f"Total: {count} reemplazos aplicados") diff --git a/backend/fix_users3.py b/backend/fix_users3.py new file mode 100644 index 0000000..4f8e7fb --- /dev/null +++ b/backend/fix_users3.py @@ -0,0 +1,36 @@ +with open("/app/app/api/v1/endpoints/users.py", "r") as f: + content = f.read() + +old1 = """ # Buscar usuario + query = select(User).where( + User.id == user_id, + User.tenant_id == current_user.tenant_id + ) + result = await db.execute(query) + db_user = result.scalar_one_or_none() + + if not db_user:""" + +new1 = """ # Buscar usuario - ADMIN global puede editar cualquier tenant + if current_user.role.value == "ADMIN": + query = select(User).where(User.id == user_id) + else: + query = select(User).where( + User.id == user_id, + User.tenant_id == current_user.tenant_id + ) + result = await db.execute(query) + db_user = result.scalar_one_or_none() + + if not db_user:""" + +count = content.count(old1) +if count > 0: + content = content.replace(old1, new1) + print(f"OK: {count} bloques reemplazados") +else: + print("ERROR: bloque no encontrado") + +with open("/app/app/api/v1/endpoints/users.py", "w") as f: + f.write(content) +print("Listo") diff --git a/backend/fix_users4.py b/backend/fix_users4.py new file mode 100644 index 0000000..4ee96b7 --- /dev/null +++ b/backend/fix_users4.py @@ -0,0 +1,38 @@ +with open("/app/app/api/v1/endpoints/users.py", "r") as f: + lines = f.readlines() + +admin_check = [ + " # Buscar usuario - ADMIN global puede editar cualquier tenant\n", + " if current_user.role.value == \"ADMIN\":\n", + " query = select(User).where(User.id == user_id)\n", + " else:\n", + " query = select(User).where(\n", + " User.id == user_id,\n", + " User.tenant_id == current_user.tenant_id\n", + " )\n", +] + +# Reemplazar bloques en lineas 198, 305, 382 (0-indexed: 197, 304, 381) +replaced = 0 +new_lines = lines[:] +i = 0 +while i < len(new_lines): + if (new_lines[i].strip() == "# Buscar usuario" and + i+1 < len(new_lines) and "select(User).where(" in new_lines[i+1] and + i+2 < len(new_lines) and "User.id == user_id," in new_lines[i+2] and + i+3 < len(new_lines) and "User.tenant_id == current_user.tenant_id" in new_lines[i+3]): + + indent = " " + new_block = admin_check[:] + # Remove old 4 lines of query block (comment + query 4 lines) + new_lines[i:i+5] = new_block + replaced += 1 + i += len(new_block) + else: + i += 1 + +print(f"Reemplazos realizados: {replaced}") + +with open("/app/app/api/v1/endpoints/users.py", "w") as f: + f.writelines(new_lines) +print("Listo") diff --git a/backend/show_context.py b/backend/show_context.py new file mode 100644 index 0000000..8be63fc --- /dev/null +++ b/backend/show_context.py @@ -0,0 +1,11 @@ +with open("/app/app/api/v1/endpoints/users.py", "r") as f: + lines = f.readlines() + +# Mostrar contexto alrededor de lineas con tenant_id +targets = [51, 92, 159, 200, 307, 384] +for t in targets: + print(f"\n=== Linea {t} ===") + start = max(0, t-5) + end = min(len(lines), t+5) + for i in range(start, end): + print(f"{i+1}: {lines[i].rstrip()}") diff --git a/check_lines.py b/check_lines.py new file mode 100644 index 0000000..378adfc --- /dev/null +++ b/check_lines.py @@ -0,0 +1,6 @@ +with open("/app/app/api/v1/endpoints/users.py", "r") as f: + lines = f.readlines() + +for i, line in enumerate(lines): + if "tenant_id == current_user.tenant_id" in line: + print(f"Linea {i+1}: {line.rstrip()}") diff --git a/check_user.py b/check_user.py new file mode 100644 index 0000000..637289d --- /dev/null +++ b/check_user.py @@ -0,0 +1,14 @@ +import asyncio +from app.core.database import AsyncSessionLocal +from app.models.user import User +from sqlalchemy import select +import uuid + +async def check(): + async with AsyncSessionLocal() as db: + result = await db.execute(select(User)) + users = result.scalars().all() + for u in users: + print(f"ID: {u.id} | Email: {u.email} | Tenant: {u.tenant_id} | Rol: {u.role}") + +asyncio.run(check()) diff --git a/fix_response.py b/fix_response.py new file mode 100644 index 0000000..b5ecf13 --- /dev/null +++ b/fix_response.py @@ -0,0 +1,16 @@ +with open("/app/app/api/v1/endpoints/auth.py", "r") as f: + lines = f.readlines() + +# Linea 286 (0-indexed 285): "tenant_id": str(user.tenant_id), +# Agregar tenant_slug despues de tenant_id +for i, line in enumerate(lines): + if '"tenant_id": str(user.tenant_id),' in line: + indent = " " + new_line = indent + '"tenant_slug": tenant.slug if tenant else str(user.tenant_id),\n' + lines.insert(i + 1, new_line) + print(f"OK: tenant_slug agregado en linea {i+2}") + break + +with open("/app/app/api/v1/endpoints/auth.py", "w") as f: + f.writelines(lines) +print("Listo") diff --git a/fix_syntax.py b/fix_syntax.py new file mode 100644 index 0000000..2d591c9 --- /dev/null +++ b/fix_syntax.py @@ -0,0 +1,18 @@ +with open("/app/app/api/v1/endpoints/users.py") as f: + lines = f.readlines() + +new_block = [ + " if current_user.role.value == 'ADMIN':\n", + " query = select(User).where(User.id == user_id)\n", + " else:\n", + " query = select(User).where(\n", + " User.id == user_id,\n", + " User.tenant_id == current_user.tenant_id\n", + " )\n", +] + +lines[150:161] = new_block + +with open("/app/app/api/v1/endpoints/users.py", "w") as f: + f.writelines(lines) +print("Listo") diff --git a/fix_users.py b/fix_users.py new file mode 100644 index 0000000..c048025 --- /dev/null +++ b/fix_users.py @@ -0,0 +1,28 @@ +with open("/app/app/api/v1/endpoints/users.py", "r") as f: + content = f.read() + +old1 = " User.tenant_id == current_user.tenant_id # " + "\u2705" + " Seguridad multi-tenant" +new1 = """ from app.models.user import UserRole as _UserRole + if current_user.role == _UserRole.ADMIN: + query = select(User).where(User.id == user_id) + else: + query = select(User).where( + User.id == user_id, + User.tenant_id == current_user.tenant_id + )""" + +if old1 in content: + content = content.replace(old1, new1) + print("OK bloque 1") +else: + print("SKIP bloque 1 - buscando alternativa") + old1b = " User.tenant_id == current_user.tenant_id\n )\n result = await db.execute(query)\n if not user:" + new1b = " User.tenant_id == current_user.tenant_id\n )\n result = await db.execute(query)\n if not user:" + print("Lineas con tenant_id encontradas:") + for i, line in enumerate(content.split("\n")): + if "tenant_id == current_user.tenant_id" in line: + print(f" Linea {i}: {line}") + +with open("/app/app/api/v1/endpoints/users.py", "w") as f: + f.write(content) +print("Listo") diff --git a/fix_users2.py b/fix_users2.py new file mode 100644 index 0000000..61ba1b9 --- /dev/null +++ b/fix_users2.py @@ -0,0 +1,60 @@ +with open("/app/app/api/v1/endpoints/users.py", "r") as f: + content = f.read() + +from app.models.user import UserRole as _UserRole + +# Reemplazar el patron comun de query con filtro de tenant +# por una version que permite a ADMIN ver todos los tenants + +old_get_user = """ query = select(User).where( + User.id == user_id, + User.tenant_id == current_user.tenant_id + ) + result = await db.execute(query) + user = result.scalar_one_or_none() + if not user:""" + +new_get_user = """ if current_user.role.value == "ADMIN": + query = select(User).where(User.id == user_id) + else: + query = select(User).where( + User.id == user_id, + User.tenant_id == current_user.tenant_id + ) + result = await db.execute(query) + user = result.scalar_one_or_none() + if not user:""" + +old_update_user = """ query = select(User).where( + User.id == user_id, + User.tenant_id == current_user.tenant_id + ) + result = await db.execute(query) + db_user = result.scalar_one_or_none() + if not db_user:""" + +new_update_user = """ if current_user.role.value == "ADMIN": + query = select(User).where(User.id == user_id) + else: + query = select(User).where( + User.id == user_id, + User.tenant_id == current_user.tenant_id + ) + result = await db.execute(query) + db_user = result.scalar_one_or_none() + if not db_user:""" + +count = 0 +for old, new in [(old_get_user, new_get_user), (old_update_user, new_update_user)]: + occurrences = content.count(old) + if occurrences > 0: + content = content.replace(old, new) + count += occurrences + print(f"OK: {occurrences} ocurrencia(s) reemplazada(s)") + else: + print(f"SKIP: bloque no encontrado") + +with open("/app/app/api/v1/endpoints/users.py", "w") as f: + f.write(content) + +print(f"Total: {count} reemplazos aplicados") diff --git a/fix_users3.py b/fix_users3.py new file mode 100644 index 0000000..4f8e7fb --- /dev/null +++ b/fix_users3.py @@ -0,0 +1,36 @@ +with open("/app/app/api/v1/endpoints/users.py", "r") as f: + content = f.read() + +old1 = """ # Buscar usuario + query = select(User).where( + User.id == user_id, + User.tenant_id == current_user.tenant_id + ) + result = await db.execute(query) + db_user = result.scalar_one_or_none() + + if not db_user:""" + +new1 = """ # Buscar usuario - ADMIN global puede editar cualquier tenant + if current_user.role.value == "ADMIN": + query = select(User).where(User.id == user_id) + else: + query = select(User).where( + User.id == user_id, + User.tenant_id == current_user.tenant_id + ) + result = await db.execute(query) + db_user = result.scalar_one_or_none() + + if not db_user:""" + +count = content.count(old1) +if count > 0: + content = content.replace(old1, new1) + print(f"OK: {count} bloques reemplazados") +else: + print("ERROR: bloque no encontrado") + +with open("/app/app/api/v1/endpoints/users.py", "w") as f: + f.write(content) +print("Listo") diff --git a/fix_users4.py b/fix_users4.py new file mode 100644 index 0000000..4ee96b7 --- /dev/null +++ b/fix_users4.py @@ -0,0 +1,38 @@ +with open("/app/app/api/v1/endpoints/users.py", "r") as f: + lines = f.readlines() + +admin_check = [ + " # Buscar usuario - ADMIN global puede editar cualquier tenant\n", + " if current_user.role.value == \"ADMIN\":\n", + " query = select(User).where(User.id == user_id)\n", + " else:\n", + " query = select(User).where(\n", + " User.id == user_id,\n", + " User.tenant_id == current_user.tenant_id\n", + " )\n", +] + +# Reemplazar bloques en lineas 198, 305, 382 (0-indexed: 197, 304, 381) +replaced = 0 +new_lines = lines[:] +i = 0 +while i < len(new_lines): + if (new_lines[i].strip() == "# Buscar usuario" and + i+1 < len(new_lines) and "select(User).where(" in new_lines[i+1] and + i+2 < len(new_lines) and "User.id == user_id," in new_lines[i+2] and + i+3 < len(new_lines) and "User.tenant_id == current_user.tenant_id" in new_lines[i+3]): + + indent = " " + new_block = admin_check[:] + # Remove old 4 lines of query block (comment + query 4 lines) + new_lines[i:i+5] = new_block + replaced += 1 + i += len(new_block) + else: + i += 1 + +print(f"Reemplazos realizados: {replaced}") + +with open("/app/app/api/v1/endpoints/users.py", "w") as f: + f.writelines(new_lines) +print("Listo") diff --git a/frontend-client/src/lib/stores/auth.ts.bak b/frontend-client/src/lib/stores/auth.ts.bak new file mode 100644 index 0000000..4acf562 --- /dev/null +++ b/frontend-client/src/lib/stores/auth.ts.bak @@ -0,0 +1,104 @@ +import type { Writable } from 'svelte/store'; +import { writable } from 'svelte/store'; +export interface User { + id: string; + email: string; + first_name: string; + last_name: string; + tenant_id: string; + role: 'CLIENT_ADMIN' | 'CLIENT_USER'; + is_active: boolean; + is_two_factor_enabled: boolean; + created_at: string; +} +export interface AuthState { + user: User | null; + token: string | null; + isAuthenticated: boolean; + isLoading: boolean; +} +export interface LoginRequest { + email: string; + password: string; + tenant_slug: string; + totp_code?: string; +} +export interface LoginResponse { + access_token: string; + token_type: string; + expires_in: number; + user: User; +} +const initialState: AuthState = { + user: null, + token: null, + isAuthenticated: false, + isLoading: false +}; +function createAuthStore() { + const { subscribe, set, update }: Writable = writable(initialState); + let _state = initialState; + subscribe(s => { _state = s; }); + return { + subscribe, + init: async () => { + if (typeof window !== 'undefined') { + try { + const response = await fetch('/api/v1/auth/me', { + credentials: 'include', + headers: { 'X-App': 'client', 'X-Tenant-Slug': 'aduanasoft' } + }); + if (response.ok) { + const user = await response.json(); + set({ user, token: null, isAuthenticated: true, isLoading: false }); + } + } catch (error) {} + } + }, + login: async (credentials: LoginRequest): Promise => { + update(state => ({ ...state, isLoading: true })); + try { + const response = await fetch('/api/v1/auth/login', { + method: 'POST', + credentials: 'include', + headers: { + 'Content-Type': 'application/json', + 'X-Tenant-Slug': credentials.tenant_slug, + }, + body: JSON.stringify(credentials) + }); + if (!response.ok) { + const error = await response.json(); + throw new Error(error.detail || 'Login failed'); + } + const data: LoginResponse = await response.json(); + set({ user: data.user, token: data.access_token, isAuthenticated: true, isLoading: false }); + } catch (error) { + update(state => ({ ...state, isLoading: false })); + throw error; + } + }, + logout: async () => { + try { + const token = _state.token; + await fetch('/api/v1/auth/logout', { + method: 'POST', + credentials: 'include', + headers: { + 'X-App': 'client', + 'X-Tenant-Slug': 'aduanasoft', + ...(token ? { 'Authorization': `Bearer ${token}` } : {}) + } + }); + } catch {} + set(initialState); + if (typeof window !== 'undefined') { + window.location.href = '/login'; + } + }, + updateUser: (user: User) => { update(state => ({ ...state, user })); }, + setUser: (user: User) => { set({ user, token: null, isAuthenticated: true, isLoading: false }); }, + setLoading: (isLoading: boolean) => { update(state => ({ ...state, isLoading })); } + }; +} +export const auth = createAuthStore(); \ No newline at end of file diff --git a/frontend-internal/src/lib/stores/auth.ts b/frontend-internal/src/lib/stores/auth.ts index 7c7632c..15684c3 100644 --- a/frontend-internal/src/lib/stores/auth.ts +++ b/frontend-internal/src/lib/stores/auth.ts @@ -1,23 +1,22 @@ -import { writable } from 'svelte/store'; import type { Writable } from 'svelte/store'; +import { writable } from 'svelte/store'; -// Types -export interface InternalUser { +export interface User { id: string; email: string; first_name: string; last_name: string; - role: 'ADMIN' | 'SUPPORT_MANAGER' | 'AGENT' | 'AUDITOR'; + tenant_id: string; + tenant_slug: string; + role: 'CLIENT_ADMIN' | 'CLIENT_USER'; is_active: boolean; is_two_factor_enabled: boolean; created_at: string; - tenant_id: string; } export interface AuthState { - user: InternalUser | null; + user: User | null; token: string | null; - refreshToken: string | null; isAuthenticated: boolean; isLoading: boolean; } @@ -25,181 +24,90 @@ export interface AuthState { export interface LoginRequest { email: string; password: string; - tenant_slug: string; + tenant_slug?: string; totp_code?: string; } export interface LoginResponse { access_token: string; - refresh_token: string; token_type: string; expires_in: number; - user: InternalUser; + user: User; } -export interface RefreshTokenRequest { - refresh_token: string; -} - -export interface TokenResponse { - access_token: string; - token_type: string; - expires_in: number; -} - -// Initial state const initialState: AuthState = { user: null, token: null, - refreshToken: null, isAuthenticated: false, isLoading: false }; -// Create auth store function createAuthStore() { - const { subscribe, set, update } = writable(initialState); - - // Track current state for uso interno (evita dependencias circulares) + const { subscribe, set, update }: Writable = writable(initialState); let _state = initialState; subscribe(s => { _state = s; }); return { subscribe, - - // Rehidrata sesión desde cookie HttpOnly (no toca localStorage) init: async () => { if (typeof window !== 'undefined') { try { const response = await fetch('/api/v1/auth/me', { credentials: 'include', - headers: { 'X-App': 'internal' } + headers: { 'X-App': 'client' } }); if (response.ok) { const user = await response.json(); - set({ - user, - token: null, - refreshToken: null, - isAuthenticated: true, - isLoading: false - }); + set({ user, token: null, isAuthenticated: true, isLoading: false }); } - // 401/400 es esperado cuando no hay sesión activa — no es un error - } catch (error) { - // Ignorar errores de red en init - } + } catch (error) {} } }, - - // Login login: async (credentials: LoginRequest): Promise => { update(state => ({ ...state, isLoading: true })); - try { const response = await fetch('/api/v1/auth/login', { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json', - 'X-Tenant-Slug': credentials.tenant_slug, + 'X-App': 'client', }, body: JSON.stringify(credentials) }); - if (!response.ok) { const error = await response.json(); throw new Error(error.detail || 'Login failed'); } - const data: LoginResponse = await response.json(); - - set({ - user: data.user, - token: data.access_token, - refreshToken: data.refresh_token, - isAuthenticated: true, - isLoading: false - }); + set({ user: data.user, token: data.access_token, isAuthenticated: true, isLoading: false }); } catch (error) { update(state => ({ ...state, isLoading: false })); throw error; } }, - - // Refresh Session - refreshSession: async (): Promise => { - const currentRefreshToken = _state.refreshToken; - - if (!currentRefreshToken) { - throw new Error("No refresh token available"); - } - - update(state => ({ ...state, isLoading: true })); - - try { - const response = await fetch('/api/v1/auth/refresh', { - method: 'POST', - credentials: 'include', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ refresh_token: currentRefreshToken }) - }); - - if (!response.ok) { - // If refresh fails, logout - if (response.status === 401 || response.status === 403) { - auth.logout(); - } - const error = await response.json(); - throw new Error(error.detail || 'Refresh failed'); - } - - const data: TokenResponse = await response.json(); - - update(state => ({ - ...state, - token: data.access_token, - isLoading: false - })); - - } catch (error) { - update(state => ({ ...state, isLoading: false })); - throw error; - } - }, - - // Logout logout: async () => { - // Llamar al backend para que borre la cookie HttpOnly try { + const token = _state.token; + const slug = _state.user?.tenant_slug || _state.user?.tenant_id || ''; await fetch('/api/v1/auth/logout', { method: 'POST', credentials: 'include', - headers: { 'X-App': 'internal' } + headers: { + 'X-App': 'client', + 'X-Tenant-Slug': slug, + ...(token ? { 'Authorization': `Bearer ${token}` } : {}) + } }); - } catch { /* ignorar errores de red */ } + } catch {} set(initialState); if (typeof window !== 'undefined') { window.location.href = '/login'; } }, - - // Update user data - updateUser: (user: InternalUser) => { - update(state => ({ ...state, user })); - }, - - // Set user from SSR pre-load (no fetch required) - setUser: (user: InternalUser) => { - set({ user, token: null, refreshToken: null, isAuthenticated: true, isLoading: false }); - }, - - // Set loading state - setLoading: (isLoading: boolean) => { - update(state => ({ ...state, isLoading })); - } + updateUser: (user: User) => { update(state => ({ ...state, user })); }, + setUser: (user: User) => { set({ user, token: null, isAuthenticated: true, isLoading: false }); }, + setLoading: (isLoading: boolean) => { update(state => ({ ...state, isLoading })); } }; } diff --git a/show_context.py b/show_context.py new file mode 100644 index 0000000..8be63fc --- /dev/null +++ b/show_context.py @@ -0,0 +1,11 @@ +with open("/app/app/api/v1/endpoints/users.py", "r") as f: + lines = f.readlines() + +# Mostrar contexto alrededor de lineas con tenant_id +targets = [51, 92, 159, 200, 307, 384] +for t in targets: + print(f"\n=== Linea {t} ===") + start = max(0, t-5) + end = min(len(lines), t+5) + for i in range(start, end): + print(f"{i+1}: {lines[i].rstrip()}")