gstack/browse/src/pty-session-cookie.ts

65 lines
2.4 KiB
TypeScript

/**
* Session cookie registry for the Terminal sidebar tab's PTY WebSocket.
*
* Why this exists: WebSocket clients in browsers cannot send Authorization
* headers on the upgrade request. The terminal-agent's /ws upgrade therefore
* authenticates via cookie. We never put the PTY token in /health (codex
* outside-voice finding #2: /health already leaked AUTH_TOKEN to any
* localhost caller in headed mode; reusing that path for shell access would
* widen an existing bug). Instead, the extension does an authenticated
* POST /pty-session with the bootstrap AUTH_TOKEN; the server mints a
* short-lived cookie scoped to this terminal session and pushes it to the
* agent via loopback. The browser then carries the cookie automatically on
* the WS upgrade.
*
* Shares the registry implementation with sse-session-cookie.ts via
* createSessionCookieStore. Two INSTANCES instead of one because the cookie
* names are different (`gstack_sse` vs `gstack_pty`) and the token spaces
* must not overlap — an SSE-read cookie must never grant PTY access, and
* vice versa.
*/
import { createSessionCookieStore } from './session-cookie-store';
const TTL_MS = 30 * 60 * 1000; // 30 minutes — matches SSE cookie
export const PTY_COOKIE_NAME = 'gstack_pty';
const store = createSessionCookieStore({ cookieName: PTY_COOKIE_NAME, ttlMs: TTL_MS });
/** Mint a fresh PTY session token. */
export function mintPtySessionToken(): { token: string; expiresAt: number } {
return store.mint();
}
/**
* Validate a token. Returns true only if the token exists AND is not expired.
* Lazily removes expired entries; opportunistically prunes a few more on
* every call so the registry stays bounded under reconnect pressure.
*/
export function validatePtySessionToken(token: string | null | undefined): boolean {
return store.validate(token);
}
/**
* Drop a session token (called on WS close so a leaked cookie can't be
* replayed against a new PTY).
*/
export function revokePtySessionToken(token: string | null | undefined): void {
store.revoke(token);
}
/** Parse the PTY session token from a Cookie header. */
export function extractPtyCookie(req: Request): string | null {
return store.extract(req);
}
/** Build the Set-Cookie header value for the PTY session cookie. */
export function buildPtySetCookie(token: string): string {
return store.buildSetCookie(token);
}
// Test-only reset.
export function __resetPtySessions(): void {
store.__reset();
}