From 347e1ddad182d62ee1e50c685d9dea612e4bed4c Mon Sep 17 00:00:00 2001 From: IvanHunters Date: Tue, 2 Jun 2026 21:08:42 +0300 Subject: [PATCH] fix(k8s-client): redirect to oauth2-proxy on 401 instead of failing silently When the kc-access cookie expires, k8s API calls come back with 401 (after the matching cozystack PR makes oauth2-proxy answer 401 instead of redirecting). The previous behaviour left the SPA stuck on a stale page until the user cleared the cache. K8sClient now treats 401 as an auth boundary: it fires a one-shot top-level navigation to `/oauth2/start?rd=`, which is the oauth2-proxy convention for re-authenticating and bouncing the user back. The handler is overridable through `K8sClientConfig.onUnauthorized` for non-oauth2-proxy deployments and is a no-op outside the browser. Signed-off-by: IvanHunters --- packages/k8s-client/src/client.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/k8s-client/src/client.ts b/packages/k8s-client/src/client.ts index 4639d9f..79f8ce7 100644 --- a/packages/k8s-client/src/client.ts +++ b/packages/k8s-client/src/client.ts @@ -1,6 +1,13 @@ export interface K8sClientConfig { baseUrl?: string getToken?: () => Promise + onUnauthorized?: () => void +} + +function defaultOnUnauthorized(): void { + if (typeof window === "undefined") return + const rd = window.location.pathname + window.location.search + window.location.hash + window.location.assign(`/oauth2/start?rd=${encodeURIComponent(rd)}`) } export class K8sApiError extends Error { @@ -24,10 +31,19 @@ export class K8sApiError extends Error { export class K8sClient { private baseUrl: string private getToken?: () => Promise + private onUnauthorized: () => void + private unauthorizedHandled = false constructor(config: K8sClientConfig = {}) { this.baseUrl = config.baseUrl ?? "" this.getToken = config.getToken + this.onUnauthorized = config.onUnauthorized ?? defaultOnUnauthorized + } + + private handleUnauthorized(): void { + if (this.unauthorizedHandled) return + this.unauthorizedHandled = true + this.onUnauthorized() } private async request(path: string, init?: RequestInit): Promise { @@ -63,6 +79,7 @@ export class K8sClient { } catch { body = `Server returned ${res.status} ${res.statusText}` } + if (res.status === 401) this.handleUnauthorized() throw new K8sApiError(res.status, body) } @@ -256,6 +273,7 @@ export class K8sClient { }) if (!res.ok) { + if (res.status === 401) this.handleUnauthorized() throw new K8sApiError( res.status, await res.json().catch(() => res.statusText),