From 0ebbcf38c7bd44c288dfd3ce1c92702688308074 Mon Sep 17 00:00:00 2001 From: Tyler Gillispie Date: Sun, 23 Aug 2026 21:56:46 -0700 Subject: [PATCH] docs: format GHOST_COCKPIT_SPEC.md --- docs/GHOST_COCKPIT_SPEC.md | 61 +++++++++++++++++++++++++++----------- 1 file changed, 43 insertions(+), 18 deletions(-) diff --git a/docs/GHOST_COCKPIT_SPEC.md b/docs/GHOST_COCKPIT_SPEC.md index 7a3de3e..28a0abc 100644 --- a/docs/GHOST_COCKPIT_SPEC.md +++ b/docs/GHOST_COCKPIT_SPEC.md @@ -2,30 +2,50 @@ ## Overview -The Ghost Cockpit Protocol provides a seamless experience for real-time applications connected via WebSockets to Auth-Yes secured services. When a user's session expires or is revoked, instead of abruptly disconnecting and redirecting the user to a login page (losing unsaved state or telemetry data), the application enters a "frozen" or "ghost" state. +The Ghost Cockpit Protocol provides a seamless experience for real-time +applications connected via WebSockets to Auth-Yes secured services. When a +user's session expires or is revoked, instead of abruptly disconnecting and +redirecting the user to a login page (losing unsaved state or telemetry data), +the application enters a "frozen" or "ghost" state. In this state: -1. The WebSocket connection is gracefully terminated by the server with a specific control frame and status code. + +1. The WebSocket connection is gracefully terminated by the server with a + specific control frame and status code. 2. The UI freezes real-time updates and input, keeping the current state intact. -3. A background or modal re-authentication process (like WebAuthn biometric prompt) is triggered. -4. Upon successful re-authentication, the WebSocket connection is re-established, and the application resumes normal operation without any page reload. +3. A background or modal re-authentication process (like WebAuthn biometric + prompt) is triggered. +4. Upon successful re-authentication, the WebSocket connection is + re-established, and the application resumes normal operation without any page + reload. ## Choreography -1. **Active Connection:** The client maintains an active WebSocket connection. The backend uses the `createWebSocketGuard` to monitor the session. -2. **Session Invalidation:** The Auth-Yes API Gateway or an admin invalidates the session. An `invalidate` event is propagated through the `AuthSdk`. -3. **Revocation Frame:** The WebSocket Guard intercepts the event and sends a JSON control frame: +1. **Active Connection:** The client maintains an active WebSocket connection. + The backend uses the `createWebSocketGuard` to monitor the session. +2. **Session Invalidation:** The Auth-Yes API Gateway or an admin invalidates + the session. An `invalidate` event is propagated through the `AuthSdk`. +3. **Revocation Frame:** The WebSocket Guard intercepts the event and sends a + JSON control frame: ```json { "type": "AUTH_REVOKED", "reason": "SESSION_EXPIRED" } ``` -4. **Connection Close:** Immediately after sending the frame, the server closes the WebSocket with status code **1008 (Policy Violation)** and reason **"Session Expired"**. -5. **Client Freeze:** The client application receives the `AUTH_REVOKED` frame (or detects the 1008 close code) and transitions to the "Ghost Cockpit" state. -6. **Re-authentication:** The client calls Auth-Yes endpoints (e.g., `/api/login/challenge` and `/api/login/verify`) using the Web Authentication API (`navigator.credentials.get()`). -7. **Resumption:** Once a new session token is obtained, the client reconnects the WebSocket and the UI unfreezes. +4. **Connection Close:** Immediately after sending the frame, the server closes + the WebSocket with status code **1008 (Policy Violation)** and reason + **"Session Expired"**. +5. **Client Freeze:** The client application receives the `AUTH_REVOKED` frame + (or detects the 1008 close code) and transitions to the "Ghost Cockpit" + state. +6. **Re-authentication:** The client calls Auth-Yes endpoints (e.g., + `/api/login/challenge` and `/api/login/verify`) using the Web Authentication + API (`navigator.credentials.get()`). +7. **Resumption:** Once a new session token is obtained, the client reconnects + the WebSocket and the UI unfreezes. ## Reference Client Implementation -Below is a complete, reference client-side TypeScript snippet demonstrating the `GhostCockpitClient` class that manages this lifecycle. +Below is a complete, reference client-side TypeScript snippet demonstrating the +`GhostCockpitClient` class that manages this lifecycle. ```typescript /** @@ -102,11 +122,15 @@ export class GhostCockpitClient { private async handleSessionExpired() { if (this.isFrozen) return; // Already handling this.setFrozen(true); - console.log("[GhostCockpit] UI Frozen. Initiating background re-authentication..."); + console.log( + "[GhostCockpit] UI Frozen. Initiating background re-authentication...", + ); try { // 1. Fetch WebAuthn Challenge from Auth-Yes - const challengeRes = await fetch("/api/login/challenge", { method: "POST" }); + const challengeRes = await fetch("/api/login/challenge", { + method: "POST", + }); if (!challengeRes.ok) throw new Error("Failed to get challenge"); const challengeData = await challengeRes.json(); @@ -115,7 +139,7 @@ export class GhostCockpitClient { // 2. Prompt user for Biometrics / Security Key via Web Authentication API const credential = await navigator.credentials.get({ - publicKey: challengeData.publicKeyRequestOptions + publicKey: challengeData.publicKeyRequestOptions, }); if (!credential) throw new Error("Credential not provided"); @@ -124,15 +148,16 @@ export class GhostCockpitClient { const verifyRes = await fetch("/api/login/verify", { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ credential }) // Mock serialization + body: JSON.stringify({ credential }), // Mock serialization }); if (!verifyRes.ok) throw new Error("Re-authentication failed"); - console.log("[GhostCockpit] Re-authentication successful! Reconnecting..."); + console.log( + "[GhostCockpit] Re-authentication successful! Reconnecting...", + ); // Reconnect with new session (implicitly via browser cookies or explicit headers) this.connect(); - } catch (err) { console.error("[GhostCockpit] Re-authentication error:", err); // Fallback: Redirect to full login page or show permanent error