From d0011b822c67c6af0d461a8e52c2cfedc8847c88 Mon Sep 17 00:00:00 2001 From: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Date: Fri, 5 Jun 2026 20:35:52 +0800 Subject: [PATCH] fix(marketplace): send Bearer token on cloud-connection install/probe MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The in-environment marketplace install and installed-state probe call the same-origin `/api/v1/cloud-connection/install` and `/installation` routes with raw `fetch()` + `credentials: 'include'` and no Authorization header. objectui authenticates with a Bearer token (better-auth, stored in localStorage and normally injected by `createAuthenticatedFetch`). On a tenant runtime the env session cookie is not reliably presented after platform SSO, so cookie-only auth makes the server's `resolveEnvSession` find no session and return 401 `unauthenticated` / "Sign in to this environment." even though the user is signed in. Inject the Bearer token (which the server's `getSession` accepts — verified: the same route returns 200 when given the session token as a Bearer) on both cloud-connection calls. `credentials: 'include'` is kept so the cookie still rides along when present. Co-Authored-By: Claude Opus 4.8 --- .../src/console/marketplace/marketplaceApi.ts | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/packages/app-shell/src/console/marketplace/marketplaceApi.ts b/packages/app-shell/src/console/marketplace/marketplaceApi.ts index e920fab28..e7ad9c469 100644 --- a/packages/app-shell/src/console/marketplace/marketplaceApi.ts +++ b/packages/app-shell/src/console/marketplace/marketplaceApi.ts @@ -14,10 +14,30 @@ */ import { getCloudBase } from '../../runtime-config'; +import { TokenStorage } from '@object-ui/auth'; const SERVER_URL = (import.meta.env.VITE_SERVER_URL || '').replace(/\/$/, ''); const API_BASE = `${SERVER_URL}/api/v1/marketplace`; +/** + * Attach the Bearer token to same-origin `/api/v1/cloud-connection/*` calls. + * + * objectui authenticates with a Bearer token (better-auth, stored in + * localStorage by `@object-ui/auth` and normally injected by the app's + * `createAuthenticatedFetch` wrapper). These marketplace routes use raw + * `fetch()` and so bypass that wrapper — relying on `credentials: 'include'` + * (the session cookie) alone is NOT enough on a tenant runtime: after + * platform SSO the env's session cookie is not reliably presented, so the + * cloud-connection route's `resolveEnvSession` finds no session and returns + * 401 "Sign in to this environment." even though the user is signed in. + * Injecting the Bearer (which the server's `getSession` accepts) fixes it. + * `credentials: 'include'` is kept so the cookie still rides along when set. + */ +function withEnvAuth(headers: Record): Record { + const token = TokenStorage.get(); + return token ? { ...headers, Authorization: `Bearer ${token}` } : headers; +} + /** * Per-locale overrides for translatable package fields. Mirrors * `PackageTranslation` from @objectstack/spec/cloud — duplicated here @@ -188,7 +208,7 @@ export async function installPackage(input: { const res = await fetch(`/api/v1/cloud-connection/install`, { method: 'POST', credentials: 'include', - headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, + headers: withEnvAuth({ 'Accept': 'application/json', 'Content-Type': 'application/json' }), body: JSON.stringify({ package_id: input.packageId, seed_sample_data: !!input.seedSampleData, @@ -380,7 +400,7 @@ export async function getCloudInstallationInfo( try { const res = await fetch( `/api/v1/cloud-connection/installation?package_id=${encodeURIComponent(packageId)}`, - { credentials: 'include', headers: { 'Accept': 'application/json' } }, + { credentials: 'include', headers: withEnvAuth({ 'Accept': 'application/json' }) }, ); if (!res.ok) return null; const payload: any = await res.json().catch(() => ({}));