Archived
基线:鉴权会话+工作组+工作台三区修复+编排工作区+产品设计文档
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
This commit is contained in:
@@ -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 },
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user