Skip to content

Serve AI Workspace under the /ai-workspace path prefix - #3119

Merged
Induwara04 merged 6 commits into
wso2:mainfrom
Thushani-Jayasekera:fallback-remove
Aug 4, 2026
Merged

Serve AI Workspace under the /ai-workspace path prefix#3119
Induwara04 merged 6 commits into
wso2:mainfrom
Thushani-Jayasekera:fallback-remove

Conversation

@Thushani-Jayasekera

@Thushani-Jayasekera Thushani-Jayasekera commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Serve AI Workspace under the /ai-workspace path prefix

Purpose

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.js and
the same-origin proxy — beneath a single /ai-workspace prefix, so:

  • an ingress can route one prefix to this service with no path rewriting, and
  • an all-in-one deployment can put several portals behind one host and port without
    their routes colliding.

Approach

The prefix is a fixed contract between the BFF and the SPA it ships, not a deployment
knob: index.html references its assets by absolute path, so a bundle built for one
prefix and served under another would 404 on every asset. Each side declares it once:

  • bff/internal/paths/paths.goconst Base = "/ai-workspace" (mounts every server route)
  • src/paths.tsBASE_PATH = '/ai-workspace', which vite.config.ts imports for
    Vite's base, so the prefix baked into the bundle and the prefix the app code composes
    URLs 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/paths package, imported by both server (which routes on
them) and config (which no longer has them as keys):

Constant Value Was
paths.Base /ai-workspace config.BasePath
paths.Proxy /proxy control_plane.proxy_prefix config key
paths.PlatformAPI /api/v0.9 inline literal in runtime_config.go / composite_handlers.go
paths.PortalAPI /api/portal/v0.9 control_plane.portal_base_path config key

The two former config keys are removed as configuration, not just defaulted:
ControlPlaneConfig.ProxyPrefix/.PortalBasePath and their defaults and trailing-slash
normalization are gone, along with proxy_prefix/portal_base_path in
configs/config-template.toml and proxyPrefix/portalBasePath in the Helm chart's
values.yaml + configmap.yaml. [ai_workspace.control_plane] is now just where the
upstream is and how its certificate is trusted.

SPA — new src/paths.ts, holding BASE_PATH, PLATFORM_API_BASE_URL,
PORTAL_API_BASE_URL and BFF_API_BASE_URL, all composed from BASE_PATH. They moved
out of config.env.ts, which is now purely the runtime-configurable values (~60 files had
their 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_URL were getEnvOrDefault(...) over
    APIP_AIW_*_BASE_URL. An override there could only ever break the app: the SPA holds
    no 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.js at all — there is no longer a
    runtime value that can disagree with the prefix the server actually strips.
  • BASE_PATH was derived from import.meta.env.BASE_URL. That guaranteed agreement with
    the bundle, but only by making Vite's config the source of truth; the dependency now
    points the other way (vite.config.ts imports BASE_PATH), which keeps the same
    guarantee while putting the value with the other prefixes.

BFF

  • Routes (server/routes.go) — every route registered via a new s.path() helper.
    The SPA subtree is wrapped in http.StripPrefix so file lookups still resolve against
    the static dir.
  • Health stays at the origin root. /healthz is registered both at the root and
    under the prefix: container HEALTHCHECK and Kubernetes probes dial the pod directly,
    bypassing the ingress that adds the prefix.
  • Root convenience redirect. 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 ServeMux already redirects the bare prefix to the
    subtree.)
  • Proxy (server/server.go) — strips base path plus proxy prefix before forwarding;
    the Platform API knows about neither segment.
  • Runtime config (config/runtime_config.go) — the SPA's API base URLs are absolute
    paths, 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 no
    change; only what the router doesn't own does: absolute fetch() paths to the BFF,
    window.location assignments, and OIDC redirect URIs.
  • The runtime-config.js script tag is now injected by a Vite plugin rather than
    written inline in index.html. It points at a path the BFF generates per request, not
    a file on disk, and Vite's index-HTML URL rewriting handles such a src inconsistently
    (build leaves it unprefixed, dev double-prefixes it). Injecting post-transform gives one
    correct URL in both modes.
  • Dev-server proxy entries moved under the prefix — forwarded prefix and all, no rewriting
    on either side.
  • vite build verified: dist/index.html references /ai-workspace/assets/... and
    /ai-workspace/runtime-config.js; tsc --noEmit reports the same pre-existing errors as
    before the change and no new ones.

Renamed: /api/bff/*/api/*

BFF_COMPOSITE_BASE_URL (/api/bff) becomes BFF_API_BASE_URL (<base>/api), and the
two composite creates move to POST <base>/api/llm-providers and
POST <base>/api/mcp-proxies. That the BFF orchestrates these rather than forwarding them
is an implementation detail the browser shouldn't read off a URL; they are now named for
their resource like every other route in the /api namespace.

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 Path is now scoped to the base path (server/cookies.go), so a
host 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 an
expiry written for one Path creates a separate cookie instead of removing one at
another Path. A pre-upgrade cookie left at / would keep matching every request, so
/api/session would report the stale session as authenticated while every proxied call
401'd on its no-longer-verifiable token — a login loop no logout could break.
clearSessionCookie now expires the cookie at both the current base-path-scoped Path
and the legacy root Path. Covered by a named regression test.

2. sanitizeReturn is tightened, not just prefixed (server/handlers.go). Return
targets 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 backslash
payloads (/\evil.com) that browsers normalize into protocol-relative URLs. The rejected
value is never echoed back. Also: the OIDC error code in the failure redirect is now
url.QueryEscaped.

Tests

New/updated, all passing (go test ./... in portals/ai-workspace/bff):

File Covers
server/routes_test.go (new) app/assets/runtime-config reachable under the prefix; health at both paths; root + bare-prefix redirects; unprefixed paths stay 404; path traversal contained behind StripPrefix (incl. encoded %2e%2e%2f and a substring-prefix payload)
server/cookies_test.go (new) session cookie scoped to base path and still HttpOnly/Secure; legacy-root-path expiry regression test
server/middleware_test.go sanitizeReturn containment cases above
proxy/reverse_proxy_test.go base path + proxy prefix both stripped upstream
config/config_test.go runtime-config.js omits the API base URLs (the SPA composes them itself) and still carries the auth mode

Also updated: PR-check workflow readiness probe, Cypress baseUrl + Makefile/
package.json E2E targets (specs keep using prefix-free relative paths), Helm chart
values (two keys dropped — helm template re-rendered to confirm the config map is still
valid), config-template.toml OIDC URLs, and the README/QUICKSTART/distribution
/production docs.

Breaking changes / upgrade notes

  • Bookmarks and links to the origin root now land on a redirect; anything deeper at
    the root 404s.
  • OIDC application registration must be updated on the IDP — the callback and
    post-logout URLs both move under the prefix:
    • https://<host>/ai-workspace/api/auth/callback
    • https://<host>/ai-workspace/login
  • Ingress should route path: /ai-workspace, pathType: Prefix with no rewriting.
    Probes keep using /healthz at the root.
  • proxy_prefix and portal_base_path are no longer read. Leaving them in an existing
    config.toml is harmless (unknown keys are ignored), but they no longer do anything —
    delete them. Same for controlPlane.proxyPrefix/portalBasePath in Helm values, and for
    the APIP_AIW_PLATFORM_API_BASE_URL / APIP_AIW_PORTAL_API_BASE_URL environment
    variables, which are no longer read at build time or runtime.
  • Existing sessions from a pre-upgrade cookie at / 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:

  1. portals/ai-workspace/VERSION: 1.1.0-SNAPSHOT1.0.0-SNAPSHOT (a downgrade —
    likely an artifact of rebasing across the 1.0.0 release commits).
  2. configs/config.toml: platform_gateway_versions bumped v1.2.0-rcv1.2.0-rc2.

There is also a stray added blank line in configs/config-template.toml.

…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.
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The AI Workspace now runs under the fixed /ai-workspace path. The BFF, SPA, proxy, authentication routes, cookies, health checks, development tooling, deployment guidance, and readiness checks use the new routing contract.

Changes

AI Workspace base-path integration

Layer / File(s) Summary
Base-path contracts and URL generation
portals/ai-workspace/bff/internal/config/*, portals/ai-workspace/bff/internal/paths/*, portals/ai-workspace/src/paths.ts, portals/ai-workspace/vite.config.ts
The BFF and SPA define fixed path constants. Runtime, API, OIDC, and proxy URLs use /ai-workspace. Configurable proxy and portal base-path settings were removed.
BFF routing, proxy, and redirects
portals/ai-workspace/bff/internal/server/*, portals/ai-workspace/bff/main.go
Application routes, static files, proxy forwarding, redirects, cookies, and portal URLs use the base path. Root and prefixed health routes remain available.
SPA authentication and API integration
portals/ai-workspace/src/auth/*, portals/ai-workspace/src/contexts/*, portals/ai-workspace/src/main.tsx, portals/ai-workspace/src/apis/*, portals/ai-workspace/src/config.env.ts
The router, authentication flows, logout handling, composite API calls, and Platform API calls use base-path-aware URLs and centralized path constants.
Routing validation and operating configuration
.github/workflows/ai-workspace-pr-check.yml, kubernetes/helm/ai-workspace-ui-helm-chart/*, portals/ai-workspace/*README.md, portals/ai-workspace/cypress/*, portals/ai-workspace/docker-compose.yaml, portals/ai-workspace/configs/*
Tests cover routing, proxy stripping, cookies, redirects, and traversal. Documentation, Cypress defaults, OIDC examples, readiness checks, image versions, and gateway metadata use the updated paths.

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
Loading

Possibly related PRs

Suggested reviewers: krishanx92, lasanthas, virajsalaka

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 80.65% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title directly and clearly summarizes the main change: moving the AI Workspace application to serve under the /ai-workspace path prefix, which is the primary objective across all modified files.
Description check ✅ Passed The description provides comprehensive coverage of purpose, goals, approach, implementation details, security considerations, tests, and upgrade notes. It follows the repository template structure with all critical sections present and detailed.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between e498dd0 and 3863e84.

📒 Files selected for processing (36)
  • .github/workflows/ai-workspace-pr-check.yml
  • kubernetes/helm/ai-workspace-ui-helm-chart/values.yaml
  • portals/ai-workspace/Makefile
  • portals/ai-workspace/QUICKSTART.md
  • portals/ai-workspace/README.md
  • portals/ai-workspace/VERSION
  • portals/ai-workspace/bff/internal/config/config.go
  • portals/ai-workspace/bff/internal/config/config_test.go
  • portals/ai-workspace/bff/internal/config/runtime_config.go
  • portals/ai-workspace/bff/internal/proxy/reverse_proxy_test.go
  • portals/ai-workspace/bff/internal/server/composite_handlers.go
  • portals/ai-workspace/bff/internal/server/composite_handlers_test.go
  • portals/ai-workspace/bff/internal/server/cookies.go
  • portals/ai-workspace/bff/internal/server/cookies_test.go
  • portals/ai-workspace/bff/internal/server/handlers.go
  • portals/ai-workspace/bff/internal/server/middleware_test.go
  • portals/ai-workspace/bff/internal/server/routes.go
  • portals/ai-workspace/bff/internal/server/routes_test.go
  • portals/ai-workspace/bff/internal/server/server.go
  • portals/ai-workspace/bff/main.go
  • portals/ai-workspace/configs/config-template.toml
  • portals/ai-workspace/configs/config.toml
  • portals/ai-workspace/cypress.config.js
  • portals/ai-workspace/distribution/README.md
  • portals/ai-workspace/index.html
  • portals/ai-workspace/package.json
  • portals/ai-workspace/production/README.md
  • portals/ai-workspace/src/apis/MCP/mcpProxiesApis.ts
  • portals/ai-workspace/src/apis/llmProviderApis.ts
  • portals/ai-workspace/src/auth/logout.ts
  • portals/ai-workspace/src/config.env.ts
  • portals/ai-workspace/src/contexts/BFFAuthProvider.tsx
  • portals/ai-workspace/src/contexts/BasicAuthProvider.tsx
  • portals/ai-workspace/src/contexts/OIDCAppAuthProvider.tsx
  • portals/ai-workspace/src/main.tsx
  • portals/ai-workspace/vite.config.ts

Comment thread portals/ai-workspace/src/main.tsx Outdated
@Thushani-Jayasekera Thushani-Jayasekera changed the title Update AI Workspace to serve under the /ai-workspace path prefix Serve AI Workspace under the /ai-workspace path prefix Aug 4, 2026
…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.
coderabbitai[bot]
coderabbitai Bot previously approved these changes Aug 4, 2026
…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.
@Induwara04
Induwara04 merged commit 8d7c3bb into wso2:main Aug 4, 2026
13 checks passed
Piumal1999 added a commit to Piumal1999/api-platform that referenced this pull request Aug 4, 2026
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants