# Single-Domain Routing Design ## Problem Customers cannot always provision subdomains. The platform must work with a single hostname and one DNS record. ## Solution Path-based routing on one domain. SaaS app at `/platform`, server-ui at `/server/`, Logto as catch-all. SPA assets moved to `/_app/` to avoid conflict with Logto's `/assets/`. ## Routing (all on `${PUBLIC_HOST}`) | Path | Target | Priority | Notes | |------|--------|----------|-------| | `/platform/*` | cameleer-saas:8080 | default | Spring context-path `/platform` | | `/server/*` | cameleer-server-ui:80 | default | Strip-prefix + `BASE_PATH=/server` | | `/` | redirect → `/platform/` | 100 | Via `docker/traefik-dynamic.yml` | | `/*` | logto:3001 | 1 (lowest) | Catch-all: sign-in, OIDC, assets | ## Configuration Two env vars control everything: ```env PUBLIC_HOST=cameleer.mycompany.com PUBLIC_PROTOCOL=https ``` ## TLS - **Dev**: `traefik-certs` init container auto-generates self-signed cert on first boot - **Production**: Traefik ACME (Let's Encrypt) - HTTP→HTTPS redirect via Traefik entrypoint config ## Logto - `ENDPOINT` = `${PUBLIC_PROTOCOL}://${PUBLIC_HOST}` (same domain as SPA) - OIDC issuer = `${PUBLIC_PROTOCOL}://${PUBLIC_HOST}/oidc` - Same origin — no CORS, cookies work - Management API only via Docker-internal networking (`http://logto:3001`) - Bootstrap (`docker/logto-bootstrap.sh`) creates apps, users, orgs, roles, scopes - Traditional app has `skipConsent: true` for first-party SSO ## SaaS App (cameleer-saas) - `server.servlet.context-path: /platform` — Spring handles prefix transparently - Vite `base: '/platform/'`, `assetsDir: '_app'` - BrowserRouter `basename="/platform"` - API client: `API_BASE = '/platform/api'` - Custom `JwtDecoder`: ES384 algorithm, `at+jwt` token type, split issuer-uri / jwk-set-uri - Redirect URIs: `${PROTO}://${HOST}/platform/callback` ## Server Integration (cameleer-server) | Env var | Value | Purpose | |---------|-------|---------| | `CAMELEER_OIDC_ISSUER_URI` | `${PROTO}://${HOST}/oidc` | Token issuer claim validation | | `CAMELEER_OIDC_JWK_SET_URI` | `http://logto:3001/oidc/jwks` | Docker-internal JWK fetch | | `CAMELEER_OIDC_TLS_SKIP_VERIFY` | `true` | Skip cert verify for OIDC discovery (dev only) | | `CAMELEER_CORS_ALLOWED_ORIGINS` | `${PROTO}://${HOST}` | Browser requests through Traefik | Server OIDC requirements: - ES384 signing algorithm (Logto default) - `at+jwt` token type acceptance - `X-Forwarded-Prefix` support for correct redirect_uri construction - Branding endpoint (`/api/v1/branding/logo`) must be publicly accessible ## Server UI (cameleer-server-ui) | Env var | Value | Purpose | |---------|-------|---------| | `BASE_PATH` | `/server` | React Router basename + `` tag | | `CAMELEER_API_URL` | `http://cameleer-server:8081` | nginx API proxy target | Traefik strip-prefix removes `/server` before forwarding to nginx. Server-ui injects `` via `BASE_PATH`. ## Bootstrap Redirect URIs ```sh # SPA (cameleer-saas) SPA_REDIRECT_URIS=["${PROTO}://${HOST}/platform/callback"] SPA_POST_LOGOUT_URIS=["${PROTO}://${HOST}/platform/login"] # Traditional (cameleer-server) — both variants until X-Forwarded-Prefix is consistent TRAD_REDIRECT_URIS=["${PROTO}://${HOST}/oidc/callback","${PROTO}://${HOST}/server/oidc/callback"] TRAD_POST_LOGOUT_URIS=["${PROTO}://${HOST}","${PROTO}://${HOST}/server"] ``` ## Customer Bootstrap ```bash # 1. Set domain echo "PUBLIC_HOST=cameleer.mycompany.com" >> .env echo "PUBLIC_PROTOCOL=https" >> .env # 2. Point DNS (1 record) # cameleer.mycompany.com → server IP # 3. Start docker compose up -d ``` ## Future: Custom Sign-In UI (Roadmap) For full auth UX control, build a custom sign-in experience using Logto's Experience API. Eliminates Logto's interaction pages — Logto becomes a pure OIDC/API backend. Separate project.