邮件模板微信化 + 评论老友自动填充 + 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:
zqlit committed 2026-10-05 07:36:26 +08:00
1 parent a8b54f7482
commit 6046eb494c
10 files changed
+443 -87

No files matched your search

+36
View File
@@ -161,6 +161,42 @@ export const publish = (ctx: Ctx) => relayJson(ctx, '/git/publish', 'POST');
export const sync = (ctx: Ctx) => relay(ctx, '/git/sync', { method: 'POST' });
// ---------------------------------------------------------------- 一键跳转
/**
* 「国内线路」一键跳转:给后台前端一个**免登录**直达国内机后台的 URL。
*
* 怎么做到免登录:浏览器直接开 writeapi.usj.cc/admin/ 是要重新输账号密码的
* (那边是独立的 Cookie 会话)。这里让 Worker 用共享令牌对当前时间戳做
* HMAC 签名,前端拿着签名去 editor-api 的 /admin/handoff 换会话 Cookie ——
* 两边本来就共享 EDITOR_TOKEN,等于用已登录的 CF 会话给国内机做了一次担保。
*
* 安全边界:签名 2 分钟内有效、同一签名只能换一次会话、且只有已登录管理员
* 能从 Worker 拿到签名。URL 全程 HTTPS,不含任何账号密码。
*/
async function hmacHex(secret: string, msg: string): Promise<string> {
const enc = new TextEncoder();
const key = await crypto.subtle.importKey(
'raw',
enc.encode(secret),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign'],
);
const sig = await crypto.subtle.sign('HMAC', key, enc.encode(msg));
return [...new Uint8Array(sig)].map((b) => b.toString(16).padStart(2, '0')).join('');
}
export async function handoff(ctx: Ctx): Promise<Response> {
const deny = await guard(ctx);
if (deny) return deny;
const base = String(ctx.env.EDITOR_API_BASE).replace(/\/+$/, '');
const ts = Date.now().toString();
const sig = await hmacHex(String(ctx.env.EDITOR_TOKEN), ts);
return json({ url: `${base}/api/v2/admin/handoff?ts=${ts}&t=${sig}` });
}
/** 健康检查:让后台能显示「编辑后端是否在线」(同样只认真实会话) */
export async function health(ctx: Ctx): Promise<Response> {
if (!(await requireAdminSession(ctx))) return fail(403, '需要管理员权限');