diff --git a/backend/internal/api/auth.go b/backend/internal/api/auth.go index f1cb714..92359e1 100644 --- a/backend/internal/api/auth.go +++ b/backend/internal/api/auth.go @@ -5,6 +5,7 @@ import ( "crypto/sha256" "database/sql" "encoding/hex" + "log" "net/http" "time" @@ -60,13 +61,27 @@ func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) { return } - // Authentification PAM via SSH - userInfo, err := h.sshAuth.Authenticate(body.Username, body.Password) + // Lire le host SSH depuis la DB à chaque login. + // L'authenticator global est créé au démarrage avec host="" (avant installation) + // et ne se met pas à jour automatiquement après configuration. + sshHost, _, _ := h.db.GetSetting("ssh_host") + if sshHost == "" { + log.Printf("[auth/login] SSH non configuré (ssh_host vide en base) — user=%s ip=%s", body.Username, ip) + JSONError(w, "SSH non configuré, veuillez vérifier l'installation", http.StatusServiceUnavailable) + return + } + + log.Printf("[auth/login] Tentative — user=%s ip=%s ssh_host=%s", body.Username, ip, sshHost) + authenticator := auth.NewSSHAuthenticator(sshHost) + + userInfo, err := authenticator.Authenticate(body.Username, body.Password) if err != nil { + log.Printf("[auth/login] Échec auth SSH — user=%s ssh_host=%s erreur=%v", body.Username, sshHost, err) h.auditLogger.Log(nil, body.Username, "login_failed", "", map[string]string{"error": err.Error()}, ip) JSONError(w, "Identifiants invalides", http.StatusUnauthorized) return } + log.Printf("[auth/login] Succès — user=%s admin=%v ssh_host=%s", body.Username, userInfo.IsAdmin, sshHost) // Créer ou mettre à jour le profil utilisateur en SQLite userID, err := h.upsertUser(userInfo) diff --git a/backend/internal/api/terminal.go b/backend/internal/api/terminal.go index cb73f88..b6ef0a6 100644 --- a/backend/internal/api/terminal.go +++ b/backend/internal/api/terminal.go @@ -5,6 +5,7 @@ package api import ( "encoding/json" "fmt" + "log" "net/http" "time" @@ -57,10 +58,12 @@ func (h *TerminalHandler) WebSocket(w http.ResponseWriter, r *http.Request) { sshPass, _ := h.encryptor.Decrypt(encryptedPass) if sshHost == "" { + log.Printf("[terminal] SSH non configuré — user=%s", claims.Username) conn.WriteMessage(gorillaws.TextMessage, []byte("\r\nErreur : SSH non configuré\r\n")) return } + log.Printf("[terminal] Ouverture session — user=%s ssh_host=%s", claims.Username, sshHost) h.auditLogger.Log(&claims.UserID, claims.Username, "terminal_open", sshHost, nil, clientIP(r)) // Établir la connexion SSH @@ -75,9 +78,11 @@ func (h *TerminalHandler) WebSocket(w http.ResponseWriter, r *http.Request) { sshClient, err := gossh.Dial("tcp", sshHost, sshConfig) if err != nil { + log.Printf("[terminal] Échec connexion SSH %s@%s : %v", sshUser, sshHost, err) conn.WriteMessage(gorillaws.TextMessage, []byte(fmt.Sprintf("\r\nErreur SSH : %v\r\n", err))) return } + log.Printf("[terminal] Connecté — %s@%s", sshUser, sshHost) defer sshClient.Close() // Créer une session SSH avec pseudo-terminal diff --git a/backend/internal/api/updates.go b/backend/internal/api/updates.go index e11bdca..5b4ad55 100644 --- a/backend/internal/api/updates.go +++ b/backend/internal/api/updates.go @@ -5,6 +5,8 @@ package api import ( "fmt" + "log" + "math/rand" "net/http" "time" @@ -14,7 +16,6 @@ import ( "git.geronzi.fr/proxmoxPanel/core/backend/internal/ssh" "git.geronzi.fr/proxmoxPanel/core/backend/internal/websocket" "github.com/go-chi/chi/v5" - "math/rand" ) // UpdatesHandler contient les handlers de mises à jour. @@ -56,7 +57,9 @@ func (h *UpdatesHandler) RunUpdate(w http.ResponseWriter, r *http.Request) { sshUser, _, _ := h.db.GetSetting("ssh_username") encryptedPass, _, _ := h.db.GetSetting("ssh_password") sshPass, _ := h.encryptor.Decrypt(encryptedPass) + log.Printf("[updates/run] Credentials SSH — host=%s user=%s password_len=%d", sshHost, sshUser, len(sshPass)) if sshHost == "" || sshUser == "" || sshPass == "" { + log.Printf("[updates/run] SSH non configuré — host=%q user=%q password_empty=%v", sshHost, sshUser, sshPass == "") JSONError(w, "SSH non configuré", http.StatusServiceUnavailable) return } @@ -71,7 +74,7 @@ func (h *UpdatesHandler) RunUpdate(w http.ResponseWriter, r *http.Request) { h.auditLogger.Log(&claims.UserID, claims.Username, "update_start", body.Target, nil, clientIP(r)) - // Lancer la mise à jour en arrière-plan + log.Printf("[updates/run] Job %s démarré — target=%s user=%d", jobID, body.Target, claims.UserID) go h.executeUpdate(jobID, body.Target, sshHost, sshUser, sshPass, claims.UserID) JSONResponse(w, http.StatusAccepted, map[string]string{ @@ -164,8 +167,14 @@ func (h *UpdatesHandler) executeUpdate(jobID, target, sshHost, sshUser, sshPass } // Lancer le streaming SSH + cmdPreview := command + if len(cmdPreview) > 80 { + cmdPreview = cmdPreview[:80] + "..." + } + log.Printf("[updates/execute] Job %s — SSH %s@%s commande: %s", jobID, sshUser, sshHost, cmdPreview) err := h.sshPool.StreamCommand(sshHost, sshUser, sshPass, command, outputChan) if err != nil { + log.Printf("[updates/execute] Job %s — Erreur SSH : %v", jobID, err) h.db.Exec(`UPDATE update_history SET status='error', output=?, finished_at=CURRENT_TIMESTAMP WHERE job_id=?`, "Erreur SSH : "+err.Error(), jobID) h.hub.Publish("update:"+jobID, "update_error", map[string]string{"error": err.Error()}) @@ -180,6 +189,7 @@ func (h *UpdatesHandler) executeUpdate(jobID, target, sshHost, sshUser, sshPass } // Finaliser le job + log.Printf("[updates/execute] Job %s — terminé (%d octets de sortie)", jobID, len(fullOutput)) h.db.Exec(`UPDATE update_history SET status='success', output=?, finished_at=CURRENT_TIMESTAMP WHERE job_id=?`, fullOutput, jobID) h.hub.Publish("update:"+jobID, "update_done", map[string]string{"job_id": jobID}) diff --git a/backend/internal/auth/ssh_auth.go b/backend/internal/auth/ssh_auth.go index 47d5952..0c5c8fc 100644 --- a/backend/internal/auth/ssh_auth.go +++ b/backend/internal/auth/ssh_auth.go @@ -6,6 +6,7 @@ package auth import ( "fmt" + "log" "net" "strings" "time" @@ -45,8 +46,10 @@ func (a *SSHAuthenticator) Authenticate(username, password string) (*UserInfo, e } // Tentative de connexion SSH + log.Printf("[ssh_auth] Dial %s@%s...", username, a.host) client, err := ssh.Dial("tcp", a.host, config) if err != nil { + log.Printf("[ssh_auth] Échec dial %s@%s : %v", username, a.host, err) // Distinguer les erreurs d'authentification des erreurs réseau if strings.Contains(err.Error(), "unable to authenticate") || strings.Contains(err.Error(), "ssh: handshake failed") || @@ -56,13 +59,15 @@ func (a *SSHAuthenticator) Authenticate(username, password string) (*UserInfo, e return nil, fmt.Errorf("connexion SSH impossible : %w", err) } defer client.Close() + log.Printf("[ssh_auth] Connexion établie — %s@%s", username, a.host) // Vérifier l'appartenance aux groupes sudo/wheel via la commande `id` isAdmin, err := checkSudoGroup(client) if err != nil { - // En cas d'erreur de vérification des groupes, l'utilisateur est authentifié mais pas admin + log.Printf("[ssh_auth] Avertissement vérification sudo — user=%s : %v", username, err) isAdmin = false } + log.Printf("[ssh_auth] Authentifié — user=%s admin=%v", username, isAdmin) return &UserInfo{ Username: username, diff --git a/frontend/src/stores/auth.store.ts b/frontend/src/stores/auth.store.ts index 0c4bef3..b048287 100644 --- a/frontend/src/stores/auth.store.ts +++ b/frontend/src/stores/auth.store.ts @@ -53,8 +53,12 @@ export const useAuthStore = defineStore('auth', () => { }) if (!res.ok) { - const err = await res.json() - throw new Error(err.error || 'Erreur d\'authentification') + const contentType = res.headers.get('content-type') || '' + if (contentType.includes('application/json')) { + const err = await res.json() + throw new Error(err.error || 'Erreur d\'authentification') + } + throw new Error(`Erreur ${res.status} — réponse inattendue du serveur`) } const data = await res.json()