This repository has been archived on 2026-09-13. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
BPOS-MVP/apps/api/src/app/session-cookie.ts
T

40 lines
1.4 KiB
TypeScript

/**
* 会话 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 },
);
}