bus-portal

Overview

bus-portal serves the Bus web portal shell. It hosts browser UI modules, serves shared frontend assets, applies the portal theme, and sets browser security headers for the app surface. Operators use it when they want a local or hosted browser entry point for Bus account, AI, accounting, and other portal modules.

Portal modules are mounted explicitly. With no module flags, the portal starts with the default module set. Add modules with --enable-module <id>; repeat the flag to mount several modules. --enable-module all selects all modules that are available without the extra --experimental opt-in.

bus portal serve --print-url
bus portal serve --print-url --enable-module auth
bus portal serve --print-url --enable-module ai

Mounted modules publish deterministic metadata at /v1/modules. The response contains each module’s stable id, public title, readiness state, default_enabled flag, navigation items, token-aware host context, and any validated page/runtime metadata that the module exposes. Browser launchers use that metadata instead of guessing paths or assets.

The portal host is frontend infrastructure. Server-side behavior such as registration, billing, LLM access, container lifecycle, terminal sessions, workspace reads, uploads, report generation, and artifact access is provided by Bus API providers. Portal modules call those APIs from the browser; they do not call backend integration workers directly.

For an end-user AI services portal, enable the auth and AI portal modules and serve the portal behind the same browser-facing gateway origin as the auth, billing, LLM, container, and terminal APIs. Users register and log in through the auth module, complete billing through the billing API, and then use chat or container-backed terminal features through the AI module.

With same-origin reverse proxying, enable the modules explicitly:

bus portal serve --print-url \
  --enable-module auth \
  --enable-module ai

The gateway should route the browser-facing API paths used by the modules, such as /api/v1/auth/*, /api/v1/billing/*, /v1/*, /api/v1/containers/*, and /api/v1/terminal/*. When the portal and APIs share the same origin through a reverse proxy, no separate module URL attributes are required. --api-connect-src and BUS_PORTAL_API_CONNECT_SRC only extend the browser CSP; they do not rewrite module API base URLs, so same-origin gateway routing is the documented configuration for the auth and AI modules on this page.

In local mode, the server prints a capability URL containing a random token. Opening that URL gives access to the local portal session. Use --print-url for scripts or terminals, or the default webview behavior for local desktop use. Hosted deployments normally place bus-portal behind the deployment’s normal public route and rely on API-provider authentication for protected data.

Protected Frontend Mode

bus-portal can require a frontend JWT before it serves portal assets and mounted modules. This is useful when the frontend itself should not be public, for example for private customer portals or operator-only deployments.

Enable it with --require-frontend-auth and provide the signing secret through --frontend-auth-secret-file <path>, BUS_PORTAL_FRONTEND_JWT_SECRET, or BUS_PORTAL_FRONTEND_JWT_SECRET_FILE. The secret file takes precedence when both are set.

bus portal serve --print-url \
  --require-frontend-auth \
  --frontend-auth-secret-file /run/secrets/bus-portal-frontend-jwt

The portal accepts the JWT from Authorization: Bearer ... or from the configured cookie. Defaults are audience ai.hg.fi/api, scope portal:read, and cookie name bus_portal_token. Override them with --frontend-auth-audience, --frontend-auth-scope, and --frontend-auth-cookie.

Frontend tokens use HS256 with the configured secret. They must include sub, aud, scope, iat, and exp. The configured scope is represented inside the space-separated scope claim, for example "scope":"portal:read".

For a local protected-frontend check, create a short-lived JWT with the same HS256 secret and pass it as either a bearer token or the configured cookie:

mkdir -p ./local
printf '%s' 'local-portal-secret-32-bytes-minimum-value' > ./local/portal-frontend.secret
BUS_AUTH_HS256_SECRET="$(cat ./local/portal-frontend.secret)" \
bus operator token --format token issue --local \
  --subject portal-user \
  --audience ai.hg.fi/api \
  --scope portal:read \
  --ttl 1h > ./local/portal-frontend.jwt

bus portal serve --print-url \
  --require-frontend-auth \
  --frontend-auth-secret-file ./local/portal-frontend.secret

curl -fsS -H "Authorization: Bearer $(cat ./local/portal-frontend.jwt)" \
  http://127.0.0.1:<port>/<token>/

Run the bus portal serve command in one terminal and the curl command in a second terminal, using the URL printed by the server. For browser testing, set cookie bus_portal_token to the same JWT unless the deployment overrides the cookie name with --frontend-auth-cookie.

Protected frontend mode only controls delivery of the browser app. The API providers still enforce account ownership, feature scopes, billing state, quota limits, and all business authorization.

The host applies a Content Security Policy, Referrer-Policy: no-referrer, X-Content-Type-Options: nosniff, frame restrictions, and a restrictive permissions policy. Add API origins to CSP connect-src with --api-connect-src <source> or BUS_PORTAL_API_CONNECT_SRC.

Themes are runtime configuration. A theme file contains validated design tokens that become CSS variables. The host rejects values that can break out of CSS declarations or load external resources, including url(...), @import, expression(...), javascript:, data:, and nested var(...) references.

bus-portal does not serve upload, report-generation, accounting-data, or artifact APIs. Those routes belong to authenticated Bus API providers. Portal modules should render provider-returned links for previews and downloads rather than exposing local files from the portal host.

Module Configuration

Configured portal modules must satisfy the validated module descriptor contract before bus-portal mounts them. This validation is strict and operator-facing: an invalid descriptor fails startup or module selection instead of silently serving a partial module.

The required descriptor shape includes a canonical lowercase module ID, a non-empty title, readiness state of stable or experimental, and a default_enabled value. Default-enabled modules must be stable. A module must provide a handler, at least one navigation item, and at least one declared page entry point. Navigation paths must be under /modules/<id>/... and must match a declared page route.

Page specs declare deterministic server-rendered entry points with a stable page name, route path, and HTML mount ID. GX-ready modules may also declare a Go/WASM runtime asset, browser behavior hooks, public runtime config keys, required provider API origins, and module assets. Runtime config keys are public browser data only; names that look like secrets, tokens, passwords, credentials, private values, or JWTs are rejected. Provider API origins must be 'self' or HTTP(S) origins without paths, queries, or fragments. Runtime and module asset paths must be same-origin paths, not external URLs or path traversal values.

This strict configured-module validation is separate from defensive metadata normalization. The live /v1/modules metadata snapshot trims, sorts, deduplicates, and omits unsafe optional fields from already mounted modules so browser clients and CSP generation stay stable. Operators should still treat descriptor validation failures as configuration errors to fix at the module or distribution boundary.

bus-portal-auth provides registration, OTP login, logout, session discovery, and account status UI. It calls bus-api-provider-auth.

bus-portal-ai provides chat, billing prompts, container session controls, and terminal entry points. It calls bus-api-provider-billing, bus-api-provider-llm, bus-api-provider-containers, and bus-api-provider-terminal.

For the auth and AI modules documented here, route the portal and APIs through the same browser origin as described above. The portal host does not embed payment provider secrets, API tokens, database credentials, or integration worker credentials.

Local Compose Stack

The BusDK superproject compose.yaml starts bus-portal behind nginx at /portal/local-dev/. The compose command enables the auth, AI, and accounting portal modules, opts into experimental modules, and sets BUS_PORTAL_API_CONNECT_SRC to the local API gateway origin. The local stack uses a stable portal capability token only inside the private compose network; browser API calls still go to the JWT-secured auth, billing, LLM, VM, and container provider routes exposed by nginx.

Deployment Notes

Use HTTPS for hosted deployments. Configure CSP connect-src for every API origin the browser needs. Keep browser session state short-lived and rely on API-provider JWT validation and scope checks for protected data.

Do not put payment provider secrets, database credentials, API keys, or worker credentials into portal frontend configuration. Browser code should receive only public API base URLs and short-lived user/session credentials.

Using from .bus files

Inside a .bus file, write the module target without the bus prefix:

# same as: bus portal serve --print-url --enable-module auth --enable-module ai
portal serve --print-url --enable-module auth --enable-module ai