diff --git a/docker/Dockerfile b/docker/Dockerfile index 657855a..33ed724 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -95,19 +95,16 @@ ENV NODE_OPTIONS="--max-old-space-size=$NODE_HEAP_MB --require /app/platform/scr ARG SITE_URL=http://cbk.localhost:3000 ENV SITE_URL=$SITE_URL -# @note space and portal sites use their apexes at runtime through proxy.ts; -# the remaining apex rewrites are generated at build time, so their runtime -# values must match the build -# @todo move the remaining apex host rewrites into the runtime proxy -# so one image digest serves any domain without a rebuild +# @note space, portal and app hosts are selected at runtime through proxy.ts; +# partner host rewrites are generated at build time, so their runtime values +# must match the build ARG APP_APEX= ENV APP_APEX=$APP_APEX ARG PARTNERS_APEX= ENV PARTNERS_APEX=$PARTNERS_APEX -# @note the app-shell hosts are rewrites of the same kind: the main shell at -# `cbk-apps.localhost`, the labs shell at `cbk-labs.localhost`. The runtime -# origins must match these too +# @note build defaults only; shell host routing uses the values supplied at +# server startup ARG APP_MAIN_ORIGIN=http://cbk-apps.localhost:3000 ENV APP_MAIN_ORIGIN=$APP_MAIN_ORIGIN ARG APP_LABS_ORIGIN=http://cbk-labs.localhost:3000 diff --git a/docker/distro/community/compose.yml b/docker/distro/community/compose.yml index 64022ba..ad54060 100644 --- a/docker/distro/community/compose.yml +++ b/docker/distro/community/compose.yml @@ -73,14 +73,12 @@ services: # needs an address it can reach instead (and TLS if the site has it) RELAY_PORT: 3001 RELAY_URL: ${RELAY_URL:-http://cbk-relay.localhost:3001} - # @note space and portal subdomains read their apexes at server startup; + # @note space, portal and app hosts read their settings at server startup; # recreate the container to change domains without rebuilding the image SPACE_APEX: ${SPACE_APEX:-cbk-space.localhost} PORTAL_APEX: ${PORTAL_APEX:-cbk-portal.localhost} - # @note the app shells, baked the same way: the main shell at - # `cbk-apps.localhost:3000`, the labs shell at - # `cbk-labs.localhost:3000`. Cookies do not cross hosts, so sign in on - # the shell host itself + APP_APEX: ${APP_APEX:-} + # @note cookies do not cross hosts, so sign in on the shell host itself APP_MAIN_ORIGIN: ${APP_MAIN_ORIGIN:-http://cbk-apps.localhost:3000} APP_LABS_ORIGIN: ${APP_LABS_ORIGIN:-http://cbk-labs.localhost:3000} # @note left empty, the image generates these secrets on first boot and diff --git a/docker/distro/studio/compose.yml b/docker/distro/studio/compose.yml index 4f71c48..eae2dcb 100644 --- a/docker/distro/studio/compose.yml +++ b/docker/distro/studio/compose.yml @@ -76,14 +76,12 @@ services: # needs an address it can reach instead (and TLS if the site has it) RELAY_PORT: 3001 RELAY_URL: ${RELAY_URL:-http://cbk-relay.localhost:3001} - # @note space and portal subdomains read their apexes at server startup; + # @note space, portal and app hosts read their settings at server startup; # recreate the container to change domains without rebuilding the image SPACE_APEX: ${SPACE_APEX:-cbk-space.localhost} PORTAL_APEX: ${PORTAL_APEX:-cbk-portal.localhost} - # @note the app shells, baked the same way: the main shell at - # `cbk-apps.localhost:3000`, the labs shell at - # `cbk-labs.localhost:3000`. Cookies do not cross hosts, so sign in on - # the shell host itself + APP_APEX: ${APP_APEX:-} + # @note cookies do not cross hosts, so sign in on the shell host itself APP_MAIN_ORIGIN: ${APP_MAIN_ORIGIN:-http://cbk-apps.localhost:3000} APP_LABS_ORIGIN: ${APP_LABS_ORIGIN:-http://cbk-labs.localhost:3000} # @note left empty, the image generates these secrets on first boot and diff --git a/docs/configuration.md b/docs/configuration.md index c26316b..f8d5741 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -219,8 +219,14 @@ and `PORTAL_APEX=cbk-portal.localhost`. Setting `PORTAL_APEX=portal.localhost` serves a portal named `test` at `http://test.portal.localhost:3000/`, with its existing authentication and app configuration. -The other apex host rewrites are still generated when Next builds, so their -runtime values must match the image; see +Standalone app host routing also reads `APP_APEX` at server startup. Setting +`APP_APEX=app.localhost` serves a registered app such as `chat` at +`http://chat.app.localhost:3000/`. Only apps included in the image's manifest +catalogue receive subdomain routes. Community and Studio leave this apex empty +by default, so apps remain available by path. + +Partner host rewrites are still generated when Next builds, so `PARTNERS_APEX` +must match the image's build-time value; see [Deployment](./deployment.md#production-boundary). ## App shell origins @@ -239,10 +245,12 @@ APP_MAIN_ORIGIN=https://apps.example.com APP_LABS_ORIGIN=https://labs.example.com ``` -Like the apexes, the shell host rewrites are generated when Next builds, so -the runtime origins must match the build. The community image bakes -`APP_MAIN_ORIGIN=http://cbk-apps.localhost:3000` and -`APP_LABS_ORIGIN=http://cbk-labs.localhost:3000`. +Shell host routing reads these origins at server startup. Recreate the container +after changing them; no image rebuild is needed. The Community and Studio stacks +default to `APP_MAIN_ORIGIN=http://cbk-apps.localhost:3000` and +`APP_LABS_ORIGIN=http://cbk-labs.localhost:3000`. Existing app paths, sign-in, +callbacks and manifests remain available on the new hosts. Cookies do not cross +hosts, so sign in on the shell host itself. ## `HOSTS_CONFIG` @@ -414,6 +422,30 @@ only when the reverse proxy removes client-supplied forwarded headers, writes its own values, and prevents clients from reaching the application origin directly. If those conditions cannot be guaranteed, leave it unset. +### Server Actions origin checks + +Next.js checks Server Actions before the platform sets up request context. +It compares the browser's `Origin` host, including any port, with +`x-forwarded-host` when present, otherwise `Host`. This framework check runs +independently of `TRUST_PROXY_HEADERS`. A reverse proxy must overwrite forwarded +headers with the public request host, including its port. Preserve the public +`Host` as well when using the platform's space, portal and app host routing. + +Ordinary page actions post back to the page's public host. Deployed builds +configure no allowed-origin exceptions, including for deployment-owned domains. +An origin that differs from the public forwarded host is rejected. +Host-classification headers do not grant cross-origin access. + +A portal gateway may route to an internal backend hostname. It must preserve +the browser's original `Origin` and overwrite `x-forwarded-host` with the public +portal host. Replacing `Origin` with the backend origin breaks the comparison +and hides where the browser request actually originated. + +Only the development server retains exceptions for `localhost:8080` through +`localhost:8089` and `localhost:9090` through `localhost:9099`. These support +the local proxy's deliberate host impersonation and are absent from production +builds. Deployment domains do not add exceptions in either mode. + ## Platform capacity cap `PLATFORM_MAX_TOKENS_PER_MONTH` is an optional deployment-wide safety ceiling, diff --git a/docs/deployment.md b/docs/deployment.md index 662f01d..4d1fbdc 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -279,7 +279,8 @@ and keep secrets out of image layers. The current community image deliberately bakes the neutral single-host topology: `SITE_URL=http://cbk.localhost:3000`, with no external zones. -The two app shells answer at `http://cbk-apps.localhost:3000` and +The Community and Studio stacks configure the two app shells at +`http://cbk-apps.localhost:3000` and `http://cbk-labs.localhost:3000` through `APP_MAIN_ORIGIN` and `APP_LABS_ORIGIN`. Browsers resolve any `*.localhost` name to loopback, so a space site published @@ -289,11 +290,10 @@ stacks default `SPACE_APEX` to `cbk-space.localhost` and `PORTAL_APEX` to `cbk-portal.localhost`. Space and portal routing read these values at server startup. Changing them and recreating the container moves those sites to the new domains without rebuilding the image. Portal authentication and app -configuration continue to apply on the new domain. - -The remaining apexes and app-shell origins must name the same hosts as the -build, which the Compose files ensure; changing those hosts still needs a -rebuild with the matching build arguments. Runtime service variables +configuration continue to apply on the new domain. App hosts work the same way +through `APP_APEX`, `APP_MAIN_ORIGIN` and `APP_LABS_ORIGIN`; see +[Configuration](./configuration.md#app-shell-origins). Only `PARTNERS_APEX` +still has to match the build. Runtime service variables such as the database, Redis, Qdrant and S3-compatible storage endpoints remain configurable. Deployment identity that Next currently exposes through `next.config.js` is still frozen at build time; do not present the same digest diff --git a/platform/app/apps/chat/components/ChatArea.jsx b/platform/app/apps/chat/components/ChatArea.jsx index 63dbb1c..466c564 100644 --- a/platform/app/apps/chat/components/ChatArea.jsx +++ b/platform/app/apps/chat/components/ChatArea.jsx @@ -395,9 +395,11 @@ export function ChatArea({ {...props} className={clsx( 'chat-area', - 'sticky left-0 right-0 bottom-0', + 'left-0 right-0 bottom-0', { - 'pb-2': hasMessages, + // @note sticky positioning can pull the empty composer over the centered intro + 'sticky pb-2': hasMessages, + relative: !hasMessages, // @note used to hide the the text as it scrolls behind the area diff --git a/platform/components/Auth.jsx b/platform/components/Auth.jsx index 68b72f7..9c8c506 100644 --- a/platform/components/Auth.jsx +++ b/platform/components/Auth.jsx @@ -309,7 +309,11 @@ export default function Auth({ const title = _title || 'ChatBotKit' return sessionStatus === 'loading' ? null : ( -