feat: move SaaS app to /platform base path, Logto becomes catch-all
All checks were successful
CI / build (push) Successful in 48s
CI / docker (push) Successful in 41s

Eliminates all Logto path enumeration in Traefik. Routing is now:
- /platform/* → cameleer-saas (SPA + API)
- /server/* → server-ui
- /* (catch-all) → Logto (sign-in, OIDC, assets, everything)

Spring context-path handles backend prefix transparently. No changes
needed in controllers, SecurityConfig, or interceptors.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-05 23:06:41 +02:00
parent 4ab72425ae
commit 4997f7a6a9
8 changed files with 20 additions and 15 deletions

View File

@@ -76,7 +76,8 @@ services:
start_period: 15s start_period: 15s
labels: labels:
- traefik.enable=true - traefik.enable=true
- traefik.http.routers.logto.rule=PathPrefix(`/oidc`) || PathPrefix(`/interaction`) || PathPrefix(`/assets`) || PathPrefix(`/sign-in`) || PathPrefix(`/register`) || PathPrefix(`/consent`) || PathPrefix(`/single-sign-on`) || PathPrefix(`/social`) || PathPrefix(`/unknown-session`) || PathPrefix(`/api/interaction`) || PathPrefix(`/api/experience`) || PathPrefix(`/api/.well-known`) - traefik.http.routers.logto.rule=PathPrefix(`/`)
- traefik.http.routers.logto.priority=1
- traefik.http.routers.logto.entrypoints=websecure - traefik.http.routers.logto.entrypoints=websecure
- traefik.http.routers.logto.tls=true - traefik.http.routers.logto.tls=true
- traefik.http.services.logto.loadbalancer.server.port=3001 - traefik.http.services.logto.loadbalancer.server.port=3001
@@ -142,15 +143,10 @@ services:
CLICKHOUSE_URL: jdbc:clickhouse://clickhouse:8123/cameleer CLICKHOUSE_URL: jdbc:clickhouse://clickhouse:8123/cameleer
labels: labels:
- traefik.enable=true - traefik.enable=true
- traefik.http.routers.api.rule=PathPrefix(`/api`) - traefik.http.routers.saas.rule=PathPrefix(`/platform`)
- traefik.http.routers.api.entrypoints=websecure - traefik.http.routers.saas.entrypoints=websecure
- traefik.http.routers.api.tls=true - traefik.http.routers.saas.tls=true
- traefik.http.routers.api.service=spa - traefik.http.services.saas.loadbalancer.server.port=8080
- traefik.http.routers.spa.rule=PathPrefix(`/`)
- traefik.http.routers.spa.priority=1
- traefik.http.routers.spa.entrypoints=websecure
- traefik.http.routers.spa.tls=true
- traefik.http.services.spa.loadbalancer.server.port=8080
networks: networks:
- cameleer - cameleer

View File

@@ -43,8 +43,8 @@ SERVER_UI_PASS="${SERVER_UI_PASS:-admin}"
# Redirect URIs (derived from PUBLIC_HOST and PUBLIC_PROTOCOL) # Redirect URIs (derived from PUBLIC_HOST and PUBLIC_PROTOCOL)
HOST="${PUBLIC_HOST:-localhost}" HOST="${PUBLIC_HOST:-localhost}"
PROTO="${PUBLIC_PROTOCOL:-https}" PROTO="${PUBLIC_PROTOCOL:-https}"
SPA_REDIRECT_URIS="[\"${PROTO}://${HOST}/callback\"]" SPA_REDIRECT_URIS="[\"${PROTO}://${HOST}/platform/callback\"]"
SPA_POST_LOGOUT_URIS="[\"${PROTO}://${HOST}/login\"]" SPA_POST_LOGOUT_URIS="[\"${PROTO}://${HOST}/platform/login\"]"
TRAD_REDIRECT_URIS="[\"http://${HOST}:8081/oidc/callback\"]" TRAD_REDIRECT_URIS="[\"http://${HOST}:8081/oidc/callback\"]"
TRAD_POST_LOGOUT_URIS="[\"http://${HOST}:8081\"]" TRAD_POST_LOGOUT_URIS="[\"http://${HOST}:8081\"]"

View File

@@ -1,3 +1,7 @@
server:
servlet:
context-path: /platform
spring: spring:
jpa: jpa:
show-sql: false show-sql: false

View File

@@ -1,3 +1,7 @@
server:
servlet:
context-path: /platform
spring: spring:
application: application:
name: cameleer-saas name: cameleer-saas

View File

@@ -1,4 +1,4 @@
const API_BASE = '/api'; const API_BASE = '/platform/api';
let tokenProvider: (() => Promise<string | undefined>) | null = null; let tokenProvider: (() => Promise<string | undefined>) | null = null;

View File

@@ -11,7 +11,7 @@ export async function fetchConfig(): Promise<AppConfig> {
if (cached) return cached; if (cached) return cached;
try { try {
const response = await fetch('/api/config'); const response = await fetch('/platform/api/config');
if (response.ok) { if (response.ok) {
cached = await response.json(); cached = await response.json();
return cached!; return cached!;

View File

@@ -80,7 +80,7 @@ function App() {
> >
<TokenSync resource={config.logtoResource} /> <TokenSync resource={config.logtoResource} />
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<BrowserRouter> <BrowserRouter basename="/platform">
<AppRouter /> <AppRouter />
</BrowserRouter> </BrowserRouter>
</QueryClientProvider> </QueryClientProvider>

View File

@@ -12,6 +12,7 @@ export default defineConfig({
}, },
}, },
}, },
base: '/platform/',
build: { build: {
outDir: 'dist', outDir: 'dist',
emptyOutDir: true, emptyOutDir: true,