diff --git a/backend/api/middleware.go b/backend/api/middleware.go index f375b74..2121994 100644 --- a/backend/api/middleware.go +++ b/backend/api/middleware.go @@ -136,6 +136,38 @@ func CombinedAuth(validKeys []string, auth *AuthHandler, next http.Handler) http }) } +// JWTOnlyAuth - Nur JWT akzeptieren, kein API-Key (z.B. fuer Terminal-Zugriff) +// Erfordert echten Login mit User/Pass (+2FA), kein Maschinentoken reicht. +func JWTOnlyAuth(auth *AuthHandler, next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var jwtToken string + + // Authorization Header + authHeader := r.Header.Get("Authorization") + if strings.HasPrefix(authHeader, "Bearer ") { + jwtToken = strings.TrimPrefix(authHeader, "Bearer ") + } + // ?token= Query-Parameter (fuer WebSocket, Browser kann keinen Header setzen) + if jwtToken == "" { + jwtToken = r.URL.Query().Get("token") + } + + if jwtToken == "" { + http.Error(w, `{"error":"unauthorized: JWT erforderlich"}`, http.StatusUnauthorized) + return + } + + claims, err := auth.ValidateToken(jwtToken) + if err != nil { + http.Error(w, `{"error":"unauthorized: ungültiger Token"}`, http.StatusUnauthorized) + return + } + + ctx := context.WithValue(r.Context(), UserContextKey, claims) + next.ServeHTTP(w, r.WithContext(ctx)) + }) +} + // CORS Middleware func CORS(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { diff --git a/backend/api/terminal.go b/backend/api/terminal.go index 2c0f953..bbda2d2 100644 --- a/backend/api/terminal.go +++ b/backend/api/terminal.go @@ -17,8 +17,8 @@ var termUpgrader = websocket.Upgrader{ CheckOrigin: func(r *http.Request) bool { return true }, } -// terminalHandler bridget Browser-WebSocket mit Agent-PTY -func (h *Handler) terminalHandler(hub *ws.Hub) http.HandlerFunc { +// TerminalHandler bridget Browser-WebSocket mit Agent-PTY (JWT-only, kein API-Key) +func (h *Handler) TerminalHandler(hub *ws.Hub) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { agentID := r.PathValue("id") if agentID == "" { diff --git a/backend/api/tunnel_proxy.go b/backend/api/tunnel_proxy.go index 25e6239..9ace270 100644 --- a/backend/api/tunnel_proxy.go +++ b/backend/api/tunnel_proxy.go @@ -45,7 +45,7 @@ func (h *Handler) setupTunnelRoutes(mux *http.ServeMux, hub *ws.Hub) { mux.HandleFunc("GET /api/v1/agents/{id}/backups/diff", h.diffBackups()) // Terminal-Endpoint (Browser -> Agent PTY) - mux.HandleFunc("GET /api/v1/agents/{id}/terminal", h.terminalHandler(hub)) + // Terminal wird NICHT hier registriert — wird in main.go unter JWTOnlyAuth eingebunden // WebSocket-Endpoint mux.HandleFunc("GET /api/v1/agent/ws", ws.NewWebSocketHandler(hub)) diff --git a/backend/main.go b/backend/main.go index cdcd841..bb24609 100644 --- a/backend/main.go +++ b/backend/main.go @@ -93,6 +93,12 @@ func main() { mux := http.NewServeMux() mux.Handle("POST /api/v1/auth/login", authMux) mux.Handle("POST /api/v1/auth/2fa/validate", authMux) // 2FA-Schritt 2 ebenfalls ohne JWT + + // Terminal: JWT-only (kein API-Key reicht — erfordert echten Login mit 2FA) + terminalMux := http.NewServeMux() + terminalMux.HandleFunc("GET /api/v1/agents/{id}/terminal", handler.TerminalHandler(hub)) + mux.Handle("GET /api/v1/agents/{id}/terminal", api.JWTOnlyAuth(authHandler, terminalMux)) + mux.Handle("/", api.CombinedAuthWithCache(apiKeyCache, authHandler, protectedMux)) // Middleware-Chain: CORS -> Logging -> Handler