From 98cdabf3e1f44e3adbe87e09806cb984988a95ba Mon Sep 17 00:00:00 2001 From: enzo Date: Sun, 22 Mar 2026 01:45:09 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20label=20session=20actuelle=20+=20fix=20?= =?UTF-8?q?bouton=20r=C3=A9voquer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - GetSessions: retourne is_current=true pour la session correspondant au cookie courant - GetSessions: select token_hash pour la comparaison (non exposé dans le JSON) - profile.html: badge "Session actuelle" + désactive révoquer pour la session courante (utiliser le bouton Déconnexion à la place) - app.js: revokeSession utilise finally pour reset + isRevoking() helper - pages.css: styles .badge-current + .session-current Co-Authored-By: Claude Sonnet 4.6 --- backend/internal/api/auth.go | 23 ++++++++++++++++------- frontend/css/pages.css | 12 ++++++++++++ frontend/js/app.js | 7 +++++-- frontend/profile.html | 12 +++++++----- 4 files changed, 40 insertions(+), 14 deletions(-) diff --git a/backend/internal/api/auth.go b/backend/internal/api/auth.go index f6ad350..7fdfa46 100644 --- a/backend/internal/api/auth.go +++ b/backend/internal/api/auth.go @@ -347,7 +347,7 @@ func (h *AuthHandler) GetSessions(w http.ResponseWriter, r *http.Request) { } rows, err := h.db.Query(` - SELECT id, user_agent, ip, created_at, last_used_at, expires_at + SELECT id, user_agent, ip, created_at, last_used_at, expires_at, token_hash FROM refresh_tokens WHERE user_id = ? AND expires_at > CURRENT_TIMESTAMP ORDER BY COALESCE(last_used_at, created_at) DESC @@ -358,21 +358,29 @@ func (h *AuthHandler) GetSessions(w http.ResponseWriter, r *http.Request) { } defer rows.Close() + // Hash du cookie courant pour marquer "session actuelle" + currentHash := "" + if cookie, err := r.Cookie("pxp_refresh"); err == nil { + currentHash = hashToken(cookie.Value) + } + type Session struct { - ID int64 `json:"id"` - UserAgent string `json:"user_agent"` - IP string `json:"ip"` - CreatedAt string `json:"created_at"` + ID int64 `json:"id"` + UserAgent string `json:"user_agent"` + IP string `json:"ip"` + CreatedAt string `json:"created_at"` LastUsedAt *string `json:"last_used_at"` - ExpiresAt string `json:"expires_at"` + ExpiresAt string `json:"expires_at"` + IsCurrent bool `json:"is_current"` } sessions := []Session{} for rows.Next() { var s Session + var tokenHash string var createdAt, expiresAt sql.NullString var lastUsedAt sql.NullString - if err := rows.Scan(&s.ID, &s.UserAgent, &s.IP, &createdAt, &lastUsedAt, &expiresAt); err != nil { + if err := rows.Scan(&s.ID, &s.UserAgent, &s.IP, &createdAt, &lastUsedAt, &expiresAt, &tokenHash); err != nil { log.Printf("[GetSessions] scan error userID=%d: %v", claims.UserID, err) continue } @@ -381,6 +389,7 @@ func (h *AuthHandler) GetSessions(w http.ResponseWriter, r *http.Request) { if lastUsedAt.Valid && lastUsedAt.String != "" { s.LastUsedAt = &lastUsedAt.String } + s.IsCurrent = currentHash != "" && tokenHash == currentHash sessions = append(sessions, s) } diff --git a/frontend/css/pages.css b/frontend/css/pages.css index 80ec9fa..f1aae25 100644 --- a/frontend/css/pages.css +++ b/frontend/css/pages.css @@ -725,6 +725,18 @@ .session-sep { opacity: .4; } .session-id { font-family: monospace; opacity: .6; } +.session-current { background: rgba(var(--neu-primary-rgb, 108,142,244), .05); border-radius: var(--neu-radius); padding-left: .5rem; padding-right: .5rem; } + +.badge-current { + font-size: .65rem; + padding: .15rem .45rem; + border-radius: 99px; + background: rgba(34,197,94,.15); + color: var(--neu-success); + font-weight: 600; + letter-spacing: .02em; +} + /* ── Logo auth LineIcons ─────────────────────────────────────────────────────── */ .logo-icon { font-size: 2.5rem; diff --git a/frontend/js/app.js b/frontend/js/app.js index dd903e7..86e7df1 100644 --- a/frontend/js/app.js +++ b/frontend/js/app.js @@ -670,7 +670,7 @@ document.addEventListener('alpine:init', () => { }, async revokeSession(id) { - this.revoking = { ...this.revoking, [id]: true } + this.revoking[id] = true try { const res = await apiFetch(`/api/auth/sessions/${id}`, { method: 'DELETE' }) if (res.ok) { @@ -682,10 +682,13 @@ document.addEventListener('alpine:init', () => { } } catch (e) { Alpine.store('toasts').error(`Erreur réseau — ${e.message}`) + } finally { + this.revoking[id] = false } - this.revoking = { ...this.revoking, [id]: false } }, + isRevoking(id) { return this.revoking[id] === true }, + formatDate(raw) { if (!raw) return '—' // SQLite retourne "YYYY-MM-DD HH:MM:SS" ou RFC3339 selon le driver diff --git a/frontend/profile.html b/frontend/profile.html index 8483b7b..cb98fa0 100644 --- a/frontend/profile.html +++ b/frontend/profile.html @@ -155,14 +155,15 @@

Aucune session active