- Sidebar, sign-in page, and favicons all use the single SVG
- Postinstall copies SVG for SaaS HTML favicon (gitignored)
- Sign-in favicon committed (baked into Logto Docker image)
- Remove old PNG favicon references
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Sidebar and sign-in logos use Vite import from @cameleer/design-system
- HTML favicons copied by postinstall script (gitignored)
- Remove manually copied PNGs from repo
- Clean up SecurityConfig permitAll (bundled assets under /_app/**)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Browser img tags don't send Bearer tokens, so the sidebar logo
needs to be in the permitAll list.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace favicon SVG with official camel-logo.svg from design-system
- Add PNG favicons (32px, 192px) with proper link tags in index.html
- Replace sidebar logo with 48px brand icon (cameleer-logo-48.png)
- Replace sign-in page logo with 48px brand icon
- Permit favicon PNGs in SecurityConfig
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CUSTOM_UI_PATH is a Logto Cloud feature, not available in OSS.
The correct approach for self-hosted Logto is to volume-mount
over /etc/logto/packages/experience/dist/.
- Use init container (sign-in-ui) to copy dist to shared volume
as root (fixes permission denied with cameleer user)
- Logto mounts signinui volume at experience/dist path
- Logto depends on sign-in-ui init container completion
- Remove saas-entrypoint.sh approach (no longer needed)
- Revert Dockerfile entrypoint to direct java -jar
- Permit /favicon.svg in SecurityConfig for sign-in page logo
Tested: full OIDC flow works end-to-end via Playwright.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add server:admin/operator/viewer scopes to bootstrap and org roles
- Grant SaaS admin Logto console access via admin:admin role
- Configure sign-in experience with Cameleer branding (colors + logos)
- Add rolesClaim and audience to server OIDC config
- Add server scopes to PublicConfigController for token inclusion
- Permit logo SVGs in SecurityConfig (fix 401 on /platform/logo.svg)
- Add cameleer3 logo SVGs (light + dark) to ui/public/
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SPA assets moved from /assets/ to /_app/ for single-domain routing,
but SecurityConfig still permitted the old path.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ConnectivityHealthCheck: /actuator/health → /api/v1/health (actuator
now requires auth on cameleer3-server after OIDC was added).
Bootstrap: POST → PUT for /api/v1/admin/oidc (server expects PUT,
POST returned 405 causing OIDC config to silently fail).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace TenantResolutionFilter + TenantOwnershipValidator (15 manual
calls across 5 controllers) with a single TenantIsolationInterceptor
that uses Spring HandlerMapping path variables for fail-closed tenant
isolation. New endpoints with {tenantId}, {environmentId}, or {appId}
path variables are automatically isolated without manual code.
Simplify OrgResolver from dual-token fetch to single token — Logto
merges all scopes into either token type.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add @PreAuthorize annotations to all API controllers (14 endpoints
across 6 controllers) enforcing OAuth2 scopes: apps:manage, apps:deploy,
billing:manage, observe:read, platform:admin.
Enforce tenant isolation: TenantResolutionFilter now rejects cross-tenant
access on /api/tenants/{id}/* paths. New TenantOwnershipValidator checks
environment/app ownership for paths without tenantId. Platform admins
bypass both layers.
Fix frontend: OrgResolver split into two useEffect hooks so scopes
refresh on org switch. Scopes now served from /api/config (single source
of truth). Bootstrap cleaned — standalone org permissions removed.
Update docs/architecture.md, docs/user-manual.md, and CLAUDE.md to
reflect all auth hardening changes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add @Primary + @ConditionalOnMissingBean so TestSecurityConfig.jwtDecoder()
wins over SecurityConfig.jwtDecoder() without needing a real OIDC endpoint
- Add spring.main.allow-bean-definition-overriding=true and
cameleer.clickhouse.enabled=false to src/test/resources/application-test.yml
so Testcontainers @ServiceConnection can supply the datasource
- Disable ClickHouse in test profile (src/main/resources/application-test.yml)
so the explicit ClickHouseConfig DataSource bean is not created, allowing
@ServiceConnection to wire the Testcontainers Postgres datasource
- Fix TenantControllerTest and LicenseControllerTest to explicitly grant
ROLE_platform-admin authority via .authorities() on the test JWT, since
spring-security-test does not run the custom JwtAuthenticationConverter
- Fix EnvironmentService.createDefaultForTenant() to use an internal
bootstrap path that skips license enforcement (chicken-and-egg: no license
exists at tenant creation time yet)
- Remove now-unnecessary license stub from EnvironmentServiceTest
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove bootstrapToken field/getter/setter from EnvironmentEntity and drop
the RuntimeConfig dependency from EnvironmentService. DeploymentService and
AgentStatusService now use a TODO-api-key placeholder until the ApiKeyService
wiring is complete. All test references to setBootstrapToken removed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove LogtoManagementClient dependency from TenantController; gate
listAll and create with @PreAuthorize("hasRole('platform-admin')"),
relying on the JWT roles claim already mapped by JwtAuthenticationConverter.
Update TenantControllerTest to supply the platform-admin role via jwt()
on all POST requests that expect 201/409.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Drop JwtConfig dependency from LicenseService; generate license tokens as
random UUIDs instead. Add findByToken to LicenseRepository and update
verifyLicenseToken to do a DB lookup. Update LicenseServiceTest to match.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The bootstrap dynamically creates the M2M app and writes credentials
to the JSON file. LogtoConfig now falls back to the bootstrap file
when LOGTO_M2M_CLIENT_ID/SECRET env vars are not set.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Logto issues access tokens with typ "at+jwt" (RFC 9068) but Spring
Security's default NimbusJwtDecoder only allows "JWT". Custom decoder
accepts any type. Also removed hard 401 redirect from API client.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The bootstrap script runs before cameleer-saas (Flyway), so tenant
tables don't exist yet. Moved DB seeding to BootstrapDataSeeder
ApplicationRunner which runs after Flyway migrations complete.
Reads bootstrap JSON and creates tenant/environment/license if missing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bootstrap script now creates:
- SaaS Owner (admin/admin) with platform-admin role
- Tenant Admin (camel/camel) in Example Tenant org
- Traditional Web App for cameleer3-server OIDC
- DB records: tenant, default environment, license
- Configures cameleer3-server OIDC via its admin API
All credentials configurable via env vars.
Backend:
- Fix LogtoManagementClient resource URL (https://default.logto.app/api)
- Add getUserRoles/getUserOrganizations to LogtoManagementClient
- Add GET /api/me endpoint (user info, platform admin status, tenants)
- Add GET /api/tenants list-all for platform admins
- Remove insecure X-header forwarding from Traefik
Frontend:
- Org-scoped tokens: getAccessToken(resource, orgId) for tenant context
- OrgResolver component populates org store from /api/me
- useOrganization Zustand store (currentOrgId + currentTenantId)
- Platform admin sidebar section + AdminTenantsPage
- View Dashboard link points to cameleer3-server on port 8081
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Logto returns opaque access tokens when no resource is specified.
Added API resource creation to bootstrap, included resource indicator
in /api/config, and SPA now passes resource parameter in auth request.
Also fixed issuer-uri to match Logto's public endpoint.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Separate LOGTO_PUBLIC_ENDPOINT (browser-facing, defaults to
http://localhost:3001) from LOGTO_ENDPOINT (Docker-internal).
Also fix bootstrap M2M verification by using correct Host header
for default tenant token endpoint.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- logto-bootstrap.sh: API-driven init script that creates SPA app,
M2M app, and default user (camel/camel) via Logto Management API.
Reads m-default secret from DB, then removes seeded apps with
known secrets (security hardening). Idempotent.
- PublicConfigController: /api/config public endpoint serves Logto
client ID from bootstrap output file (runtime, not build-time)
- Frontend: LoginPage + CallbackPage fetch config from /api/config
instead of import.meta.env (fixes Vite build-time baking issue)
- Docker Compose: logto-bootstrap init service with health-gated
dependency chain, shared volume for bootstrap config
- SecurityConfig: permit /api/config without auth
Flow: docker compose up → bootstrap creates apps/user → SPA fetches
config → login page shows → sign in with Logto → camel/camel
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The SPA (index.html, /login, /callback, /assets/*) must be accessible
without authentication. API routes remain protected.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Explicit spring.flyway.url/user/password used SPRING_DATASOURCE_URL env
var but Flyway resolves its own defaults independently, falling back to
localhost when the env var mapping doesn't match. Removing the explicit
Flyway connection config lets it inherit from the datasource, which is
correctly configured.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add ClickHouseProperties with @ConfigurationProperties
- @ConditionalOnProperty to toggle ClickHouse
- @Primary DataSource + JdbcTemplate for PostgreSQL (prevents Spring
Boot from routing JPA/Flyway to ClickHouse)
- HikariDataSource for ClickHouse with explicit credentials
- Remove separate DataSourceConfig.java (merged into ClickHouseConfig)
- Remove database-platform override (no longer needed with @Primary)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
With two DataSource beans (PostgreSQL + ClickHouse), Flyway was picking
up the ClickHouse DataSource and failing with auth errors. Explicitly
configure Flyway's url/user/password to target PostgreSQL.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Without an explicit spring.datasource.url, Spring Boot falls back to
jdbc:postgresql://localhost:5432 when the SPRING_DATASOURCE_URL env var
is missing or not picked up. Default now points to the docker-compose
service name (postgres:5432/cameleer_saas).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Without these, `mvn spring-boot:run -Dspring-boot.run.profiles=dev` fails
because ClickHouse URL defaults to docker hostname 'clickhouse'.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implements AgentStatusService (TDD) that proxies cameleer3-server agent
registry API and queries ClickHouse for trace counts. Gracefully degrades
to UNKNOWN state when server is unreachable or DataSource is absent.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds domain config to RuntimeConfig/application.yml, expands AppResponse
with exposedPort and computed routeUrl, adds updateRouting to AppService,
and adds PATCH /{appId}/routing endpoint to AppController.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add DeploymentResponse DTO, DeploymentController at /api/apps/{appId} with POST /deploy (202), GET /deployments, GET /deployments/{id}, POST /stop, POST /restart (202), and integration tests covering empty list, 404, and 401 cases.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements DeploymentService with TDD: builds Docker images, starts containers with Cameleer env vars, polls for health, and handles stop/restart lifecycle. All 3 unit tests pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds AppController at /api/environments/{environmentId}/apps with POST (multipart
metadata+JAR), GET list, GET by ID, PUT jar reupload, and DELETE endpoints.
Also adds CreateAppRequest and AppResponse DTOs, integration tests (AppControllerTest),
and fixes ClickHouseConfig to be excluded in test profile via @Profile("!test").
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>