fix: corriger bug multi-sessions (upsertUser wrong ID + schema repair + logs refresh)
- auth.go: upsertUser utilise toujours SELECT explicite au lieu de LastInsertId() qui retournait un rowid obsolète pour ON CONFLICT DO UPDATE sur ligne existante - auth.go: vérifier l'erreur de l'INSERT refresh_tokens (était silencieusement ignorée) - auth.go: logs détaillés dans Refresh handler pour diagnostiquer les 401 - db.go: repairSchema() ajoute les colonnes manquantes (ip, last_used_at) dans les bases où migration 002 était partiellement appliquée (ancien bug multi-statements) - app.js: tryRefresh et fetchMe affichent le vrai message d'erreur du backend Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
dc0c67b89c
commit
95757124de
3 changed files with 88 additions and 19 deletions
|
|
@ -5,6 +5,7 @@ import (
|
|||
"crypto/sha256"
|
||||
"database/sql"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
|
@ -107,10 +108,14 @@ func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) {
|
|||
// Stocker le hash du refresh token en base pour permettre la révocation
|
||||
tokenHash := hashToken(refreshToken)
|
||||
expiry := time.Now().Add(auth.RefreshTokenDuration())
|
||||
h.db.Exec(`
|
||||
if _, err := h.db.Exec(`
|
||||
INSERT INTO refresh_tokens (user_id, token_hash, expires_at, user_agent, ip, last_used_at)
|
||||
VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
||||
`, userID, tokenHash, expiry, r.UserAgent(), clientIP(r))
|
||||
`, userID, tokenHash, expiry, r.UserAgent(), clientIP(r)); err != nil {
|
||||
log.Printf("[auth/login] ERREUR stockage refresh token — user=%s userID=%d err=%v", body.Username, userID, err)
|
||||
JSONError(w, "Erreur création session", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Mettre à jour la date de dernier login
|
||||
h.db.Exec(`UPDATE users SET last_login_at = CURRENT_TIMESTAMP WHERE id = ?`, userID)
|
||||
|
|
@ -177,14 +182,18 @@ func (h *AuthHandler) Logout(w http.ResponseWriter, r *http.Request) {
|
|||
// Refresh renouvelle l'access token via le refresh token (cookie httpOnly).
|
||||
// POST /api/auth/refresh
|
||||
func (h *AuthHandler) Refresh(w http.ResponseWriter, r *http.Request) {
|
||||
ip := clientIP(r)
|
||||
|
||||
cookie, err := r.Cookie("pxp_refresh")
|
||||
if err != nil {
|
||||
log.Printf("[auth/refresh] cookie absent — ip=%s err=%v", ip, err)
|
||||
JSONError(w, "Refresh token manquant", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
userID, err := h.jwtManager.ValidateRefreshToken(cookie.Value)
|
||||
if err != nil {
|
||||
log.Printf("[auth/refresh] JWT invalide — ip=%s err=%v", ip, err)
|
||||
JSONError(w, "Refresh token invalide ou expiré", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
|
@ -194,10 +203,17 @@ func (h *AuthHandler) Refresh(w http.ResponseWriter, r *http.Request) {
|
|||
var count int
|
||||
h.db.QueryRow(`SELECT COUNT(*) FROM refresh_tokens WHERE user_id = ? AND token_hash = ? AND expires_at > CURRENT_TIMESTAMP`, userID, tokenHash).Scan(&count)
|
||||
if count == 0 {
|
||||
// Diagnostic : vérifier si le token existe mais avec un mauvais user_id
|
||||
var anyCount int
|
||||
h.db.QueryRow(`SELECT COUNT(*) FROM refresh_tokens WHERE token_hash = ?`, tokenHash).Scan(&anyCount)
|
||||
log.Printf("[auth/refresh] token non trouvé en base — userID=%d tokenHash=%s anyMatch=%d ip=%s",
|
||||
userID, tokenHash[:8], anyCount, ip)
|
||||
JSONError(w, "Session expirée ou révoquée", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("[auth/refresh] token valide — userID=%d ip=%s", userID, ip)
|
||||
|
||||
// Mettre à jour la date de dernière utilisation
|
||||
h.db.Exec(`UPDATE refresh_tokens SET last_used_at = CURRENT_TIMESTAMP WHERE token_hash = ?`, tokenHash)
|
||||
|
||||
|
|
@ -206,6 +222,7 @@ func (h *AuthHandler) Refresh(w http.ResponseWriter, r *http.Request) {
|
|||
var isAdmin int
|
||||
err = h.db.QueryRow(`SELECT username, is_admin FROM users WHERE id = ?`, userID).Scan(&username, &isAdmin)
|
||||
if err != nil {
|
||||
log.Printf("[auth/refresh] utilisateur introuvable — userID=%d ip=%s err=%v", userID, ip, err)
|
||||
JSONError(w, "Utilisateur introuvable", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
|
@ -304,21 +321,20 @@ func (h *AuthHandler) upsertUser(info *auth.UserInfo) (int64, error) {
|
|||
}
|
||||
|
||||
// Mise à jour du statut admin à chaque connexion (peut changer côté Linux)
|
||||
result, err := h.db.Exec(`
|
||||
if _, err := h.db.Exec(`
|
||||
INSERT INTO users (username, is_admin) VALUES (?, ?)
|
||||
ON CONFLICT(username) DO UPDATE SET is_admin = excluded.is_admin
|
||||
`, info.Username, isAdmin)
|
||||
if err != nil {
|
||||
`, info.Username, isAdmin); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Tenter de récupérer l'ID (insertions ou update)
|
||||
id, err := result.LastInsertId()
|
||||
if err != nil || id == 0 {
|
||||
// En cas de ON CONFLICT DO UPDATE, LastInsertId peut retourner 0
|
||||
err = h.db.QueryRow(`SELECT id FROM users WHERE username = ?`, info.Username).Scan(&id)
|
||||
// Toujours faire un SELECT explicite : avec ON CONFLICT DO UPDATE sur une ligne
|
||||
// existante, LastInsertId() peut retourner un rowid obsolète (comportement SQLite).
|
||||
var id int64
|
||||
if err := h.db.QueryRow(`SELECT id FROM users WHERE username = ?`, info.Username).Scan(&id); err != nil {
|
||||
return 0, fmt.Errorf("utilisateur introuvable après upsert: %w", err)
|
||||
}
|
||||
return id, err
|
||||
return id, nil
|
||||
}
|
||||
|
||||
// GetSessions retourne les sessions actives de l'utilisateur connecté.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue