fix: A19 terminé — plus aucun préfixe cookie-auth exempt du CSRF (v7.3.6)
- 46 appels non-GET équipés de `X-CSRF-Token` (expression cookie en ligne, portée indifférente) : agent_panel (9), settings (12), local_workspace (15), gitea_workspace (4), workspace (2), workspaces (5), library (2), welcome (2 en v7.3.5) - 5 derniers préfixes sortis d'`EXCLUDED_PATHS` : /api/workspace (+/api/workspaces), /api/local-workspace, /api/settings, /api/gitea, /api/agent - il ne reste exempté que : Bearer (/api/webhook, /api/v1, /api/v2, /scim/v2), callbacks /auth/*, pages publiques (/s/, /f/), /api/csrf-token et /api/frontend-error - vérif : `node --check` des <script> des 39 templates → 0 échec (avant et après) - tests : `anon_csrf()` sur les 5 tests d'anonymat devenus CSRF-first - suite **1026/1026** · `ruff check app tests` OK
This commit is contained in:
+1
-1
@@ -153,7 +153,7 @@ async def lifespan(_app: FastAPI):
|
||||
|
||||
app = FastAPI(
|
||||
title="FlowDeck",
|
||||
version="7.3.5",
|
||||
version="7.3.6",
|
||||
docs_url="/docs",
|
||||
redoc_url="/redoc",
|
||||
lifespan=lifespan,
|
||||
|
||||
+10
-11
@@ -18,21 +18,20 @@ class CSRFMiddleware(BaseHTTPMiddleware):
|
||||
SAFE_METHODS = {"GET", "HEAD", "OPTIONS"}
|
||||
# NOTE: ``/scim/v2`` is Bearer-token only (no cookie auth), so CSRF does not
|
||||
# apply — IdP SCIM clients (Okta, Entra) cannot send an X-CSRF-Token.
|
||||
# A19 : le CSRF ne s'exempte plus que pour ce qui ne dépend PAS d'un cookie de
|
||||
# session (Bearer, webhooks entrants, callbacks OAuth/SSO, pages publiques,
|
||||
# probe d'infra, remontée d'erreur client).
|
||||
# Retirés après scan du front (tous les appels non-GET envoient déjà
|
||||
# `X-CSRF-Token`) : /db/, /workspace, /workspace/automations,
|
||||
# /board/api/favorites, /api/pages, /api/recents, /api/notifications,
|
||||
# /api/comments, /api/automations, /api/github, /api/admin, /api/onboarding.
|
||||
# Reste A19 (front à équiper d'abord) : /api/workspace (couvre aussi
|
||||
# /api/workspaces), /api/local-workspace, /api/settings, /api/gitea, /api/agent.
|
||||
# A19 — LISTE FINALE : plus aucun préfixe cookie-auth n'est exempté.
|
||||
# Tous les appels non-GET du front envoient désormais `X-CSRF-Token`
|
||||
# (46 sites équipés en v7.3.6 : agent_panel, settings, local_workspace,
|
||||
# library, gitea_workspace, workspace, workspaces, welcome).
|
||||
# Ne restent que du machine-to-machine / hors session :
|
||||
# - Bearer : /api/webhook, /api/v1, /api/v2, /scim/v2
|
||||
# - callbacks : /auth/* (login, register, SSO, 2FA, WebAuthn)
|
||||
# - publics : /s/ (sites), /f/ (forms)
|
||||
# - infra/diag : /api/csrf-token (le jeton lui-même), /api/frontend-error
|
||||
EXCLUDED_PATHS = {
|
||||
"/api/webhook", "/api/v1", "/api/v2", "/scim/v2",
|
||||
"/auth/callback", "/auth/register", "/auth/local-login", "/auth/local-verify",
|
||||
"/auth/2fa", "/auth/saml", "/auth/oidc", "/auth/webauthn",
|
||||
"/s/", "/f/", "/api/frontend-error", "/api/csrf-token",
|
||||
"/api/workspace", "/api/local-workspace", "/api/settings", "/api/gitea", "/api/agent",
|
||||
"/s/", "/f/", "/api/csrf-token", "/api/frontend-error",
|
||||
}
|
||||
|
||||
async def dispatch(self, request: Request, call_next):
|
||||
|
||||
@@ -555,7 +555,7 @@
|
||||
var payload = {prompt: message};
|
||||
if(context && context.trim()) payload.context = String(context).slice(0, 20000);
|
||||
return fetch('/api/agent/generate', {
|
||||
method:'POST', headers:{'Content-Type':'application/json'},
|
||||
method:'POST', headers:{'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||'', 'Content-Type':'application/json'},
|
||||
body: JSON.stringify(payload)
|
||||
}).then(function(resp){
|
||||
return resp.json();
|
||||
@@ -701,7 +701,7 @@
|
||||
if(self.llmModel) payload.model = self.llmModel;
|
||||
|
||||
fetch('/api/agent/generate', {
|
||||
method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(payload)
|
||||
method:'POST', headers:{'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||'', 'Content-Type':'application/json'}, body: JSON.stringify(payload)
|
||||
}).then(function(r){ return r.json(); }).then(function(d){
|
||||
self.messages = self.messages.filter(function(m){ return m.id !== thinkId; });
|
||||
if(d && d.ok && d.text){
|
||||
@@ -824,7 +824,7 @@
|
||||
installGallerySkill(slug, icon, name){
|
||||
var self = this;
|
||||
fetch('/api/agent/skills/gallery/' + encodeURIComponent(slug) + '/install', {
|
||||
method: 'POST', headers: {'Content-Type': 'application/json'}, body: '{}'
|
||||
method: 'POST', headers: {'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||'', 'Content-Type': 'application/json'}, body: '{}'
|
||||
}).then(function(r){
|
||||
return r.json().then(function(d){ return {ok: r.ok, d: d}; });
|
||||
}).then(function(res){
|
||||
@@ -988,7 +988,7 @@
|
||||
var body = {title:'Nouvelle conversation'};
|
||||
if(self.llmProvider) body.provider = self.llmProvider;
|
||||
if(self.llmModel) body.model = self.llmModel;
|
||||
fetch('/api/agent/conversations', {method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(body)})
|
||||
fetch('/api/agent/conversations', {method:'POST', headers:{'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||'', 'Content-Type':'application/json'}, body: JSON.stringify(body)})
|
||||
.then(function(r){return r.json()}).then(function(d){
|
||||
var conv = {id:d.id, title:d.title, updated_at:new Date().toISOString()};
|
||||
self.conversations.unshift(conv); self.currentConv = conv;
|
||||
@@ -1005,7 +1005,7 @@
|
||||
if(self.llmProvider) body.provider = self.llmProvider;
|
||||
if(self.llmModel) body.model = self.llmModel;
|
||||
self._ensuring = fetch('/api/agent/conversations', {
|
||||
method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(body)
|
||||
method:'POST', headers:{'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||'', 'Content-Type':'application/json'}, body: JSON.stringify(body)
|
||||
}).then(function(r){return r.json()}).then(function(d){
|
||||
var conv = {id:d.id, title:d.title, updated_at:new Date().toISOString()};
|
||||
self.conversations.unshift(conv); self.currentConv = conv;
|
||||
@@ -1027,7 +1027,7 @@
|
||||
if(self.llmProvider) body.provider = self.llmProvider;
|
||||
if(self.llmModel) body.model = self.llmModel;
|
||||
fetch('/api/agent/conversations/'+self.currentConv.id, {
|
||||
method:'PATCH', headers:{'Content-Type':'application/json'}, body: JSON.stringify(body)
|
||||
method:'PATCH', headers:{'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||'', 'Content-Type':'application/json'}, body: JSON.stringify(body)
|
||||
}).catch(function(){});
|
||||
},
|
||||
|
||||
@@ -1832,7 +1832,7 @@
|
||||
if(m.id && /^\d+$/.test(String(m.id))) payload.message_id = Number(m.id);
|
||||
fetch('/api/agent/feedback', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
headers: {'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||'', 'Content-Type': 'application/json'},
|
||||
body: JSON.stringify(payload)
|
||||
}).then(function(r){ return r.json(); }).then(function(d){
|
||||
if(d && d.status === 'recorded'){ m.fb = rating; }
|
||||
@@ -1930,7 +1930,7 @@
|
||||
|
||||
return fetch('/api/agent/conversations/'+self.currentConv.id+'/run', {
|
||||
method:'POST',
|
||||
headers:{'Content-Type':'application/json'},
|
||||
headers:{'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||'', 'Content-Type':'application/json'},
|
||||
body: JSON.stringify(payload)
|
||||
}).then(function(resp){
|
||||
if(!resp.ok){ return resp.json().then(function(j){ throw new Error(j.detail || ('HTTP '+resp.status)); }); }
|
||||
|
||||
@@ -135,7 +135,7 @@ document.addEventListener('alpine:init', () => {
|
||||
var newPath = oldPath.replace(/[^/]+$/, newName.trim());
|
||||
fetch('/api/gitea/projects/' + this.owner + '/' + this.repo + '/file', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
headers: {'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||'', 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ path: oldPath, new_path: newPath, message: 'Rename ' + oldPath + ' to ' + newPath })
|
||||
}).then(function(r){ return r.json(); })
|
||||
.then(function(d){
|
||||
@@ -150,7 +150,7 @@ document.addEventListener('alpine:init', () => {
|
||||
if (!item) return;
|
||||
if (!confirm('Delete ' + item.name + '?')) return;
|
||||
var self = this;
|
||||
fetch('/api/gitea/projects/' + this.owner + '/' + this.repo + '/file?path=' + encodeURIComponent(item.path) + '&sha=' + (item.sha || '') + '&message=Delete ' + item.path, {
|
||||
fetch('/api/gitea/projects/' + this.owner + '/' + this.repo + '/file?path=' + encodeURIComponent(item.path) + '&sha=' + (item.sha || '') + '&message=Delete ' + item.path, {headers: {'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||''},
|
||||
method: 'DELETE'
|
||||
}).then(function(r){
|
||||
if (r.ok) { self.refreshTree(); }
|
||||
@@ -245,7 +245,7 @@ document.addEventListener('alpine:init', () => {
|
||||
if (!path) return;
|
||||
if (!confirm('Delete ' + path + ' from ' + self.owner + '/' + self.repo + '?')) return;
|
||||
var sha = el.getAttribute('data-gitea-sha') || '';
|
||||
fetch('/api/gitea/projects/' + self.owner + '/' + self.repo + '/file?path=' + encodeURIComponent(path) + '&sha=' + encodeURIComponent(sha) + '&message=' + encodeURIComponent('Delete via FlowDeck'), {
|
||||
fetch('/api/gitea/projects/' + self.owner + '/' + self.repo + '/file?path=' + encodeURIComponent(path) + '&sha=' + encodeURIComponent(sha) + '&message=' + encodeURIComponent('Delete via FlowDeck'), {headers: {'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||''},
|
||||
method: 'DELETE',
|
||||
}).then(function(r) {
|
||||
if (r.ok) self.refreshTree();
|
||||
@@ -387,7 +387,7 @@ document.addEventListener('alpine:init', () => {
|
||||
if (!this.filePath) return;
|
||||
if (!confirm('Delete ' + this.filePath + ' from ' + this.owner + '/' + this.repo + '?')) return;
|
||||
try {
|
||||
var r = await fetch('/api/gitea/projects/' + this.owner + '/' + this.repo + '/file?path=' + encodeURIComponent(this.filePath) + '&sha=' + encodeURIComponent(this.fileSha) + '&message=' + encodeURIComponent('Delete via FlowDeck'), {
|
||||
var r = await fetch('/api/gitea/projects/' + this.owner + '/' + this.repo + '/file?path=' + encodeURIComponent(this.filePath) + '&sha=' + encodeURIComponent(this.fileSha) + '&message=' + encodeURIComponent('Delete via FlowDeck'), {headers: {'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||''},
|
||||
method: 'DELETE',
|
||||
});
|
||||
if (r.ok) {
|
||||
|
||||
@@ -1198,7 +1198,7 @@ function libraryPage() {
|
||||
var item = store && store.node;
|
||||
if (!item) return;
|
||||
var self = this;
|
||||
fetch('/api/local-workspace/items/' + item.id + '/tags/' + tagId, { method: 'DELETE' })
|
||||
fetch('/api/local-workspace/items/' + item.id + '/tags/' + tagId, {headers: {'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||''}, method: 'DELETE' })
|
||||
.then(function(r) {
|
||||
if (!r.ok) return;
|
||||
item.tags = (item.tags || []).filter(function(t) { return t.id !== tagId; });
|
||||
@@ -1218,7 +1218,7 @@ function libraryPage() {
|
||||
try {
|
||||
var r = await fetch('/api/local-workspace/items/' + item.id + '/tags', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
headers: {'Content-Type': 'application/json', 'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||''},
|
||||
body: JSON.stringify({name: tagName, color: color})
|
||||
});
|
||||
if (r.ok) {
|
||||
|
||||
@@ -781,7 +781,7 @@ window._wsInitData = (function() {
|
||||
var self = this;
|
||||
// Soft-delete all selected items
|
||||
for (var i=0; i<ids.length; i++) {
|
||||
await fetch('/api/local-workspace/items/' + ids[i], { method: 'DELETE' });
|
||||
await fetch('/api/local-workspace/items/' + ids[i], {headers: {'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||''}, method: 'DELETE' });
|
||||
}
|
||||
this.clearSelection();
|
||||
this._reloadAfterAction();
|
||||
@@ -1207,7 +1207,7 @@ window._wsInitData = (function() {
|
||||
color = color || (store && store.newTagColor) || '#787774';
|
||||
try {
|
||||
var r = await fetch('/api/local-workspace/items/' + node.id + '/tags', {
|
||||
method: 'POST', headers: {'Content-Type':'application/json'},
|
||||
method: 'POST', headers: {'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||'', 'Content-Type':'application/json'},
|
||||
body: JSON.stringify({name: tagName, color: color})
|
||||
});
|
||||
if (r.ok) {
|
||||
@@ -1344,7 +1344,7 @@ window._wsInitData = (function() {
|
||||
if (!newName) return;
|
||||
try {
|
||||
var r = await fetch('/api/local-workspace/items/' + id, {
|
||||
method: 'PUT', headers: {'Content-Type':'application/json'},
|
||||
method: 'PUT', headers: {'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||'', 'Content-Type':'application/json'},
|
||||
body: JSON.stringify({name: newName})
|
||||
});
|
||||
if (r.ok) {
|
||||
@@ -1524,7 +1524,7 @@ window._wsInitData = (function() {
|
||||
this.renamingId = null;
|
||||
if (!n || n === node.name) return;
|
||||
var r = await fetch('/api/local-workspace/items/' + node.id, {
|
||||
method: 'PUT', headers: {'Content-Type':'application/json'},
|
||||
method: 'PUT', headers: {'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||'', 'Content-Type':'application/json'},
|
||||
body: JSON.stringify({name: n})
|
||||
});
|
||||
if (r.ok) {
|
||||
@@ -1579,7 +1579,7 @@ window._wsInitData = (function() {
|
||||
var targetId = this.currentFolder > 0 ? this.currentFolder : null;
|
||||
this.clipboard.forEach(function(id) {
|
||||
fetch('/api/local-workspace/items/' + id + '/move', {
|
||||
method: 'PUT', headers: {'Content-Type':'application/json'},
|
||||
method: 'PUT', headers: {'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||'', 'Content-Type':'application/json'},
|
||||
body: JSON.stringify({parent_id: targetId})
|
||||
}).then(function(r) { if (r.ok) self._reloadAfterAction(); });
|
||||
});
|
||||
@@ -1590,7 +1590,7 @@ window._wsInitData = (function() {
|
||||
// ── Duplicate ──
|
||||
async duplicateItem(node) {
|
||||
var r = await fetch('/api/local-workspace/items', {
|
||||
method: 'POST', headers: {'Content-Type':'application/json'},
|
||||
method: 'POST', headers: {'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||'', 'Content-Type':'application/json'},
|
||||
body: JSON.stringify({name: node.name + ' (copy)', type: node.type || 'page',
|
||||
parent_id: this.currentFolder > 0 ? this.currentFolder : null})
|
||||
});
|
||||
@@ -1618,7 +1618,7 @@ window._wsInitData = (function() {
|
||||
this.undoMessage = 'Deleted "' + (node.name || 'item') + '"';
|
||||
this.undoVisible = true;
|
||||
// Delete via API
|
||||
var r = await fetch('/api/local-workspace/items/' + this.undoItemId, { method:'DELETE' });
|
||||
var r = await fetch('/api/local-workspace/items/' + this.undoItemId, {headers: {'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||''}, method:'DELETE' });
|
||||
if (!r.ok) { this.undoVisible = false; return; }
|
||||
// Reload from API for reliability (sidebar refresh handled by _reloadAfterAction)
|
||||
this._reloadAfterAction();
|
||||
@@ -1643,7 +1643,7 @@ window._wsInitData = (function() {
|
||||
self._reloadAfterAction();
|
||||
return;
|
||||
}
|
||||
fetch('/api/local-workspace/items/' + ids[i] + '/restore', { method: 'POST' })
|
||||
fetch('/api/local-workspace/items/' + ids[i] + '/restore', {headers: {'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||''}, method: 'POST' })
|
||||
.then(function(r) { if (r.ok) restored++; })
|
||||
.finally(function() { restoreOne(i + 1); });
|
||||
}
|
||||
@@ -1941,7 +1941,7 @@ window._wsInitData = (function() {
|
||||
if (this.parentFolder) body.parent_id = this.parentFolder.db_id;
|
||||
var r = await fetch('/api/local-workspace/items', {
|
||||
method:'POST',
|
||||
headers:{'Content-Type':'application/json'},
|
||||
headers:{'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||'', 'Content-Type':'application/json'},
|
||||
body:JSON.stringify(body)
|
||||
});
|
||||
if (r.ok) {
|
||||
@@ -2025,7 +2025,7 @@ window._wsInitData = (function() {
|
||||
if (!n||!this.target) return;
|
||||
var r = await fetch('/api/local-workspace/items/'+this.target.db_id, {
|
||||
method:'PUT',
|
||||
headers:{'Content-Type':'application/json'},
|
||||
headers:{'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||'', 'Content-Type':'application/json'},
|
||||
body:JSON.stringify({name:n})
|
||||
});
|
||||
if (r.ok) { this._reloadAfterAction(); }
|
||||
@@ -2038,7 +2038,7 @@ window._wsInitData = (function() {
|
||||
|
||||
async doDelete() {
|
||||
if (!this.target) return;
|
||||
var r = await fetch('/api/local-workspace/items/'+this.target.db_id, { method:'DELETE' });
|
||||
var r = await fetch('/api/local-workspace/items/'+this.target.db_id, {headers: {'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||''}, method:'DELETE' });
|
||||
if (r.ok) { this._reloadAfterAction(); }
|
||||
},
|
||||
|
||||
@@ -2231,7 +2231,7 @@ window._wsInitData = (function() {
|
||||
try {
|
||||
var r = await fetch('/api/local-workspace/items/' + itemId + '/move', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
headers: {'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||'', 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
if (r.ok) {
|
||||
@@ -2327,7 +2327,7 @@ window._wsInitData = (function() {
|
||||
try {
|
||||
var r = await fetch('/api/local-workspace/items/' + itemId + '/tags', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
headers: {'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||'', 'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({name: tagName})
|
||||
});
|
||||
if (r.ok) {
|
||||
@@ -2346,7 +2346,7 @@ window._wsInitData = (function() {
|
||||
|
||||
async removeTag(itemId, tagId) {
|
||||
try {
|
||||
var r = await fetch('/api/local-workspace/items/' + itemId + '/tags/' + tagId, {
|
||||
var r = await fetch('/api/local-workspace/items/' + itemId + '/tags/' + tagId, {headers: {'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||''},
|
||||
method: 'DELETE'
|
||||
});
|
||||
if (r.ok) {
|
||||
@@ -2492,7 +2492,7 @@ window._wsInitData = (function() {
|
||||
try {
|
||||
await fetch('/api/local-workspace/items/' + ids[i] + '/move', {
|
||||
method: 'PUT',
|
||||
headers: {'Content-Type':'application/json'},
|
||||
headers: {'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||'', 'Content-Type':'application/json'},
|
||||
body: JSON.stringify({ parent_id: parentId || null })
|
||||
});
|
||||
} catch(e) {}
|
||||
|
||||
+12
-12
@@ -1337,7 +1337,7 @@ function settingsInit() {
|
||||
var n = this.newTagName.trim();
|
||||
if (!n) return;
|
||||
var r = await fetch('/api/settings/tags', {
|
||||
method: 'POST', headers: {'Content-Type':'application/json'},
|
||||
method: 'POST', headers: {'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||'', 'Content-Type':'application/json'},
|
||||
body: JSON.stringify({name: n, color: this.newTagColor})
|
||||
});
|
||||
if (r.ok) { this.newTagName = ''; this.newTagColor = '#787774'; await this.loadTags(); }
|
||||
@@ -1345,7 +1345,7 @@ function settingsInit() {
|
||||
|
||||
async updateTagColor(id, color) {
|
||||
await fetch('/api/settings/tags/' + id, {
|
||||
method: 'PUT', headers: {'Content-Type':'application/json'},
|
||||
method: 'PUT', headers: {'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||'', 'Content-Type':'application/json'},
|
||||
body: JSON.stringify({color: color})
|
||||
});
|
||||
await this.loadTags();
|
||||
@@ -1353,7 +1353,7 @@ function settingsInit() {
|
||||
|
||||
async deleteTag(id) {
|
||||
if (!confirm('Delete this tag?')) return;
|
||||
await fetch('/api/settings/tags/' + id, { method: 'DELETE' });
|
||||
await fetch('/api/settings/tags/' + id, {headers: {'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||''}, method: 'DELETE' });
|
||||
await this.loadTags();
|
||||
},
|
||||
|
||||
@@ -1375,7 +1375,7 @@ function settingsInit() {
|
||||
this.renamingTag = null; return;
|
||||
}
|
||||
await fetch('/api/settings/tags/' + tag.id, {
|
||||
method: 'PUT', headers: {'Content-Type':'application/json'},
|
||||
method: 'PUT', headers: {'Content-Type':'application/json', 'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||''},
|
||||
body: JSON.stringify({name: newName})
|
||||
});
|
||||
this.renamingTag = null;
|
||||
@@ -1648,7 +1648,7 @@ function settingsInit() {
|
||||
try {
|
||||
var r = await fetch('/api/agent/keys/' + id + '/models', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type':'application/json'},
|
||||
headers: {'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||'', 'Content-Type':'application/json'},
|
||||
body: JSON.stringify({api_key: f.api_key, api_base: f.api_base})
|
||||
});
|
||||
var d = await r.json();
|
||||
@@ -1677,7 +1677,7 @@ function settingsInit() {
|
||||
if (f.models && f.models.length) body.models = f.models;
|
||||
var r = await fetch('/api/agent/keys/' + id, {
|
||||
method: 'PUT',
|
||||
headers: {'Content-Type':'application/json'},
|
||||
headers: {'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||'', 'Content-Type':'application/json'},
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
var d = await r.json();
|
||||
@@ -1713,7 +1713,7 @@ function settingsInit() {
|
||||
if (f.api_key) body.api_key = f.api_key;
|
||||
var r = await fetch('/api/agent/keys/' + id + '/test', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type':'application/json'},
|
||||
headers: {'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||'', 'Content-Type':'application/json'},
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
var d = await r.json();
|
||||
@@ -1739,7 +1739,7 @@ function settingsInit() {
|
||||
var f = this.keyForm(id);
|
||||
f.deleting = true; f.msg = ''; f.ok = false;
|
||||
try {
|
||||
var r = await fetch('/api/agent/keys/' + id, { method: 'DELETE' });
|
||||
var r = await fetch('/api/agent/keys/' + id, {headers: {'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||''}, method: 'DELETE' });
|
||||
var d = await r.json();
|
||||
if (r.ok) {
|
||||
f.has_key = false; f.api_key = ''; f.models = []; f.model = ''; f.last_error = '';
|
||||
@@ -1899,7 +1899,7 @@ function settingsInit() {
|
||||
if (!file) return;
|
||||
var form = new FormData();
|
||||
form.append('file', file);
|
||||
var r = await fetch('/api/settings/avatar', { method: 'POST', body: form });
|
||||
var r = await fetch('/api/settings/avatar', {headers: {'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||''}, method: 'POST', body: form });
|
||||
if (r.ok) {
|
||||
var d = await r.json();
|
||||
this.avatarUrl = d.avatar_url + '?t=' + Date.now();
|
||||
@@ -1910,7 +1910,7 @@ function settingsInit() {
|
||||
async selectAvatarColor(color) {
|
||||
this.avatarColor = color;
|
||||
var r = await fetch('/api/settings/avatar-color', {
|
||||
method: 'POST', headers: {'Content-Type':'application/json'},
|
||||
method: 'POST', headers: {'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||'', 'Content-Type':'application/json'},
|
||||
body: JSON.stringify({color: color})
|
||||
});
|
||||
if (r.ok) { this.avatarUrl = ''; }
|
||||
@@ -2055,7 +2055,7 @@ function settingsInit() {
|
||||
// ── v5.2.0 API tokens ──
|
||||
async loadApiTokens() {
|
||||
try {
|
||||
var r = await fetch('/api/settings/tokens', {credentials:'same-origin'});
|
||||
var r = await fetch('/api/settings/tokens', {headers: {'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||''}, credentials:'same-origin'});
|
||||
var d = await r.json();
|
||||
this.apiTokens = d.tokens || [];
|
||||
} catch(e) { this.apiTokens = []; }
|
||||
@@ -2065,7 +2065,7 @@ function settingsInit() {
|
||||
if (!name) return;
|
||||
try {
|
||||
var r = await fetch('/api/settings/tokens', {method:'POST', credentials:'same-origin',
|
||||
headers: {'Content-Type':'application/json'}, body: JSON.stringify({name: name})});
|
||||
headers: {'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||'', 'Content-Type':'application/json'}, body: JSON.stringify({name: name})});
|
||||
var d = await r.json();
|
||||
if (!r.ok) { window.showToast && window.showToast(d.detail || 'Erreur', 'error'); return; }
|
||||
this.newToken = d;
|
||||
|
||||
@@ -174,7 +174,7 @@ function workspacePage() {
|
||||
if (!this.newProjectName.trim()) return;
|
||||
const r = await fetch('/api/workspace/projects', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
headers: {'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||'', 'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({name: this.newProjectName.trim()})
|
||||
});
|
||||
if (r.ok) {
|
||||
|
||||
@@ -214,7 +214,7 @@ function workspacesPage() {
|
||||
},
|
||||
|
||||
async selectLocal(ws) {
|
||||
await fetch(`/api/workspaces/${ws.id}/select`, {method:'POST'});
|
||||
await fetch(`/api/workspaces/${ws.id}/select`, {headers: {'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||''}, method:'POST'});
|
||||
window.location = '/local-workspace';
|
||||
},
|
||||
|
||||
@@ -222,7 +222,7 @@ function workspacesPage() {
|
||||
if (!this.wsName.trim()) return;
|
||||
await fetch('/api/workspaces', {
|
||||
method:'POST',
|
||||
headers:{'Content-Type':'application/json'},
|
||||
headers:{'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||'', 'Content-Type':'application/json'},
|
||||
body:JSON.stringify({name:this.wsName.trim()})
|
||||
});
|
||||
this.wsName = '';
|
||||
@@ -240,7 +240,7 @@ function workspacesPage() {
|
||||
if (!this.wsName.trim()||!this.renameTarget) return;
|
||||
await fetch(`/api/workspaces/${this.renameTarget.id}`, {
|
||||
method:'PUT',
|
||||
headers:{'Content-Type':'application/json'},
|
||||
headers:{'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||'', 'Content-Type':'application/json'},
|
||||
body:JSON.stringify({name:this.wsName.trim()})
|
||||
});
|
||||
this.wsName = '';
|
||||
@@ -251,7 +251,7 @@ function workspacesPage() {
|
||||
|
||||
async deleteWs(ws) {
|
||||
if (!confirm(`Delete workspace "${ws.name}" and all its pages?`)) return;
|
||||
await fetch(`/api/workspaces/${ws.id}`, {method:'DELETE'});
|
||||
await fetch(`/api/workspaces/${ws.id}`, {headers: {'X-CSRF-Token': (document.cookie.match(/csrf_token=([^;]+)/)||[])[1]||''}, method:'DELETE'});
|
||||
await this.load();
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user