pty-session-cookie.ts
64 lines · browse/src/pty-session-cookie.ts at 3f79d81
1/**
2 * Session cookie registry for the Terminal sidebar tab's PTY WebSocket.
3 *
4 * Why this exists: WebSocket clients in browsers cannot send Authorization
5 * headers on the upgrade request. The terminal-agent's /ws upgrade therefore
6 * authenticates via cookie. We never put the PTY token in /health (codex
7 * outside-voice finding #2: /health already leaked AUTH_TOKEN to any
8 * localhost caller in headed mode; reusing that path for shell access would
9 * widen an existing bug). Instead, the extension does an authenticated
10 * POST /pty-session with the bootstrap AUTH_TOKEN; the server mints a
11 * short-lived cookie scoped to this terminal session and pushes it to the
12 * agent via loopback. The browser then carries the cookie automatically on
13 * the WS upgrade.
14 *
15 * Shares the registry implementation with sse-session-cookie.ts via
16 * createSessionCookieStore. Two INSTANCES instead of one because the cookie
17 * names are different (`gstack_sse` vs `gstack_pty`) and the token spaces
18 * must not overlap — an SSE-read cookie must never grant PTY access, and
19 * vice versa.
20 */
21import { createSessionCookieStore } from './session-cookie-store';
23const TTL_MS = 30 * 60 * 1000; // 30 minutes — matches SSE cookie
25export const PTY_COOKIE_NAME = 'gstack_pty';
27const store = createSessionCookieStore({ cookieName: PTY_COOKIE_NAME, ttlMs: TTL_MS });
29/** Mint a fresh PTY session token. */
30export function mintPtySessionToken(): { token: string; expiresAt: number } {
31 return store.mint();
32}
34/**
35 * Validate a token. Returns true only if the token exists AND is not expired.
36 * Lazily removes expired entries; opportunistically prunes a few more on
37 * every call so the registry stays bounded under reconnect pressure.
38 */
39export function validatePtySessionToken(token: string | null | undefined): boolean {
40 return store.validate(token);
41}
43/**
44 * Drop a session token (called on WS close so a leaked cookie can't be
45 * replayed against a new PTY).
46 */
47export function revokePtySessionToken(token: string | null | undefined): void {
48 store.revoke(token);
49}
51/** Parse the PTY session token from a Cookie header. */
52export function extractPtyCookie(req: Request): string | null {
53 return store.extract(req);
54}
56/** Build the Set-Cookie header value for the PTY session cookie. */
57export function buildPtySetCookie(token: string): string {
58 return store.buildSetCookie(token);
59}
61// Test-only reset.
62export function __resetPtySessions(): void {
63 store.__reset();
64}