From 7b42b70554c3ff8582f8a6e230b5f7729b27f1e3 Mon Sep 17 00:00:00 2001 From: htjulia Date: Mon, 12 Oct 2026 11:09:59 +0900 Subject: [PATCH] docs(auth): correct four claims the code no longer backs docs/authentication.md is the public guide an operator follows to wire auth into the console. Four of its statements describe code that is not there, or leave out a condition, so following them would not work. - REST is not a fallback. The guide named fetchSnapshot as the REST fallback that shares getJson with the module-routes drawer, but nothing calls fetchSnapshot (its only reference is its declaration). The drawer is the only REST call; the diagram now starts there and a note says what fetchSnapshot is - The 401/403 surface is not already half wired. The guide said fetchSnapshot classifies auth responses for the top bar to reuse. The top bar and banner do render AUTH REQUIRED / AUTH REJECTED, but no path produces the auth reason: the stream reports every failure as unreachable and the drawer shows "Failed to load routes." - The dev proxy note is true but dev-only. vite build has no proxy, so a production bundle sends /platform to its own origin; the guide now says so and how to keep the same-origin cookie path - Cross-origin cookies need SameSite=None; Secure, so they require the platform over HTTPS. The recommended cookie scheme did not say that The guide stays in English: it is a public operator guide, like the README, and is kept as an exception to the Korean docs/ default. Tags: #docs #auth Co-Authored-By: htjulia --- docs/authentication.md | 39 ++++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/docs/authentication.md b/docs/authentication.md index 725ec6e..144c46b 100644 --- a/docs/authentication.md +++ b/docs/authentication.md @@ -27,23 +27,28 @@ this is the part to get right: | Path | Where | Transport | Can send headers? | |---|---|---|---| | **SSE stream** (primary, live) | `src/hooks/use-console-data.ts` | `EventSource` | **No** | -| **REST** (fallback + module-routes drawer) | `src/lib/api.ts` (`getJson`) | `fetch` | Yes | +| **REST** (module-routes drawer) | `src/lib/api.ts` (`getJson`) | `fetch` | Yes | Wiring auth on the `fetch` path is easy; the SSE stream is the one that needs care, because `EventSource` cannot set an `Authorization` header. ## REST requests — the `getJson` choke point -The REST fallback (`fetchSnapshot`) and the module-routes drawer -(`getModuleRoutes`) both pass through **one** function: `getJson` in -`src/lib/api.ts`. Add the credential here and both are covered. +The only REST call the console makes today is the module-routes drawer +(`getModuleRoutes`). It passes through **one** function, `getJson` in +`src/lib/api.ts`, and so should any REST call added later. Add the credential +here and every REST request is covered. ``` -fetchSnapshot ──▶ getJson(path) ──▶ fetch(`/platform${path}`, { headers }) +getModuleRoutes ──▶ getJson(path) ──▶ fetch(`/platform${path}`, { headers }) ▲ └── inject the auth header here ``` +`api.ts` also still exports `fetchSnapshot`, a REST snapshot left over from +before the console moved to the stream. Nothing calls it, so it is not a +fallback path and it does not need credentials. + Introduce an **auth-header provider** — a `() => Record` function — and have `getJson` merge its result into the request headers. To support a different scheme you swap only the provider. @@ -74,7 +79,9 @@ these ways instead: `/platform` proxy, so a cookie set by the consuming app's Spring Security is sent automatically with no client change. Cross-origin (no proxy) needs `new EventSource(url, { withCredentials: true })` plus the platform's CORS - `Access-Control-Allow-Credentials: true`. No token handling in the client — + `Access-Control-Allow-Credentials: true`, and the cookie itself must be + `SameSite=None; Secure` — so a cross-origin cookie requires the platform to + be served over **HTTPS**. No token handling in the client — the cleanest fit, and it also covers the `fetch` path (send `credentials: 'include'`). - **Query-param token.** Append `?access_token=…` to `STREAM_URL`. Simple, but @@ -98,16 +105,26 @@ The Vite `/platform` proxy forwards request headers and cookies unchanged, so both an auth header (REST) and a session cookie (stream) reach the target without any proxy change. +This applies to `npm run dev` only. `vite build` carries no proxy, so a +production bundle sends `/platform` requests to the origin it is served from. +Serve it from the platform's origin (or behind the same reverse proxy) to keep +the same-origin cookie path, or take the cross-origin route above. + ## Security rules - **Never bake a token into the build** — use runtime (`localStorage`) or a gitignored `.env` only. - On a **401/403 from the REST path**, do not silently fall back to sample data — - surface an explicit "auth required / token rejected" state in the top bar - (`fetchSnapshot` already classifies `auth` responses; the top bar reuses the - `LIVE` / `SAMPLE DATA` badge area). The SSE stream cannot make this - distinction (see above), so prefer a cookie scheme when you need the platform - to reject the console cleanly. + surface an explicit "auth required / token rejected" state. The display side + exists: a disconnected state with reason `auth` renders as `AUTH REQUIRED` / + `AUTH REJECTED` in the top bar badge and as a banner + (`src/components/top-bar.tsx`, `src/components/connection-banner.tsx`). But + **nothing produces that reason today** — the stream reports every failure as + `unreachable`, and the module-routes drawer reports a 401/403 as a generic + error. Wiring auth means classifying 401/403 on the REST path and feeding it + into that state. The SSE stream cannot make this distinction (see above), so + prefer a cookie scheme when you need the platform to reject the console + cleanly. - If you choose **OAuth2**, the redirect/callback route and token storage + refresh are a separate piece of work, not a header swap.