Merge pull request 'fix(invites): logging detallado cuando Hub invite creation falla' (#464) from fix/hub-invite-credentials into development
Reviewed-on: ADUANASOFT/anexo76#464
This commit is contained in:
@@ -4,13 +4,15 @@ import logging
|
|||||||
|
|
||||||
from core.database import get_core_db
|
from core.database import get_core_db
|
||||||
from core.security import get_current_user, validate_access_to_resource
|
from core.security import get_current_user, validate_access_to_resource
|
||||||
from fastapi import APIRouter, Depends, Query, Request
|
from fastapi import APIRouter, Depends, Request
|
||||||
|
from fastapi.security import HTTPBearer
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from .dto import CreateInviteDTO, InviteResponseDTO
|
from .dto import CreateInviteDTO, InviteResponseDTO
|
||||||
from .service import InviteService
|
from .service import InviteService
|
||||||
|
|
||||||
router = APIRouter(prefix="/invites", tags=["Invites"])
|
router = APIRouter(prefix="/invites", tags=["Invites"])
|
||||||
|
_bearer = HTTPBearer()
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -21,12 +23,13 @@ async def create_invite(
|
|||||||
request: Request,
|
request: Request,
|
||||||
db: Session = Depends(get_core_db),
|
db: Session = Depends(get_core_db),
|
||||||
current_user: dict = Depends(get_current_user),
|
current_user: dict = Depends(get_current_user),
|
||||||
|
credentials=Depends(_bearer),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Genera un token de invitación para que un usuario externo se registre.
|
Genera un token de invitación para que un usuario externo se registre.
|
||||||
Requiere permiso user.create.
|
Requiere permiso user.create. Usa el token del usuario actual para crear
|
||||||
|
el invite en el Hub — no requiere credenciales de hub_admin.
|
||||||
"""
|
"""
|
||||||
# Validar acceso (user.create) y obtener tenant_id
|
|
||||||
validate_access_to_resource(
|
validate_access_to_resource(
|
||||||
db,
|
db,
|
||||||
data.company_id,
|
data.company_id,
|
||||||
@@ -42,4 +45,5 @@ async def create_invite(
|
|||||||
data=data,
|
data=data,
|
||||||
created_by=created_by,
|
created_by=created_by,
|
||||||
tenant_slug=tenant_slug,
|
tenant_slug=tenant_slug,
|
||||||
|
user_access_token=credentials.credentials,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ class InviteService:
|
|||||||
data: CreateInviteDTO,
|
data: CreateInviteDTO,
|
||||||
created_by: str,
|
created_by: str,
|
||||||
tenant_slug: str,
|
tenant_slug: str,
|
||||||
|
user_access_token: str = "",
|
||||||
) -> InviteResponseDTO:
|
) -> InviteResponseDTO:
|
||||||
import httpx
|
import httpx
|
||||||
from api.v1.modules.core.tenants.models import Tenant
|
from api.v1.modules.core.tenants.models import Tenant
|
||||||
@@ -75,28 +76,40 @@ class InviteService:
|
|||||||
if not company_role:
|
if not company_role:
|
||||||
raise HTTPException(status_code=404, detail="Rol no encontrado")
|
raise HTTPException(status_code=404, detail="Rol no encontrado")
|
||||||
|
|
||||||
# Crear invite en el Hub para que el usuario use el form del workspace
|
# Crear invite en el Hub usando el token del usuario actual.
|
||||||
|
# El usuario debe tener role='admin' en su tenant dentro del Hub.
|
||||||
|
# No se requieren credenciales de hub_admin — sin secretos en el .env del cliente.
|
||||||
hub_invite_token: Optional[str] = None
|
hub_invite_token: Optional[str] = None
|
||||||
invite_url: str = ""
|
invite_url: str = ""
|
||||||
try:
|
if not user_access_token:
|
||||||
async with httpx.AsyncClient(timeout=10.0) as client:
|
logger.error(
|
||||||
login_resp = await client.post(
|
"[invite] user_access_token vacío — no se puede crear el invite en el Hub. "
|
||||||
f"{settings.HUB_URL}api/v1/auth/login",
|
"tenant=%s email=%s",
|
||||||
json={"username": settings.HUB_ADMIN_EMAIL, "password": settings.HUB_ADMIN_PASSWORD},
|
tenant_slug,
|
||||||
)
|
data.email,
|
||||||
if login_resp.status_code == 200:
|
)
|
||||||
svc_token = login_resp.json().get("access_token", "")
|
else:
|
||||||
|
try:
|
||||||
|
async with httpx.AsyncClient(timeout=10.0) as client:
|
||||||
hub_resp = await client.post(
|
hub_resp = await client.post(
|
||||||
f"{settings.HUB_URL}api/v1/hub/invites",
|
f"{settings.HUB_URL}api/v1/hub/invites",
|
||||||
json={"email": str(data.email), "tenant_slug": tenant_slug},
|
json={"email": str(data.email), "tenant_slug": tenant_slug},
|
||||||
headers={"Authorization": f"Bearer {svc_token}"},
|
headers={"Authorization": f"Bearer {user_access_token}"},
|
||||||
)
|
)
|
||||||
if hub_resp.status_code in (200, 201):
|
if hub_resp.status_code in (200, 201):
|
||||||
hub_data = hub_resp.json()
|
hub_data = hub_resp.json()
|
||||||
hub_invite_token = hub_data.get("invite_token") or _extract_token_from_url(hub_data.get("invite_url", ""))
|
hub_invite_token = hub_data.get("invite_token") or _extract_token_from_url(hub_data.get("invite_url", ""))
|
||||||
invite_url = hub_data.get("invite_url", "")
|
invite_url = hub_data.get("invite_url", "")
|
||||||
except Exception as exc:
|
else:
|
||||||
logger.warning("Hub invite creation failed (non-blocking): %s", exc)
|
logger.error(
|
||||||
|
"[invite] Hub invite creation falló: status=%s body=%s tenant=%s email=%s",
|
||||||
|
hub_resp.status_code,
|
||||||
|
hub_resp.text[:300],
|
||||||
|
tenant_slug,
|
||||||
|
data.email,
|
||||||
|
)
|
||||||
|
except Exception as exc:
|
||||||
|
logger.error("[invite] Hub invite creation excepción (non-blocking): %s", exc)
|
||||||
|
|
||||||
# Fallback: URL del workspace (Hub) si la creación de invitación en Hub falló
|
# Fallback: URL del workspace (Hub) si la creación de invitación en Hub falló
|
||||||
if not invite_url:
|
if not invite_url:
|
||||||
|
|||||||
Reference in New Issue
Block a user