fix: CSRF token dans toutes les requêtes admin (adminFetch)
FlowDeck CI / test (push) Failing after 4s
FlowDeck CI / docker (push) Has been skipped

Tous les appels /api/admin/* passent maintenant par adminFetch()
qui ajoute X-CSRF-Token depuis le cookie csrf_token
This commit is contained in:
2026-07-13 13:26:07 -04:00
parent 9e9ea181e6
commit e01e410d43
+15 -6
View File
@@ -551,9 +551,18 @@ document.addEventListener('alpine:init', function() {
},
// ── Admin ──
getCsrfToken() {
return document.cookie.split('; ').find(r => r.startsWith('csrf_token='))?.split('=')[1] || '';
},
async adminFetch(url, opts) {
opts = opts || {};
opts.headers = opts.headers || {};
opts.headers['X-CSRF-Token'] = this.getCsrfToken();
return await fetch(url, opts);
},
async loadAdminUsers() {
try {
var r = await fetch('/api/admin/users');
var r = await this.adminFetch('/api/admin/users');
var d = await r.json();
this.adminUsers = d.users || [];
} catch(e) { this.adminUsers = []; }
@@ -562,14 +571,14 @@ document.addEventListener('alpine:init', function() {
async loadAdminStats() {
try {
var r = await fetch('/api/admin/stats');
var r = await this.adminFetch('/api/admin/stats');
this.adminStats = await r.json();
} catch(e) { this.adminStats = {}; }
},
async loadAdminAudit() {
try {
var r = await fetch('/api/admin/audit?limit=200');
var r = await this.adminFetch('/api/admin/audit?limit=200');
var d = await r.json();
this.auditEntries = d.entries || [];
} catch(e) { this.auditEntries = []; }
@@ -579,7 +588,7 @@ document.addEventListener('alpine:init', function() {
var u = this.newUser;
if (!u.login || !u.password) return;
try {
var r = await fetch('/api/admin/users', {
var r = await this.adminFetch('/api/admin/users', {
method: 'POST', headers: {'Content-Type':'application/json'},
body: JSON.stringify({login:u.login, name:u.name, email:u.email, password:u.password, is_admin:u.is_admin?1:0})
});
@@ -600,7 +609,7 @@ document.addEventListener('alpine:init', function() {
var body = {name: f.name, email: f.email, is_admin: f.is_admin?1:0, is_active: f.is_active?1:0};
if (f.password) body.password = f.password;
try {
var r = await fetch('/api/admin/users/' + this.editingUser.id, {
var r = await this.adminFetch('/api/admin/users/' + this.editingUser.id, {
method: 'PUT', headers: {'Content-Type':'application/json'},
body: JSON.stringify(body)
});
@@ -614,7 +623,7 @@ document.addEventListener('alpine:init', function() {
async adminDeleteUser(id) {
if (!confirm('Permanently delete this user and all their data?')) return;
try {
await fetch('/api/admin/users/' + id, { method: 'DELETE' });
await this.adminFetch('/api/admin/users/' + id, { method: 'DELETE' });
await this.loadAdminUsers();
} catch(e) {}
},