iatoolkit 0.55.2__py3-none-any.whl → 0.56.0__py3-none-any.whl
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/base_company.py +1 -1
- iatoolkit/common/routes.py +25 -13
- iatoolkit/iatoolkit.py +3 -4
- iatoolkit/repositories/database_manager.py +13 -10
- iatoolkit/repositories/profile_repo.py +7 -3
- iatoolkit/services/auth_service.py +5 -2
- iatoolkit/services/dispatcher_service.py +2 -24
- iatoolkit/services/jwt_service.py +15 -24
- iatoolkit/services/profile_service.py +13 -11
- iatoolkit/services/query_service.py +0 -4
- iatoolkit/static/js/chat_feedback.js +1 -1
- iatoolkit/static/js/chat_history.js +1 -1
- iatoolkit/static/js/chat_main.js +40 -14
- iatoolkit/templates/chat.html +7 -3
- iatoolkit/templates/login_simulation.html +34 -0
- iatoolkit/views/base_login_view.py +32 -4
- iatoolkit/views/external_login_view.py +50 -10
- iatoolkit/views/login_simulation_view.py +45 -24
- iatoolkit/views/login_view.py +27 -11
- {iatoolkit-0.55.2.dist-info → iatoolkit-0.56.0.dist-info}/METADATA +1 -1
- {iatoolkit-0.55.2.dist-info → iatoolkit-0.56.0.dist-info}/RECORD +23 -23
- iatoolkit/templates/login_test.html +0 -118
- {iatoolkit-0.55.2.dist-info → iatoolkit-0.56.0.dist-info}/WHEEL +0 -0
- {iatoolkit-0.55.2.dist-info → iatoolkit-0.56.0.dist-info}/top_level.txt +0 -0
iatoolkit/base_company.py
CHANGED
|
@@ -88,7 +88,7 @@ class BaseCompany(ABC):
|
|
|
88
88
|
|
|
89
89
|
@abstractmethod
|
|
90
90
|
# get context specific for this company
|
|
91
|
-
def get_user_info(self, user_identifier: str) ->
|
|
91
|
+
def get_user_info(self, user_identifier: str) -> dict:
|
|
92
92
|
raise NotImplementedError("La subclase debe implementar el método get_user_info()")
|
|
93
93
|
|
|
94
94
|
@abstractmethod
|
iatoolkit/common/routes.py
CHANGED
|
@@ -26,8 +26,6 @@ def register_views(injector, app):
|
|
|
26
26
|
from iatoolkit.views.tasks_view import TaskView
|
|
27
27
|
from iatoolkit.views.tasks_review_view import TaskReviewView
|
|
28
28
|
from iatoolkit.views.login_simulation_view import LoginSimulationView
|
|
29
|
-
from iatoolkit.views.login_view import LoginView, FinalizeContextView
|
|
30
|
-
from iatoolkit.views.external_login_view import ExternalLoginView
|
|
31
29
|
from iatoolkit.views.signup_view import SignupView
|
|
32
30
|
from iatoolkit.views.verify_user_view import VerifyAccountView
|
|
33
31
|
from iatoolkit.views.forgot_password_view import ForgotPasswordView
|
|
@@ -36,27 +34,41 @@ def register_views(injector, app):
|
|
|
36
34
|
from iatoolkit.views.user_feedback_api_view import UserFeedbackApiView
|
|
37
35
|
from iatoolkit.views.prompt_api_view import PromptApiView
|
|
38
36
|
from iatoolkit.views.chat_token_request_view import ChatTokenRequestView
|
|
37
|
+
from iatoolkit.views.login_view import LoginView, FinalizeContextView
|
|
38
|
+
from iatoolkit.views.external_login_view import ExternalLoginView, RedeemTokenApiView
|
|
39
39
|
|
|
40
40
|
# iatoolkit home page
|
|
41
41
|
app.add_url_rule('/<company_short_name>', view_func=IndexView.as_view('index'))
|
|
42
42
|
|
|
43
|
-
#
|
|
44
|
-
app.add_url_rule('/<company_short_name>/
|
|
45
|
-
view_func=InitContextApiView.as_view('init_context_api'))
|
|
43
|
+
# login for the iatoolkit integrated frontend
|
|
44
|
+
app.add_url_rule('/<company_short_name>/login', view_func=LoginView.as_view('login'))
|
|
46
45
|
|
|
47
|
-
# this
|
|
48
|
-
# only the first one should be used from an external app
|
|
46
|
+
# this is the login for external users
|
|
49
47
|
app.add_url_rule('/<company_short_name>/external_login',
|
|
50
48
|
view_func=ExternalLoginView.as_view('external_login'))
|
|
51
49
|
|
|
50
|
+
# this endpoint is called when onboarding_shell finish the context load
|
|
51
|
+
app.add_url_rule(
|
|
52
|
+
'/<company_short_name>/finalize',
|
|
53
|
+
view_func=FinalizeContextView.as_view('finalize_no_token')
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
app.add_url_rule(
|
|
57
|
+
'/<company_short_name>/finalize/<token>',
|
|
58
|
+
view_func=FinalizeContextView.as_view('finalize_with_token')
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
# this endpoint is called by the JS for changing the token for a session
|
|
62
|
+
app.add_url_rule('/<string:company_short_name>/api/redeem_token',
|
|
63
|
+
view_func = RedeemTokenApiView.as_view('redeem_token'))
|
|
64
|
+
|
|
52
65
|
# this endpoint is for requesting a chat token for external users
|
|
53
66
|
app.add_url_rule('/auth/chat_token',
|
|
54
67
|
view_func=ChatTokenRequestView.as_view('chat-token'))
|
|
55
68
|
|
|
56
|
-
#
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
app.add_url_rule('/<company_short_name>/finalize_context_load', view_func=FinalizeContextView.as_view('finalize_context_load'))
|
|
69
|
+
# init (reset) the company context (with api-key)
|
|
70
|
+
app.add_url_rule('/<company_short_name>/api/init_context_api',
|
|
71
|
+
view_func=InitContextApiView.as_view('init_context_api'))
|
|
60
72
|
|
|
61
73
|
# register new user, account verification and forgot password
|
|
62
74
|
app.add_url_rule('/<company_short_name>/signup',view_func=SignupView.as_view('signup'))
|
|
@@ -111,8 +123,8 @@ def register_views(injector, app):
|
|
|
111
123
|
except FileNotFoundError:
|
|
112
124
|
abort(404)
|
|
113
125
|
|
|
114
|
-
# login testing
|
|
115
|
-
app.add_url_rule('
|
|
126
|
+
# login testing
|
|
127
|
+
app.add_url_rule('/<company_short_name>/login_test',
|
|
116
128
|
view_func=LoginSimulationView.as_view('login_test'))
|
|
117
129
|
|
|
118
130
|
app.add_url_rule(
|
iatoolkit/iatoolkit.py
CHANGED
|
@@ -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.56.0"
|
|
23
23
|
|
|
24
24
|
# global variable for the unique instance of IAToolkit
|
|
25
25
|
_iatoolkit_instance: Optional['IAToolkit'] = None
|
|
@@ -155,10 +155,9 @@ class IAToolkit:
|
|
|
155
155
|
|
|
156
156
|
self.app.config.update({
|
|
157
157
|
'VERSION': self.version,
|
|
158
|
-
'SERVER_NAME': domain,
|
|
159
158
|
'SECRET_KEY': self._get_config_value('FLASK_SECRET_KEY', 'iatoolkit-default-secret'),
|
|
160
|
-
'SESSION_COOKIE_SAMESITE': "None"
|
|
161
|
-
'SESSION_COOKIE_SECURE':
|
|
159
|
+
'SESSION_COOKIE_SAMESITE': "None",
|
|
160
|
+
'SESSION_COOKIE_SECURE': True,
|
|
162
161
|
'SESSION_PERMANENT': False,
|
|
163
162
|
'SESSION_USE_SIGNER': True,
|
|
164
163
|
'JWT_SECRET_KEY': self._get_config_value('JWT_SECRET_KEY', 'iatoolkit-jwt-secret'),
|
|
@@ -21,16 +21,19 @@ class DatabaseManager:
|
|
|
21
21
|
:param echo: Si True, habilita logs de SQL.
|
|
22
22
|
"""
|
|
23
23
|
self.url = make_url(database_url)
|
|
24
|
-
|
|
25
|
-
database_url,
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
24
|
+
if database_url.startswith('sqlite'): # for tests
|
|
25
|
+
self._engine = create_engine(database_url, echo=False)
|
|
26
|
+
else:
|
|
27
|
+
self._engine = create_engine(
|
|
28
|
+
database_url,
|
|
29
|
+
echo=False,
|
|
30
|
+
pool_size=2, # per worker
|
|
31
|
+
max_overflow=3,
|
|
32
|
+
pool_timeout=30,
|
|
33
|
+
pool_recycle=1800,
|
|
34
|
+
pool_pre_ping=True,
|
|
35
|
+
future=True,
|
|
36
|
+
)
|
|
34
37
|
self.SessionFactory = sessionmaker(bind=self._engine,
|
|
35
38
|
autoflush=False,
|
|
36
39
|
autocommit=False,
|
|
@@ -72,10 +72,14 @@ class ProfileRepo:
|
|
|
72
72
|
def create_company(self, new_company: Company):
|
|
73
73
|
company = self.session.query(Company).filter_by(name=new_company.name).first()
|
|
74
74
|
if company:
|
|
75
|
-
company.parameters
|
|
76
|
-
|
|
77
|
-
company.
|
|
75
|
+
if company.parameters != new_company.parameters:
|
|
76
|
+
company.parameters = new_company.parameters
|
|
77
|
+
if company.branding != new_company.branding:
|
|
78
|
+
company.branding = new_company.branding
|
|
79
|
+
if company.onboarding_cards != new_company.onboarding_cards:
|
|
80
|
+
company.onboarding_cards = new_company.onboarding_cards
|
|
78
81
|
else:
|
|
82
|
+
# Si la compañía no existe, la añade a la sesión.
|
|
79
83
|
self.session.add(new_company)
|
|
80
84
|
company = new_company
|
|
81
85
|
|
|
@@ -7,6 +7,7 @@ from flask import request
|
|
|
7
7
|
from injector import inject
|
|
8
8
|
from iatoolkit.services.profile_service import ProfileService
|
|
9
9
|
from flask import request
|
|
10
|
+
import logging
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
class AuthService:
|
|
@@ -52,6 +53,7 @@ class AuthService:
|
|
|
52
53
|
if api_key:
|
|
53
54
|
api_key_entry = self.profile_service.get_active_api_key_entry(api_key)
|
|
54
55
|
if not api_key_entry:
|
|
56
|
+
logging.info(f"Invalid or inactive API Key {api_key}")
|
|
55
57
|
return {"success": False, "error": "Invalid or inactive API Key", "status_code": 401}
|
|
56
58
|
|
|
57
59
|
# obtain the company from the api_key_entry
|
|
@@ -61,7 +63,7 @@ class AuthService:
|
|
|
61
63
|
user_identifier = ''
|
|
62
64
|
if request.is_json:
|
|
63
65
|
data = request.get_json() or {}
|
|
64
|
-
user_identifier = data.get('
|
|
66
|
+
user_identifier = data.get('user_identifier', '')
|
|
65
67
|
|
|
66
68
|
return {
|
|
67
69
|
"success": True,
|
|
@@ -70,5 +72,6 @@ class AuthService:
|
|
|
70
72
|
}
|
|
71
73
|
|
|
72
74
|
# --- Failure: No valid credentials found ---
|
|
75
|
+
logging.info(f"Authentication required. No session cookie or API Key provided.")
|
|
73
76
|
return {"success": False, "error": "Authentication required. No session cookie or API Key provided.",
|
|
74
|
-
"status_code":
|
|
77
|
+
"status_code": 402}
|
|
@@ -178,35 +178,13 @@ class Dispatcher:
|
|
|
178
178
|
# source 2: external company user
|
|
179
179
|
company_instance = self.company_instances[company_name]
|
|
180
180
|
try:
|
|
181
|
-
|
|
181
|
+
external_user_profile = company_instance.get_user_info(user_identifier)
|
|
182
182
|
except Exception as e:
|
|
183
183
|
logging.exception(e)
|
|
184
184
|
raise IAToolkitException(IAToolkitException.ErrorType.EXTERNAL_SOURCE_ERROR,
|
|
185
185
|
f"Error en get_user_info de {company_name}: {str(e)}") from e
|
|
186
186
|
|
|
187
|
-
|
|
188
|
-
return self._normalize_user_data(raw_user_data)
|
|
189
|
-
|
|
190
|
-
def _normalize_user_data(self, raw_data: dict) -> dict:
|
|
191
|
-
"""
|
|
192
|
-
Asegura que los datos del usuario siempre tengan una estructura consistente.
|
|
193
|
-
"""
|
|
194
|
-
# default values
|
|
195
|
-
normalized_user = {
|
|
196
|
-
"id": raw_data.get("id", 0),
|
|
197
|
-
"username": raw_data.get("id", 0),
|
|
198
|
-
"user_email": raw_data.get("email", ""),
|
|
199
|
-
"user_fullname": raw_data.get("user_fullname", ""),
|
|
200
|
-
"is_local": False,
|
|
201
|
-
"extras": raw_data.get("extras", {})
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
# get the extras from the raw data, if any
|
|
205
|
-
extras = raw_data.get("extras", {})
|
|
206
|
-
if isinstance(extras, dict):
|
|
207
|
-
normalized_user.update(extras)
|
|
208
|
-
|
|
209
|
-
return normalized_user
|
|
187
|
+
return external_user_profile
|
|
210
188
|
|
|
211
189
|
def get_metadata_from_filename(self, company_name: str, filename: str) -> dict:
|
|
212
190
|
if company_name not in self.company_instances:
|
|
@@ -24,16 +24,18 @@ class JWTService:
|
|
|
24
24
|
raise RuntimeError(f"Configuración JWT esencial faltante: {e}")
|
|
25
25
|
|
|
26
26
|
def generate_chat_jwt(self,
|
|
27
|
-
company_id: int,
|
|
28
27
|
company_short_name: str,
|
|
29
|
-
|
|
28
|
+
user_identifier: str,
|
|
30
29
|
expires_delta_seconds: int) -> Optional[str]:
|
|
31
30
|
# generate a JWT for a chat session
|
|
32
31
|
try:
|
|
32
|
+
if not company_short_name or not user_identifier:
|
|
33
|
+
logging.error(f"Missing token ID: {company_short_name}/{user_identifier}")
|
|
34
|
+
return None
|
|
35
|
+
|
|
33
36
|
payload = {
|
|
34
|
-
'company_id': company_id,
|
|
35
37
|
'company_short_name': company_short_name,
|
|
36
|
-
'
|
|
38
|
+
'user_identifier': user_identifier,
|
|
37
39
|
'exp': time.time() + expires_delta_seconds,
|
|
38
40
|
'iat': time.time(),
|
|
39
41
|
'type': 'chat_session' # Identificador del tipo de token
|
|
@@ -41,10 +43,10 @@ class JWTService:
|
|
|
41
43
|
token = jwt.encode(payload, self.secret_key, algorithm=self.algorithm)
|
|
42
44
|
return token
|
|
43
45
|
except Exception as e:
|
|
44
|
-
logging.error(f"Error al generar JWT para
|
|
46
|
+
logging.error(f"Error al generar JWT para {company_short_name}/{user_identifier}: {e}")
|
|
45
47
|
return None
|
|
46
48
|
|
|
47
|
-
def validate_chat_jwt(self, token: str
|
|
49
|
+
def validate_chat_jwt(self, token: str) -> Optional[Dict[str, Any]]:
|
|
48
50
|
"""
|
|
49
51
|
Valida un JWT de sesión de chat.
|
|
50
52
|
Retorna el payload decodificado si es válido y coincide con la empresa, o None.
|
|
@@ -59,33 +61,22 @@ class JWTService:
|
|
|
59
61
|
logging.warning(f"Validación JWT fallida: tipo incorrecto '{payload.get('type')}'")
|
|
60
62
|
return None
|
|
61
63
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
f"Esperado: {expected_company_short_name}, Obtenido: {payload.get('company_short_name')}"
|
|
66
|
-
)
|
|
64
|
+
# user_identifier debe estar presente
|
|
65
|
+
if not payload.get('user_identifier'):
|
|
66
|
+
logging.warning(f"Validación JWT fallida: user_identifier ausente o vacío.")
|
|
67
67
|
return None
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
logging.warning(f"Validación JWT fallida: external_user_id ausente o vacío.")
|
|
72
|
-
return None
|
|
73
|
-
|
|
74
|
-
# company_id debe estar presente
|
|
75
|
-
if 'company_id' not in payload or not isinstance(payload['company_id'], int):
|
|
76
|
-
logging.warning(f"Validación JWT fallida: company_id ausente o tipo incorrecto.")
|
|
69
|
+
if not payload.get('company_short_name'):
|
|
70
|
+
logging.warning(f"Validación JWT fallida: company_short_name ausente.")
|
|
77
71
|
return None
|
|
78
72
|
|
|
79
73
|
logging.debug(
|
|
80
74
|
f"JWT validado exitosamente para company: {payload.get('company_short_name')}, user: {payload.get('external_user_id')}")
|
|
81
75
|
return payload
|
|
82
76
|
|
|
83
|
-
except jwt.ExpiredSignatureError:
|
|
84
|
-
logging.info(f"Validación JWT fallida: token expirado para {expected_company_short_name}")
|
|
85
|
-
return None
|
|
86
77
|
except jwt.InvalidTokenError as e:
|
|
87
|
-
logging.warning(f"Validación JWT fallida: token inválido
|
|
78
|
+
logging.warning(f"Validación JWT fallida: token inválido . Error: {e}")
|
|
88
79
|
return None
|
|
89
80
|
except Exception as e:
|
|
90
|
-
logging.error(f"Error inesperado durante validación de JWT
|
|
81
|
+
logging.error(f"Error inesperado durante validación de JWT : {e}")
|
|
91
82
|
return None
|
|
@@ -60,7 +60,6 @@ class ProfileService:
|
|
|
60
60
|
# the user_profile variables are used on the LLM templates also (see in query_main.prompt)
|
|
61
61
|
user_identifier = user.email # no longer de ID
|
|
62
62
|
user_profile = {
|
|
63
|
-
"id": user_identifier,
|
|
64
63
|
"user_email": user.email,
|
|
65
64
|
"user_fullname": f'{user.first_name} {user.last_name}',
|
|
66
65
|
"user_is_local": True,
|
|
@@ -74,21 +73,21 @@ class ProfileService:
|
|
|
74
73
|
except Exception as e:
|
|
75
74
|
return {'success': False, "message": str(e)}
|
|
76
75
|
|
|
77
|
-
def create_external_user_session(self, company: Company,
|
|
76
|
+
def create_external_user_session(self, company: Company, user_identifier: str):
|
|
78
77
|
"""
|
|
79
78
|
Public method for views to create a web session for an external user.
|
|
80
79
|
"""
|
|
81
|
-
# 1. Fetch the
|
|
82
|
-
|
|
80
|
+
# 1. Fetch the external user profile via Dispatcher.
|
|
81
|
+
external_user_profile = self.dispatcher.get_user_info(
|
|
83
82
|
company_name=company.short_name,
|
|
84
|
-
user_identifier=
|
|
83
|
+
user_identifier=user_identifier
|
|
85
84
|
)
|
|
86
85
|
|
|
87
86
|
# 2. Call the session creation helper with external_user_id as user_identifier
|
|
88
87
|
self.create_web_session(
|
|
89
88
|
company=company,
|
|
90
|
-
user_identifier=
|
|
91
|
-
user_profile=
|
|
89
|
+
user_identifier=user_identifier,
|
|
90
|
+
user_profile=external_user_profile)
|
|
92
91
|
|
|
93
92
|
def create_web_session(self, company: Company, user_identifier: str, user_profile: dict):
|
|
94
93
|
"""
|
|
@@ -96,16 +95,19 @@ class ProfileService:
|
|
|
96
95
|
"""
|
|
97
96
|
user_profile['company_short_name'] = company.short_name
|
|
98
97
|
user_profile['user_identifier'] = user_identifier
|
|
99
|
-
user_profile['
|
|
98
|
+
user_profile['id'] = user_identifier
|
|
100
99
|
user_profile['company_id'] = company.id
|
|
101
100
|
user_profile['company'] = company.name
|
|
102
101
|
|
|
103
|
-
# user_profile
|
|
102
|
+
# save user_profile in Redis session
|
|
104
103
|
self.session_context.save_profile_data(company.short_name, user_identifier, user_profile)
|
|
105
104
|
|
|
106
|
-
# save
|
|
105
|
+
# save a min Flask session cookie for this user
|
|
106
|
+
self.set_session_for_user(company.short_name, user_identifier)
|
|
107
|
+
|
|
108
|
+
def set_session_for_user(self, company_short_name: str, user_identifier:str ):
|
|
109
|
+
SessionManager.set('company_short_name', company_short_name)
|
|
107
110
|
SessionManager.set('user_identifier', user_identifier)
|
|
108
|
-
SessionManager.set('company_short_name', company.short_name)
|
|
109
111
|
|
|
110
112
|
def get_current_session_info(self) -> dict:
|
|
111
113
|
"""
|
|
@@ -114,10 +114,6 @@ class QueryService:
|
|
|
114
114
|
self._has_valid_cached_context(company_short_name, user_identifier))
|
|
115
115
|
|
|
116
116
|
if rebuild_is_needed:
|
|
117
|
-
logging.info(
|
|
118
|
-
f"Se necesita reconstrucción de contexto para {company_short_name}/{user_identifier}. Preparando...")
|
|
119
|
-
|
|
120
|
-
|
|
121
117
|
# Guardar el contexto preparado y su versión para que `finalize_context_rebuild` los use.
|
|
122
118
|
self.session_context.save_prepared_context(company_short_name, user_identifier, final_system_context,
|
|
123
119
|
current_version)
|
|
@@ -106,7 +106,7 @@ const sendFeedback = async function(message) {
|
|
|
106
106
|
};
|
|
107
107
|
try {
|
|
108
108
|
// Asumiendo que callLLMAPI está definido globalmente en otro archivo (ej. chat_main.js)
|
|
109
|
-
const responseData = await
|
|
109
|
+
const responseData = await callToolkit('/feedback', data, "POST");
|
|
110
110
|
return responseData;
|
|
111
111
|
} catch (error) {
|
|
112
112
|
console.error("Error al enviar feedback:", error);
|
|
@@ -40,7 +40,7 @@ $(document).ready(function () {
|
|
|
40
40
|
historyContent.hide();
|
|
41
41
|
|
|
42
42
|
try {
|
|
43
|
-
const responseData = await
|
|
43
|
+
const responseData = await callToolkit("/api/history", {}, "POST");
|
|
44
44
|
|
|
45
45
|
if (responseData && responseData.history) {
|
|
46
46
|
// Guardar datos globalmente
|
iatoolkit/static/js/chat_main.js
CHANGED
|
@@ -5,6 +5,13 @@ let abortController = null;
|
|
|
5
5
|
let selectedPrompt = null; // Will hold a lightweight prompt object
|
|
6
6
|
|
|
7
7
|
$(document).ready(function () {
|
|
8
|
+
// Gatilla el redeem sin esperar ni manejar respuesta aquí
|
|
9
|
+
if (window.redeemToken !== '') {
|
|
10
|
+
const url = `/api/redeem_token`;
|
|
11
|
+
// No await: dejamos que callToolkit maneje todo internamente
|
|
12
|
+
callToolkit(url, {'token': window.redeemToken}, "POST").catch(() => {});
|
|
13
|
+
}
|
|
14
|
+
|
|
8
15
|
// --- MAIN EVENT HANDLERS ---
|
|
9
16
|
$('#send-button').on('click', handleChatMessage);
|
|
10
17
|
$('#stop-button').on('click', abortCurrentRequest);
|
|
@@ -176,7 +183,7 @@ const handleChatMessage = async function () {
|
|
|
176
183
|
user_identifier: window.user_identifier
|
|
177
184
|
};
|
|
178
185
|
|
|
179
|
-
const responseData = await
|
|
186
|
+
const responseData = await callToolkit("/llm_query", data, "POST");
|
|
180
187
|
if (responseData && responseData.answer) {
|
|
181
188
|
const answerSection = $('<div>').addClass('answer-section llm-output').append(responseData.answer);
|
|
182
189
|
displayBotMessage(answerSection);
|
|
@@ -292,28 +299,39 @@ function resetSpecificDataInput() {
|
|
|
292
299
|
* @param {number} timeoutMs - Timeout in milliseconds.
|
|
293
300
|
* @returns {Promise<object|null>} The response data or null on error.
|
|
294
301
|
*/
|
|
295
|
-
const
|
|
302
|
+
const callToolkit = async function(apiPath, data, method, timeoutMs = 500000) {
|
|
296
303
|
const url = `${window.iatoolkit_base_url}/${window.companyShortName}${apiPath}`;
|
|
297
304
|
|
|
298
|
-
const headers = {"Content-Type": "application/json"};
|
|
299
|
-
if (window.sessionJWT) {
|
|
300
|
-
headers['X-Chat-Token'] = window.sessionJWT;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
305
|
abortController = new AbortController();
|
|
304
306
|
const timeoutId = setTimeout(() => abortController.abort(), timeoutMs);
|
|
305
307
|
|
|
306
308
|
try {
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
309
|
+
const fetchOptions = {
|
|
310
|
+
method: method,
|
|
311
|
+
signal: abortController.signal,
|
|
312
|
+
credentials: 'include'
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
// Solo agrega body si el método lo soporta y hay datos
|
|
316
|
+
const methodUpper = (method || '').toUpperCase();
|
|
317
|
+
const canHaveBody = !['GET', 'HEAD'].includes(methodUpper);
|
|
318
|
+
if (canHaveBody && data !== undefined && data !== null) {
|
|
319
|
+
fetchOptions.body = JSON.stringify(data);
|
|
320
|
+
fetchOptions.headers = {"Content-Type": "application/json"};
|
|
321
|
+
|
|
322
|
+
}
|
|
323
|
+
const response = await fetch(url, fetchOptions);
|
|
324
|
+
|
|
314
325
|
clearTimeout(timeoutId);
|
|
315
326
|
|
|
316
327
|
if (!response.ok) {
|
|
328
|
+
if (response.status === 401) {
|
|
329
|
+
const errorMessage = `Tu sesión ha expirado. `;
|
|
330
|
+
const errorIcon = '<i class="bi bi-exclamation-triangle"></i>';
|
|
331
|
+
const infrastructureError = $('<div>').addClass('error-section').html(errorIcon + `<p>${errorMessage}</p>`);
|
|
332
|
+
displayBotMessage(infrastructureError);
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
317
335
|
try {
|
|
318
336
|
// Intentamos leer el error como JSON, que es el formato esperado de nuestra API.
|
|
319
337
|
const errorData = await response.json();
|
|
@@ -337,6 +355,14 @@ const callLLMAPI = async function(apiPath, data, method, timeoutMs = 500000) {
|
|
|
337
355
|
if (error.name === 'AbortError') {
|
|
338
356
|
throw error; // Re-throw to be handled by handleChatMessage
|
|
339
357
|
} else {
|
|
358
|
+
// Log detallado en consola
|
|
359
|
+
console.error('Error de red en callToolkit:', {
|
|
360
|
+
url,
|
|
361
|
+
method,
|
|
362
|
+
error,
|
|
363
|
+
message: error?.message,
|
|
364
|
+
stack: error?.stack,
|
|
365
|
+
});
|
|
340
366
|
const friendlyMessage = "Ocurrió un error de red. Por favor, inténtalo de nuevo en unos momentos.";
|
|
341
367
|
const errorIcon = '<i class="bi bi-exclamation-triangle"></i>';
|
|
342
368
|
const commError = $('<div>').addClass('error-section').html(errorIcon + `<p>${friendlyMessage}</p>`);
|
iatoolkit/templates/chat.html
CHANGED
|
@@ -111,8 +111,11 @@
|
|
|
111
111
|
data-custom-fields='{{ prompt.custom_fields | tojson }}'>
|
|
112
112
|
{{ prompt.description }}
|
|
113
113
|
</a>
|
|
114
|
-
</li>
|
|
115
|
-
{%
|
|
114
|
+
</li>
|
|
115
|
+
{% endfor %}
|
|
116
|
+
{% if not loop.last %}
|
|
117
|
+
<li><hr class="dropdown-divider"></li>
|
|
118
|
+
{% endif %}
|
|
116
119
|
{% endfor %}
|
|
117
120
|
{% endif %}
|
|
118
121
|
</ul>
|
|
@@ -180,10 +183,11 @@
|
|
|
180
183
|
// --- Global Configuration from Backend ---
|
|
181
184
|
window.companyShortName = "{{ company_short_name }}";
|
|
182
185
|
window.user_identifier = "{{ user_identifier }}";
|
|
186
|
+
window.redeemToken = "{{ redeem_token }}";
|
|
183
187
|
window.iatoolkit_base_url = "{{ iatoolkit_base_url }}";
|
|
184
188
|
window.availablePrompts = {{ prompts.message | tojson }};
|
|
185
|
-
window.sendButtonColor = "{{ branding.send_button_color }}";
|
|
186
189
|
window.onboardingCards = {{ onboarding_cards | tojson }};
|
|
190
|
+
window.sendButtonColor = "{{ branding.send_button_color }}";
|
|
187
191
|
</script>
|
|
188
192
|
|
|
189
193
|
<!-- Carga de los scripts JS externos después de definir las variables globales -->
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{% extends "base.html" %}
|
|
2
|
+
|
|
3
|
+
{% block title %}Prueba de Login para {{ company_short_name }}{% endblock %}
|
|
4
|
+
|
|
5
|
+
{% block content %}
|
|
6
|
+
<div class="container-fluid">
|
|
7
|
+
<div class="row flex-fill mt-5 justify-content-center">
|
|
8
|
+
<div class="col-12 col-lg-6">
|
|
9
|
+
<div class="border rounded p-4 p-md-5 shadow-sm bg-light">
|
|
10
|
+
<h3 class="text-muted fw-semibold text-start mb-3">
|
|
11
|
+
Login Externo para <span style="color:#0d6efd;">{{ company_short_name }}</span>
|
|
12
|
+
</h3>
|
|
13
|
+
<div class="text-center mb-4">
|
|
14
|
+
<p class="text-muted widget-intro-text">
|
|
15
|
+
Este formulario simula el inicio de una sesión externa. Al enviar, serás redirigido a la URL de login final.
|
|
16
|
+
</p>
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<!-- Formulario HTML estándar que hace un POST a la misma URL -->
|
|
20
|
+
<form method="POST" action="">
|
|
21
|
+
<div class="mb-3">
|
|
22
|
+
<label for="external_user_id" class="form-label d-block">External user ID</label>
|
|
23
|
+
<input type="text" id="external_user_id" name="external_user_id" class="form-control" required>
|
|
24
|
+
</div>
|
|
25
|
+
<button type="submit" class="btn btn-primary">
|
|
26
|
+
Redirigir a External Login
|
|
27
|
+
</button>
|
|
28
|
+
</form>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
{% endblock %}
|
|
34
|
+
|
|
@@ -12,6 +12,7 @@ from iatoolkit.services.query_service import QueryService
|
|
|
12
12
|
from iatoolkit.services.branding_service import BrandingService
|
|
13
13
|
from iatoolkit.services.onboarding_service import OnboardingService
|
|
14
14
|
from iatoolkit.services.prompt_manager_service import PromptService
|
|
15
|
+
from iatoolkit.services.jwt_service import JWTService
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
class BaseLoginView(MethodView):
|
|
@@ -22,16 +23,20 @@ class BaseLoginView(MethodView):
|
|
|
22
23
|
@inject
|
|
23
24
|
def __init__(self,
|
|
24
25
|
profile_service: ProfileService,
|
|
26
|
+
jwt_service: JWTService,
|
|
25
27
|
branding_service: BrandingService,
|
|
26
28
|
prompt_service: PromptService,
|
|
27
29
|
onboarding_service: OnboardingService,
|
|
28
|
-
query_service: QueryService
|
|
30
|
+
query_service: QueryService
|
|
31
|
+
):
|
|
29
32
|
self.profile_service = profile_service
|
|
33
|
+
self.jwt_service = jwt_service
|
|
30
34
|
self.branding_service = branding_service
|
|
31
35
|
self.prompt_service = prompt_service
|
|
32
36
|
self.onboarding_service = onboarding_service
|
|
33
37
|
self.query_service = query_service
|
|
34
38
|
|
|
39
|
+
|
|
35
40
|
def _handle_login_path(self, company_short_name: str, user_identifier: str, company):
|
|
36
41
|
"""
|
|
37
42
|
Centralized logic to decide between the fast path and the slow path.
|
|
@@ -43,13 +48,33 @@ class BaseLoginView(MethodView):
|
|
|
43
48
|
prep_result = self.query_service.prepare_context(
|
|
44
49
|
company_short_name=company_short_name, user_identifier=user_identifier
|
|
45
50
|
)
|
|
51
|
+
|
|
52
|
+
# generate continuation token for external login
|
|
53
|
+
redeem_token = ''
|
|
54
|
+
if self.__class__.__name__ == 'ExternalLoginView':
|
|
55
|
+
redeem_token = self.jwt_service.generate_chat_jwt(
|
|
56
|
+
company_short_name=company_short_name,
|
|
57
|
+
user_identifier=user_identifier,
|
|
58
|
+
expires_delta_seconds=300
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
if not redeem_token:
|
|
62
|
+
return "Error al generar el redeem_token para login externo.", 500
|
|
63
|
+
|
|
46
64
|
if prep_result.get('rebuild_needed'):
|
|
47
65
|
# --- SLOW PATH: Render the loading shell ---
|
|
48
66
|
onboarding_cards = self.onboarding_service.get_onboarding_cards(company)
|
|
49
67
|
|
|
50
68
|
# callback url to call when the context finish loading
|
|
51
|
-
|
|
52
|
-
|
|
69
|
+
if redeem_token:
|
|
70
|
+
target_url = url_for('finalize_with_token',
|
|
71
|
+
company_short_name=company_short_name,
|
|
72
|
+
token=redeem_token,
|
|
73
|
+
_external=True)
|
|
74
|
+
else:
|
|
75
|
+
target_url = url_for('finalize_no_token',
|
|
76
|
+
company_short_name=company_short_name,
|
|
77
|
+
_external=True)
|
|
53
78
|
return render_template(
|
|
54
79
|
"onboarding_shell.html",
|
|
55
80
|
iframe_src_url=target_url,
|
|
@@ -62,7 +87,10 @@ class BaseLoginView(MethodView):
|
|
|
62
87
|
onboarding_cards = self.onboarding_service.get_onboarding_cards(company)
|
|
63
88
|
return render_template(
|
|
64
89
|
"chat.html",
|
|
90
|
+
company_short_name=company_short_name,
|
|
91
|
+
user_identifier=user_identifier,
|
|
65
92
|
branding=branding_data,
|
|
66
93
|
prompts=prompts,
|
|
67
|
-
onboarding_cards=onboarding_cards
|
|
94
|
+
onboarding_cards=onboarding_cards,
|
|
95
|
+
redeem_token=redeem_token
|
|
68
96
|
)
|
|
@@ -6,12 +6,14 @@
|
|
|
6
6
|
import os
|
|
7
7
|
import logging
|
|
8
8
|
from flask import request, jsonify
|
|
9
|
+
from flask.views import MethodView
|
|
9
10
|
from injector import inject
|
|
10
11
|
from iatoolkit.services.auth_service import AuthService
|
|
11
12
|
from iatoolkit.views.base_login_view import BaseLoginView
|
|
12
13
|
|
|
13
14
|
# Importar los servicios que necesita la clase base
|
|
14
15
|
from iatoolkit.services.profile_service import ProfileService
|
|
16
|
+
from iatoolkit.services.jwt_service import JWTService
|
|
15
17
|
from iatoolkit.services.branding_service import BrandingService
|
|
16
18
|
from iatoolkit.services.onboarding_service import OnboardingService
|
|
17
19
|
from iatoolkit.services.query_service import QueryService
|
|
@@ -25,6 +27,7 @@ class ExternalLoginView(BaseLoginView):
|
|
|
25
27
|
@inject
|
|
26
28
|
def __init__(self,
|
|
27
29
|
iauthentication: AuthService,
|
|
30
|
+
jwt_service: JWTService,
|
|
28
31
|
profile_service: ProfileService,
|
|
29
32
|
branding_service: BrandingService,
|
|
30
33
|
prompt_service: PromptService,
|
|
@@ -33,26 +36,27 @@ class ExternalLoginView(BaseLoginView):
|
|
|
33
36
|
# Pass the dependencies for the base class to its __init__
|
|
34
37
|
super().__init__(
|
|
35
38
|
profile_service=profile_service,
|
|
39
|
+
jwt_service=jwt_service,
|
|
36
40
|
branding_service=branding_service,
|
|
37
41
|
onboarding_service=onboarding_service,
|
|
38
42
|
query_service=query_service,
|
|
39
|
-
prompt_service=prompt_service
|
|
43
|
+
prompt_service=prompt_service,
|
|
40
44
|
)
|
|
41
45
|
# Handle the dependency specific to this child class
|
|
42
46
|
self.iauthentication = iauthentication
|
|
43
47
|
|
|
44
48
|
def post(self, company_short_name: str):
|
|
45
49
|
data = request.get_json()
|
|
46
|
-
if not data or '
|
|
47
|
-
return jsonify({"error": "Falta
|
|
50
|
+
if not data or 'user_identifier' not in data:
|
|
51
|
+
return jsonify({"error": "Falta user_identifier"}), 400
|
|
48
52
|
|
|
49
53
|
company = self.profile_service.get_company_by_short_name(company_short_name)
|
|
50
54
|
if not company:
|
|
51
55
|
return jsonify({"error": "Empresa no encontrada"}), 404
|
|
52
56
|
|
|
53
|
-
|
|
54
|
-
if not
|
|
55
|
-
return jsonify({"error": "missing
|
|
57
|
+
user_identifier = data.get('user_identifier')
|
|
58
|
+
if not user_identifier:
|
|
59
|
+
return jsonify({"error": "missing user_identifier"}), 404
|
|
56
60
|
|
|
57
61
|
# 1. Authenticate the API call.
|
|
58
62
|
iaut = self.iauthentication.verify()
|
|
@@ -60,11 +64,47 @@ class ExternalLoginView(BaseLoginView):
|
|
|
60
64
|
return jsonify(iaut), 401
|
|
61
65
|
|
|
62
66
|
# 2. Create the external user session.
|
|
63
|
-
self.profile_service.create_external_user_session(company,
|
|
67
|
+
self.profile_service.create_external_user_session(company, user_identifier)
|
|
64
68
|
|
|
65
69
|
# 3. Delegate the path decision to the centralized logic.
|
|
66
70
|
try:
|
|
67
|
-
return self._handle_login_path(company_short_name,
|
|
71
|
+
return self._handle_login_path(company_short_name, user_identifier, company)
|
|
68
72
|
except Exception as e:
|
|
69
|
-
logging.exception(f"Error processing external login path for {company_short_name}/{
|
|
70
|
-
return jsonify({"error": f"Internal server error while starting chat. {str(e)}"}), 500
|
|
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
|
|
@@ -1,53 +1,74 @@
|
|
|
1
|
+
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
+
# Product: IAToolkit
|
|
3
|
+
#
|
|
4
|
+
# IAToolkit is open source software.
|
|
5
|
+
|
|
1
6
|
import requests
|
|
2
7
|
import json
|
|
3
8
|
import os
|
|
4
9
|
from flask.views import MethodView
|
|
5
|
-
from flask import
|
|
10
|
+
from flask import render_template, request, Response
|
|
11
|
+
from injector import inject
|
|
12
|
+
from iatoolkit.services.profile_service import ProfileService
|
|
6
13
|
|
|
7
14
|
|
|
8
15
|
class LoginSimulationView(MethodView):
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
16
|
+
@inject
|
|
17
|
+
def __init__(self,
|
|
18
|
+
profile_service: ProfileService):
|
|
19
|
+
self.profile_service = profile_service
|
|
20
|
+
|
|
21
|
+
def get(self, company_short_name: str = None):
|
|
22
|
+
"""Muestra el formulario para iniciar la simulación."""
|
|
23
|
+
return render_template('login_simulation.html',
|
|
24
|
+
company_short_name=company_short_name
|
|
25
|
+
)
|
|
14
26
|
|
|
15
|
-
def
|
|
27
|
+
def post(self, company_short_name: str):
|
|
28
|
+
"""
|
|
29
|
+
Recibe el POST del formulario y actúa como un proxy servidor-a-servidor.
|
|
30
|
+
Llama al endpoint 'external_login' y devuelve su respuesta (HTML y headers).
|
|
31
|
+
"""
|
|
16
32
|
api_key = os.getenv("IATOOLKIT_API_KEY")
|
|
33
|
+
# Obtenemos la URL base de la petición actual para construir la URL interna
|
|
17
34
|
base_url = request.host_url.rstrip('/')
|
|
18
35
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if not external_user_id:
|
|
22
|
-
abort(400, "Error: Debes proporcionar un external_user_id en la URL.")
|
|
36
|
+
# 1. Obtener el user_identifier del formulario
|
|
37
|
+
user_identifier = request.form.get('external_user_id')
|
|
23
38
|
|
|
24
|
-
|
|
39
|
+
if not user_identifier:
|
|
40
|
+
return Response("Error: El campo 'external_user_id' es requerido.", status=400)
|
|
25
41
|
|
|
26
|
-
#
|
|
27
|
-
|
|
42
|
+
# 2. Preparar la llamada a la API real de external_login
|
|
43
|
+
target_url = f"{base_url}/{company_short_name}/external_login"
|
|
28
44
|
headers = {
|
|
29
45
|
'Content-Type': 'application/json',
|
|
30
46
|
'Authorization': f'Bearer {api_key}'
|
|
31
47
|
}
|
|
32
|
-
#
|
|
33
|
-
|
|
34
|
-
payload = {'external_user_id': external_user_id}
|
|
48
|
+
# El payload debe ser un diccionario que se convertirá a JSON
|
|
49
|
+
payload = {'user_identifier': user_identifier}
|
|
35
50
|
|
|
36
51
|
try:
|
|
37
|
-
# Llamada POST
|
|
38
|
-
internal_response = requests.post(
|
|
39
|
-
|
|
52
|
+
# 3. Llamada POST segura desde este servidor al endpoint de IAToolkit
|
|
53
|
+
internal_response = requests.post(
|
|
54
|
+
target_url,
|
|
55
|
+
headers=headers,
|
|
56
|
+
data=json.dumps(payload),
|
|
57
|
+
timeout=120,
|
|
58
|
+
stream=True # Usamos stream para manejar la respuesta eficientemente
|
|
59
|
+
)
|
|
40
60
|
internal_response.raise_for_status()
|
|
41
61
|
|
|
42
|
-
# Creamos una nueva Response de Flask para el navegador del usuario.
|
|
62
|
+
# 4. Creamos una nueva Response de Flask para el navegador del usuario.
|
|
43
63
|
user_response = Response(
|
|
44
64
|
internal_response.iter_content(chunk_size=1024),
|
|
45
65
|
status=internal_response.status_code
|
|
46
66
|
)
|
|
47
67
|
|
|
48
|
-
# Copiamos TODAS las cabeceras de la respuesta interna a la respuesta final
|
|
49
|
-
#
|
|
68
|
+
# 5. Copiamos TODAS las cabeceras de la respuesta interna a la respuesta final.
|
|
69
|
+
# Esto es CRUCIAL para que las cookies ('Set-Cookie') lleguen al navegador.
|
|
50
70
|
for key, value in internal_response.headers.items():
|
|
71
|
+
# Excluimos cabeceras que no debemos pasar (controladas por el servidor WSGI)
|
|
51
72
|
if key.lower() not in ['content-encoding', 'content-length', 'transfer-encoding', 'connection']:
|
|
52
73
|
user_response.headers[key] = value
|
|
53
74
|
|
|
@@ -57,4 +78,4 @@ class LoginSimulationView(MethodView):
|
|
|
57
78
|
error_text = f"Error en la llamada interna a la API: {e.response.status_code}. Respuesta: {e.response.text}"
|
|
58
79
|
return Response(error_text, status=e.response.status_code, mimetype='text/plain')
|
|
59
80
|
except requests.exceptions.RequestException as e:
|
|
60
|
-
return Response(f'Error de conexión con el servicio de IA: {str(e)}', status=502, mimetype='text/plain')
|
|
81
|
+
return Response(f'Error de conexión con el servicio de IA: {str(e)}', status=502, mimetype='text/plain')
|
iatoolkit/views/login_view.py
CHANGED
|
@@ -7,11 +7,13 @@ 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.jwt_service import JWTService
|
|
10
11
|
from iatoolkit.services.query_service import QueryService
|
|
11
12
|
from iatoolkit.services.prompt_manager_service import PromptService
|
|
12
13
|
from iatoolkit.services.branding_service import BrandingService
|
|
13
14
|
from iatoolkit.services.onboarding_service import OnboardingService
|
|
14
15
|
from iatoolkit.views.base_login_view import BaseLoginView
|
|
16
|
+
import logging
|
|
15
17
|
|
|
16
18
|
|
|
17
19
|
class LoginView(BaseLoginView):
|
|
@@ -28,7 +30,7 @@ class LoginView(BaseLoginView):
|
|
|
28
30
|
email = request.form.get('email')
|
|
29
31
|
password = request.form.get('password')
|
|
30
32
|
|
|
31
|
-
# 1. Authenticate user and create the
|
|
33
|
+
# 1. Authenticate user and create the session for internal user
|
|
32
34
|
auth_response = self.profile_service.login(
|
|
33
35
|
company_short_name=company_short_name,
|
|
34
36
|
email=email,
|
|
@@ -62,28 +64,38 @@ class FinalizeContextView(MethodView):
|
|
|
62
64
|
Finalizes context loading in the slow path.
|
|
63
65
|
This view is invoked by the iframe inside onboarding_shell.html.
|
|
64
66
|
"""
|
|
65
|
-
|
|
66
67
|
@inject
|
|
67
68
|
def __init__(self,
|
|
68
69
|
profile_service: ProfileService,
|
|
69
70
|
query_service: QueryService,
|
|
70
71
|
prompt_service: PromptService,
|
|
71
72
|
branding_service: BrandingService,
|
|
72
|
-
onboarding_service: OnboardingService
|
|
73
|
+
onboarding_service: OnboardingService,
|
|
74
|
+
jwt_service: JWTService,
|
|
73
75
|
):
|
|
74
76
|
self.profile_service = profile_service
|
|
77
|
+
self.jwt_service = jwt_service
|
|
75
78
|
self.query_service = query_service
|
|
76
79
|
self.prompt_service = prompt_service
|
|
77
80
|
self.branding_service = branding_service
|
|
78
81
|
self.onboarding_service = onboarding_service
|
|
79
82
|
|
|
80
|
-
def get(self, company_short_name: str):
|
|
81
|
-
# 1. Use the centralized method to get session info.
|
|
83
|
+
def get(self, company_short_name: str, token: str = None):
|
|
82
84
|
session_info = self.profile_service.get_current_session_info()
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
if session_info:
|
|
86
|
+
# session exists, internal user
|
|
87
|
+
user_identifier = session_info.get('user_identifier')
|
|
88
|
+
token = ''
|
|
89
|
+
elif token:
|
|
90
|
+
# user identified by api-key
|
|
91
|
+
payload = self.jwt_service.validate_chat_jwt(token)
|
|
92
|
+
if not payload:
|
|
93
|
+
logging.warning("Fallo crítico: No se pudo leer el auth token.")
|
|
94
|
+
return redirect(url_for('index', company_short_name=company_short_name))
|
|
95
|
+
|
|
96
|
+
user_identifier = payload.get('user_identifier')
|
|
97
|
+
else:
|
|
98
|
+
logging.warning("Fallo crítico: missing session information or auth token")
|
|
87
99
|
return redirect(url_for('index', company_short_name=company_short_name))
|
|
88
100
|
|
|
89
101
|
company = self.profile_service.get_company_by_short_name(company_short_name)
|
|
@@ -104,13 +116,17 @@ class FinalizeContextView(MethodView):
|
|
|
104
116
|
|
|
105
117
|
return render_template(
|
|
106
118
|
"chat.html",
|
|
119
|
+
company_short_name=company_short_name,
|
|
120
|
+
user_identifier=user_identifier,
|
|
107
121
|
branding=branding_data,
|
|
108
122
|
prompts=prompts,
|
|
109
|
-
onboarding_cards=onboarding_cards
|
|
123
|
+
onboarding_cards=onboarding_cards,
|
|
124
|
+
redeem_token=token
|
|
110
125
|
)
|
|
111
126
|
|
|
112
127
|
except Exception as e:
|
|
113
128
|
return render_template("error.html",
|
|
114
129
|
company=company,
|
|
115
130
|
company_short_name=company_short_name,
|
|
116
|
-
message=f"An unexpected error occurred during context loading: {str(e)}"), 500
|
|
131
|
+
message=f"An unexpected error occurred during context loading: {str(e)}"), 500
|
|
132
|
+
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
iatoolkit/__init__.py,sha256=4PWjMJjktixtrxF6BY405qyA50Sv967kEP2x-oil6qk,1120
|
|
2
|
-
iatoolkit/base_company.py,sha256=
|
|
2
|
+
iatoolkit/base_company.py,sha256=nfF-G0h63jy3Qh9kCnvx8Ozx76IjG2p7a34HpweWhOk,4608
|
|
3
3
|
iatoolkit/cli_commands.py,sha256=G5L9xQXZ0lVFXQWBaE_KEZHyfuiT6PL1nTQRoSdnBzc,2302
|
|
4
4
|
iatoolkit/company_registry.py,sha256=tduqt3oV8iDX_IB1eA7KIgvIxE4edTcy-3qZIXh3Lzw,2549
|
|
5
|
-
iatoolkit/iatoolkit.py,sha256=
|
|
5
|
+
iatoolkit/iatoolkit.py,sha256=ve63cb-1L-m_RLPsU7KRG9IPhhMREb1fDQjqOIgHx9w,17583
|
|
6
6
|
iatoolkit/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
7
|
iatoolkit/common/exceptions.py,sha256=EXx40n5htp7UiOM6P1xfJ9U6NMcADqm62dlFaKz7ICU,1154
|
|
8
|
-
iatoolkit/common/routes.py,sha256=
|
|
8
|
+
iatoolkit/common/routes.py,sha256=c8kRk8pEK_nV-2P1LiYXRERmW_QoW21Hk6CZkkizv6Y,6419
|
|
9
9
|
iatoolkit/common/session_manager.py,sha256=UeKfD15bcEA3P5e0WSURfotLqpsiIMp3AXxAMhtgHs0,471
|
|
10
10
|
iatoolkit/common/util.py,sha256=w9dTd3csK0gKtFSp-a4t7XmCPZiYDhiON92uXRbTT8A,14624
|
|
11
11
|
iatoolkit/infra/__init__.py,sha256=5JqK9sZ6jBuK83zDQokUhxQ0wuJJJ9DXB8pYCLkX7X4,102
|
|
@@ -26,39 +26,39 @@ iatoolkit/infra/connectors/google_drive_connector.py,sha256=WR1AlO5-Bl3W89opdja0
|
|
|
26
26
|
iatoolkit/infra/connectors/local_file_connector.py,sha256=hrzIgpMJOTuwTqzlQeTIU_50ZbZ6yl8lcWPv6hMnoqI,1739
|
|
27
27
|
iatoolkit/infra/connectors/s3_connector.py,sha256=Nj4_YaLobjfcnbZewJf21_K2EXohgcc3mJll1Pzn4zg,1123
|
|
28
28
|
iatoolkit/repositories/__init__.py,sha256=5JqK9sZ6jBuK83zDQokUhxQ0wuJJJ9DXB8pYCLkX7X4,102
|
|
29
|
-
iatoolkit/repositories/database_manager.py,sha256=
|
|
29
|
+
iatoolkit/repositories/database_manager.py,sha256=QgV8hNnVv9RmeOvUdomdj_mfk0bf3Rl8Ti41a-5zIAY,3700
|
|
30
30
|
iatoolkit/repositories/document_repo.py,sha256=Y7bF1kZB1HWJsAGjWdF7P2aVYeTYNufq9ngQXp7mDkY,1124
|
|
31
31
|
iatoolkit/repositories/llm_query_repo.py,sha256=YT_t7cYGQk8rwzH_17-28aTzO-e2jUfa2rvXy8tugvA,3612
|
|
32
32
|
iatoolkit/repositories/models.py,sha256=3YbIJXNMZiTkMbPyiSOiyzqUKEQF0JIfN4VSWYzwr44,13146
|
|
33
|
-
iatoolkit/repositories/profile_repo.py,sha256=
|
|
33
|
+
iatoolkit/repositories/profile_repo.py,sha256=21am3GP7XCG0nq6i3pArQ7mfGsrRn8rdcWT98fsdwlU,4397
|
|
34
34
|
iatoolkit/repositories/tasks_repo.py,sha256=icVO_r2oPagGnnBhwVFzznnvEEU2EAx-2dlWuWvoDC4,1745
|
|
35
35
|
iatoolkit/repositories/vs_repo.py,sha256=UkpmQQiocgM5IwRBmmWhw3HHzHP6zK1nN3J3TcQgjhc,5300
|
|
36
36
|
iatoolkit/services/__init__.py,sha256=5JqK9sZ6jBuK83zDQokUhxQ0wuJJJ9DXB8pYCLkX7X4,102
|
|
37
|
-
iatoolkit/services/auth_service.py,sha256=
|
|
37
|
+
iatoolkit/services/auth_service.py,sha256=XHG2F0Vf2wDFjylc_OdDEIJL8I4cdhxeUIZIltgE4DU,2936
|
|
38
38
|
iatoolkit/services/benchmark_service.py,sha256=CdbFYyS3FHFhNzWQEa9ZNjUlmON10DT1nKNbZQ1EUi8,5880
|
|
39
39
|
iatoolkit/services/branding_service.py,sha256=gXj9Lj6EIFNIHT6wAHia5lr4_2a2sD-ExMbewno5YD8,7505
|
|
40
|
-
iatoolkit/services/dispatcher_service.py,sha256=
|
|
40
|
+
iatoolkit/services/dispatcher_service.py,sha256=Qdn2x4cozpgpKg2448sUxkhO6tuplzb8xPWUxdTTFBE,12772
|
|
41
41
|
iatoolkit/services/document_service.py,sha256=nMXrNtbHQuc9pSaten0LvKY0kT8_WngBDmZJUP3jNPw,5936
|
|
42
42
|
iatoolkit/services/excel_service.py,sha256=JdAcg7_vPz3J16cf2chC6j7WYVpiT55tDX9667tfMUc,3764
|
|
43
43
|
iatoolkit/services/file_processor_service.py,sha256=B1sUUhZNFf-rT4_1wrD38GKNoBFMp2g0dYrXYMCWe2E,4122
|
|
44
44
|
iatoolkit/services/history_service.py,sha256=3IxcdpKV1mHBGIiv2KIYV3LsVQJ0GPdFuCOiGYRszMU,1255
|
|
45
|
-
iatoolkit/services/jwt_service.py,sha256=
|
|
45
|
+
iatoolkit/services/jwt_service.py,sha256=W2kQVNQheQSLkNLS7RZ4jd3hmySBPLbHAS5hvBrUI10,3244
|
|
46
46
|
iatoolkit/services/load_documents_service.py,sha256=ZpB0BZ3qX1fGJGBtZtMLbFdWWx0hkPoeCS3OqJKwCTs,7291
|
|
47
47
|
iatoolkit/services/mail_service.py,sha256=2h-fcF3swZDya_o7IpgXkmuj3iEVHVCiHi7oVxU99sQ,2182
|
|
48
48
|
iatoolkit/services/onboarding_service.py,sha256=cMO2Ho1-G3wAeVNl-j25LwCMJjRwj3yKHpYKnZUFLDE,2001
|
|
49
|
-
iatoolkit/services/profile_service.py,sha256=
|
|
49
|
+
iatoolkit/services/profile_service.py,sha256=uWlCsoGAPZaJkvVTBZVBWdh6Rk5LHcpDHWgYqi5vTp4,20317
|
|
50
50
|
iatoolkit/services/prompt_manager_service.py,sha256=U-XmSpkeXvv1KRN4dytdMxSYBMRSB7y-UHcb18mk0nA,8342
|
|
51
|
-
iatoolkit/services/query_service.py,sha256=
|
|
51
|
+
iatoolkit/services/query_service.py,sha256=J42KzthYIIsUX8cUS2E7vcXA6IZOJ28Y8I637m-9za4,17649
|
|
52
52
|
iatoolkit/services/search_service.py,sha256=i1xGWu7ORKIIDH0aAQBkF86dVVbLQ0Yrooz5TiZ6aGo,1823
|
|
53
53
|
iatoolkit/services/sql_service.py,sha256=MIslAtpJWnTMgSD74nnqTvQj27p-lHiyRXc6OiA2C_c,2172
|
|
54
54
|
iatoolkit/services/tasks_service.py,sha256=itREO5rDnUIgsqtyCOBKDtH30QL5v1egs4qPTiBK8xU,6865
|
|
55
55
|
iatoolkit/services/user_feedback_service.py,sha256=ooy750qWmYOeJi-IJQofu8pLG4svGjGU_JKpKMURZkw,2353
|
|
56
56
|
iatoolkit/services/user_session_context_service.py,sha256=TeYi4xF04Xk-4Qnp6cVTmxuNzA4B1nMVUuFDjTHeiZQ,6764
|
|
57
57
|
iatoolkit/static/images/fernando.jpeg,sha256=W68TYMuo5hZVpbP-evwH6Nu4xWFv2bc8pJzSKDoLTeQ,100612
|
|
58
|
-
iatoolkit/static/js/chat_feedback.js,sha256=
|
|
58
|
+
iatoolkit/static/js/chat_feedback.js,sha256=zlLEDQfEocGK7RKG2baqI-9fyQlqe6hVuAHOKTPmWek,4399
|
|
59
59
|
iatoolkit/static/js/chat_filepond.js,sha256=mzXafm7a506EpM37KATTK3zvAswO1E0KSUY1vKbwuRc,3163
|
|
60
|
-
iatoolkit/static/js/chat_history.js,sha256=
|
|
61
|
-
iatoolkit/static/js/chat_main.js,sha256=
|
|
60
|
+
iatoolkit/static/js/chat_history.js,sha256=4h6ldU7cDvgkW84fMKB8JReoxCX0NKSQAir_4CzAF9I,4382
|
|
61
|
+
iatoolkit/static/js/chat_main.js,sha256=3ZsMy6NauobPRaYDvSJAws26l16FDslXGm8kGfXLt_E,18538
|
|
62
62
|
iatoolkit/static/js/chat_onboarding.js,sha256=b6ofiFcPhuCaPmSFIvDQZqcMUVvbI7LpIsjZOZJUSAU,3185
|
|
63
63
|
iatoolkit/static/styles/chat_iatoolkit.css,sha256=aA-PZ2TGl_k82JSVVBC2-CJT0NiZAuLOGoiaJhdeVUU,11416
|
|
64
64
|
iatoolkit/static/styles/chat_info.css,sha256=17DbgoNYE21VYWfb5L9-QLCpD2R1idK4imKRLwXtJLY,1058
|
|
@@ -75,21 +75,21 @@ iatoolkit/templates/_navbar.html,sha256=o1PvZE5ueLmVpGUAmsjtu-vS_WPROTlJc2sTXl6A
|
|
|
75
75
|
iatoolkit/templates/about.html,sha256=ciC08grUVz5qLzdzDDqDX31xirg5PrJIRYabWpV9oA8,294
|
|
76
76
|
iatoolkit/templates/base.html,sha256=hHfBqZJsPcZGlb4BxAbHvpJFcSjckLIAVTYTmoyXrz0,2323
|
|
77
77
|
iatoolkit/templates/change_password.html,sha256=G5a3hYLTpz_5Q_eZ4LNcYSqLeW-CuT4NCHD8bkhAd9k,3573
|
|
78
|
-
iatoolkit/templates/chat.html,sha256=
|
|
78
|
+
iatoolkit/templates/chat.html,sha256=usmyuke0Moh6jNMWvxK2p_N15V6UzHNeXyXDd45XWIU,14087
|
|
79
79
|
iatoolkit/templates/chat_modals.html,sha256=NwwgPoOmVbjy4aO2eHsy1TUMXRiOfTOC5Jx_F2ehhcs,6947
|
|
80
80
|
iatoolkit/templates/error.html,sha256=c3dxieMygsvdjQMiQu_sn6kqqag9zFtVu-z5FunX6so,580
|
|
81
81
|
iatoolkit/templates/forgot_password.html,sha256=NRZqbNHJXSLNArF_KLbzuem-U57v07awS0ikI_DJbfM,2360
|
|
82
82
|
iatoolkit/templates/header.html,sha256=179agI7rnYwP_rvJNXIiVde5E8Ec5649_XKq6eew2Hk,1263
|
|
83
83
|
iatoolkit/templates/index.html,sha256=Q2wBHDv3HTBt-jURVLggye7XOqoqDY8E0LnrmLdc3SQ,7910
|
|
84
|
-
iatoolkit/templates/
|
|
84
|
+
iatoolkit/templates/login_simulation.html,sha256=1svwCBPrJ3Gy6bD9WMuz25NBSdFgZt4j8_sC7HE6MFU,1270
|
|
85
85
|
iatoolkit/templates/onboarding_shell.html,sha256=r1ivSR2ci8GrDSm1uaD-cf78rfO1bKT5gXa-v5aHLAk,4659
|
|
86
86
|
iatoolkit/templates/signup.html,sha256=9ArDvcNQgHFR2dwxy-37AXzGUOeOsT7Nz5u0y6fAB3U,4385
|
|
87
87
|
iatoolkit/templates/test.html,sha256=rwNtxC83tbCl5COZFXYvmRBxxmgFJtPNuVBd_nq9KWY,133
|
|
88
88
|
iatoolkit/views/__init__.py,sha256=5JqK9sZ6jBuK83zDQokUhxQ0wuJJJ9DXB8pYCLkX7X4,102
|
|
89
|
-
iatoolkit/views/base_login_view.py,sha256=
|
|
89
|
+
iatoolkit/views/base_login_view.py,sha256=6AANVhwhs5MyT3WVjJqKZLoS1TwyErsrKnb1xhpqP4Y,3970
|
|
90
90
|
iatoolkit/views/change_password_view.py,sha256=tM0woZyKdhY4XYjS_YXg2sKq3RYkXGfcq_eVAKrNvNM,4498
|
|
91
91
|
iatoolkit/views/chat_token_request_view.py,sha256=wf32_A2Sq8NHYWshCwL10Tovd1znLoD0jQjzutR3sVE,4408
|
|
92
|
-
iatoolkit/views/external_login_view.py,sha256=
|
|
92
|
+
iatoolkit/views/external_login_view.py,sha256=nuWebrl2Opnhj6H-UW6dUOHwi8pj4s1_-dN3VEKNQdY,4483
|
|
93
93
|
iatoolkit/views/file_store_api_view.py,sha256=Uz9f6sey3_F5K8zuyQz6SwYRKAalCjD1ekf-Mkl_Kfo,2326
|
|
94
94
|
iatoolkit/views/forgot_password_view.py,sha256=-qKJeeOBqJFdvDUk7rCNg1E1cDQnJQkozPpb0T0FgwA,3159
|
|
95
95
|
iatoolkit/views/history_api_view.py,sha256=x-tZhB8UzqrD2n-WDIfmHK9iVhGZ9f0yncsGs9mxwt0,1953
|
|
@@ -97,15 +97,15 @@ iatoolkit/views/index_view.py,sha256=P5aVdEWxsYOZGbzcXd6WFE733qZ7YXIoeqriUMAM6V8
|
|
|
97
97
|
iatoolkit/views/init_context_api_view.py,sha256=1j8NKfODfPrffbA5YO8TPMHh-ildlLNzReIxv_qO-W4,2586
|
|
98
98
|
iatoolkit/views/llmquery_api_view.py,sha256=Rh-y-VENwwtNsDrYAD_SWKwjK16fW-pFRWlEvI-OYwY,2120
|
|
99
99
|
iatoolkit/views/llmquery_web_view.py,sha256=WhjlA1mfsoL8hL9tlKQfjCUcaTzT43odlp_uQKmT314,1500
|
|
100
|
-
iatoolkit/views/login_simulation_view.py,sha256=
|
|
101
|
-
iatoolkit/views/login_view.py,sha256=
|
|
100
|
+
iatoolkit/views/login_simulation_view.py,sha256=0Qt-puRnltI2HZxlfdyJmOf26-hQp3xjknGV_jkwV7E,3484
|
|
101
|
+
iatoolkit/views/login_view.py,sha256=sjM8ki_F7C9S0a9pukLd5jn5KnM2sbRabnAIrtIK8KQ,5373
|
|
102
102
|
iatoolkit/views/prompt_api_view.py,sha256=MP0r-MiswwKcbNc_5KY7aVbHkrR218I8XCiCX1D0yTA,1244
|
|
103
103
|
iatoolkit/views/signup_view.py,sha256=BCjhM2lMiDPwYrlW_eEwPl-ZLupblbFfsonWtq0E4vU,3922
|
|
104
104
|
iatoolkit/views/tasks_review_view.py,sha256=keLsLCyOTTlcoIapnB_lbuSvLwrPVZVpBiFC_7ChbLg,3388
|
|
105
105
|
iatoolkit/views/tasks_view.py,sha256=a3anTXrJTTvbQuc6PSpOzidLKQFL4hWa7PI2Cppcz8w,4110
|
|
106
106
|
iatoolkit/views/user_feedback_api_view.py,sha256=59XB9uQLHI4Q6QA4_XhK787HzfXb-c6EY7k1Ccyr4hI,2424
|
|
107
107
|
iatoolkit/views/verify_user_view.py,sha256=7XLSaxvs8LjBr3cYOUDa9B8DqW_50IGlq0IvmOQcD0Y,2340
|
|
108
|
-
iatoolkit-0.
|
|
109
|
-
iatoolkit-0.
|
|
110
|
-
iatoolkit-0.
|
|
111
|
-
iatoolkit-0.
|
|
108
|
+
iatoolkit-0.56.0.dist-info/METADATA,sha256=Td4cobpNkkpPaIPZALITKGwwBHowzfG3klOYoGANjsk,9301
|
|
109
|
+
iatoolkit-0.56.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
110
|
+
iatoolkit-0.56.0.dist-info/top_level.txt,sha256=V_w4QvDx0b1RXiy8zTCrD1Bp7AZkFe3_O0-9fMiwogg,10
|
|
111
|
+
iatoolkit-0.56.0.dist-info/RECORD,,
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
{% extends "base.html" %}
|
|
2
|
-
|
|
3
|
-
{% block title %}Prueba de Login para {{ branding.name }}{% endblock %}
|
|
4
|
-
|
|
5
|
-
{% block styles %}
|
|
6
|
-
{# Este bloque asegura que los colores de la marca estén disponibles como variables CSS #}
|
|
7
|
-
{% if branding and branding.css_variables %}
|
|
8
|
-
<style>
|
|
9
|
-
{{ branding.css_variables|safe }}
|
|
10
|
-
/* Usa la variable de CSS para el color primario del botón */
|
|
11
|
-
#initiateJwtChatButton {
|
|
12
|
-
background-color: var(--brand-primary-color, #0d6efd);
|
|
13
|
-
border-color: var(--brand-primary-color, #0d6efd);
|
|
14
|
-
}
|
|
15
|
-
</style>
|
|
16
|
-
{% endif %}
|
|
17
|
-
{% endblock %}
|
|
18
|
-
|
|
19
|
-
{% block content %}
|
|
20
|
-
<div class="container-fluid">
|
|
21
|
-
<div class="row flex-fill mt-5 justify-content-center">
|
|
22
|
-
<!-- login desde sistema externo -->
|
|
23
|
-
<div class="col-12 col-lg-6">
|
|
24
|
-
<div class="border rounded p-4 p-md-5 shadow-sm bg-light">
|
|
25
|
-
{# El título ahora muestra dinámicamente el nombre de la empresa #}
|
|
26
|
-
<h3 class="text-muted fw-semibold text-start mb-3">
|
|
27
|
-
Login Externo para <span style="color: var(--brand-primary-color, #0d6efd);">{{ branding.name }}</span>
|
|
28
|
-
</h3>
|
|
29
|
-
<div class="text-center mb-4">
|
|
30
|
-
<p class="text-muted widget-intro-text">
|
|
31
|
-
Este formulario permite testear el acceso a IAToolkit desde un portal externo utilizando una api-key. El external user ID es el nombre del usuario ya autentificado en algún portal interno de la empresa.
|
|
32
|
-
</p>
|
|
33
|
-
</div>
|
|
34
|
-
|
|
35
|
-
{# El 'action' y 'method' son manejados por JS, pero el id es crucial #}
|
|
36
|
-
<form id="jwt-form">
|
|
37
|
-
|
|
38
|
-
<div class="mb-3">
|
|
39
|
-
<label for="external_user_id" class="form-label d-block">External user ID</label>
|
|
40
|
-
<input type="text" id="external_user_id" name="external_user_id" class="form-control" required>
|
|
41
|
-
</div>
|
|
42
|
-
|
|
43
|
-
<button type="submit" id="initiateJwtChatButton" class="btn btn-primary">
|
|
44
|
-
<span class="spinner-border spinner-border-sm d-none" role="status" aria-hidden="true"></span>
|
|
45
|
-
Iniciar Sesión
|
|
46
|
-
</button>
|
|
47
|
-
</form>
|
|
48
|
-
</div>
|
|
49
|
-
</div>
|
|
50
|
-
</div>
|
|
51
|
-
</div>
|
|
52
|
-
{% endblock %}
|
|
53
|
-
|
|
54
|
-
{% block scripts %}
|
|
55
|
-
<script>
|
|
56
|
-
document.addEventListener('DOMContentLoaded', function() {
|
|
57
|
-
const companyShortName = "{{ company_short_name }}";
|
|
58
|
-
const apiKey = "{{ api_key }}";
|
|
59
|
-
const loginForm = document.getElementById('jwt-form');
|
|
60
|
-
const userIdInput = document.getElementById('external_user_id');
|
|
61
|
-
const submitButton = document.getElementById('initiateJwtChatButton');
|
|
62
|
-
const spinner = submitButton.querySelector('.spinner-border');
|
|
63
|
-
|
|
64
|
-
loginForm.addEventListener('submit', function(event) {
|
|
65
|
-
event.preventDefault();
|
|
66
|
-
const userIdentifier = userIdInput.value.trim();
|
|
67
|
-
if (!userIdentifier) return;
|
|
68
|
-
|
|
69
|
-
// Deshabilitar botón y mostrar spinner
|
|
70
|
-
submitButton.disabled = true;
|
|
71
|
-
spinner.classList.remove('d-none');
|
|
72
|
-
|
|
73
|
-
const newWindow = window.open('', '_blank');
|
|
74
|
-
const apiUrl = `/${companyShortName}/external_login`;
|
|
75
|
-
|
|
76
|
-
fetch(apiUrl, {
|
|
77
|
-
method: 'POST',
|
|
78
|
-
headers: {
|
|
79
|
-
'Content-Type': 'application/json',
|
|
80
|
-
'Authorization': `Bearer ${apiKey}`
|
|
81
|
-
},
|
|
82
|
-
body: JSON.stringify({ external_user_id: userIdentifier })
|
|
83
|
-
})
|
|
84
|
-
.then(response => response.ok ? response.text() : response.text().then(text => { throw new Error(text) }))
|
|
85
|
-
.then(html => {
|
|
86
|
-
newWindow.document.documentElement.innerHTML = html;
|
|
87
|
-
// Buscamos todos los scripts en el HTML inyectado y los re-ejecutamos.
|
|
88
|
-
const scripts = newWindow.document.querySelectorAll('script');
|
|
89
|
-
scripts.forEach(oldScript => {
|
|
90
|
-
const newScript = newWindow.document.createElement('script');
|
|
91
|
-
|
|
92
|
-
// Copiamos los atributos (como 'src' para archivos externos)
|
|
93
|
-
Array.from(oldScript.attributes).forEach(attr => {
|
|
94
|
-
newScript.setAttribute(attr.name, attr.value);
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
// Copiamos el contenido para scripts inline
|
|
98
|
-
newScript.appendChild(newWindow.document.createTextNode(oldScript.innerHTML));
|
|
99
|
-
|
|
100
|
-
// Reemplazamos el script viejo por el nuevo para que se ejecute.
|
|
101
|
-
oldScript.parentNode.replaceChild(newScript, oldScript);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
})
|
|
105
|
-
.catch(error => {
|
|
106
|
-
console.error("Falló la prueba de login externo:", error);
|
|
107
|
-
const errorMessage = error.message || "Error desconocido.";
|
|
108
|
-
newWindow.document.body.innerHTML = `<div style="padding: 20px; font-family: monospace;"><h2>Error</h2><pre>${errorMessage}</pre></div>`;
|
|
109
|
-
})
|
|
110
|
-
.finally(() => {
|
|
111
|
-
// Volver a habilitar el botón y ocultar el spinner
|
|
112
|
-
submitButton.disabled = false;
|
|
113
|
-
spinner.classList.add('d-none');
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
});
|
|
117
|
-
</script>
|
|
118
|
-
{% endblock %}
|
|
File without changes
|
|
File without changes
|