Commit Graph

265 Commits

Author SHA1 Message Date
hsiegeln
417e6024b0 fix(test): update LicenseControllerTest to expect STARTER tier (default changed from TEAM)
All checks were successful
CI / build (push) Successful in 1m45s
CI / docker (push) Successful in 1m8s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-29 12:06:18 +02:00
hsiegeln
385d79aa0f fix(test): update CertificateServiceTest and TenantPortalServiceTest for new audit constructor params
Some checks failed
CI / docker (push) Has been cancelled
CI / build (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-29 12:04:25 +02:00
hsiegeln
809f1e8a09 fix(audit): add SLF4J logging to 19 operations missing application-level logs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-29 11:41:25 +02:00
hsiegeln
cb411ff337 feat(audit): add audit logging to vendor server ops and audit_log immutability migration
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-29 11:31:46 +02:00
hsiegeln
da52707aec feat(audit): add SOC 2 audit logging to tenant CA certs, account security, team management, SSO, and server operations
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-29 11:29:55 +02:00
hsiegeln
88733d76c0 feat(audit): add SOC 2 audit logging to vendor admin, auth policy, email connector, and certificate operations
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-29 11:23:13 +02:00
hsiegeln
295a185a03 refactor(security): per-tenant JWT secret instead of shared global secret
All checks were successful
CI / build (push) Successful in 2m17s
CI / docker (push) Successful in 1m49s
Generate a unique JWT secret per tenant at provision time, stored on
TenantEntity (same pattern as dbPassword). On upgrade, the existing
secret is reused so agent tokens survive container recreation.

- V005 migration: add jwt_secret column to tenants table
- TenantEntity: add jwtSecret field
- TenantProvisionRequest: add jwtSecret field
- VendorTenantService: generate secret in provisionAsync(), reuse on upgrade
- DockerTenantProvisioner: read from req.jwtSecret() not props
- ProvisioningProperties: remove jwtSecret (no longer global config)

Installer team: CAMELEER_SERVER_SECURITY_JWTSECRET and
CAMELEER_SAAS_PROVISIONING_JWTSECRET can be removed from compose
templates and .env — no longer consumed by the SaaS app.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-29 09:38:33 +02:00
hsiegeln
529028f0c3 fix(security): patch 5 vulnerabilities from full codebase security review
- Replace hardcoded JWT secret in DockerTenantProvisioner with config
  property (CAMELEER_SAAS_PROVISIONING_JWTSECRET) — every provisioned
  tenant server was sharing the same publicly-visible dev secret
- Add @PreAuthorize("SCOPE_tenant:manage") to 11 admin endpoints in
  TenantPortalController (team invite/remove/role, password resets,
  server restart/upgrade, CA cert management, MFA reset) — previously
  any org member including viewers could perform admin operations
- Remove dead PATCH /api/tenant/settings endpoint (duplicate of
  /auth-settings without authorization) and POST /api/tenant/password
  (allowed password change without current password verification) —
  frontend uses the secure alternatives
- Add @PreAuthorize("SCOPE_platform:admin") to TenantController
  getById and getBySlug — were exposing serverEndpoint, adminEmail,
  and provisionError to any authenticated user

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-29 09:13:39 +02:00
hsiegeln
df03c1a4cd fix(tenant): show email/role in team table, set username on invite
Some checks failed
CI / build (push) Successful in 2m35s
CI / docker (push) Successful in 1m23s
SonarQube Analysis / sonarqube (push) Failing after 2m34s
Team table showed dashes for email and role because the raw Logto
response uses primaryEmail (not email) and excludes org roles.
Enrich each member with normalized email and fetched role name.

Invited users couldn't sign in after password reset because
createAndInviteUser omitted the username field — the sign-in page
sent type:username for non-email input but Logto had no username
to match. Now sets username to the email local part, matching how
createUserWithPassword works for bootstrap admins.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-28 22:36:25 +02:00
hsiegeln
df25dcf81a fix(tenant): reuse existing Logto users and clean up on delete
All checks were successful
CI / build (push) Successful in 2m14s
CI / docker (push) Successful in 1m10s
Create: if admin email matches an existing Logto user, add them to the
tenant org instead of creating a duplicate account. Only creates a new
user when no match is found and a password is provided.

Delete: before deleting the Logto org, list its members. After org
deletion, delete tenant-only users (those with no remaining org
memberships). Users who belong to other orgs are preserved.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-28 20:25:21 +02:00
hsiegeln
345bc4a92b feat(tenant): welcome email, admin email display, async delete fix
All checks were successful
CI / build (push) Successful in 2m20s
CI / docker (push) Successful in 1m29s
- Send branded welcome email to tenant admin after provisioning completes
  (includes username and dashboard URL)
- Store admin_email on tenant entity (V004 migration)
- Show admin email in vendor tenant list table and detail page
- Fix ClickHouse cleanup: skip materialized views (can't ALTER DELETE on MVs)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-28 19:53:47 +02:00
hsiegeln
bd301ad1fe refactor(tenant): replace tier+username with email-first creation
All checks were successful
CI / build (push) Successful in 2m9s
CI / docker (push) Successful in 1m37s
- Remove tier from create tenant form (always defaults to STARTER,
  controlled via license minting)
- Admin email is now the primary identity field
- Username auto-derived from email local part, optionally overridable
- Set primaryEmail on Logto user at creation (prevents invalid accounts)
- Async tenant delete: PG/ClickHouse cleanup runs after commit instead
  of blocking the HTTP response
- Remove legacy /server/* OIDC redirect URIs from bootstrap

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-28 19:34:00 +02:00
hsiegeln
15c47fe36c fix(auth): register tenant /login as OIDC post-logout redirect URI
All checks were successful
CI / build (push) Successful in 2m22s
CI / docker (push) Successful in 1m7s
Server sends /t/{slug}/login as post_logout_redirect_uri on logout but
only /t/{slug} and /t/{slug}/login?local were registered, causing
"post_logout_redirect_uri not registered" error from Logto.

Also removes legacy /server/* redirect URIs from bootstrap (greenfield).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-28 19:15:18 +02:00
hsiegeln
3aba32302a refactor: update license-minter dependency to io.cameleer
All checks were successful
CI / build (push) Successful in 2m57s
CI / docker (push) Successful in 1m35s
Other teams completed their com.cameleer → io.cameleer migration.
Update Maven groupId and Java imports to match.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-28 17:13:28 +02:00
hsiegeln
966691f2c8 refactor: rebrand from net.siegeln to io.cameleer
Some checks failed
CI / build (push) Successful in 3m7s
CI / docker (push) Failing after 7s
Institutionalize the product identity ahead of public release:

- Java package: net.siegeln.cameleer.saas → io.cameleer.saas (109 files)
- Maven groupId: net.siegeln.cameleer → io.cameleer
- Public image defaults: gitea.siegeln.net/cameleer/ → registry.cameleer.io/cameleer/
- Updated docs (architecture, user-manual, HOWTO, runtime-loader README)
- Updated CLAUDE.md path references

Internal build infra (CI workflows, .gitmodules, npm registry, Maven repo)
intentionally kept at gitea.siegeln.net — code stays on internal Gitea.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-28 16:26:34 +02:00
hsiegeln
bc32d7e994 fix: use license/usage endpoint for agent/env/app counts
Some checks failed
CI / docker (push) Has been cancelled
CI / build (push) Has been cancelled
Server moved GET /agents to /environments/{envSlug}/agents and removed
GET /admin/apps. Replace three broken individual calls with a single
GET /admin/license/usage call that returns all counts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-28 12:58:35 +02:00
hsiegeln
06134d6e67 fix: TOTP label includes org name, passkeys show device as default name
Some checks failed
CI / build (push) Successful in 2m10s
CI / docker (push) Successful in 1m27s
SonarQube Analysis / sonarqube (push) Failing after 2m57s
- TOTP otpauth URI issuer changed from "Cameleer" to "Cameleer - <org>"
  so authenticator apps display the organization name
- Passkeys without a custom name now show parsed device info (e.g.
  "Chrome on Windows") instead of "Unnamed passkey"

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-27 22:53:05 +02:00
hsiegeln
7fc8a4d407 fix: team invite role resolution, user cleanup, and settings page redesign
All checks were successful
CI / build (push) Successful in 2m9s
CI / docker (push) Successful in 1m33s
- Resolve org role names to Logto role IDs in invite and role change flows
  (fixes entity.relation_foreign_key_not_found on invite)
- Handle existing Logto users on re-invite instead of failing with
  email_already_in_use
- Delete users from Logto when removed from last org membership
- Consolidate tenant settings page into 3 cards: Tenant Details, MFA,
  Authentication Policy — remove duplicate MFA Enforcement and Change
  Password (now in Account Settings)
- Make passkey list scrollable

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-27 22:36:21 +02:00
hsiegeln
cba420fbeb fix: always offer MFA+passkey enrollment, separate availability from enforcement
All checks were successful
CI / build (push) Successful in 2m19s
CI / docker (push) Successful in 1m43s
Two fundamental fixes:

- user.missing_mfa now triggers MfaEnrollmentError (enroll UI) instead
  of MfaRequiredError (verify UI). Users without MFA were shown a TOTP
  code prompt they couldn't fill.
- Logto MFA factors always set to [Totp, WebAuthn, BackupCode] with
  UserControlled policy on startup. Availability is always-on for all
  users. The vendor auth policy controls enforcement (via
  MfaEnforcementFilter), not what Logto offers during sign-in.
- Removed syncMfaConfigToLogto from VendorAuthPolicyController — vendor
  policy changes no longer modify Logto's sign-in experience.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-27 18:59:21 +02:00
hsiegeln
67ec409383 fix: null display name, settings scrollbar, redundant passkey offer
All checks were successful
CI / build (push) Successful in 2m20s
CI / docker (push) Successful in 1m36s
- Profile API returns empty string instead of "null" when Logto user
  has no display name set (String.valueOf(null) → "null" bug).
- SettingsPage: add overflowY auto + flex 1 so content scrolls within
  the AppShell layout (which uses overflow: hidden).
- Remove redundant passkey offer from onboarding page — passkey
  enrollment now happens during sign-in via the Experience API.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-27 18:53:13 +02:00
hsiegeln
3384510f3c fix: passkeys work independently of MFA mode
All checks were successful
CI / build (push) Successful in 2m15s
CI / docker (push) Successful in 1m1s
When MFA mode is off but passkeys are enabled, WebAuthn + BackupCode
factors are still synced to Logto. Previously, MFA off cleared all
factors including WebAuthn, so passkey enrollment was never offered.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-27 18:45:30 +02:00
hsiegeln
18e6f32f90 refactor: move passkey enrollment to sign-in UI via Experience API
All checks were successful
CI / build (push) Successful in 2m12s
CI / docker (push) Successful in 1m49s
Remove the SaaS backend proxy approach for passkey registration (Account
API binding, Management API proxy, password modal in PasskeySection).
Instead, offer passkey enrollment natively during sign-in via Logto's
Experience API — the correct architectural layer.

Sign-in flow: when Logto returns MFA enrollment available (422), show a
"Secure your account" screen with Register passkey / Set up later. Uses
Experience API WebAuthn registration endpoints. Works for all users
(SaaS and future server users) since the sign-in UI is shared.

PasskeySection in account settings now only manages existing passkeys
(list/rename/delete) and directs users to register during sign-in.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-27 18:33:46 +02:00
hsiegeln
4df6fc9e03 fix: proxy passkey bind through Management API
All checks were successful
CI / build (push) Successful in 2m17s
CI / docker (push) Successful in 1m29s
Logto's /api/my-account/ endpoints reject the opaque access token with
401 even though /api/verifications/ accepts it. The bind step now goes
through the SaaS backend which calls the Management API instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-27 18:23:41 +02:00
hsiegeln
687598952f fix: correct Account Center enablement — mfa field is a string enum
All checks were successful
CI / build (push) Successful in 2m6s
CI / docker (push) Successful in 1m6s
Logto's PATCH /api/account-center expects mfa as 'Off'|'ReadOnly'|'Edit',
not a nested object. Fixes 400 Bad Request on startup.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-27 17:14:31 +02:00
hsiegeln
c22580e124 feat: always enable WebAuthn in MFA factors and add passkey registration
All checks were successful
CI / build (push) Successful in 2m3s
CI / docker (push) Successful in 1m26s
- Sync vendor auth policy to Logto sign-in experience on save and on
  startup. Always include WebAuthn + TOTP + BackupCode in MFA factors
  when MFA is enabled — no reason to gate passkeys behind a toggle.
- Enable Logto Account Center on startup for user-facing MFA management.
- Add passkey registration to account settings via Logto Account API.
  Frontend calls Logto directly (same domain) for the WebAuthn ceremony:
  generate options, browser credential creation, verify, and bind.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-27 17:01:58 +02:00
hsiegeln
a5c20830a7 fix: prevent MFA lockout and move enrollment to modal dialog
All checks were successful
CI / build (push) Successful in 1m58s
CI / docker (push) Successful in 1m47s
Three fixes for MFA enrollment and sign-in:

- Defer TOTP registration with Logto until after 6-digit code verification.
  Previously setupTotp() immediately registered the secret, so abandoning
  enrollment mid-way left MFA active without a working authenticator.
- Move entire MFA enrollment flow (QR code, verify, backup codes) into a
  Modal dialog instead of replacing the Card content inline.
- Fix sign-in MFA flow: submitMfa() no longer calls identifyUser() after
  TOTP verify — user is already identified, and passing the MFA
  verificationId to identification returned 422 ("method not activated").

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-27 16:25:15 +02:00
hsiegeln
15d6c7abc1 fix: remove explicit pagination from Logto role API calls
All checks were successful
CI / build (push) Successful in 2m3s
CI / docker (push) Successful in 59s
Logto's /api/roles/{id}/users endpoint rejects page=1 with
guard.invalid_pagination. Remove explicit pagination params and
let Logto use its defaults.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-27 15:40:05 +02:00
hsiegeln
f823a409d0 fix: add AccountService mock to TenantPortalServiceTest constructor
All checks were successful
CI / build (push) Successful in 3m9s
CI / build (pull_request) Successful in 3m8s
CI / docker (pull_request) Has been skipped
CI / docker (push) Successful in 1m43s
The TenantPortalService constructor gained an AccountService parameter
in the consolidation refactor — the test was missing it.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-27 15:15:19 +02:00
hsiegeln
372d3c77a0 fix: code review findings — dead catch blocks, notification email, role verification
- Remove dead IllegalArgumentException catch blocks in TenantPortalController
  (delegated methods now throw ResponseStatusException, handled by Spring)
- Add password reset notification email in VendorAdminService.resetAdminPassword
- Add verifyIsVendorAdmin guard to resetAdminPassword and resetAdminMfa
  to prevent platform admins from resetting arbitrary non-admin users

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-27 15:06:16 +02:00
hsiegeln
0da1ffea7f fix: guard against null orgId in createAndInviteUser and createUserWithPassword
Vendor admins use global roles, not org roles — passing null orgId
would previously cause addUserToOrganization to call
/api/organizations/null/users and fail.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-27 14:48:00 +02:00
hsiegeln
022b6d9722 feat: add vendor admin management (list, create/invite, remove, reset password/MFA)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 14:46:42 +02:00
hsiegeln
665ffefd3e refactor: use AccountService for display name in OnboardingService
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 14:46:20 +02:00
hsiegeln
cc3d2dc111 refactor: delegate TenantPortalService MFA/password/passkey methods to AccountService
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 14:44:20 +02:00
hsiegeln
ab240e42b0 feat: add /api/account/** security config and MFA enforcement exemptions
Permit /settings/** SPA route, gate /api/account/** as authenticated,
and exempt account MFA/profile/password paths from MFA enforcement filter.
2026-04-27 14:41:21 +02:00
hsiegeln
b63e5e9c81 feat: add AccountController with /api/account/* endpoints 2026-04-27 14:41:05 +02:00
hsiegeln
90d84ffd00 feat: add AccountService extracting user identity operations from TenantPortalService
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 14:39:40 +02:00
hsiegeln
19428b4e27 feat: add password verify and role management methods to LogtoManagementClient
Adds verifyUserPassword (for current-password check before password change) and
four global role methods (listRoleUsers, getRoleByName, assignGlobalRole,
revokeGlobalRole) needed by the upcoming AccountService and VendorAdminService.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 14:36:59 +02:00
hsiegeln
292adeea4c docs: update documentation for passkey MFA feature
All checks were successful
CI / build (push) Successful in 2m23s
CI / docker (push) Successful in 2m19s
- Add V002/V003 migrations and VendorAuthPolicy classes to CLAUDE.md
- Document MFA & passkey enforcement model in config CLAUDE.md
- Mark passkey MFA design spec as Implemented

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-27 11:51:12 +02:00
hsiegeln
43a1058f33 fix: code review findings — auth-settings HTTP method, authorization, redirect
- Change auth-settings endpoint from PUT to PATCH (matches partial update semantics and frontend hook)
- Add @PreAuthorize("SCOPE_tenant:manage") to updateAuthSettings endpoint
- Consolidate MFA/passkey 403 redirect handling in API client

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-27 09:01:23 +02:00
hsiegeln
9057479da7 feat: expose vendor auth policy in public config endpoint
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 08:48:13 +02:00
hsiegeln
89c83ec7b8 feat: expand MfaEnforcementFilter for vendor policy and passkey checks
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 08:48:09 +02:00
hsiegeln
b3104dc410 feat: add passkey and auth settings endpoints to TenantPortalController 2026-04-27 08:45:52 +02:00
hsiegeln
5bf94c6d4e feat: add passkey management and auth settings to TenantPortalService 2026-04-27 08:45:49 +02:00
hsiegeln
40daca36a0 feat: add WebAuthn credential and custom data methods to LogtoManagementClient 2026-04-27 08:45:45 +02:00
hsiegeln
25f4afcddc feat: add vendor auth policy REST endpoints
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 08:42:59 +02:00
hsiegeln
02be1d9264 feat: add VendorAuthPolicy entity and repository
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 08:42:55 +02:00
hsiegeln
cc7c87a520 feat: add vendor_auth_policy table for passkey MFA support
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 08:42:51 +02:00
hsiegeln
6afc337b16 feat: add usage data to license and vendor detail endpoints
Add getAppCount() to ServerApiClient, include usage counts (agents,
environments, apps, users) in tenant license and vendor detail responses
so the frontend can render progress bars against license limits.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-26 22:08:47 +02:00
hsiegeln
d7ef2c488b fix: use valid STARTER tier in onboarding tenant creation
All checks were successful
CI / build (push) Successful in 2m17s
CI / docker (push) Successful in 1m16s
OnboardingService passed "LOW" as the tier, but the Tier enum only has
STARTER/TEAM/BUSINESS/ENTERPRISE. Tier.valueOf("LOW") threw
IllegalArgumentException, which the controller caught as a blanket 409
Conflict — masking the real cause. Also catch IllegalStateException
(user already has a tenant) to return 409 instead of 500.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-26 21:17:23 +02:00
hsiegeln
7c82ba93b0 refactor: update imports for cameleer-license-api package extraction
Some checks failed
CI / build (push) Successful in 3m6s
CI / docker (push) Failing after 30s
Server team extracted license types into cameleer-license-api (#156).
Package moved from com.cameleer.server.core.license to com.cameleer.license.
Dependency tree is now: cameleer-saas → minter → license-api (no server-core).

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