Serve AI Workspace under the /ai-workspace path prefix - #3119
Conversation
…usting configurations, routes, and documentation accordingly. This includes changes to API endpoints, runtime configuration, and session handling to ensure compatibility with the new base path.
📝 WalkthroughWalkthroughThe AI Workspace now runs under the fixed ChangesAI Workspace base-path integration
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Browser
participant ViteSPA
participant BFF
participant PlatformAPI
Browser->>ViteSPA: Open /ai-workspace
ViteSPA->>BFF: Request /ai-workspace/runtime-config.js
BFF-->>ViteSPA: Return runtime configuration
ViteSPA->>BFF: Request /ai-workspace/proxy/api/v0.9/organizations
BFF->>PlatformAPI: Forward /api/v0.9/organizations
PlatformAPI-->>BFF: Return API response
BFF-->>ViteSPA: Return API response
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ 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 |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@portals/ai-workspace/src/main.tsx`:
- Around line 74-80: Update the window.location destructuring in the redirect
logic to include hash, then append it to the non-login target alongside pathname
and search. Keep the existing BASE_PATH handling and login/signin fallback
unchanged.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: aaa5520b-1ab0-4376-b517-81b17b304c00
📒 Files selected for processing (36)
.github/workflows/ai-workspace-pr-check.ymlkubernetes/helm/ai-workspace-ui-helm-chart/values.yamlportals/ai-workspace/Makefileportals/ai-workspace/QUICKSTART.mdportals/ai-workspace/README.mdportals/ai-workspace/VERSIONportals/ai-workspace/bff/internal/config/config.goportals/ai-workspace/bff/internal/config/config_test.goportals/ai-workspace/bff/internal/config/runtime_config.goportals/ai-workspace/bff/internal/proxy/reverse_proxy_test.goportals/ai-workspace/bff/internal/server/composite_handlers.goportals/ai-workspace/bff/internal/server/composite_handlers_test.goportals/ai-workspace/bff/internal/server/cookies.goportals/ai-workspace/bff/internal/server/cookies_test.goportals/ai-workspace/bff/internal/server/handlers.goportals/ai-workspace/bff/internal/server/middleware_test.goportals/ai-workspace/bff/internal/server/routes.goportals/ai-workspace/bff/internal/server/routes_test.goportals/ai-workspace/bff/internal/server/server.goportals/ai-workspace/bff/main.goportals/ai-workspace/configs/config-template.tomlportals/ai-workspace/configs/config.tomlportals/ai-workspace/cypress.config.jsportals/ai-workspace/distribution/README.mdportals/ai-workspace/index.htmlportals/ai-workspace/package.jsonportals/ai-workspace/production/README.mdportals/ai-workspace/src/apis/MCP/mcpProxiesApis.tsportals/ai-workspace/src/apis/llmProviderApis.tsportals/ai-workspace/src/auth/logout.tsportals/ai-workspace/src/config.env.tsportals/ai-workspace/src/contexts/BFFAuthProvider.tsxportals/ai-workspace/src/contexts/BasicAuthProvider.tsxportals/ai-workspace/src/contexts/OIDCAppAuthProvider.tsxportals/ai-workspace/src/main.tsxportals/ai-workspace/vite.config.ts
/ai-workspace path prefix
…o a dedicated paths package. Removed deprecated configuration options and updated related components to ensure consistent usage of the new path constants across the application. This change enhances maintainability and clarity in routing and API interactions.
… target. This ensures that the full URL, including any fragment identifiers, is preserved during navigation after successful login.
…Path and appPathPattern functions. This change improves URL assertions across multiple test files, ensuring consistency with the application's base path configuration.
….16.0-SNAPSHOT and ai-workspace to version 1.0.0-SNAPSHOT, ensuring compatibility with the latest development changes.
Both follow the precedent set for AI Workspace in wso2#3119. Session and XSRF cookies moved from Path=/ to Path=/api-portal, but a browser keys a cookie by (name, domain, path), so an expiry written for one path creates a separate cookie instead of removing one at another. A pre-upgrade cookie at / would keep being sent with nothing able to remove it — and express-session emits no Set-Cookie at all once req.session is destroyed, so it never expires even its own. Expire both names at both paths wherever a session is torn down. Health is now served at /health and ${BASE_PATH}/health: probes dial the pod directly with no ingress to add the prefix, while an ingress-routed check only ever sees the prefixed path. Also corrects the rationale comment on the BASE_PATH-scoped session mount. It described a cookie-clobbering bug that express-session's own pathname-mismatch guard already prevents; the real defect was that req.session is absent for root-path requests, so passport.session() errored and every unmatched root path (including the /favicon.ico browsers fetch unprompted) answered 500 instead of 404. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Serve AI Workspace under the
/ai-workspacepath prefixPurpose
AI Workspace was served at the origin root, so it needed a host (or at least a port) of
its own. This moves the whole app — SPA, assets, auth endpoints,
runtime-config.jsandthe same-origin proxy — beneath a single
/ai-workspaceprefix, so:their routes colliding.
Approach
The prefix is a fixed contract between the BFF and the SPA it ships, not a deployment
knob:
index.htmlreferences its assets by absolute path, so a bundle built for oneprefix and served under another would 404 on every asset. Each side declares it once:
bff/internal/paths/paths.go—const Base = "/ai-workspace"(mounts every server route)src/paths.ts—BASE_PATH = '/ai-workspace', whichvite.config.tsimports forVite's
base, so the prefix baked into the bundle and the prefix the app code composesURLs from are one value rather than two that could drift.
Every URL prefix is a constant — none are config keys or runtime-config values
The same reasoning applies to every other prefix the app is wired with, so each side now
keeps its whole set in one file, mirroring the other name for name.
BFF — new
bff/internal/pathspackage, imported by bothserver(which routes onthem) and
config(which no longer has them as keys):paths.Base/ai-workspaceconfig.BasePathpaths.Proxy/proxycontrol_plane.proxy_prefixconfig keypaths.PlatformAPI/api/v0.9runtime_config.go/composite_handlers.gopaths.PortalAPI/api/portal/v0.9control_plane.portal_base_pathconfig keyThe two former config keys are removed as configuration, not just defaulted:
ControlPlaneConfig.ProxyPrefix/.PortalBasePathand their defaults and trailing-slashnormalization are gone, along with
proxy_prefix/portal_base_pathinconfigs/config-template.tomlandproxyPrefix/portalBasePathin the Helm chart'svalues.yaml+configmap.yaml.[ai_workspace.control_plane]is now just where theupstream is and how its certificate is trusted.
SPA — new
src/paths.ts, holdingBASE_PATH,PLATFORM_API_BASE_URL,PORTAL_API_BASE_URLandBFF_API_BASE_URL, all composed fromBASE_PATH. They movedout of
config.env.ts, which is now purely the runtime-configurable values (~60 files hadtheir import line updated; nothing else changed at the call sites). Two of them stopped
being environment reads in the process:
PLATFORM_API_BASE_URL/PORTAL_API_BASE_URLweregetEnvOrDefault(...)overAPIP_AIW_*_BASE_URL. An override there could only ever break the app: the SPA holdsno token in this BFF-only auth flow, so pointing it anywhere but the BFF proxy loses the
session, and pointing it at a different prefix than the BFF strips loses the route. They
are now plain constants, the two env names are out of Vite's browser-safe allowlist, and
the BFF no longer emits them in
runtime-config.jsat all — there is no longer aruntime value that can disagree with the prefix the server actually strips.
BASE_PATHwas derived fromimport.meta.env.BASE_URL. That guaranteed agreement withthe bundle, but only by making Vite's config the source of truth; the dependency now
points the other way (
vite.config.tsimportsBASE_PATH), which keeps the sameguarantee while putting the value with the other prefixes.
BFF
server/routes.go) — every route registered via a news.path()helper.The SPA subtree is wrapped in
http.StripPrefixso file lookups still resolve againstthe static dir.
/healthzis registered both at the root andunder the prefix: container
HEALTHCHECKand Kubernetes probes dial the pod directly,bypassing the ingress that adds the prefix.
GET /{$}→/ai-workspace/. Only the exact root —every other unprefixed path stays a 404, since on a shared host it belongs to whatever
else the ingress routes. (Go's
ServeMuxalready redirects the bare prefix to thesubtree.)
server/server.go) — strips base path plus proxy prefix before forwarding;the Platform API knows about neither segment.
config/runtime_config.go) — the SPA's API base URLs are absolutepaths, so they are now emitted with the prefix (
/ai-workspace/proxy/api/v0.9).Frontend
BrowserRouter basename={BASE_PATH}— router paths (navigate(),<Link to>) need nochange; only what the router doesn't own does: absolute
fetch()paths to the BFF,window.locationassignments, and OIDC redirect URIs.runtime-config.jsscript tag is now injected by a Vite plugin rather thanwritten inline in
index.html. It points at a path the BFF generates per request, nota file on disk, and Vite's index-HTML URL rewriting handles such a
srcinconsistently(build leaves it unprefixed, dev double-prefixes it). Injecting post-transform gives one
correct URL in both modes.
on either side.
vite buildverified:dist/index.htmlreferences/ai-workspace/assets/...and/ai-workspace/runtime-config.js;tsc --noEmitreports the same pre-existing errors asbefore the change and no new ones.
Renamed:
/api/bff/*→/api/*BFF_COMPOSITE_BASE_URL(/api/bff) becomesBFF_API_BASE_URL(<base>/api), and thetwo composite creates move to
POST <base>/api/llm-providersandPOST <base>/api/mcp-proxies. That the BFF orchestrates these rather than forwarding themis an implementation detail the browser shouldn't read off a URL; they are now named for
their resource like every other route in the
/apinamespace.They remain registered outside the proxy prefix rather than intercepting the pass-through
path for the same resource — that would put the upstream API version in a browser-facing
route, where bumping it would silently stop matching and disable the compensation with no
error anywhere.
Security-relevant changes
Two things here are not mechanical prefixing and are worth reviewing closely.
1. Session cookie
Pathis now scoped to the base path (server/cookies.go), so ahost serving several portals under different prefixes never forwards this session to the
others. It stays
HttpOnly+Secure.This required a logout fix: a browser keys a cookie by
(name, domain, path), so anexpiry written for one
Pathcreates a separate cookie instead of removing one atanother
Path. A pre-upgrade cookie left at/would keep matching every request, so/api/sessionwould report the stale session as authenticated while every proxied call401'd on its no-longer-verifiable token — a login loop no logout could break.
clearSessionCookienow expires the cookie at both the current base-path-scopedPathand the legacy root
Path. Covered by a named regression test.2.
sanitizeReturnis tightened, not just prefixed (server/handlers.go). Returntargets must now land inside the app's own prefix; anything else falls back to the app
root. That structurally rules out targets belonging to another app on the same host
(
/api-portal/apis), lookalike prefixes (/ai-workspace-admin/users), and backslashpayloads (
/\evil.com) that browsers normalize into protocol-relative URLs. The rejectedvalue is never echoed back. Also: the OIDC error code in the failure redirect is now
url.QueryEscaped.Tests
New/updated, all passing (
go test ./...inportals/ai-workspace/bff):server/routes_test.go(new)StripPrefix(incl. encoded%2e%2e%2fand a substring-prefix payload)server/cookies_test.go(new)HttpOnly/Secure; legacy-root-path expiry regression testserver/middleware_test.gosanitizeReturncontainment cases aboveproxy/reverse_proxy_test.goconfig/config_test.goruntime-config.jsomits the API base URLs (the SPA composes them itself) and still carries the auth modeAlso updated: PR-check workflow readiness probe, Cypress
baseUrl+Makefile/package.jsonE2E targets (specs keep using prefix-free relative paths), Helm chartvalues (two keys dropped —
helm templatere-rendered to confirm the config map is stillvalid),
config-template.tomlOIDC URLs, and the README/QUICKSTART/distribution/production docs.
Breaking changes / upgrade notes
the root 404s.
post-logout URLs both move under the prefix:
https://<host>/ai-workspace/api/auth/callbackhttps://<host>/ai-workspace/loginpath: /ai-workspace, pathType: Prefixwith no rewriting.Probes keep using
/healthzat the root.proxy_prefixandportal_base_pathare no longer read. Leaving them in an existingconfig.tomlis harmless (unknown keys are ignored), but they no longer do anything —delete them. Same for
controlPlane.proxyPrefix/portalBasePathin Helm values, and forthe
APIP_AIW_PLATFORM_API_BASE_URL/APIP_AIW_PORTAL_API_BASE_URLenvironmentvariables, which are no longer read at build time or runtime.
/are cleaned up on the next logout(see above) — no manual cookie clearing needed.
Review notes / open questions
Two changes in this branch look unrelated to the base-path work — please confirm they are
intentional before merge:
portals/ai-workspace/VERSION:1.1.0-SNAPSHOT→1.0.0-SNAPSHOT(a downgrade —likely an artifact of rebasing across the 1.0.0 release commits).
configs/config.toml:platform_gateway_versionsbumpedv1.2.0-rc→v1.2.0-rc2.There is also a stray added blank line in
configs/config-template.toml.