Files
Remote-Control-Center/guacamole_test_11_26/docs/DELETE_CONNECTION_FIX.md
2025-11-25 09:58:37 +03:00

13 KiB
Executable File
Raw Blame History

🔧 Delete Connection Fix - 500 Error

Проблема

При попытке удалить активное подключение возникала ошибка:

DELETE /connections/38
500 (Internal Server Error)

[guacamole-service] Failed to delete connection | 
context: {"connectionId":"38","error":{}}

Причина

После исправления middleware (для решения проблемы с восстановлением сессий), возникла новая проблема:

Что происходило:

  1. Пользователь создает подключение:

    • Guacamole выдает auth_token_A
    • Сохраняется в Redis: conn_data['auth_token'] = auth_token_A
  2. Пользователь делает logout/login:

    • Guacamole выдает НОВЫЙ auth_token_B
    • Старый auth_token_A становится невалидным
    • Но в Redis для подключения все еще хранится auth_token_A
  3. Пользователь пытается удалить подключение:

    • Код пытается удалить используя conn_data['auth_token'] (старый auth_token_A)
    • Guacamole отклоняет запрос: токен невалиден
    • Результат: 500 Internal Server Error

Решение

1. Для ручного удаления (DELETE /connections/{id})

Используем ТЕКУЩИЙ токен пользователя из активной сессии:

ДО ( НЕПРАВИЛЬНО):

@app.delete("/connections/{connection_id}")
async def delete_connection(connection_id: str, request: Request, ...):
    user_info = get_current_user(request)
    conn_data = redis_connection_storage.get_connection(connection_id)
    
    # ❌ Использует СТАРЫЙ токен из Redis
    if guacamole_client.delete_connection_with_user_token(
        connection_id, 
        conn_data['auth_token']  # ← Старый, невалидный токен!
    ):
        # ...

ПОСЛЕ ( ПРАВИЛЬНО):

@app.delete("/connections/{connection_id}")
async def delete_connection(connection_id: str, request: Request, ...):
    user_info = get_current_user(request)
    
    # ✅ Получаем ТЕКУЩИЙ токен из активной сессии пользователя
    current_user_token = get_current_user_token(request)
    if not current_user_token:
        raise HTTPException(status_code=401, detail="Authentication token not available")
    
    conn_data = redis_connection_storage.get_connection(connection_id)
    
    # ✅ Используем ТЕКУЩИЙ токен пользователя
    if guacamole_client.delete_connection_with_user_token(
        connection_id, 
        current_user_token  # ← Актуальный токен!
    ):
        # ...

2. Для автоматического cleanup (фоновая задача)

Используем СИСТЕМНЫЙ токен (не пользовательский):

ДО ( НЕПРАВИЛЬНО):

def cleanup_expired_or_orphaned_connections(log_action: str = "expired"):
    for conn_id in expired_connections:
        conn_data = redis_connection_storage.get_connection(conn_id)
        
        # ❌ Использует токен пользователя (может быть невалидным)
        if guacamole_client.delete_connection_with_user_token(
            conn_id, 
            conn_data['auth_token']  # ← Старый токен пользователя
        ):
            # ...

ПОСЛЕ ( ПРАВИЛЬНО):

def cleanup_expired_or_orphaned_connections(log_action: str = "expired"):
    for conn_id in expired_connections:
        conn_data = redis_connection_storage.get_connection(conn_id)
        
        # ✅ Cleanup - системная операция, используем системный токен
        if guacamole_client.delete_connection_with_system_token(conn_id):
            # ...

Почему системный токен:

  • Cleanup - это фоновая системная задача
  • Не привязана к конкретному пользователю
  • Системный токен всегда валиден
  • Правильнее с точки зрения архитектуры

Архитектура токенов

┌─────────────────────────────────────────────────────────────┐
│ Пользователь логинится                                      │
└────────────┬────────────────────────────────────────────────┘
             │
             ▼
┌─────────────────────────────────────────────────────────────┐
│ Guacamole выдает auth_token_NEW                             │
└────────────┬────────────────────────────────────────────────┘
             │
             ├─► Сохраняется в Redis Session (для middleware)
             │
             └─► СТАРЫЕ подключения все еще хранят auth_token_OLD
                 в Redis (невалидный!)

Решение проблемы:

Для операций от лица пользователя:

Пользователь → JWT → Middleware → Redis Session → ТЕКУЩИЙ токен
                                                          │
                                                          ▼
                                                   Guacamole API ✅

Для системных операций:

Система (Cleanup) → Системный токен → Guacamole API ✅

Изменённые файлы

1. GuacamoleRemoteAccess/api/main.py

Строки 3111-3120 - Добавлено получение текущего токена:

# ✅ КРИТИЧНО: Получаем ТЕКУЩИЙ токен пользователя для удаления
current_user_token = get_current_user_token(request)
if not current_user_token:
    logger.error("No Guacamole token available for user",
                username=user_info["username"],
                connection_id=connection_id)
    raise HTTPException(
        status_code=401,
        detail="Authentication token not available"
    )

Строки 3156-3158 - Используется текущий токен:

# ✅ КРИТИЧНО: Удаляем из Guacamole используя ТЕКУЩИЙ токен пользователя
# Не используем conn_data['auth_token'] так как он может быть невалидным после logout/login
if guacamole_client.delete_connection_with_user_token(connection_id, current_user_token):

Строки 1295-1297 - Cleanup использует системный токен:

# ✅ КРИТИЧНО: Удаляем из Guacamole используя СИСТЕМНЫЙ токен
# Cleanup - это системная операция, не используем auth_token пользователя
if guacamole_client.delete_connection_with_system_token(conn_id):

Как это работает

Сценарий 1: Пользователь удаляет подключение

Client                API                 Redis Session       Guacamole
  │                    │                         │                │
  │───DELETE /conn/38─>│                         │                │
  │   JWT Token        │                         │                │
  │                    │──get_current_user_token>│                │
  │                    │<─current_token──────────│                │
  │                    │                         │                │
  │                    │────DELETE connection────────────────────>│
  │                    │    (current_token)      │                │
  │                    │<───200 OK───────────────────────────────│
  │<──200 OK───────────│                         │                │
  │   Success          │                         │                │

Ключевой момент: Используется current_token из активной сессии, а НЕ auth_token из Redis подключения!


Сценарий 2: Cleanup удаляет истекшие подключения

Background Task       API                 Guacamole
      │               │                       │
      │─cleanup()────>│                       │
      │               │──get_system_token()──>│
      │               │<─system_token─────────│
      │               │                       │
      │               │──DELETE connections──>│
      │               │  (system_token)       │
      │               │<─200 OK──────────────│
      │<─complete─────│                       │

Ключевой момент: Используется system_token, не токен какого-либо пользователя!


Связь с предыдущими исправлениями

1. Middleware Fix (исправление восстановления сессий)

Проблема: JWT содержал session_id, но не guac_token Решение: Middleware загружает токен из Redis сессии

Побочный эффект: Токены в старых подключениях становятся невалидными после logout/login


2. Delete Connection Fix (это исправление)

Проблема: Удаление использовало старый токен из conn_data['auth_token'] Решение: Удаление использует текущий токен из активной сессии


Проверка исправления

1. Перезапустить API

cd GuacamoleRemoteAccess
docker-compose restart api

2. Протестировать удаление подключения

# 1. Залогиниться
curl -X POST https://mc.exbytestudios.com/auth/login-ecdh \
  -H "Content-Type: application/json" \
  -d '{"username":"user","password":"pass","session_id":"..."}' \
  > login_response.json

# Извлечь JWT
JWT=$(jq -r '.access_token' login_response.json)

# 2. Создать подключение
curl -X POST https://mc.exbytestudios.com/connect \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '{"hostname":"test","protocol":"ssh"}' \
  > connection.json

# Извлечь connection_id
CONN_ID=$(echo $connection | jq -r '.connection_id')

# 3. Удалить подключение
curl -X DELETE https://mc.exbytestudios.com/connections/$CONN_ID \
  -H "Authorization: Bearer $JWT"

# Должен вернуть:
# HTTP/1.1 200 OK
# {"message":"Connection deleted successfully"}

3. Проверить логи

docker-compose logs api | grep -i "delete"

# Должно быть:
# [info] Connection deleted successfully
# НЕ должно быть:
# [error] Failed to delete connection

Результат

После исправления:

  • Удаление подключения работает даже после logout/login
  • Используется актуальный токен из активной сессии
  • Cleanup использует системный токен (более правильно)
  • Нет 500 ошибок при удалении
  • Логика токенов консистентна

📝 Checklist

  • Добавлено получение current_user_token в delete_connection
  • delete_connection использует current_user_token вместо auth_token
  • Cleanup использует delete_connection_with_system_token
  • Перезапущен API: docker-compose restart api
  • Протестировано удаление подключения
  • Проверены логи (нет ошибок)

Дата исправления: 2025-11-04 Связанные исправления: Middleware Fix, CORS Duplicate Fix Статус: 🟢 ИСПРАВЛЕНО