fix: 统一 CSRF token 获取方式,修复 token 刷新后缓存失效
- 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 获取,统一用共享方法
This commit is contained in:
@ -133,12 +133,10 @@
|
|||||||
if (link) {
|
if (link) {
|
||||||
// 有链接的通知:使用 fetch keepalive 确保页面跳转前标记已读不会丢失
|
// 有链接的通知:使用 fetch keepalive 确保页面跳转前标记已读不会丢失
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
var csrfMeta = document.querySelector('meta[name="csrf-token"]');
|
|
||||||
var csrfToken = csrfMeta ? csrfMeta.getAttribute('content') : '';
|
|
||||||
fetch('/api/messages/' + id + '/read', {
|
fetch('/api/messages/' + id + '/read', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
keepalive: true,
|
keepalive: true,
|
||||||
headers: { 'X-CSRF-Token': csrfToken }
|
headers: { 'X-CSRF-Token': getCSRFToken() }
|
||||||
});
|
});
|
||||||
card.classList.remove('unread');
|
card.classList.remove('unread');
|
||||||
var badge = card.querySelector('.msg-badge');
|
var badge = card.querySelector('.msg-badge');
|
||||||
|
|||||||
@ -187,12 +187,11 @@
|
|||||||
if (!btn) return;
|
if (!btn) return;
|
||||||
btn.addEventListener('click', function() {
|
btn.addEventListener('click', function() {
|
||||||
if (!confirm('确认提交审核?审核通过后将公开发布。')) return;
|
if (!confirm('确认提交审核?审核通过后将公开发布。')) return;
|
||||||
var csrf = document.querySelector('meta[name="csrf-token"]');
|
|
||||||
fetch('/api/posts/' + btn.dataset.id + '/submit', {
|
fetch('/api/posts/' + btn.dataset.id + '/submit', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-CSRF-Token': csrf ? csrf.getAttribute('content') : ''
|
'X-CSRF-Token': getCSRFToken()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(function(r) { return r.json(); })
|
.then(function(r) { return r.json(); })
|
||||||
@ -217,8 +216,6 @@
|
|||||||
if (!btn || !overlay) return;
|
if (!btn || !overlay) return;
|
||||||
|
|
||||||
var postId = btn.dataset.id;
|
var postId = btn.dataset.id;
|
||||||
var csrfMeta = document.querySelector('meta[name="csrf-token"]');
|
|
||||||
var csrfToken = csrfMeta ? csrfMeta.getAttribute('content') : '';
|
|
||||||
|
|
||||||
function showOverlay() {
|
function showOverlay() {
|
||||||
overlay.style.display = 'flex';
|
overlay.style.display = 'flex';
|
||||||
@ -256,7 +253,7 @@
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-CSRF-Token': csrfToken
|
'X-CSRF-Token': getCSRFToken()
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ post_id: parseInt(postId), amount: amount })
|
body: JSON.stringify({ post_id: parseInt(postId), amount: amount })
|
||||||
})
|
})
|
||||||
@ -297,8 +294,6 @@
|
|||||||
if (!container || !likeBtn || !dislikeBtn) return;
|
if (!container || !likeBtn || !dislikeBtn) return;
|
||||||
|
|
||||||
var postId = container.dataset.postId;
|
var postId = container.dataset.postId;
|
||||||
var csrfMeta = document.querySelector('meta[name="csrf-token"]');
|
|
||||||
var csrfToken = csrfMeta ? csrfMeta.getAttribute('content') : '';
|
|
||||||
|
|
||||||
// 页面加载时获取当前反应状态
|
// 页面加载时获取当前反应状态
|
||||||
fetch('/api/posts/' + postId + '/reaction')
|
fetch('/api/posts/' + postId + '/reaction')
|
||||||
@ -321,7 +316,7 @@
|
|||||||
likeBtn.disabled = true;
|
likeBtn.disabled = true;
|
||||||
fetch('/api/posts/' + postId + '/like', {
|
fetch('/api/posts/' + postId + '/like', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrfToken }
|
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': getCSRFToken() }
|
||||||
})
|
})
|
||||||
.then(function(r) {
|
.then(function(r) {
|
||||||
if (r.status === 401) { window.location.href = '/auth/login?redirect=/posts/' + postId; return null; }
|
if (r.status === 401) { window.location.href = '/auth/login?redirect=/posts/' + postId; return null; }
|
||||||
@ -343,7 +338,7 @@
|
|||||||
dislikeBtn.disabled = true;
|
dislikeBtn.disabled = true;
|
||||||
fetch('/api/posts/' + postId + '/dislike', {
|
fetch('/api/posts/' + postId + '/dislike', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrfToken }
|
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': getCSRFToken() }
|
||||||
})
|
})
|
||||||
.then(function(r) {
|
.then(function(r) {
|
||||||
if (r.status === 401) { window.location.href = '/auth/login?redirect=/posts/' + postId; return null; }
|
if (r.status === 401) { window.location.href = '/auth/login?redirect=/posts/' + postId; return null; }
|
||||||
@ -377,15 +372,14 @@
|
|||||||
if (!favBtn || !container) return;
|
if (!favBtn || !container) return;
|
||||||
|
|
||||||
var postId = container.dataset.postId;
|
var postId = container.dataset.postId;
|
||||||
var csrfMeta = document.querySelector('meta[name="csrf-token"]');
|
|
||||||
var csrfToken = csrfMeta ? csrfMeta.getAttribute('content') : '';
|
|
||||||
var isFavorited = false;
|
var isFavorited = false;
|
||||||
var currentFolderId = null;
|
var currentFolderId = null;
|
||||||
var loading = false;
|
var loading = false;
|
||||||
|
|
||||||
function api(method, url, data) {
|
function api(method, url, data) {
|
||||||
var opts = { method: method, headers: { 'Content-Type': 'application/json' } };
|
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);
|
if (data) opts.body = JSON.stringify(data);
|
||||||
return fetch(url, opts).then(function(r) {
|
return fetch(url, opts).then(function(r) {
|
||||||
if (r.status === 401) { window.location.href = '/auth/login?redirect=/posts/' + postId; throw new Error('unauthorized'); }
|
if (r.status === 401) { window.location.href = '/auth/login?redirect=/posts/' + postId; throw new Error('unauthorized'); }
|
||||||
@ -469,7 +463,7 @@
|
|||||||
|
|
||||||
// 无弹窗回退(未登录跳转)
|
// 无弹窗回退(未登录跳转)
|
||||||
function handleNoModal() {
|
function handleNoModal() {
|
||||||
if (!csrfToken) {
|
if (!getCSRFToken()) {
|
||||||
window.location.href = '/auth/login?redirect=/posts/' + postId;
|
window.location.href = '/auth/login?redirect=/posts/' + postId;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -571,7 +565,7 @@
|
|||||||
|
|
||||||
// 事件:点击收藏按钮 → 打开弹窗
|
// 事件:点击收藏按钮 → 打开弹窗
|
||||||
favBtn.addEventListener('click', function() {
|
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();
|
showModal();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -627,15 +621,14 @@
|
|||||||
var commentInput = document.getElementById('commentInput');
|
var commentInput = document.getElementById('commentInput');
|
||||||
var commentSubmitBtn = document.getElementById('commentSubmitBtn');
|
var commentSubmitBtn = document.getElementById('commentSubmitBtn');
|
||||||
var mentionDropdown = document.getElementById('mentionDropdown');
|
var mentionDropdown = document.getElementById('mentionDropdown');
|
||||||
var csrfMeta = document.querySelector('meta[name="csrf-token"]');
|
|
||||||
var csrfToken = csrfMeta ? csrfMeta.getAttribute('content') : '';
|
|
||||||
var currentPage = 1;
|
var currentPage = 1;
|
||||||
var totalPages = 1;
|
var totalPages = 1;
|
||||||
|
|
||||||
// ====== 工具函数 ======
|
// ====== 工具函数 ======
|
||||||
function api(method, url, data) {
|
function api(method, url, data) {
|
||||||
var opts = { method: method, headers: { 'Content-Type': 'application/json' } };
|
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);
|
if (data) opts.body = JSON.stringify(data);
|
||||||
return fetch(url, opts).then(function(r) { return r.json(); });
|
return fetch(url, opts).then(function(r) { return r.json(); });
|
||||||
}
|
}
|
||||||
@ -1031,7 +1024,7 @@
|
|||||||
|
|
||||||
fetch('/api/comments/upload-image', {
|
fetch('/api/comments/upload-image', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'X-CSRF-Token': csrfToken },
|
headers: { 'X-CSRF-Token': getCSRFToken() },
|
||||||
body: formData
|
body: formData
|
||||||
})
|
})
|
||||||
.then(function(r) { return r.json(); })
|
.then(function(r) { return r.json(); })
|
||||||
|
|||||||
@ -1032,9 +1032,6 @@
|
|||||||
var createBtn = document.getElementById('createFolderBtn');
|
var createBtn = document.getElementById('createFolderBtn');
|
||||||
if (!listEl) return;
|
if (!listEl) return;
|
||||||
|
|
||||||
var csrfMeta = document.querySelector('meta[name="csrf-token"]');
|
|
||||||
var csrfToken = csrfMeta ? csrfMeta.getAttribute('content') : '';
|
|
||||||
|
|
||||||
function esc(s) {
|
function esc(s) {
|
||||||
var d = document.createElement('div');
|
var d = document.createElement('div');
|
||||||
d.textContent = s || '';
|
d.textContent = s || '';
|
||||||
@ -1043,7 +1040,8 @@
|
|||||||
|
|
||||||
function api(method, url, data) {
|
function api(method, url, data) {
|
||||||
var opts = { method: method, headers: { 'Content-Type': 'application/json' } };
|
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);
|
if (data) opts.body = JSON.stringify(data);
|
||||||
return fetch(url, opts).then(function (r) { return r.json(); });
|
return fetch(url, opts).then(function (r) { return r.json(); });
|
||||||
}
|
}
|
||||||
|
|||||||
@ -65,7 +65,7 @@ function submitPost(id) {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-CSRF-Token': '{{.CSRFToken}}'
|
'X-CSRF-Token': getCSRFToken()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(r => r.json())
|
.then(r => r.json())
|
||||||
@ -85,7 +85,7 @@ function deletePost(id) {
|
|||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-CSRF-Token': '{{.CSRFToken}}'
|
'X-CSRF-Token': getCSRFToken()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(r => r.json())
|
.then(r => r.json())
|
||||||
|
|||||||
@ -86,7 +86,7 @@ function deletePost(id) {
|
|||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-CSRF-Token': '{{.CSRFToken}}'
|
'X-CSRF-Token': getCSRFToken()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(r => r.json())
|
.then(r => r.json())
|
||||||
|
|||||||
@ -1,17 +1,13 @@
|
|||||||
/**
|
/**
|
||||||
* Studio Editor — 创作中心 Vditor 编辑器
|
* Studio Editor — 创作中心 Vditor 编辑器
|
||||||
* 基于 editor.js,使用 /api/studio/posts 端点
|
* 基于 editor.js,使用 /api/studio/posts 端点
|
||||||
|
* CSRF token 由 shared/utils.js 的 getCSRFToken() 统一提供
|
||||||
*/
|
*/
|
||||||
|
|
||||||
let vditor = null
|
let vditor = null
|
||||||
let draftTimer = null
|
let draftTimer = null
|
||||||
let submitting = false
|
let submitting = false
|
||||||
|
|
||||||
function getCsrfToken() {
|
|
||||||
var match = document.cookie.match(/(?:^|;\s*)mlb_csrf=([^;]*)/)
|
|
||||||
return match ? match[1] : ''
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
const container = document.getElementById('vditor')
|
const container = document.getElementById('vditor')
|
||||||
if (!container) return
|
if (!container) return
|
||||||
@ -186,7 +182,7 @@ async function handleSubmit(e) {
|
|||||||
method,
|
method,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-CSRF-Token': getCsrfToken(),
|
'X-CSRF-Token': getCSRFToken(),
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ title, body }),
|
body: JSON.stringify({ title, body }),
|
||||||
})
|
})
|
||||||
|
|||||||
@ -3,8 +3,6 @@
|
|||||||
var currentPage = 1;
|
var currentPage = 1;
|
||||||
var currentKeyword = '';
|
var currentKeyword = '';
|
||||||
var totalPages = 1;
|
var totalPages = 1;
|
||||||
var csrfMeta = document.querySelector('meta[name="csrf-token"]');
|
|
||||||
var csrfToken = csrfMeta ? csrfMeta.getAttribute('content') : '';
|
|
||||||
|
|
||||||
var searchInput = document.getElementById('commentSearch');
|
var searchInput = document.getElementById('commentSearch');
|
||||||
var searchBtn = document.getElementById('commentSearchBtn');
|
var searchBtn = document.getElementById('commentSearchBtn');
|
||||||
|
|||||||
@ -1,8 +1,5 @@
|
|||||||
(function() {
|
(function() {
|
||||||
function getCSRFToken() {
|
// CSRF token 由 shared/utils.js 的 getCSRFToken() 统一提供
|
||||||
var meta = document.querySelector('meta[name="csrf-token"]');
|
|
||||||
return meta ? meta.getAttribute('content') : '';
|
|
||||||
}
|
|
||||||
|
|
||||||
function api(url, method, body) {
|
function api(url, method, body) {
|
||||||
return fetch(url, {
|
return fetch(url, {
|
||||||
|
|||||||
@ -4,11 +4,13 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从 <meta name="csrf-token"> 读取 CSRF 令牌
|
* 获取 CSRF 令牌 — 优先从 cookie 读取(与服务端验证源一致,token 刷新后保持最新),
|
||||||
* 由服务端 CSRF 中间件注入到页面模板的 <meta> 标签中
|
* meta 标签作为后备(部分页面可能未设置 cookie)
|
||||||
* @returns {string} CSRF 令牌,未找到时返回空字符串
|
* @returns {string} CSRF 令牌,未找到时返回空字符串
|
||||||
*/
|
*/
|
||||||
function getCSRFToken() {
|
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"]');
|
var meta = document.querySelector('meta[name="csrf-token"]');
|
||||||
return meta ? meta.getAttribute('content') : '';
|
return meta ? meta.getAttribute('content') : '';
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user