From abe1388f8c21d7f2bf1cb6eea24b88f93a0dd0db Mon Sep 17 00:00:00 2001 From: Victor_Jay Date: Tue, 2 Jun 2026 18:47:22 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E7=BB=9F=E4=B8=80=20CSRF=20token=20?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E6=96=B9=E5=BC=8F=EF=BC=8C=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=20token=20=E5=88=B7=E6=96=B0=E5=90=8E=E7=BC=93=E5=AD=98?= =?UTF-8?q?=E5=A4=B1=E6=95=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - utils.js getCSRFToken() 改为从 cookie 优先读取,meta 标签作为后备 - show.html 5处移除 csrfToken 缓存变量,改用 getCSRFToken() 动态获取 - studio/posts.html/drafts.html 移除 {{ .CSRFToken }} 模板硬编码 - studio-editor.js 移除本地 getCsrfToken(),改用共享 getCSRFToken() - settings/index.html messages/index.html 改用 getCSRFToken() - admin posts.js/comments.js 移除本地/死代码 CSRF 获取,统一用共享方法 --- .../MetaLab-2026/html/messages/index.html | 4 +-- templates/MetaLab-2026/html/posts/show.html | 29 +++++++------------ .../MetaLab-2026/html/settings/index.html | 6 ++-- .../MetaLab-2026/html/studio/drafts.html | 4 +-- templates/MetaLab-2026/html/studio/posts.html | 2 +- .../MetaLab-2026/static/js/studio-editor.js | 8 ++--- templates/admin/static/js/comments.js | 2 -- templates/admin/static/js/posts.js | 5 +--- templates/shared/static/js/utils.js | 6 ++-- 9 files changed, 24 insertions(+), 42 deletions(-) diff --git a/templates/MetaLab-2026/html/messages/index.html b/templates/MetaLab-2026/html/messages/index.html index 86dd26b..31ad093 100644 --- a/templates/MetaLab-2026/html/messages/index.html +++ b/templates/MetaLab-2026/html/messages/index.html @@ -133,12 +133,10 @@ if (link) { // 有链接的通知:使用 fetch keepalive 确保页面跳转前标记已读不会丢失 e.preventDefault(); - var csrfMeta = document.querySelector('meta[name="csrf-token"]'); - var csrfToken = csrfMeta ? csrfMeta.getAttribute('content') : ''; fetch('/api/messages/' + id + '/read', { method: 'POST', keepalive: true, - headers: { 'X-CSRF-Token': csrfToken } + headers: { 'X-CSRF-Token': getCSRFToken() } }); card.classList.remove('unread'); var badge = card.querySelector('.msg-badge'); diff --git a/templates/MetaLab-2026/html/posts/show.html b/templates/MetaLab-2026/html/posts/show.html index 14ff0b5..0a739e7 100644 --- a/templates/MetaLab-2026/html/posts/show.html +++ b/templates/MetaLab-2026/html/posts/show.html @@ -187,12 +187,11 @@ if (!btn) return; btn.addEventListener('click', function() { if (!confirm('确认提交审核?审核通过后将公开发布。')) return; - var csrf = document.querySelector('meta[name="csrf-token"]'); fetch('/api/posts/' + btn.dataset.id + '/submit', { method: 'POST', headers: { 'Content-Type': 'application/json', - 'X-CSRF-Token': csrf ? csrf.getAttribute('content') : '' + 'X-CSRF-Token': getCSRFToken() } }) .then(function(r) { return r.json(); }) @@ -217,8 +216,6 @@ if (!btn || !overlay) return; var postId = btn.dataset.id; - var csrfMeta = document.querySelector('meta[name="csrf-token"]'); - var csrfToken = csrfMeta ? csrfMeta.getAttribute('content') : ''; function showOverlay() { overlay.style.display = 'flex'; @@ -256,7 +253,7 @@ method: 'POST', headers: { 'Content-Type': 'application/json', - 'X-CSRF-Token': csrfToken + 'X-CSRF-Token': getCSRFToken() }, body: JSON.stringify({ post_id: parseInt(postId), amount: amount }) }) @@ -297,8 +294,6 @@ if (!container || !likeBtn || !dislikeBtn) return; var postId = container.dataset.postId; - var csrfMeta = document.querySelector('meta[name="csrf-token"]'); - var csrfToken = csrfMeta ? csrfMeta.getAttribute('content') : ''; // 页面加载时获取当前反应状态 fetch('/api/posts/' + postId + '/reaction') @@ -321,7 +316,7 @@ likeBtn.disabled = true; fetch('/api/posts/' + postId + '/like', { method: 'POST', - headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrfToken } + headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': getCSRFToken() } }) .then(function(r) { if (r.status === 401) { window.location.href = '/auth/login?redirect=/posts/' + postId; return null; } @@ -343,7 +338,7 @@ dislikeBtn.disabled = true; fetch('/api/posts/' + postId + '/dislike', { method: 'POST', - headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrfToken } + headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': getCSRFToken() } }) .then(function(r) { if (r.status === 401) { window.location.href = '/auth/login?redirect=/posts/' + postId; return null; } @@ -377,15 +372,14 @@ if (!favBtn || !container) return; var postId = container.dataset.postId; - var csrfMeta = document.querySelector('meta[name="csrf-token"]'); - var csrfToken = csrfMeta ? csrfMeta.getAttribute('content') : ''; var isFavorited = false; var currentFolderId = null; var loading = false; function api(method, url, data) { var opts = { method: method, headers: { 'Content-Type': 'application/json' } }; - if (csrfToken) opts.headers['X-CSRF-Token'] = csrfToken; + var token = getCSRFToken(); + if (token) opts.headers['X-CSRF-Token'] = token; if (data) opts.body = JSON.stringify(data); return fetch(url, opts).then(function(r) { if (r.status === 401) { window.location.href = '/auth/login?redirect=/posts/' + postId; throw new Error('unauthorized'); } @@ -469,7 +463,7 @@ // 无弹窗回退(未登录跳转) function handleNoModal() { - if (!csrfToken) { + if (!getCSRFToken()) { window.location.href = '/auth/login?redirect=/posts/' + postId; return; } @@ -571,7 +565,7 @@ // 事件:点击收藏按钮 → 打开弹窗 favBtn.addEventListener('click', function() { - if (!csrfToken) { window.location.href = '/auth/login?redirect=/posts/' + postId; return; } + if (!getCSRFToken()) { window.location.href = '/auth/login?redirect=/posts/' + postId; return; } showModal(); }); @@ -627,15 +621,14 @@ var commentInput = document.getElementById('commentInput'); var commentSubmitBtn = document.getElementById('commentSubmitBtn'); var mentionDropdown = document.getElementById('mentionDropdown'); - var csrfMeta = document.querySelector('meta[name="csrf-token"]'); - var csrfToken = csrfMeta ? csrfMeta.getAttribute('content') : ''; var currentPage = 1; var totalPages = 1; // ====== 工具函数 ====== function api(method, url, data) { var opts = { method: method, headers: { 'Content-Type': 'application/json' } }; - if (csrfToken) opts.headers['X-CSRF-Token'] = csrfToken; + var token = getCSRFToken(); + if (token) opts.headers['X-CSRF-Token'] = token; if (data) opts.body = JSON.stringify(data); return fetch(url, opts).then(function(r) { return r.json(); }); } @@ -1031,7 +1024,7 @@ fetch('/api/comments/upload-image', { method: 'POST', - headers: { 'X-CSRF-Token': csrfToken }, + headers: { 'X-CSRF-Token': getCSRFToken() }, body: formData }) .then(function(r) { return r.json(); }) diff --git a/templates/MetaLab-2026/html/settings/index.html b/templates/MetaLab-2026/html/settings/index.html index f6f65af..30055dd 100644 --- a/templates/MetaLab-2026/html/settings/index.html +++ b/templates/MetaLab-2026/html/settings/index.html @@ -1032,9 +1032,6 @@ var createBtn = document.getElementById('createFolderBtn'); if (!listEl) return; - var csrfMeta = document.querySelector('meta[name="csrf-token"]'); - var csrfToken = csrfMeta ? csrfMeta.getAttribute('content') : ''; - function esc(s) { var d = document.createElement('div'); d.textContent = s || ''; @@ -1043,7 +1040,8 @@ function api(method, url, data) { var opts = { method: method, headers: { 'Content-Type': 'application/json' } }; - if (csrfToken) opts.headers['X-CSRF-Token'] = csrfToken; + var token = getCSRFToken(); + if (token) opts.headers['X-CSRF-Token'] = token; if (data) opts.body = JSON.stringify(data); return fetch(url, opts).then(function (r) { return r.json(); }); } diff --git a/templates/MetaLab-2026/html/studio/drafts.html b/templates/MetaLab-2026/html/studio/drafts.html index 4d8d1e7..df6d666 100644 --- a/templates/MetaLab-2026/html/studio/drafts.html +++ b/templates/MetaLab-2026/html/studio/drafts.html @@ -65,7 +65,7 @@ function submitPost(id) { method: 'POST', headers: { 'Content-Type': 'application/json', - 'X-CSRF-Token': '{{.CSRFToken}}' + 'X-CSRF-Token': getCSRFToken() } }) .then(r => r.json()) @@ -85,7 +85,7 @@ function deletePost(id) { method: 'DELETE', headers: { 'Content-Type': 'application/json', - 'X-CSRF-Token': '{{.CSRFToken}}' + 'X-CSRF-Token': getCSRFToken() } }) .then(r => r.json()) diff --git a/templates/MetaLab-2026/html/studio/posts.html b/templates/MetaLab-2026/html/studio/posts.html index 4f1f1cc..618924d 100644 --- a/templates/MetaLab-2026/html/studio/posts.html +++ b/templates/MetaLab-2026/html/studio/posts.html @@ -86,7 +86,7 @@ function deletePost(id) { method: 'DELETE', headers: { 'Content-Type': 'application/json', - 'X-CSRF-Token': '{{.CSRFToken}}' + 'X-CSRF-Token': getCSRFToken() } }) .then(r => r.json()) diff --git a/templates/MetaLab-2026/static/js/studio-editor.js b/templates/MetaLab-2026/static/js/studio-editor.js index 86c061b..8a2f95f 100644 --- a/templates/MetaLab-2026/static/js/studio-editor.js +++ b/templates/MetaLab-2026/static/js/studio-editor.js @@ -1,17 +1,13 @@ /** * Studio Editor — 创作中心 Vditor 编辑器 * 基于 editor.js,使用 /api/studio/posts 端点 + * CSRF token 由 shared/utils.js 的 getCSRFToken() 统一提供 */ let vditor = null let draftTimer = null let submitting = false -function getCsrfToken() { - var match = document.cookie.match(/(?:^|;\s*)mlb_csrf=([^;]*)/) - return match ? match[1] : '' -} - document.addEventListener('DOMContentLoaded', () => { const container = document.getElementById('vditor') if (!container) return @@ -186,7 +182,7 @@ async function handleSubmit(e) { method, headers: { 'Content-Type': 'application/json', - 'X-CSRF-Token': getCsrfToken(), + 'X-CSRF-Token': getCSRFToken(), }, body: JSON.stringify({ title, body }), }) diff --git a/templates/admin/static/js/comments.js b/templates/admin/static/js/comments.js index 011d193..4bbc047 100644 --- a/templates/admin/static/js/comments.js +++ b/templates/admin/static/js/comments.js @@ -3,8 +3,6 @@ var currentPage = 1; var currentKeyword = ''; var totalPages = 1; - var csrfMeta = document.querySelector('meta[name="csrf-token"]'); - var csrfToken = csrfMeta ? csrfMeta.getAttribute('content') : ''; var searchInput = document.getElementById('commentSearch'); var searchBtn = document.getElementById('commentSearchBtn'); diff --git a/templates/admin/static/js/posts.js b/templates/admin/static/js/posts.js index 1f9f53c..0a43de3 100644 --- a/templates/admin/static/js/posts.js +++ b/templates/admin/static/js/posts.js @@ -1,8 +1,5 @@ (function() { - function getCSRFToken() { - var meta = document.querySelector('meta[name="csrf-token"]'); - return meta ? meta.getAttribute('content') : ''; - } + // CSRF token 由 shared/utils.js 的 getCSRFToken() 统一提供 function api(url, method, body) { return fetch(url, { diff --git a/templates/shared/static/js/utils.js b/templates/shared/static/js/utils.js index 09ef3ac..77353a8 100644 --- a/templates/shared/static/js/utils.js +++ b/templates/shared/static/js/utils.js @@ -4,11 +4,13 @@ 'use strict'; /** - * 从 读取 CSRF 令牌 - * 由服务端 CSRF 中间件注入到页面模板的 标签中 + * 获取 CSRF 令牌 — 优先从 cookie 读取(与服务端验证源一致,token 刷新后保持最新), + * meta 标签作为后备(部分页面可能未设置 cookie) * @returns {string} CSRF 令牌,未找到时返回空字符串 */ function getCSRFToken() { + var match = document.cookie.match(/(?:^|;\s*)mlb_csrf=([^;]*)/); + if (match && match[1]) return match[1]; var meta = document.querySelector('meta[name="csrf-token"]'); return meta ? meta.getAttribute('content') : ''; }