mirror of https://github.com/garrytan/gstack.git
65 lines
2.4 KiB
TypeScript
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();
|
|
}
|