Repository navigation
fix(dashboard): unbreak CORS on expired session for k8s API calls - #2788
Conversation
… paths Two related issues caused the dashboard SPA to break with CORS errors after the kc-access cookie expired: 1. KeycloakClient for dashboard had no webOrigins set, so the operator never touched the field in Keycloak after initial creation. After any rootUrl change the existing webOrigins value became stale and blocked CORS from the SPA. Setting "+" tells Keycloak to derive allowed origins from redirectUris. 2. oauth2-proxy returned 302 -> Keycloak /auth for unauthenticated XHR/fetch on /api and /apis. The login page does not emit Access-Control-Allow-Origin, so the SPA's fetch failed with CORS. --api-route makes oauth2-proxy answer 401 without redirect, so the SPA can handle the re-auth itself via top-level navigation. Signed-off-by: Ivan Okhotnikov <[email protected]>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughThis PR updates dashboard authentication configuration across two Kubernetes templates. The Gatekeeper deployment adds API routing rules to forward requests under ChangesDashboard Authentication Configuration
Estimated code review effort🎯 1 (Trivial) | ⏱️ ~3 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request addresses CORS-related failures in the dashboard SPA that occur when the session cookie expires. By adjusting the Keycloak client configuration and modifying how the proxy handles unauthenticated API requests, the system now correctly signals the need for re-authentication to the frontend without triggering browser-level CORS blocks. Highlights
New Features🧠 You can now enable Memory (public preview) to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on Gemini (@gemini-code-assist) comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request configures additional API routes (/api and /apis) for the gatekeeper template and adds wildcard web origins (+) to the Keycloak client template. The reviewer suggests also adding /openapi and /version to the gatekeeper's --api-route list to prevent potential CORS or redirect issues on session expiration.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| - --api-route=^/api(/|$) | ||
| - --api-route=^/apis(/|$) |
There was a problem hiding this comment.
In addition to /api and /apis, Kubernetes frontend applications and SPAs often query other root-level API endpoints such as /version (to retrieve cluster version info) and /openapi (to fetch OpenAPI schemas for resource validation). To prevent potential CORS/302 redirect issues on these endpoints when the session expires, it is recommended to add them to the --api-route list as well.
- --api-route=^/api(/|$)
- --api-route=^/apis(/|$)
- --api-route=^/openapi(/|$)
- --api-route=^/version(/|$)
Aleksei Sviridkin (lexfrei)
left a comment
There was a problem hiding this comment.
LGTM — the CORS fix is correct: marking /api and /apis as oauth2-proxy api-routes returns 401 (not a cross-origin 302) on an expired session, and webOrigins: "+" scopes CORS headers to the client's redirect-URI origins. Two non-blocking coverage gaps.
Business context: when a dashboard session expires, k8s API calls (XHR to /api / /apis) hit oauth2-proxy's default 302-to-keycloak, which the browser blocks as a cross-origin redirect — so the SPA breaks instead of re-authenticating. This returns 401 for those paths and lets keycloak emit CORS headers.
Verified:
--api-route=^/api(/|$)/^/apis(/|$): oauth2-proxy returns 401 instead of a 302 for matched paths on auth failure, so the SPA's fetch sees a clean 401 rather than a blocked cross-origin redirect to keycloak. Correct.webOrigins: ["+"]: theKeycloakClientCRD documents+as "permit all origins of Valid Redirect URIs" (explicitly not the*wildcard). The dashboard client'sredirectUrisresolve to originhttps://dashboard.<host>, so+scopes CORS to exactly that origin (plus anyextra-keycloak-redirect-uri-for-dashboard). Consistent with the existingpost.logout.redirect.uris: "+"on the same client — safe and idiomatic.
Non-blocking follow-ups
- Coverage of other root paths. The fix covers
/apiand/apis. oauth2-proxy fronts the wholecozy-dashboard-consoleupstream, so if the SPA also calls/versionor/openapicross-path on an expired session, those still 302 → the same CORS break (raised by the bot review). The SPA source isn't in this repo, so I can't confirm it calls them — worth either extending--api-routeto^/version(/|$)and^/openapi(/|$), or confirming the console never serves those to the SPA on an expired session. - No test assertion for the new args.
tests/gatekeeper_test.yamlpins the auth-proxy container's probes/ports but not its args, so the new--api-routeflags aren't covered. Given that file's own stated goal ("pin both branches so structural drift is a test failure"), an assertion that the OIDC-enabled branch carries--api-route=^/api(/|$)and^/apis(/|$)would guard against a future refactor silently dropping them and reintroducing the CORS break.
…lently When the kc-access cookie expires, k8s API calls return 401 and the SPA was left stuck on a stale page until the user manually cleared the cache. K8sClient now treats 401 as an auth boundary: a one-shot top-level navigation to /oauth2/start?rd=<current-url> (the oauth2-proxy re-auth convention). Overridable via K8sClientConfig.onUnauthorized; no-op outside the browser. A unauthorizedHandled latch prevents repeated redirects on concurrent 401s. Re-applied from cozystack/cozystack-ui#34 onto the in-tree console after the UI was vendored (#2963); the source repo is being archived. Related: #2788. A unit regression test is a follow-up — the k8s-client workspace package has no test harness yet. Co-authored-by: IvanHunters <[email protected]> Assisted-By: Claude <[email protected]> Signed-off-by: Myasnikov Daniil <[email protected]>
|
Successfully created backport PR for |
…lently When the kc-access cookie expires, k8s API calls return 401 and the SPA was left stuck on a stale page until the user manually cleared the cache. K8sClient now treats 401 as an auth boundary: a one-shot top-level navigation to /oauth2/start?rd=<current-url> (the oauth2-proxy re-auth convention). Overridable via K8sClientConfig.onUnauthorized; no-op outside the browser. A unauthorizedHandled latch prevents repeated redirects on concurrent 401s. Re-applied from cozystack/cozystack-ui#34 onto the in-tree console after the UI was vendored (#2963); the source repo is being archived. Related: #2788. A unit regression test is a follow-up — the k8s-client workspace package has no test harness yet. Co-authored-by: IvanHunters <[email protected]> Assisted-By: Claude <[email protected]> Signed-off-by: Myasnikov Daniil <[email protected]>
…lently When the kc-access cookie expires, k8s API calls return 401 and the SPA was left stuck on a stale page until the user manually cleared the cache. K8sClient now treats 401 as an auth boundary: a one-shot top-level navigation to /oauth2/start?rd=<current-url> (the oauth2-proxy re-auth convention). Overridable via K8sClientConfig.onUnauthorized; no-op outside the browser. A unauthorizedHandled latch prevents repeated redirects on concurrent 401s. Re-applied from cozystack/cozystack-ui#34 onto the in-tree console after the UI was vendored (#2963); the source repo is being archived. Related: #2788. A unit regression test is a follow-up — the k8s-client workspace package has no test harness yet. Co-authored-by: IvanHunters <[email protected]> Assisted-by: LLM Signed-off-by: Myasnikov Daniil <[email protected]>
…lently When the kc-access cookie expires, k8s API calls return 401 and the SPA stays on a stale page until the user clears the cache by hand. K8sClient now treats 401 as an auth boundary and makes one top-level navigation into the oauth2-proxy sign-in flow, passing the current location as rd so the user comes back to the same page. The handler can be replaced through K8sClientConfig.onUnauthorized and does nothing outside the browser. The unauthorizedHandled latch keeps concurrent 401s from redirecting more than once. Re-applied from cozystack/cozystack-ui#34 onto the in-tree console after the UI was vendored (#2963), since the source repo is being archived. Related: #2788. Co-authored-by: IvanHunters <[email protected]> Assisted-by: LLM Signed-off-by: Myasnikov Daniil <[email protected]>
…3115) ## What this PR does When the dashboard session expires, the console now sends the user back to sign-in instead of sitting on a stale page. This re-applies cozystack/cozystack-ui#34 by @IvanHunters onto the in-tree console, since the UI was vendored into the monorepo (#2963) and the standalone repo is being archived. It is part of the cozystack-ui retirement tracked in #3097. When the `kc-access` cookie expires, k8s API calls return 401. `K8sClient` now treats a 401 as an auth boundary and makes one top-level navigation to `/oauth2/sign_in?rd=<current-url>`. That path is the sign-in entry both gatekeeper modes serve. With OIDC enabled, oauth2-proxy goes straight to the provider (`--skip-provider-button`) and returns the user to `rd`. Without OIDC, token-proxy renders its token form there, but it does not honour `rd` yet, so the user lands on `/` after sign-in. The handler can be replaced through `K8sClientConfig.onUnauthorized` and does nothing outside the browser. A latch keeps concurrent 401s from redirecting more than once, and any 2xx from either fetch site re-arms it, so a handler that re-authenticates in place still sees the next expiry. Related: #2788. ### Verification `client.unauthorized.test.ts` covers the hook on the request and watch paths, the latch across concurrent 401s, the re-arm after a success, and the default handler's target and relative `rd`. `pnpm typecheck` and the console test suite pass. Original PR: cozystack/cozystack-ui#34, credited via `Co-authored-by`. ### Screenshots Not a UI change. ### Downstream repositories - [x] No downstream repository is affected by this change - [ ] [cozystack/website](https://github.com/cozystack/website) - follow-up: - [ ] [cozystack/terraform-provider-cozystack](https://github.com/cozystack/terraform-provider-cozystack) - follow-up: - [ ] [cozystack/ansible-cozystack](https://github.com/cozystack/ansible-cozystack) - follow-up: - [ ] [cozystack/ccp](https://github.com/cozystack/ccp) - follow-up: - [ ] [cozystack/talm](https://github.com/cozystack/talm) - follow-up: - [ ] [cozystack/cozyhr](https://github.com/cozystack/cozyhr) - follow-up: - [ ] [cozystack/cozy-proxy](https://github.com/cozystack/cozy-proxy) - follow-up: - [ ] [cozystack/cozystack-telemetry-server](https://github.com/cozystack/cozystack-telemetry-server) - follow-up: - [ ] [cozystack/external-apps-example](https://github.com/cozystack/external-apps-example) - follow-up: - [ ] [cozystack/examples](https://github.com/cozystack/examples) - follow-up: - [ ] [cozystack/community](https://github.com/cozystack/community) - follow-up: ### Release note ```release-note fix(dashboard): on a 401 (expired session) the console now sends the user to sign in again instead of silently showing a stale page. With OIDC enabled the user returns to the page they were on; without OIDC they land on the start page after entering a token. ```
What this PR does
Fix two related issues that cause the dashboard SPA to break with CORS errors once the
kc-accesscookie expires.KeycloakClientfor dashboard had nowebOriginsset, so the operator did not touch the field in Keycloak after initial creation. After anyrootUrlchange the existingwebOriginsvalue got stuck at an old hostname and blocked CORS from the SPA. Setting it to+makes Keycloak derive the allowed origins fromredirectUris.oauth2-proxyanswered302(redirect to Keycloak/auth) for unauthenticated XHR/fetch on/apiand/apis. The login page does not emitAccess-Control-Allow-Origin, so the browser blocked the fetch with a CORS error and the SPA had no way to recover. Adding--api-routemakesoauth2-proxyanswer401without a redirect for these paths, so the SPA can handle re-auth itself via a top-level navigation.Release note
Summary by CodeRabbit
Chores