Skip to content

Commit 6719eb8

Browse files
fix(pairing): console proxy calls service directly + don't log out on downstream 401
Two stacked bugs the user hit on the QR-pair page: 1. Backend — the console proxy at /api/console/proof-pairing/* forwarded to /v1/proof-pairing/* over loopback HTTP using x-zeroauth-tenant-* headers that the /v1 layer never actually trusts. The /v1 layer ran authenticateTenantApiKey() like every other v1 route, found no Authorization: Bearer za_live_* header, and returned 401 missing_api_key. The dashboard surfaced it as 'Something went sideways'. Fix: drop the HTTP roundtrip. Same Node process; just call the service functions in src/services/proof-pairing.ts directly. The console JWT already authenticated the tenant — we have the tenantId in hand. Mint the session_bind cookie at Path=/api/console/proof-pairing/ so the browser ships it back on subsequent same-origin reads, identical UX, no upstream API key needed. createSession()'s apiKeyId is now nullable; null means the call came via the console JWT path (audit row records actorType='console' instead of api_key). Dropped helpers no longer referenced: PAIRING_UPSTREAM_BASE, PAIRING_INTERNAL_HEADER, rewriteSetCookiePath, FetchResponse, PairingProxyResult, pairingFetch, applyPairingCookies, pipeJson, buildPairingError, handleUpstreamFailure. ~250 lines deleted, ~150 lines added. 2. Frontend — dashboard/src/lib/api.ts cleared the console token on ANY 401 from /api/console/*, even when the 401 was a downstream symptom (like the missing_api_key cascade above). Clicking 'Start over' on the QR-pair error card retried createSession, got 401, wiped the token, next render = signed-out. Narrowed the token-purge to JWT-specific machine codes ('unauthorized' and 'session_expired') so a stale tenant config doesn't kick the user out of the whole console. Test sweep: 276/276 backend (jest), 31/31 dashboard (vitest).
1 parent 0224be4 commit 6719eb8

3 files changed

Lines changed: 158 additions & 255 deletions

File tree

dashboard/src/lib/api.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,26 @@ async function request<T>(path: string, opts: RequestOpts = {}): Promise<T> {
110110
const code = errBody.error ?? `http_${res.status}`;
111111
const msg = errBody.message ?? res.statusText ?? 'Request failed';
112112

113-
// 401 from the console means our token is gone or expired — purge it
114-
// so the next render kicks the user to /login.
115-
if (res.status === 401 && path.startsWith('/api/console/')) {
113+
// 401 from the console can mean two different things:
114+
// 1. The console JWT itself is gone/expired (`unauthorized` /
115+
// `session_expired` from requireConsoleAuth). The token is
116+
// dead; purge it so RequireAuth kicks the user to /login.
117+
// 2. The route authenticated the JWT fine, but a downstream
118+
// auth check failed (e.g. the proof-pairing proxy not having
119+
// a tenant API key with the right scope → `missing_api_key`).
120+
// The JWT is still good; clearing the token here would log
121+
// the user out for a tenant-side configuration issue, which
122+
// is exactly the "click Start over → signed out" bug we hit
123+
// on the W3 QR-pair page.
124+
//
125+
// Narrow the purge to the JWT-specific machine codes that
126+
// requireConsoleAuth in src/routes/console.ts emits.
127+
const JWT_DEAD_CODES = new Set(['unauthorized', 'session_expired']);
128+
if (
129+
res.status === 401
130+
&& path.startsWith('/api/console/')
131+
&& JWT_DEAD_CODES.has(code)
132+
) {
116133
setToken(null);
117134
}
118135

0 commit comments

Comments
 (0)