- 新增媒体库:media_library/media_thumbs 服务(WebP 缩略图)、管理端 media 页面 - 移动端底部导航 MobileTabBar 替换 MobilePostBar - 旧数据导入增强与测试、路由与登录会话/板块侧边栏测试补强 - site-doc 组件精简(移除 Breadcrumb),文档新增迁移公告说明
74 lines
3.0 KiB
TypeScript
74 lines
3.0 KiB
TypeScript
// 认证 cookie 名(前后端必须严格一致):
|
||
// 与后端 ConfigureCookieNames(!DevMode) 对齐——显式 DEV_MODE=true|1 禁用 __Host-;
|
||
// 显式 DEV_MODE=false|0 启用;未设置时回退 NODE_ENV=production(官方镜像两者同时满足)。
|
||
function hostCookiePrefix(): string {
|
||
const dm = (process.env.DEV_MODE || "").trim().toLowerCase();
|
||
if (dm === "true" || dm === "1") return "";
|
||
if (dm === "false" || dm === "0") return "__Host-";
|
||
return process.env.NODE_ENV === "production" ? "__Host-" : "";
|
||
}
|
||
|
||
const HOST_PREFIX = hostCookiePrefix();
|
||
|
||
export const TOKEN_COOKIE = `${HOST_PREFIX}j13_token`;
|
||
export const REFRESH_COOKIE = `${HOST_PREFIX}j13_refresh`;
|
||
export const CSRF_COOKIE = `${HOST_PREFIX}j13_csrf`;
|
||
|
||
/** 密码隐藏块解锁 cookie 前缀(与 backend HidePasswordCookie 一致) */
|
||
export const HIDE_PWD_COOKIE_PREFIX = "j13_hp_";
|
||
|
||
type CookieReader = {
|
||
get(name: string): { value: string } | undefined;
|
||
};
|
||
|
||
type CookieStoreLike = CookieReader & {
|
||
getAll?: () => { name: string; value: string }[];
|
||
};
|
||
|
||
// 构造转发给后端的最小 Cookie 头:SSR 公开/鉴权接口只需要 access token 来
|
||
// 识别用户(填充 liked 等状态),不应把 refresh token、主题等无关 cookie 全量转发。
|
||
// 返回空串表示当前无登录凭据,调用方应省略 Cookie 头。
|
||
export function authCookieHeader(store: CookieReader): string {
|
||
const token = store.get(TOKEN_COOKIE);
|
||
return token ? `${TOKEN_COOKIE}=${token.value}` : "";
|
||
}
|
||
|
||
/**
|
||
* 登录设备列表 SSR:必须同时转发 refresh cookie,才能识别「当前会话」。
|
||
* 其它 SSR 接口不要用这个,避免把 refresh token 带到无关后端请求。
|
||
*/
|
||
export function sessionCookieHeader(store: CookieReader): string {
|
||
const parts: string[] = [];
|
||
const token = store.get(TOKEN_COOKIE);
|
||
if (token) parts.push(`${TOKEN_COOKIE}=${token.value}`);
|
||
const refresh = store.get(REFRESH_COOKIE);
|
||
if (refresh) parts.push(`${REFRESH_COOKIE}=${refresh.value}`);
|
||
return parts.join("; ");
|
||
}
|
||
|
||
/**
|
||
* 帖子详情 SSR:access token + 密码隐藏块解锁 cookie。
|
||
* 解锁 cookie 为 HttpOnly,必须随 SSR 转发,否则 refresh 后密码块会再次锁上。
|
||
*/
|
||
export function postViewerCookieHeader(store: CookieStoreLike): string {
|
||
const parts: string[] = [];
|
||
const auth = authCookieHeader(store);
|
||
if (auth) parts.push(auth);
|
||
if (typeof store.getAll === "function") {
|
||
for (const c of store.getAll()) {
|
||
if (c.name.startsWith(HIDE_PWD_COOKIE_PREFIX)) {
|
||
parts.push(`${c.name}=${c.value}`);
|
||
}
|
||
}
|
||
}
|
||
return parts.join("; ");
|
||
}
|
||
|
||
// 客户端判断浏览器是否可能持有登录态:j13_csrf 非 HttpOnly 可读,
|
||
// 生命周期与 refresh 一致,适合作为挂载后静默校正的触发信号。
|
||
export function hasAuthCookieHint(): boolean {
|
||
if (typeof document === "undefined") return false;
|
||
const escaped = CSRF_COOKIE.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||
return new RegExp(`(?:^|;\\s*)${escaped}=`).test(document.cookie);
|
||
}
|