From e9a47bee2688ab7615b8a8896bede5477e0017b6 Mon Sep 17 00:00:00 2001 From: cynfo3000 Date: Mon, 9 Mar 2026 23:39:38 +0100 Subject: [PATCH] fix: Terminal Auth per JWT-Token statt leerem API-Key - TerminalModal nutzt jetzt JWT aus localStorage (api.getToken()) fuer WS-Auth - Backend Middleware: ?token= Query-Parameter fuer WebSocket JWT-Auth - Behebt: api_key= leer -> 401 -> Terminal nicht erreichbar --- backend/api/middleware.go | 13 +++++++++---- frontend/src/components/TerminalModal.jsx | 11 ++++++++++- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/backend/api/middleware.go b/backend/api/middleware.go index bbb47b0..f375b74 100644 --- a/backend/api/middleware.go +++ b/backend/api/middleware.go @@ -73,7 +73,7 @@ func (c *APIKeyCache) IsValid(key string) bool { // CombinedAuthWithCache - API-Key (aus DB-Cache) ODER JWT Token func CombinedAuthWithCache(cache *APIKeyCache, auth *AuthHandler, next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Try API key first + // Try API key first (Header oder Query-Parameter) key := r.Header.Get("X-API-Key") if key == "" { key = r.URL.Query().Get("api_key") @@ -83,11 +83,16 @@ func CombinedAuthWithCache(cache *APIKeyCache, auth *AuthHandler, next http.Hand return } - // Try JWT + // Try JWT — Authorization Header oder ?token= Query-Parameter (fuer WebSocket) + var jwtToken string authHeader := r.Header.Get("Authorization") if strings.HasPrefix(authHeader, "Bearer ") { - token := strings.TrimPrefix(authHeader, "Bearer ") - claims, err := auth.ValidateToken(token) + jwtToken = strings.TrimPrefix(authHeader, "Bearer ") + } else if t := r.URL.Query().Get("token"); t != "" { + jwtToken = t + } + if jwtToken != "" { + claims, err := auth.ValidateToken(jwtToken) if err == nil { ctx := context.WithValue(r.Context(), UserContextKey, claims) next.ServeHTTP(w, r.WithContext(ctx)) diff --git a/frontend/src/components/TerminalModal.jsx b/frontend/src/components/TerminalModal.jsx index 4c3e8ce..3270c66 100644 --- a/frontend/src/components/TerminalModal.jsx +++ b/frontend/src/components/TerminalModal.jsx @@ -4,6 +4,7 @@ import { FitAddon } from '@xterm/addon-fit' import '@xterm/xterm/css/xterm.css' import { useSettingsStore } from '../stores/settings' import { API_KEY } from '../config' +import api from '../api/client' export default function TerminalModal({ agentId, agentName, onClose }) { const termRef = useRef(null) @@ -13,6 +14,8 @@ export default function TerminalModal({ agentId, agentName, onClose }) { const [status, setStatus] = useState('connecting') // connecting | open | error | closed const backendHost = useSettingsStore.getState().getBackendHost() + // JWT-Token bevorzugen (Browser ist per Login authentifiziert), API-Key als Fallback + const jwtToken = api.getToken() const apiKey = API_KEY useEffect(() => { @@ -58,7 +61,13 @@ export default function TerminalModal({ agentId, agentName, onClose }) { const cols = term.cols || 220 const rows = term.rows || 50 const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:' - const wsUrl = `${wsProtocol}//${window.location.host}/api/v1/agents/${agentId}/terminal?cols=${cols}&rows=${rows}&api_key=${apiKey}` + // JWT bevorzugen, API-Key als Fallback (API-Key nur wenn konfiguriert) + const authParam = jwtToken + ? `token=${encodeURIComponent(jwtToken)}` + : apiKey + ? `api_key=${encodeURIComponent(apiKey)}` + : '' + const wsUrl = `${wsProtocol}//${window.location.host}/api/v1/agents/${agentId}/terminal?cols=${cols}&rows=${rows}&${authParam}` const ws = new WebSocket(wsUrl) ws.binaryType = 'arraybuffer'