iatoolkit 0.56.0__tar.gz → 0.57.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of iatoolkit might be problematic. Click here for more details.
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/PKG-INFO +1 -1
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/pyproject.toml +1 -1
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/iatoolkit.py +1 -1
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/repositories/models.py +26 -1
- iatoolkit-0.57.0/src/iatoolkit/services/auth_service.py +181 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/views/base_login_view.py +3 -0
- iatoolkit-0.57.0/src/iatoolkit/views/external_login_view.py +67 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/views/login_view.py +4 -3
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit.egg-info/PKG-INFO +1 -1
- iatoolkit-0.56.0/src/iatoolkit/services/auth_service.py +0 -77
- iatoolkit-0.56.0/src/iatoolkit/views/external_login_view.py +0 -110
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/readme.md +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/requirements.txt +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/setup.cfg +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/__init__.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/base_company.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/cli_commands.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/common/__init__.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/common/exceptions.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/common/routes.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/common/session_manager.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/common/util.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/company_registry.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/infra/__init__.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/infra/call_service.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/infra/connectors/__init__.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/infra/connectors/file_connector.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/infra/connectors/file_connector_factory.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/infra/connectors/google_cloud_storage_connector.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/infra/connectors/google_drive_connector.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/infra/connectors/local_file_connector.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/infra/connectors/s3_connector.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/infra/gemini_adapter.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/infra/google_chat_app.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/infra/llm_client.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/infra/llm_proxy.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/infra/llm_response.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/infra/mail_app.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/infra/openai_adapter.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/infra/redis_session_manager.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/repositories/__init__.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/repositories/database_manager.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/repositories/document_repo.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/repositories/llm_query_repo.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/repositories/profile_repo.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/repositories/tasks_repo.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/repositories/vs_repo.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/services/__init__.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/services/benchmark_service.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/services/branding_service.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/services/dispatcher_service.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/services/document_service.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/services/excel_service.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/services/file_processor_service.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/services/history_service.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/services/jwt_service.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/services/load_documents_service.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/services/mail_service.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/services/onboarding_service.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/services/profile_service.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/services/prompt_manager_service.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/services/query_service.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/services/search_service.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/services/sql_service.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/services/tasks_service.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/services/user_feedback_service.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/services/user_session_context_service.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/static/images/fernando.jpeg +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/static/js/chat_feedback.js +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/static/js/chat_filepond.js +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/static/js/chat_history.js +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/static/js/chat_main.js +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/static/js/chat_onboarding.js +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/static/styles/chat_iatoolkit.css +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/static/styles/chat_info.css +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/static/styles/chat_modal.css +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/static/styles/landing_page.css +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/static/styles/llm_output.css +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/static/styles/onboarding.css +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/system_prompts/format_styles.prompt +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/system_prompts/query_main.prompt +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/system_prompts/sql_rules.prompt +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/templates/_branding_styles.html +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/templates/_login_widget.html +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/templates/_navbar.html +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/templates/about.html +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/templates/base.html +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/templates/change_password.html +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/templates/chat.html +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/templates/chat_modals.html +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/templates/error.html +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/templates/forgot_password.html +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/templates/header.html +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/templates/index.html +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/templates/login_simulation.html +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/templates/onboarding_shell.html +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/templates/signup.html +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/templates/test.html +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/views/__init__.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/views/change_password_view.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/views/chat_token_request_view.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/views/file_store_api_view.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/views/forgot_password_view.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/views/history_api_view.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/views/index_view.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/views/init_context_api_view.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/views/llmquery_api_view.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/views/llmquery_web_view.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/views/login_simulation_view.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/views/prompt_api_view.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/views/signup_view.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/views/tasks_review_view.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/views/tasks_view.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/views/user_feedback_api_view.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/views/verify_user_view.py +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit.egg-info/SOURCES.txt +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit.egg-info/dependency_links.txt +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit.egg-info/requires.txt +0 -0
- {iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit.egg-info/top_level.txt +0 -0
|
@@ -19,7 +19,7 @@ from werkzeug.middleware.proxy_fix import ProxyFix
|
|
|
19
19
|
from injector import Binder, singleton, Injector
|
|
20
20
|
from importlib.metadata import version as _pkg_version, PackageNotFoundError
|
|
21
21
|
|
|
22
|
-
IATOOLKIT_VERSION = "0.
|
|
22
|
+
IATOOLKIT_VERSION = "0.57.0"
|
|
23
23
|
|
|
24
24
|
# global variable for the unique instance of IAToolkit
|
|
25
25
|
_iatoolkit_instance: Optional['IAToolkit'] = None
|
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
#
|
|
4
4
|
# IAToolkit is open source software.
|
|
5
5
|
|
|
6
|
-
from sqlalchemy import Column, Integer, String, DateTime, Enum, Text, JSON, Boolean, ForeignKey, Table
|
|
6
|
+
from sqlalchemy import Column, Integer, BigInteger, String, DateTime, Enum, Text, JSON, Boolean, ForeignKey, Table
|
|
7
7
|
from sqlalchemy.orm import DeclarativeBase
|
|
8
8
|
from sqlalchemy.orm import relationship, class_mapper, declarative_base
|
|
9
|
+
from sqlalchemy.sql import func
|
|
9
10
|
from datetime import datetime
|
|
10
11
|
from pgvector.sqlalchemy import Vector
|
|
11
12
|
from enum import Enum as PyEnum
|
|
@@ -307,3 +308,27 @@ class Prompt(Base):
|
|
|
307
308
|
|
|
308
309
|
company = relationship("Company", back_populates="prompts")
|
|
309
310
|
category = relationship("PromptCategory", back_populates="prompts")
|
|
311
|
+
|
|
312
|
+
class AccessLog(Base):
|
|
313
|
+
# Modelo ORM para registrar cada intento de acceso a la plataforma.
|
|
314
|
+
__tablename__ = 'iat_access_log'
|
|
315
|
+
|
|
316
|
+
id = Column(BigInteger, primary_key=True)
|
|
317
|
+
|
|
318
|
+
timestamp = Column(DateTime(timezone=True), server_default=func.now(), nullable=False, index=True)
|
|
319
|
+
company_short_name = Column(String(100), nullable=False, index=True)
|
|
320
|
+
user_identifier = Column(String(255), index=True)
|
|
321
|
+
|
|
322
|
+
# Cómo y el Resultado
|
|
323
|
+
auth_type = Column(String(20), nullable=False) # 'local', 'external_api', 'redeem_token', etc.
|
|
324
|
+
outcome = Column(String(10), nullable=False) # 'success' o 'failure'
|
|
325
|
+
reason_code = Column(String(50)) # Causa de fallo, ej: 'INVALID_CREDENTIALS'
|
|
326
|
+
|
|
327
|
+
# Contexto de la Petición
|
|
328
|
+
source_ip = Column(String(45), nullable=False)
|
|
329
|
+
user_agent_hash = Column(String(16)) # Hash corto del User-Agent
|
|
330
|
+
request_path = Column(String(255), nullable=False)
|
|
331
|
+
|
|
332
|
+
def __repr__(self):
|
|
333
|
+
return (f"<AccessLog(id={self.id}, company='{self.company_short_name}', "
|
|
334
|
+
f"user='{self.user_identifier}', outcome='{self.outcome}')>")
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
+
# Product: IAToolkit
|
|
3
|
+
#
|
|
4
|
+
# IAToolkit is open source software.
|
|
5
|
+
|
|
6
|
+
from flask import request
|
|
7
|
+
from injector import inject
|
|
8
|
+
from iatoolkit.services.profile_service import ProfileService
|
|
9
|
+
from iatoolkit.services.jwt_service import JWTService
|
|
10
|
+
from iatoolkit.repositories.database_manager import DatabaseManager
|
|
11
|
+
from iatoolkit.repositories.models import AccessLog
|
|
12
|
+
from flask import request
|
|
13
|
+
import logging
|
|
14
|
+
import hashlib
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class AuthService:
|
|
18
|
+
"""
|
|
19
|
+
Centralized service for handling authentication for all incoming requests.
|
|
20
|
+
It determines the user's identity based on either a Flask session cookie or an API Key.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
@inject
|
|
24
|
+
def __init__(self, profile_service: ProfileService,
|
|
25
|
+
jwt_service: JWTService,
|
|
26
|
+
db_manager: DatabaseManager
|
|
27
|
+
):
|
|
28
|
+
self.profile_service = profile_service
|
|
29
|
+
self.jwt_service = jwt_service
|
|
30
|
+
self.db_manager = db_manager
|
|
31
|
+
|
|
32
|
+
def login_local_user(self, company_short_name: str, email: str, password: str) -> dict:
|
|
33
|
+
# try to autenticate a local user, register the event and return the result
|
|
34
|
+
auth_response = self.profile_service.login(
|
|
35
|
+
company_short_name=company_short_name,
|
|
36
|
+
email=email,
|
|
37
|
+
password=password
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
if not auth_response.get('success'):
|
|
41
|
+
self.log_access(
|
|
42
|
+
company_short_name=company_short_name,
|
|
43
|
+
user_identifier=email,
|
|
44
|
+
auth_type='local',
|
|
45
|
+
outcome='failure',
|
|
46
|
+
reason_code='INVALID_CREDENTIALS',
|
|
47
|
+
)
|
|
48
|
+
else:
|
|
49
|
+
self.log_access(
|
|
50
|
+
company_short_name=company_short_name,
|
|
51
|
+
auth_type='local',
|
|
52
|
+
outcome='success',
|
|
53
|
+
user_identifier=auth_response.get('user_identifier')
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
return auth_response
|
|
57
|
+
|
|
58
|
+
def redeem_token_for_session(self, company_short_name: str, token: str) -> dict:
|
|
59
|
+
# redeem a token for a session, register the event and return the result
|
|
60
|
+
payload = self.jwt_service.validate_chat_jwt(token)
|
|
61
|
+
|
|
62
|
+
if not payload:
|
|
63
|
+
self.log_access(
|
|
64
|
+
company_short_name=company_short_name,
|
|
65
|
+
auth_type='redeem_token',
|
|
66
|
+
outcome='failure',
|
|
67
|
+
reason_code='JWT_INVALID'
|
|
68
|
+
)
|
|
69
|
+
return {'success': False, 'error': 'Token inválido o expirado'}
|
|
70
|
+
|
|
71
|
+
# 2. if token is valid, extract the user_identifier
|
|
72
|
+
user_identifier = payload.get('user_identifier')
|
|
73
|
+
try:
|
|
74
|
+
# create the Flask session
|
|
75
|
+
self.profile_service.set_session_for_user(company_short_name, user_identifier)
|
|
76
|
+
self.log_access(
|
|
77
|
+
company_short_name=company_short_name,
|
|
78
|
+
auth_type='redeem_token',
|
|
79
|
+
outcome='success',
|
|
80
|
+
user_identifier=user_identifier
|
|
81
|
+
)
|
|
82
|
+
return {'success': True, 'user_identifier': user_identifier}
|
|
83
|
+
except Exception as e:
|
|
84
|
+
logging.error(f"Error al crear la sesión desde token para {user_identifier}: {e}")
|
|
85
|
+
self.log_access(
|
|
86
|
+
company_short_name=company_short_name,
|
|
87
|
+
auth_type='redeem_token',
|
|
88
|
+
outcome='failure',
|
|
89
|
+
reason_code='SESSION_CREATION_FAILED',
|
|
90
|
+
user_identifier=user_identifier
|
|
91
|
+
)
|
|
92
|
+
return {'success': False, 'error': 'No se pudo crear la sesión del usuario'}
|
|
93
|
+
|
|
94
|
+
def verify(self) -> dict:
|
|
95
|
+
"""
|
|
96
|
+
Verifies the current request and identifies the user.
|
|
97
|
+
|
|
98
|
+
Returns a dictionary with:
|
|
99
|
+
- success: bool
|
|
100
|
+
- user_identifier: str (if successful)
|
|
101
|
+
- company_short_name: str (if successful)
|
|
102
|
+
- error_message: str (on failure)
|
|
103
|
+
- status_code: int (on failure)
|
|
104
|
+
"""
|
|
105
|
+
# --- Priority 1: Check for a valid Flask web session ---
|
|
106
|
+
session_info = self.profile_service.get_current_session_info()
|
|
107
|
+
if session_info and session_info.get('user_identifier'):
|
|
108
|
+
# User is authenticated via a web session cookie.
|
|
109
|
+
return {
|
|
110
|
+
"success": True,
|
|
111
|
+
"company_short_name": session_info['company_short_name'],
|
|
112
|
+
"user_identifier": session_info['user_identifier']
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
# --- Priority 2: Check for a valid API Key in headers ---
|
|
116
|
+
api_key = None
|
|
117
|
+
auth = request.headers.get('Authorization', '')
|
|
118
|
+
if isinstance(auth, str) and auth.lower().startswith('bearer '):
|
|
119
|
+
api_key = auth.split(' ', 1)[1].strip()
|
|
120
|
+
|
|
121
|
+
if api_key:
|
|
122
|
+
api_key_entry = self.profile_service.get_active_api_key_entry(api_key)
|
|
123
|
+
if not api_key_entry:
|
|
124
|
+
logging.info(f"Invalid or inactive API Key {api_key}")
|
|
125
|
+
return {"success": False, "error": "Invalid or inactive API Key", "status_code": 401}
|
|
126
|
+
|
|
127
|
+
# obtain the company from the api_key_entry
|
|
128
|
+
company = api_key_entry.company
|
|
129
|
+
|
|
130
|
+
# For API calls, the external_user_id must be provided in the request.
|
|
131
|
+
user_identifier = ''
|
|
132
|
+
if request.is_json:
|
|
133
|
+
data = request.get_json() or {}
|
|
134
|
+
user_identifier = data.get('user_identifier', '')
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
"success": True,
|
|
138
|
+
"company_short_name": company.short_name,
|
|
139
|
+
"user_identifier": user_identifier
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
# --- Failure: No valid credentials found ---
|
|
143
|
+
logging.info(f"Authentication required. No session cookie or API Key provided.")
|
|
144
|
+
return {"success": False, "error": "Authentication required. No session cookie or API Key provided.",
|
|
145
|
+
"status_code": 402}
|
|
146
|
+
|
|
147
|
+
def log_access(self,
|
|
148
|
+
company_short_name: str,
|
|
149
|
+
auth_type: str,
|
|
150
|
+
outcome: str,
|
|
151
|
+
user_identifier: str = None,
|
|
152
|
+
reason_code: str = None):
|
|
153
|
+
"""
|
|
154
|
+
Registra un intento de acceso en la base de datos.
|
|
155
|
+
Es "best-effort" y no debe interrumpir el flujo de autenticación.
|
|
156
|
+
"""
|
|
157
|
+
session = self.db_manager.scoped_session()
|
|
158
|
+
try:
|
|
159
|
+
# Capturar datos del contexto de la petición de Flask
|
|
160
|
+
source_ip = request.headers.get('X-Forwarded-For', request.remote_addr)
|
|
161
|
+
path = request.path
|
|
162
|
+
ua = request.headers.get('User-Agent', '')
|
|
163
|
+
ua_hash = hashlib.sha256(ua.encode()).hexdigest()[:16] if ua else None
|
|
164
|
+
|
|
165
|
+
# Crear la entrada de log
|
|
166
|
+
log_entry = AccessLog(
|
|
167
|
+
company_short_name=company_short_name,
|
|
168
|
+
user_identifier=user_identifier,
|
|
169
|
+
auth_type=auth_type,
|
|
170
|
+
outcome=outcome,
|
|
171
|
+
reason_code=reason_code,
|
|
172
|
+
source_ip=source_ip,
|
|
173
|
+
user_agent_hash=ua_hash,
|
|
174
|
+
request_path=path,
|
|
175
|
+
)
|
|
176
|
+
session.add(log_entry)
|
|
177
|
+
session.commit()
|
|
178
|
+
|
|
179
|
+
except Exception as e:
|
|
180
|
+
logging.error(f"Fallo al escribir en AccessLog: {e}", exc_info=False)
|
|
181
|
+
session.rollback()
|
|
@@ -8,6 +8,7 @@ from flask.views import MethodView
|
|
|
8
8
|
from flask import render_template, url_for
|
|
9
9
|
from injector import inject
|
|
10
10
|
from iatoolkit.services.profile_service import ProfileService
|
|
11
|
+
from iatoolkit.services.auth_service import AuthService
|
|
11
12
|
from iatoolkit.services.query_service import QueryService
|
|
12
13
|
from iatoolkit.services.branding_service import BrandingService
|
|
13
14
|
from iatoolkit.services.onboarding_service import OnboardingService
|
|
@@ -23,6 +24,7 @@ class BaseLoginView(MethodView):
|
|
|
23
24
|
@inject
|
|
24
25
|
def __init__(self,
|
|
25
26
|
profile_service: ProfileService,
|
|
27
|
+
auth_service: AuthService,
|
|
26
28
|
jwt_service: JWTService,
|
|
27
29
|
branding_service: BrandingService,
|
|
28
30
|
prompt_service: PromptService,
|
|
@@ -30,6 +32,7 @@ class BaseLoginView(MethodView):
|
|
|
30
32
|
query_service: QueryService
|
|
31
33
|
):
|
|
32
34
|
self.profile_service = profile_service
|
|
35
|
+
self.auth_service = auth_service
|
|
33
36
|
self.jwt_service = jwt_service
|
|
34
37
|
self.branding_service = branding_service
|
|
35
38
|
self.prompt_service = prompt_service
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
+
# Product: IAToolkit
|
|
3
|
+
#
|
|
4
|
+
# IAToolkit is open source software.
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import logging
|
|
8
|
+
from flask import request, jsonify
|
|
9
|
+
from injector import inject
|
|
10
|
+
from iatoolkit.views.base_login_view import BaseLoginView
|
|
11
|
+
|
|
12
|
+
# Importar los servicios que necesita la clase base
|
|
13
|
+
from iatoolkit.services.profile_service import ProfileService
|
|
14
|
+
from iatoolkit.services.jwt_service import JWTService
|
|
15
|
+
|
|
16
|
+
class ExternalLoginView(BaseLoginView):
|
|
17
|
+
"""
|
|
18
|
+
Handles login for external users via API.
|
|
19
|
+
Authenticates and then delegates the path decision (fast/slow) to the base class.
|
|
20
|
+
"""
|
|
21
|
+
def post(self, company_short_name: str):
|
|
22
|
+
data = request.get_json()
|
|
23
|
+
if not data or 'user_identifier' not in data:
|
|
24
|
+
return jsonify({"error": "Falta user_identifier"}), 400
|
|
25
|
+
|
|
26
|
+
company = self.profile_service.get_company_by_short_name(company_short_name)
|
|
27
|
+
if not company:
|
|
28
|
+
return jsonify({"error": "Empresa no encontrada"}), 404
|
|
29
|
+
|
|
30
|
+
user_identifier = data.get('user_identifier')
|
|
31
|
+
if not user_identifier:
|
|
32
|
+
return jsonify({"error": "missing user_identifier"}), 404
|
|
33
|
+
|
|
34
|
+
# 1. Authenticate the API call.
|
|
35
|
+
auth_response = self.auth_service.verify()
|
|
36
|
+
if not auth_response.get("success"):
|
|
37
|
+
return jsonify(auth_response), 401
|
|
38
|
+
|
|
39
|
+
# 2. Create the external user session.
|
|
40
|
+
self.profile_service.create_external_user_session(company, user_identifier)
|
|
41
|
+
|
|
42
|
+
# 3. Delegate the path decision to the centralized logic.
|
|
43
|
+
try:
|
|
44
|
+
return self._handle_login_path(company_short_name, user_identifier, company)
|
|
45
|
+
except Exception as e:
|
|
46
|
+
logging.exception(f"Error processing external login path for {company_short_name}/{user_identifier}: {e}")
|
|
47
|
+
return jsonify({"error": f"Internal server error while starting chat. {str(e)}"}), 500
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class RedeemTokenApiView(BaseLoginView):
|
|
51
|
+
# this endpoint is only used ONLY by chat_main.js to redeem a chat token
|
|
52
|
+
def post(self, company_short_name: str):
|
|
53
|
+
data = request.get_json()
|
|
54
|
+
if not data or 'token' not in data:
|
|
55
|
+
return jsonify({"error": "Falta token de validación"}), 400
|
|
56
|
+
|
|
57
|
+
# get the token and validate with auth service
|
|
58
|
+
token = data.get('token')
|
|
59
|
+
redeem_result = self.auth_service.redeem_token_for_session(
|
|
60
|
+
company_short_name=company_short_name,
|
|
61
|
+
token=token
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
if not redeem_result['success']:
|
|
65
|
+
return {"error": redeem_result['error']}, 401
|
|
66
|
+
|
|
67
|
+
return {"status": "ok"}, 200
|
|
@@ -7,6 +7,7 @@ from flask.views import MethodView
|
|
|
7
7
|
from flask import request, redirect, render_template, url_for
|
|
8
8
|
from injector import inject
|
|
9
9
|
from iatoolkit.services.profile_service import ProfileService
|
|
10
|
+
from iatoolkit.services.auth_service import AuthService
|
|
10
11
|
from iatoolkit.services.jwt_service import JWTService
|
|
11
12
|
from iatoolkit.services.query_service import QueryService
|
|
12
13
|
from iatoolkit.services.prompt_manager_service import PromptService
|
|
@@ -21,7 +22,6 @@ class LoginView(BaseLoginView):
|
|
|
21
22
|
Handles login for local users.
|
|
22
23
|
Authenticates and then delegates the path decision (fast/slow) to the base class.
|
|
23
24
|
"""
|
|
24
|
-
|
|
25
25
|
def post(self, company_short_name: str):
|
|
26
26
|
company = self.profile_service.get_company_by_short_name(company_short_name)
|
|
27
27
|
if not company:
|
|
@@ -30,8 +30,8 @@ class LoginView(BaseLoginView):
|
|
|
30
30
|
email = request.form.get('email')
|
|
31
31
|
password = request.form.get('password')
|
|
32
32
|
|
|
33
|
-
# 1. Authenticate
|
|
34
|
-
auth_response = self.
|
|
33
|
+
# 1. Authenticate internal user
|
|
34
|
+
auth_response = self.auth_service.login_local_user(
|
|
35
35
|
company_short_name=company_short_name,
|
|
36
36
|
email=email,
|
|
37
37
|
password=password
|
|
@@ -67,6 +67,7 @@ class FinalizeContextView(MethodView):
|
|
|
67
67
|
@inject
|
|
68
68
|
def __init__(self,
|
|
69
69
|
profile_service: ProfileService,
|
|
70
|
+
auth_service: AuthService,
|
|
70
71
|
query_service: QueryService,
|
|
71
72
|
prompt_service: PromptService,
|
|
72
73
|
branding_service: BrandingService,
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
-
# Product: IAToolkit
|
|
3
|
-
#
|
|
4
|
-
# IAToolkit is open source software.
|
|
5
|
-
|
|
6
|
-
from flask import request
|
|
7
|
-
from injector import inject
|
|
8
|
-
from iatoolkit.services.profile_service import ProfileService
|
|
9
|
-
from flask import request
|
|
10
|
-
import logging
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class AuthService:
|
|
14
|
-
"""
|
|
15
|
-
Centralized service for handling authentication for all incoming requests.
|
|
16
|
-
It determines the user's identity based on either a Flask session cookie or an API Key.
|
|
17
|
-
"""
|
|
18
|
-
|
|
19
|
-
@inject
|
|
20
|
-
def __init__(self, profile_service: ProfileService):
|
|
21
|
-
"""
|
|
22
|
-
Injects ProfileService to access session information and validate API keys.
|
|
23
|
-
"""
|
|
24
|
-
self.profile_service = profile_service
|
|
25
|
-
|
|
26
|
-
def verify(self) -> dict:
|
|
27
|
-
"""
|
|
28
|
-
Verifies the current request and identifies the user.
|
|
29
|
-
|
|
30
|
-
Returns a dictionary with:
|
|
31
|
-
- success: bool
|
|
32
|
-
- user_identifier: str (if successful)
|
|
33
|
-
- company_short_name: str (if successful)
|
|
34
|
-
- error_message: str (on failure)
|
|
35
|
-
- status_code: int (on failure)
|
|
36
|
-
"""
|
|
37
|
-
# --- Priority 1: Check for a valid Flask web session ---
|
|
38
|
-
session_info = self.profile_service.get_current_session_info()
|
|
39
|
-
if session_info and session_info.get('user_identifier'):
|
|
40
|
-
# User is authenticated via a web session cookie.
|
|
41
|
-
return {
|
|
42
|
-
"success": True,
|
|
43
|
-
"company_short_name": session_info['company_short_name'],
|
|
44
|
-
"user_identifier": session_info['user_identifier']
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
# --- Priority 2: Check for a valid API Key in headers ---
|
|
48
|
-
api_key = None
|
|
49
|
-
auth = request.headers.get('Authorization', '')
|
|
50
|
-
if isinstance(auth, str) and auth.lower().startswith('bearer '):
|
|
51
|
-
api_key = auth.split(' ', 1)[1].strip()
|
|
52
|
-
|
|
53
|
-
if api_key:
|
|
54
|
-
api_key_entry = self.profile_service.get_active_api_key_entry(api_key)
|
|
55
|
-
if not api_key_entry:
|
|
56
|
-
logging.info(f"Invalid or inactive API Key {api_key}")
|
|
57
|
-
return {"success": False, "error": "Invalid or inactive API Key", "status_code": 401}
|
|
58
|
-
|
|
59
|
-
# obtain the company from the api_key_entry
|
|
60
|
-
company = api_key_entry.company
|
|
61
|
-
|
|
62
|
-
# For API calls, the external_user_id must be provided in the request.
|
|
63
|
-
user_identifier = ''
|
|
64
|
-
if request.is_json:
|
|
65
|
-
data = request.get_json() or {}
|
|
66
|
-
user_identifier = data.get('user_identifier', '')
|
|
67
|
-
|
|
68
|
-
return {
|
|
69
|
-
"success": True,
|
|
70
|
-
"company_short_name": company.short_name,
|
|
71
|
-
"user_identifier": user_identifier
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
# --- Failure: No valid credentials found ---
|
|
75
|
-
logging.info(f"Authentication required. No session cookie or API Key provided.")
|
|
76
|
-
return {"success": False, "error": "Authentication required. No session cookie or API Key provided.",
|
|
77
|
-
"status_code": 402}
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
-
# Product: IAToolkit
|
|
3
|
-
#
|
|
4
|
-
# IAToolkit is open source software.
|
|
5
|
-
|
|
6
|
-
import os
|
|
7
|
-
import logging
|
|
8
|
-
from flask import request, jsonify
|
|
9
|
-
from flask.views import MethodView
|
|
10
|
-
from injector import inject
|
|
11
|
-
from iatoolkit.services.auth_service import AuthService
|
|
12
|
-
from iatoolkit.views.base_login_view import BaseLoginView
|
|
13
|
-
|
|
14
|
-
# Importar los servicios que necesita la clase base
|
|
15
|
-
from iatoolkit.services.profile_service import ProfileService
|
|
16
|
-
from iatoolkit.services.jwt_service import JWTService
|
|
17
|
-
from iatoolkit.services.branding_service import BrandingService
|
|
18
|
-
from iatoolkit.services.onboarding_service import OnboardingService
|
|
19
|
-
from iatoolkit.services.query_service import QueryService
|
|
20
|
-
from iatoolkit.services.prompt_manager_service import PromptService
|
|
21
|
-
|
|
22
|
-
class ExternalLoginView(BaseLoginView):
|
|
23
|
-
"""
|
|
24
|
-
Handles login for external users via API.
|
|
25
|
-
Authenticates and then delegates the path decision (fast/slow) to the base class.
|
|
26
|
-
"""
|
|
27
|
-
@inject
|
|
28
|
-
def __init__(self,
|
|
29
|
-
iauthentication: AuthService,
|
|
30
|
-
jwt_service: JWTService,
|
|
31
|
-
profile_service: ProfileService,
|
|
32
|
-
branding_service: BrandingService,
|
|
33
|
-
prompt_service: PromptService,
|
|
34
|
-
onboarding_service: OnboardingService,
|
|
35
|
-
query_service: QueryService):
|
|
36
|
-
# Pass the dependencies for the base class to its __init__
|
|
37
|
-
super().__init__(
|
|
38
|
-
profile_service=profile_service,
|
|
39
|
-
jwt_service=jwt_service,
|
|
40
|
-
branding_service=branding_service,
|
|
41
|
-
onboarding_service=onboarding_service,
|
|
42
|
-
query_service=query_service,
|
|
43
|
-
prompt_service=prompt_service,
|
|
44
|
-
)
|
|
45
|
-
# Handle the dependency specific to this child class
|
|
46
|
-
self.iauthentication = iauthentication
|
|
47
|
-
|
|
48
|
-
def post(self, company_short_name: str):
|
|
49
|
-
data = request.get_json()
|
|
50
|
-
if not data or 'user_identifier' not in data:
|
|
51
|
-
return jsonify({"error": "Falta user_identifier"}), 400
|
|
52
|
-
|
|
53
|
-
company = self.profile_service.get_company_by_short_name(company_short_name)
|
|
54
|
-
if not company:
|
|
55
|
-
return jsonify({"error": "Empresa no encontrada"}), 404
|
|
56
|
-
|
|
57
|
-
user_identifier = data.get('user_identifier')
|
|
58
|
-
if not user_identifier:
|
|
59
|
-
return jsonify({"error": "missing user_identifier"}), 404
|
|
60
|
-
|
|
61
|
-
# 1. Authenticate the API call.
|
|
62
|
-
iaut = self.iauthentication.verify()
|
|
63
|
-
if not iaut.get("success"):
|
|
64
|
-
return jsonify(iaut), 401
|
|
65
|
-
|
|
66
|
-
# 2. Create the external user session.
|
|
67
|
-
self.profile_service.create_external_user_session(company, user_identifier)
|
|
68
|
-
|
|
69
|
-
# 3. Delegate the path decision to the centralized logic.
|
|
70
|
-
try:
|
|
71
|
-
return self._handle_login_path(company_short_name, user_identifier, company)
|
|
72
|
-
except Exception as e:
|
|
73
|
-
logging.exception(f"Error processing external login path for {company_short_name}/{user_identifier}: {e}")
|
|
74
|
-
return jsonify({"error": f"Internal server error while starting chat. {str(e)}"}), 500
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
class RedeemTokenApiView(MethodView):
|
|
78
|
-
# this endpoint is only used ONLY by chat_main.js to redeem a chat token
|
|
79
|
-
@inject
|
|
80
|
-
def __init__(self,
|
|
81
|
-
profile_service: ProfileService,
|
|
82
|
-
jwt_service: JWTService):
|
|
83
|
-
self.profile_service = profile_service
|
|
84
|
-
self.jwt_service = jwt_service
|
|
85
|
-
|
|
86
|
-
def post(self, company_short_name: str):
|
|
87
|
-
data = request.get_json()
|
|
88
|
-
if not data or 'token' not in data:
|
|
89
|
-
return jsonify({"error": "Falta token de validación"}), 400
|
|
90
|
-
|
|
91
|
-
# 1. validate the token
|
|
92
|
-
token = data.get('token')
|
|
93
|
-
payload = self.jwt_service.validate_chat_jwt(token)
|
|
94
|
-
if not payload:
|
|
95
|
-
logging.warning("Intento de canjear un token inválido o expirado.")
|
|
96
|
-
return {"error": "Token inválido o expirado."}, 401
|
|
97
|
-
|
|
98
|
-
# 2. if token is valid, extract the user_identifier
|
|
99
|
-
user_identifier = payload.get('user_identifier')
|
|
100
|
-
|
|
101
|
-
try:
|
|
102
|
-
# 3. create the Flask session
|
|
103
|
-
self.profile_service.set_session_for_user(company_short_name, user_identifier)
|
|
104
|
-
logging.info(f"Token de sesión canjeado exitosamente para {user_identifier}.")
|
|
105
|
-
|
|
106
|
-
return {"status": "ok"}, 200
|
|
107
|
-
|
|
108
|
-
except Exception as e:
|
|
109
|
-
logging.error(f"Error al crear la sesión desde token para {user_identifier}: {e}")
|
|
110
|
-
return {"error": "No se pudo crear la sesión del usuario."}, 500
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/infra/connectors/file_connector_factory.py
RENAMED
|
File without changes
|
|
File without changes
|
{iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/infra/connectors/google_drive_connector.py
RENAMED
|
File without changes
|
{iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/infra/connectors/local_file_connector.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{iatoolkit-0.56.0 → iatoolkit-0.57.0}/src/iatoolkit/services/user_session_context_service.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|