diff --git a/modules/interfaces/interfaceAppModel.py b/modules/interfaces/interfaceAppModel.py index 6e087f63..ec95aaf5 100644 --- a/modules/interfaces/interfaceAppModel.py +++ b/modules/interfaces/interfaceAppModel.py @@ -156,6 +156,25 @@ class UserConnection(BaseModel, ModelMixin): frontend_readonly=True, frontend_required=False ) + tokenStatus: Optional[str] = Field( + None, + description="Current token status: active, expired, none", + frontend_type="select", + frontend_readonly=True, + frontend_required=False, + frontend_options=[ + {"value": "active", "label": {"en": "Active", "fr": "Actif"}}, + {"value": "expired", "label": {"en": "Expired", "fr": "Expiré"}}, + {"value": "none", "label": {"en": "None", "fr": "Aucun"}} + ] + ) + tokenExpiresAt: Optional[float] = Field( + None, + description="When the current token expires (UTC timestamp in seconds)", + frontend_type="timestamp", + frontend_readonly=True, + frontend_required=False + ) # Register labels for UserConnection register_model_labels( @@ -171,7 +190,9 @@ register_model_labels( "status": {"en": "Status", "fr": "Statut"}, "connectedAt": {"en": "Connected At", "fr": "Connecté le"}, "lastChecked": {"en": "Last Checked", "fr": "Dernière vérification"}, - "expiresAt": {"en": "Expires At", "fr": "Expire le"} + "expiresAt": {"en": "Expires At", "fr": "Expire le"}, + "tokenStatus": {"en": "Connection Status", "fr": "Statut de connexion"}, + "tokenExpiresAt": {"en": "Expires At", "fr": "Expire le"} } ) diff --git a/modules/routes/routeDataConnections.py b/modules/routes/routeDataConnections.py index c108480e..cf861e85 100644 --- a/modules/routes/routeDataConnections.py +++ b/modules/routes/routeDataConnections.py @@ -15,7 +15,7 @@ from datetime import datetime import logging import json -from modules.interfaces.interfaceAppModel import User, UserConnection, AuthAuthority, ConnectionStatus +from modules.interfaces.interfaceAppModel import User, UserConnection, AuthAuthority, ConnectionStatus, Token from modules.security.auth import getCurrentUser, limiter from modules.interfaces.interfaceAppObjects import getInterface, getRootInterface from modules.shared.timezoneUtils import get_utc_timestamp @@ -23,6 +23,57 @@ from modules.shared.timezoneUtils import get_utc_timestamp # Configure logger logger = logging.getLogger(__name__) +def get_token_status_for_connection(interface, connection_id: str) -> tuple[str, Optional[float]]: + """ + Get token status and expiration for a connection. + + Args: + interface: The database interface + connection_id: The connection ID to check + + Returns: + tuple: (token_status, token_expires_at) + - token_status: 'active', 'expired', or 'none' + - token_expires_at: UTC timestamp or None + """ + try: + # Query tokens table for the latest token for this connection + tokens = interface.db.getRecordset( + table="tokens", + recordFilter={"connectionId": connection_id} + ) + + if not tokens: + return "none", None + + # Find the most recent token (highest createdAt timestamp) + latest_token = None + latest_created_at = 0 + + for token_data in tokens: + created_at = token_data.get("createdAt", 0) + if created_at > latest_created_at: + latest_created_at = created_at + latest_token = token_data + + if not latest_token: + return "none", None + + # Check if token is expired + expires_at = latest_token.get("expiresAt") + if not expires_at: + return "none", None + + current_time = get_utc_timestamp() + if expires_at <= current_time: + return "expired", expires_at + else: + return "active", expires_at + + except Exception as e: + logger.error(f"Error getting token status for connection {connection_id}: {str(e)}") + return "none", None + router = APIRouter( prefix="/api/connections", tags=["Manage Connections"], @@ -47,7 +98,32 @@ async def get_connections( # SECURITY FIX: All users (including admins) can only see their own connections # This prevents admin from seeing other users' connections and causing confusion - return interface.getUserConnections(currentUser.id) + connections = interface.getUserConnections(currentUser.id) + + # Enhance each connection with token status information + enhanced_connections = [] + for connection in connections: + # Get token status for this connection + token_status, token_expires_at = get_token_status_for_connection(interface, connection.id) + + # Create enhanced connection with token status + enhanced_connection = UserConnection( + id=connection.id, + userId=connection.userId, + authority=connection.authority, + externalId=connection.externalId, + externalUsername=connection.externalUsername, + externalEmail=connection.externalEmail, + status=connection.status, + connectedAt=connection.connectedAt, + lastChecked=connection.lastChecked, + expiresAt=connection.expiresAt, + tokenStatus=token_status, + tokenExpiresAt=token_expires_at + ) + enhanced_connections.append(enhanced_connection) + + return enhanced_connections except Exception as e: logger.error(f"Error getting connections: {str(e)}") @@ -164,8 +240,26 @@ async def update_connection( # Clear cache to ensure fresh data interface.db.clearTableCache("connections") - # Get updated connection - return connection + # Get token status for the updated connection + token_status, token_expires_at = get_token_status_for_connection(interface, connectionId) + + # Create enhanced connection with token status + enhanced_connection = UserConnection( + id=connection.id, + userId=connection.userId, + authority=connection.authority, + externalId=connection.externalId, + externalUsername=connection.externalUsername, + externalEmail=connection.externalEmail, + status=connection.status, + connectedAt=connection.connectedAt, + lastChecked=connection.lastChecked, + expiresAt=connection.expiresAt, + tokenStatus=token_status, + tokenExpiresAt=token_expires_at + ) + + return enhanced_connection except HTTPException: raise