13 KiB
Executable File
🔧 Delete Connection Fix - 500 Error
Проблема
При попытке удалить активное подключение возникала ошибка:
DELETE /connections/38
500 (Internal Server Error)
[guacamole-service] Failed to delete connection |
context: {"connectionId":"38","error":{}}
Причина
После исправления middleware (для решения проблемы с восстановлением сессий), возникла новая проблема:
Что происходило:
-
Пользователь создает подключение:
- Guacamole выдает
auth_token_A - Сохраняется в Redis:
conn_data['auth_token'] = auth_token_A
- Guacamole выдает
-
Пользователь делает logout/login:
- Guacamole выдает НОВЫЙ
auth_token_B - Старый
auth_token_Aстановится невалидным - Но в Redis для подключения все еще хранится
auth_token_A❌
- Guacamole выдает НОВЫЙ
-
Пользователь пытается удалить подключение:
- Код пытается удалить используя
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 Статус: 🟢 ИСПРАВЛЕНО