// 认证 cookie 名(前后端必须严格一致): // 生产构建(HTTPS)启用 __Host- 前缀,浏览器强制 Secure + Path=/ + 无 Domain; // 后端按 !DevMode 同步启用(见 backend/service/auth.go ConfigureCookieNames)。 const HOST_PREFIX = process.env.NODE_ENV === "production" ? "__Host-" : ""; 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,才能识别「当前会话」并校准 IP/UA。 * 其它 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); }