sessions.html
227 lignes · 11673 octets
{% extends "base.html" %} {% block title %}{{ page_title }}{% endblock %} {% block navbar %} {% include "components/navbar.html" %} {% endblock %} {% block content %} <div class="container mx-auto px-4 py-8"> <!-- Breadcrumb --> <div class="text-sm breadcrumbs mb-6"> <ul> <li><a href="/">{{ breadcrumb_home }}</a></li> <li><a href="/profile">{{ breadcrumb_settings }}</a></li> <li>{{ breadcrumb_sessions }}</li> </ul> </div> <!-- Page Header --> <div class="mb-8"> <h1 class="text-3xl font-bold mb-2">{{ title }}</h1> <p class="text-base-content/70">{{ subtitle }}</p> </div> <!-- Active Sessions List --> <div class="space-y-4 max-w-4xl"> {% if sessions|length > 0 %} {% for session in sessions %} <div class="card bg-base-100 shadow-xl"> <div class="card-body"> <div class="flex justify-between items-start"> <div class="flex-1"> <h3 class="card-title text-lg"> {% if session.is_current %} <span class="badge badge-primary badge-sm">{{ current_session }}</span> {% endif %} {% if !session.device_info.is_empty() %} {{ session.device_info }} {% else %} {{ unknown_device }} {% endif %} </h3> <div class="mt-3 space-y-2 text-sm"> <!-- IP Address --> <div class="flex items-center gap-2"> <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-base-content/60" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3.055 11H5a2 2 0 012 2v1a2 2 0 002 2 2 2 0 012 2v2.945M8 3.935V5.5A2.5 2.5 0 0010.5 8h.5a2 2 0 012 2 2 2 0 104 0 2 2 0 012-2h1.064M15 20.488V18a2 2 0 012-2h3.064M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg> <span class="text-base-content/70">{{ ip_address }}</span> <span class="font-mono">{{ session.ip_address }}</span> </div> <!-- Created At --> <div class="flex items-center gap-2"> <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-base-content/60" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" /></svg> <span class="text-base-content/70">{{ created_at }}</span> <span>{{ session.created_at }}</span> </div> <!-- Last Activity --> <div class="flex items-center gap-2"> <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-base-content/60" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" /></svg> <span class="text-base-content/70">{{ last_activity }}</span> <span>{{ session.last_activity }}</span> </div> <!-- Session ID (for debugging) --> <div class="flex items-center gap-2"> <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-base-content/60" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" /></svg> <span class="text-base-content/70">{{ session_id }}</span> <span class="font-mono text-xs">{{ session.id }}</span> </div> </div> </div> <!-- Revoke Button --> <div class="card-actions"> {% if !session.is_current %} <button class="btn btn-error btn-sm btn-revoke-session" data-session-id="{{ session.id }}" data-device-info="{{ session.device_info }}" > <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /></svg> {{ revoke }} </button> {% else %} <div class="badge badge-lg badge-success gap-2"> <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" /></svg> {{ current_badge }} </div> {% endif %} </div> </div> </div> </div> {% endfor %} <!-- Revoke All Button --> {% if sessions|length > 1 %} <div class="card bg-base-200 shadow-xl"> <div class="card-body"> <h3 class="card-title text-base text-error"> <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /></svg> {{ danger_zone }} </h3> <p class="text-sm text-base-content/70"> {{ danger_zone_desc }} </p> <div class="card-actions justify-end"> <button class="btn btn-error" onclick="revokeAllSessions()"> <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /></svg> {{ revoke_all }} </button> </div> </div> </div> {% endif %} {% else %} <!-- No Sessions State --> <div class="card bg-base-100 shadow-xl"> <div class="card-body items-center text-center"> <svg xmlns="http://www.w3.org/2000/svg" class="h-16 w-16 text-base-content/30 mb-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" /></svg> <h3 class="card-title">{{ no_sessions }}</h3> <p class="text-base-content/70">{{ no_sessions_desc }}</p> </div> </div> {% endif %} </div> <!-- Info Card --> <div class="card bg-info text-info-content shadow-xl max-w-4xl mt-8"> <div class="card-body"> <h3 class="card-title text-base"> <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg> {{ about_title }} </h3> <p class="text-sm"> {{ about_desc }} </p> </div> </div> <!-- Back to Settings --> <div class="mt-8"> <a href="/profile" class="btn btn-outline"> <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" /></svg> {{ back_to_settings }} </a> </div> </div> <script> // SEC-FIX: Use data-* attributes instead of inline onclick to prevent XSS document.querySelectorAll('.btn-revoke-session').forEach(function(btn) { btn.addEventListener('click', async function() { var sessionId = this.dataset.sessionId; var deviceInfo = this.dataset.deviceInfo; if (!confirm('Êtes-vous sûr de vouloir révoquer la session pour "' + deviceInfo + '" ?\n\nCet appareil sera déconnecté immédiatement.')) { return; } try { const response = await fetch('/settings/sessions/' + encodeURIComponent(sessionId), { method: 'DELETE', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': '{{ csrf_token }}', } }); if (response.ok) { alert('Session révoquée avec succès !'); window.location.reload(); } else { const result = await response.json(); alert('Erreur : ' + (result.error || result.message || 'Échec de la révocation')); } } catch (error) { console.error('Error:', error); alert('Erreur réseau. Veuillez réessayer.'); } }); }); // Revoke all other sessions const revokeAllConfirmMsg = "{{ revoke_all_confirm }}"; const revokeAllSuccessMsg = "{{ revoke_all_success }}"; const revokeAllErrorMsg = "{{ revoke_all_error }}"; async function revokeAllSessions() { if (!confirm(revokeAllConfirmMsg)) { return; } // Get all session IDs except current one const sessionCards = document.querySelectorAll('.card'); const promises = []; sessionCards.forEach(card => { const revokeBtn = card.querySelector('.btn-revoke-session'); if (revokeBtn) { const sessionId = revokeBtn.dataset.sessionId; promises.push( fetch('/settings/sessions/' + encodeURIComponent(sessionId), { method: 'DELETE', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': '{{ csrf_token }}', } }) ); } }); try { await Promise.all(promises); alert('✅ ' + revokeAllSuccessMsg); window.location.reload(); } catch (error) { console.error('Error:', error); alert('❌ ' + revokeAllErrorMsg); window.location.reload(); } } </script> {% endblock %}
GitRust