Files
cameleer-saas/CLAUDE.md
hsiegeln c96faa4f3f
All checks were successful
CI / build (push) Successful in 1m0s
CI / docker (push) Successful in 55s
fix: display username in UI, fix license limits key mismatch
- Read user profile from Logto ID token in OrgResolver, store in
  Zustand org store, display in sidebar footer and TopBar avatar
- Fix license limits showing "—" by aligning frontend LIMIT_LABELS
  keys with backend snake_case convention (max_agents, retention_days,
  max_environments)
- Bump @cameleer/design-system to v0.1.38 (font-size floor)
- Add dev volume mount for local UI hot-reload without image rebuild

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 12:20:40 +02:00

8.1 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project

Cameleer SaaS — multi-tenant SaaS platform wrapping the Cameleer observability stack (Java agent + server) for Apache Camel applications. Customers get managed observability for their Camel integrations without running infrastructure.

Ecosystem

This repo is the SaaS layer on top of two proven components:

  • cameleer3 (sibling repo) — Java agent using ByteBuddy for zero-code instrumentation of Camel apps. Captures route executions, processor traces, payloads, metrics, and route graph topology. Deploys as -javaagent JAR.
  • cameleer3-server (sibling repo) — Spring Boot observability backend. Receives agent data via HTTP, pushes config/commands via SSE. PostgreSQL + OpenSearch storage. React SPA dashboard. JWT auth with Ed25519 config signing.
  • cameleer-website — Marketing site (Astro 5)
  • design-system — Shared React component library (@cameleer/design-system on Gitea npm registry)

Agent-server protocol is defined in cameleer3/cameleer3-common/PROTOCOL.md. The agent and server are mature, proven components — this repo wraps them with multi-tenancy, billing, and self-service onboarding.

Architecture Context

The existing cameleer3-server already has single-tenant auth (JWT, RBAC, bootstrap tokens, OIDC). The SaaS layer must:

  • Add multi-tenancy (tenant isolation of agent data, diagrams, configs)
  • Provide self-service signup, billing, and team management
  • Generate per-tenant bootstrap tokens for agent registration
  • Proxy or federate access to tenant-specific cameleer3-server instances
  • Enforce usage quotas and metered billing

Routing (single-domain, path-based via Traefik)

All services on one hostname. Two env vars control everything: PUBLIC_HOST + PUBLIC_PROTOCOL.

Path Target Notes
/platform/* cameleer-saas:8080 SPA + API (server.servlet.context-path: /platform)
/server/* cameleer3-server-ui:80 Server dashboard (strip-prefix + BASE_PATH=/server)
/ redirect → /platform/ Via docker/traefik-dynamic.yml
/* (catch-all) cameleer-logto:3001 (priority=1) Custom sign-in UI, OIDC, interaction
  • SPA assets at /_app/ (Vite assetsDir: '_app') to avoid conflict with Logto's /assets/
  • Logto ENDPOINT = ${PUBLIC_PROTOCOL}://${PUBLIC_HOST} (same domain, same origin)
  • TLS: self-signed cert init container (traefik-certs) for dev, ACME for production
  • Root //platform/ redirect via Traefik file provider (docker/traefik-dynamic.yml)
  • LoginPage auto-redirects to Logto OIDC (no intermediate button)

Custom sign-in UI (ui/sign-in/)

Separate Vite+React SPA replacing Logto's default sign-in page. Visually matches cameleer3-server LoginPage.

  • Built as custom Logto Docker image (cameleer-logto): ui/sign-in/Dockerfile = node build stage + FROM ghcr.io/logto-io/logto:latest + COPY dist over /etc/logto/packages/experience/dist/
  • Uses @cameleer/design-system components (Card, Input, Button, FormField, Alert)
  • Authenticates via Logto Experience API (4-step: init → verify password → identify → submit → redirect)
  • CUSTOM_UI_PATH env var does NOT work for Logto OSS — must volume-mount or replace the experience dist directory
  • Favicon bundled in ui/sign-in/public/favicon.svg (served by Logto, not SaaS)

Auth enforcement

  • All API endpoints enforce OAuth2 scopes via @PreAuthorize("hasAuthority('SCOPE_xxx')") annotations
  • Tenant isolation enforced by TenantIsolationInterceptor (a single HandlerInterceptor on /api/** that resolves JWT org_id to TenantContext and validates {tenantId}, {environmentId}, {appId} path variables; fail-closed, platform admins bypass)
  • 13 OAuth2 scopes on the Logto API resource (https://api.cameleer.local): 10 platform scopes + 3 server scopes (server:admin, server:operator, server:viewer), served to the frontend from GET /platform/api/config
  • Server scopes map to server RBAC roles via JWT scope claim (SaaS platform path) or roles claim (server-ui OIDC login path)
  • Org role admin gets server:admin, org role member gets server:viewer
  • Custom JwtDecoder in SecurityConfig.java — ES384 algorithm, at+jwt token type, split issuer-uri (string validation) / jwk-set-uri (Docker-internal fetch), audience validation (https://api.cameleer.local)
  • Logto Custom JWT (Phase 7b in bootstrap) injects a roles claim into access tokens based on org roles and global roles — this makes role data available to the server without Logto-specific code

Server integration (cameleer3-server env vars)

Env var Value Purpose
CAMELEER_OIDC_ISSUER_URI ${PUBLIC_PROTOCOL}://${PUBLIC_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 — disable in production)
CAMELEER_CORS_ALLOWED_ORIGINS ${PUBLIC_PROTOCOL}://${PUBLIC_HOST} Allow browser requests through Traefik
BASE_PATH (server-ui) /server React Router basename + <base> tag

Server OIDC role extraction (two paths)

Path Token type Role source How it works
SaaS platform → server API Logto org-scoped access token scope claim JwtAuthenticationFilter.extractRolesFromScopes() reads server:admin from scope
Server-ui SSO login Logto JWT access token (via Traditional Web App) roles claim OidcTokenExchanger decodes access_token, reads roles injected by Custom JWT

The server's OIDC config (OidcConfig) includes audience (RFC 8707 resource indicator) and additionalScopes. The audience is sent as resource in both the authorization request and token exchange, which makes Logto return a JWT access token instead of opaque. The Custom JWT script maps org roles to roles: ["server:admin"]. If OIDC returns no roles and the user already exists, syncOidcRoles preserves existing local roles.

Bootstrap (docker/logto-bootstrap.sh)

Idempotent script run via logto-bootstrap init container. Phases:

  1. Wait for Logto + server health
  2. Get Management API token (reads m-default secret from DB)
  3. Create Logto apps (SPA, Traditional with skipConsent, M2M with Management API role) 3b. Create API resource scopes (10 platform + 3 server scopes)
  4. Create roles (platform-admin, org admin/member with API resource scope assignments)
  5. Create users (SaaS admin with platform-admin role + Logto console access, tenant admin)
  6. Create organization, add users with org roles
  7. Configure cameleer3-server OIDC (rolesClaim: "roles", audience, defaultRoles: ["VIEWER"]) 7b. Configure Logto Custom JWT for access tokens (maps org roles → roles claim: admin→server:admin, member→server:viewer)
  8. Configure Logto sign-in branding (Cameleer colors #C6820E/#D4941E, logo from /platform/logo.svg)
  9. Cleanup seeded Logto apps
  10. Write bootstrap results to /data/logto-bootstrap.json

SaaS admin credentials (SAAS_ADMIN_USER/SAAS_ADMIN_PASS) work for both the SaaS platform and the Logto console (port 3002).

  • Gitea-hosted: gitea.siegeln.net/cameleer/
  • CI: .gitea/workflows/ — Gitea Actions
  • K8s target: k3s cluster at 192.168.50.86
  • Docker images: CI builds and pushes all images — Dockerfiles use multi-stage builds, no local builds needed
    • cameleer-saas — SaaS app (frontend + JAR baked in)
    • cameleer-logto — custom Logto with sign-in UI baked in
  • Docker builds: --no-cache, --provenance=false for Gitea compatibility
  • docker-compose.dev.yml — exposes ports for direct access, sets SPRING_PROFILES_ACTIVE: dev. Volume-mounts ./ui/dist into the container so local UI builds are served without rebuilding the Docker image (SPRING_WEB_RESOURCES_STATIC_LOCATIONS overrides classpath)
  • Design system: import from @cameleer/design-system (Gitea npm registry)

Disabled Skills

  • Do NOT use any gsd:* skills in this project. This includes all /gsd: prefixed commands.