基线:鉴权会话+工作组+工作台三区修复+编排工作区+产品设计文档

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
This commit is contained in:
2026-07-27 20:43:35 +08:00
co-authored by Claude Fable 5
parent 61fe181678
commit ac85ecf2ea
119 changed files with 16634 additions and 1246 deletions
+39
View File
@@ -0,0 +1,39 @@
/**
* 会话 cookie 的读写(ADR-010)—— 只此一处拼 `Set-Cookie`,属性不许各写各的。
*
* `HttpOnly`:脚本读不到 token,XSS 拿不走会话。
* `SameSite=Lax`:跨站表单 POST 不带 cookie,挡掉 CSRF 的常见形态;
* 同站导航仍带,登录后跳转不受影响。
* 开发期走 http://localhost,故 `Secure` 随部署环境开关(生产必开)。
*/
import type { Context } from 'hono';
import type { AppEnv } from './http-env.ts';
export const SESSION_COOKIE = 'jcai_session';
/** 生产环境必带 Secure;开发期 localhost 是 http,带了浏览器就不存 */
function secureAttr(): string {
return process.env['NODE_ENV'] === 'production' ? '; Secure' : '';
}
export function readSessionToken(cookieHeader: string | undefined): string | undefined {
if (cookieHeader === undefined) return undefined;
const match = new RegExp(`(?:^|;\\s*)${SESSION_COOKIE}=([^;]+)`).exec(cookieHeader);
return match?.[1];
}
export function setSessionCookie(c: Context<AppEnv>, token: string, expiresAt: Date): void {
c.header(
'Set-Cookie',
`${SESSION_COOKIE}=${token}; Path=/; HttpOnly; SameSite=Lax${secureAttr()}; Expires=${expiresAt.toUTCString()}`,
{ append: true },
);
}
export function clearSessionCookie(c: Context<AppEnv>): void {
c.header(
'Set-Cookie',
`${SESSION_COOKIE}=; Path=/; HttpOnly; SameSite=Lax${secureAttr()}; Max-Age=0`,
{ append: true },
);
}