邮件模板微信化 + 评论老友自动填充 + CF 后台国内线路一键跳
- 邮件模板重做成微信聊天式:灰底、自己评论绿色气泡靠右、对方白色气泡靠左、 40px 圆角方头像(weavatar,邮箱 md5 不外泄)、时间居中、#07C160 主按钮; 布局全嵌套表格(Outlook 稳),表情内联 max-height:28px(修掉「表情太大」)、 配图限宽 100%;管理员主题从「新的评论待审」改成「您有一个新的评论」 - 老友自动填充:新增 /comments/lookup(只回昵称+网址,绝不含邮箱;IP 限速 30 次/10 分钟)+ 主题 comment-autofill 模块(填昵称弹提示、填邮箱自动带出 上次的昵称/网址,事件委托不怕 Artalk 重建编辑器) - CF 后台新增「国内线路」一键跳:/editor/handoff 用共享令牌对时间戳 HMAC 签名 → editor-api /admin/handoff 验签发会话 Cookie(2 分钟有效、防重放), 晚高峰丢包时直达国内机后台免重新登录
This commit is contained in:
1 parent
18a4882577
commit
bcfa229105
10 files changed
+443
-87
No files matched your search
@@ -184,6 +184,18 @@ function setCookie(res, sid, maxAgeSec) {
|
||||
res.setHeader('Set-Cookie', parts.join('; '));
|
||||
}
|
||||
|
||||
/** HMAC-SHA256(key=共享令牌, msg=时间戳)的 hex,供「一键跳转」验签 */
|
||||
function hmacHex(key, msg) {
|
||||
try {
|
||||
return crypto.createHmac('sha256', key).update(msg).digest('hex');
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/** 已消费的一键跳转签名(防重放):同一签名只允许换一次会话 */
|
||||
const usedHandoff = new Set();
|
||||
|
||||
// 暴力破解防护:同一 IP 连续失败到阈值就冷却一段时间
|
||||
const FAIL_MAX = 5;
|
||||
const FAIL_COOL_MS = 5 * 60 * 1000;
|
||||
@@ -441,6 +453,33 @@ const server = http.createServer(async (req, res) => {
|
||||
return;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------ 一键跳转(免登录)
|
||||
// Cloudflare 后台点「国内线路」时走这里:Worker 用共享令牌对时间戳做
|
||||
// HMAC 签名,本服务验签通过就直接发会话 Cookie 并 302 到 /admin/。
|
||||
// 三重防线:签名只有两边知道 / 时间窗 2 分钟 / 同一签名只用一次。
|
||||
if (url.pathname === '/admin/handoff' && req.method === 'GET') {
|
||||
const ts = String(url.searchParams.get('ts') || '');
|
||||
const sig = String(url.searchParams.get('t') || '');
|
||||
const bad = () => {
|
||||
res.writeHead(403, { 'Content-Type': 'text/plain; charset=utf-8' });
|
||||
res.end('handoff rejected');
|
||||
};
|
||||
if (!/^\d{13}$/.test(ts) || Math.abs(Date.now() - Number(ts)) > 120_000) return bad();
|
||||
const expect = hmacHex(cfg.token, ts);
|
||||
if (!expect || !safeEqual(sig, expect)) return bad();
|
||||
if (usedHandoff.has(sig)) return bad(); // 防重放:一个签名只能换一次会话
|
||||
usedHandoff.add(sig);
|
||||
if (usedHandoff.size > 500) usedHandoff.clear(); // 简单兜底,防集合无限涨
|
||||
|
||||
const sid = crypto.randomBytes(32).toString('hex');
|
||||
sessions.set(sid, { user: cfg.adminUser, exp: Date.now() + cfg.sessionTtlMs });
|
||||
saveSessions();
|
||||
setCookie(res, sid, Math.floor(cfg.sessionTtlMs / 1000));
|
||||
res.writeHead(302, { Location: '/admin/' });
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------- 鉴权
|
||||
// 两条通道:Cloudflare Worker 注入的共享令牌(浏览器看不到),
|
||||
// 或本机签发的浏览器会话 Cookie。满足其一即放行。
|
||||
|
||||
Reference in new issue
Block a user