Commit Graph

528 Commits

Author SHA1 Message Date
hsiegeln
d720c0500f fix: force fresh OIDC sign-in after onboarding to pick up new org membership
All checks were successful
CI / build (push) Successful in 1m55s
CI / docker (push) Successful in 1m22s
After creating a tenant, the existing Logto tokens don't include the new
org membership/scopes. A hard page reload reused stale tokens, causing
the SDK to either lose auth state (redirect loop to login) or fail to
resolve org scopes (falling through to server UI instead of tenant UI).

Replace window.location.href with signIn() to trigger a fresh OIDC flow.
The existing Logto session cookie means auto-approval — no login form.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-26 12:06:39 +02:00
hsiegeln
cfa9d41b36 docs: add email template polish spec, plan, and update GitNexus index
All checks were successful
CI / build (push) Successful in 1m54s
CI / docker (push) Successful in 1m2s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-26 10:37:41 +02:00
hsiegeln
b974f233f4 feat: load email templates from classpath with watermark URL resolution
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 10:35:16 +02:00
hsiegeln
3741ac2658 feat: add branded HTML email templates with desert/caravan copy 2026-04-26 10:31:50 +02:00
hsiegeln
e8a726af80 feat: permit /assets/** for unauthenticated access (email watermark) 2026-04-26 10:30:33 +02:00
hsiegeln
53f0e55e93 feat: add pre-faded logo watermark for email templates 2026-04-26 10:24:02 +02:00
hsiegeln
06d114b46b feat: validate slug uniqueness during onboarding
All checks were successful
CI / build (push) Successful in 1m50s
CI / docker (push) Successful in 1m22s
Add GET /api/onboarding/slug-available endpoint to check if a slug is
already taken. Frontend checks availability with 400ms debounce as the
user types and shows inline feedback. Submit button disabled when slug
is taken. POST /api/onboarding/tenant now returns 409 instead of 500
for duplicate slugs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-26 09:40:17 +02:00
hsiegeln
171ed1a6ab fix: provisioning race condition and noisy ClickHouse logs
Some checks failed
CI / build (push) Successful in 2m3s
CI / docker (push) Successful in 1m30s
SonarQube Analysis / sonarqube (push) Failing after 2m22s
Defer provisionAsync() until after the transaction commits using
TransactionSynchronization.afterCommit(). Previously the @Async thread
raced the @Transactional commit — findById returned null because the
tenant INSERT wasn't visible yet.

Downgrade ClickHouse UNKNOWN_TABLE errors to DEBUG level in
InfrastructureService. These are expected on fresh installs before any
cameleer-server has created the tables.

Make the onboarding slug field read-only (derived from org name).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 22:05:48 +02:00
hsiegeln
dee1f39554 fix: align button icons and polish vendor sidebar
All checks were successful
CI / build (push) Successful in 2m8s
CI / docker (push) Successful in 1m41s
Fix vertical alignment of Lucide icons inside Button children across
all pages by adding verticalAlign offsets (-3px for 16px icons, -2px
for 14px icons). The design system Button wraps children in an inline
span, so SVG icons defaulted to baseline alignment.

Hide the redundant top-right "Create Tenant" button on VendorTenantsPage
when no tenants exist — the EmptyState already provides that action.

Add icons to all vendor sidebar sub-items for consistency (previously
only Email Connector had one).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 21:30:37 +02:00
hsiegeln
adb4ef1af8 fix: enable email sign-in method alongside username in all modes
All checks were successful
CI / build (push) Successful in 1m50s
CI / docker (push) Successful in 59s
The sign-in experience must always include both email+password and
username+password methods. The admin user signs in with their email
(admin@company.com) which the sign-in UI detects as email type.
With only username method enabled, Logto rejects it with "this
sign-in method is not activated."

Fixes both bootstrap Phase 8c and EmailConnectorService disable path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 21:11:07 +02:00
hsiegeln
4cc3e096b5 fix: bootstrap extracts username from admin email for Logto
All checks were successful
CI / build (push) Successful in 1m47s
CI / docker (push) Successful in 20s
Logto rejects @ in usernames. Extract local part (before @) as the
Logto username, use full email as primaryEmail. Also validates admin
user creation succeeded (logs error instead of silently continuing
with null ID).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 21:03:54 +02:00
hsiegeln
1d26ae481e docs: update user manual for current UI and identity model
All checks were successful
CI / build (push) Successful in 1m58s
CI / docker (push) Successful in 21s
- Sign-in instructions: "Enter your email" (not "email or username")
- Troubleshooting: remove reference to deleted "Sign in with Logto" button
- Sidebar navigation: replace outdated single table with vendor console
  and tenant portal sections reflecting current sidebar structure

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 20:53:19 +02:00
hsiegeln
8fe18c7f83 feat: unify admin identity — SAAS_ADMIN_USER is the email in SaaS mode
All checks were successful
CI / build (push) Successful in 1m56s
CI / docker (push) Successful in 1m32s
In SaaS mode, SAAS_ADMIN_USER must be an email address. It's used as
both the Logto username and primaryEmail. No separate SAAS_ADMIN_EMAIL.
Installer enforces email format in SaaS mode (moved deployment mode
question before admin credentials), accepts any username in standalone.
Sign-in form label changed to "Login".

Removes SAAS_ADMIN_EMAIL from bootstrap, compose template, installers,
and all documentation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 20:46:24 +02:00
hsiegeln
929e7d5aed chore: update installer submodule (add SAAS_ADMIN_EMAIL to both installers)
All checks were successful
CI / build (push) Successful in 1m57s
CI / docker (push) Successful in 20s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 20:26:57 +02:00
hsiegeln
3ab6408258 feat: enforce email as primary user identity in SaaS mode
All checks were successful
CI / build (push) Successful in 2m23s
CI / docker (push) Successful in 53s
All users in SaaS mode must have an email address. The bootstrap creates
the admin user with primaryEmail set to SAAS_ADMIN_EMAIL (defaults to
<SAAS_ADMIN_USER>@<PUBLIC_HOST>). This prevents the admin from being
locked out when self-service registration (which requires email) is
enabled via the Email Connector UI.

Documentation updated across all CLAUDE.md files, .env.example,
user-manual.md, and installer submodule (README, .env.example, compose).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 20:23:30 +02:00
hsiegeln
f0aa2b7d3a fix: reset signUp identifiers when disabling registration
All checks were successful
CI / build (push) Successful in 1m45s
CI / docker (push) Successful in 1m17s
When registration is disabled, signUp.identifiers must be reset to
["username"] with verify:false. Otherwise Logto enforces email as a
mandatory profile field on all users, blocking username-only users
(like the admin) from signing in.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 20:08:46 +02:00
hsiegeln
9bf6c17d63 fix: hide registration option when sign-in mode is SignIn only
Some checks failed
CI / build (push) Successful in 2m4s
CI / docker (push) Has been cancelled
Fetch /api/.well-known/sign-in-exp on mount and check signInMode.
If not SignInAndRegister, hide the "Sign up" link and force sign-in
mode (even if ?first_screen=register was in the URL).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 20:06:17 +02:00
hsiegeln
1a4ae5b49b fix: style signed-out page to match sign-in UI
Some checks failed
CI / build (push) Successful in 2m12s
CI / docker (push) Has been cancelled
Use same layout as SignInPage: bg-base background, 400px card,
Cameleer logo with text header, matching font sizes and spacing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 20:03:04 +02:00
hsiegeln
400c32a539 fix: use sessionStorage instead of query param for logout flag
All checks were successful
CI / build (push) Successful in 2m2s
CI / docker (push) Successful in 1m12s
Logto does exact-match on post_logout_redirect_uri, so ?signed_out
caused "not registered" error. Use sessionStorage flag instead —
set before signOut, read and cleared on LoginPage mount.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 19:56:10 +02:00
hsiegeln
2cb818ec71 fix: prevent logout loop by showing signed-out state instead of auto-redirecting
All checks were successful
CI / build (push) Successful in 2m45s
CI / docker (push) Successful in 1m50s
After logout, redirect to /platform/login?signed_out which shows a
"Signed out" card with a "Sign in again" button instead of immediately
redirecting back to Logto OIDC (which would auto-authenticate if the
Logto session cookie persists).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 18:52:26 +02:00
hsiegeln
37668dcfe0 docs: update all documentation for email connector UI migration
All checks were successful
CI / build (push) Successful in 2m3s
CI / docker (push) Successful in 1m34s
- CLAUDE.md: add EmailConnectorService/Controller to vendor package
- .env.example: replace SMTP vars with note about runtime UI config
- docker/CLAUDE.md: update sign-in UI and bootstrap descriptions
- ui/CLAUDE.md: add EmailConfigPage, update sidebar and registration notes
- ui/sign-in/Dockerfile: update connector install comment
- installer: update README, .env.example (submodule)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 18:16:19 +02:00
hsiegeln
40ea6e5e69 docs: update docker CLAUDE.md and installer submodule for SMTP removal
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 18:09:57 +02:00
hsiegeln
6ab0a3c5a1 chore: update installer submodule (remove SMTP from both installers) 2026-04-25 18:08:51 +02:00
hsiegeln
8130f2053d chore: update installer submodule (remove SMTP from compose)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 18:05:42 +02:00
hsiegeln
9da908e4d2 feat: remove SMTP connector from bootstrap, default to sign-in only
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 18:05:17 +02:00
hsiegeln
d0dba73a29 feat: add email connector route and sidebar navigation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 18:03:39 +02:00
hsiegeln
9aa535ace8 feat: add EmailConfigPage with SMTP form, registration toggle, and test email 2026-04-25 18:02:30 +02:00
hsiegeln
f85b5a3634 feat: add React Query hooks for email connector API 2026-04-25 18:00:47 +02:00
hsiegeln
39e1b39f7a feat: add EmailConnectorController with CRUD, test, and registration toggle endpoints 2026-04-25 17:59:40 +02:00
hsiegeln
283d3e34a0 feat: add EmailConnectorService for Logto email connector management 2026-04-25 17:58:26 +02:00
hsiegeln
2cd15509ba feat: add email connector and sign-in experience methods to LogtoManagementClient
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 17:56:37 +02:00
hsiegeln
9d87f71bc1 docs: add email connector UI design spec and implementation plan
Move email connector configuration from installer/bootstrap into the
vendor admin UI for runtime control over SMTP delivery and self-service
registration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 17:50:47 +02:00
hsiegeln
6b77a96d52 fix: handle special characters in passwords during setup
All checks were successful
CI / build (push) Successful in 1m38s
CI / docker (push) Successful in 22s
- Logto entrypoint builds DB_URL from PG_USER/PG_PASSWORD/PG_HOST with
  URL-encoding via node's encodeURIComponent, instead of embedding the
  raw password in the connection string
- Installer submodule updated: passwords single-quoted in .env/.conf

Fixes SMTP and DB auth failures when passwords contain $, &, ;, [, etc.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 16:34:00 +02:00
hsiegeln
c58bf90604 chore: update installer submodule (restore install.ps1)
All checks were successful
CI / build (push) Successful in 1m56s
CI / docker (push) Successful in 21s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 15:32:09 +02:00
hsiegeln
273baf7996 chore: use registry.cameleer.io as default image registry
All checks were successful
CI / build (push) Successful in 2m0s
CI / docker (push) Successful in 59s
Customer-facing image defaults now reference the public registry URL.
Updates installer templates and Spring Boot provisioning defaults.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 15:24:03 +02:00
hsiegeln
5ca118dc93 docs: update CLAUDE.md for installer submodule structure
All checks were successful
CI / build (push) Successful in 1m17s
CI / docker (push) Successful in 14s
Reflect that installer/ is now a git submodule pointing to the public
cameleer-saas-installer repo, and that docker-compose.yml is a thin
dev overlay chained via COMPOSE_FILE.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 13:05:20 +02:00
hsiegeln
0b8cdf6dd9 refactor: move installer to dedicated repo, wire as git submodule
All checks were successful
CI / build (push) Successful in 1m19s
CI / docker (push) Successful in 20s
The installer (install.sh, templates/, bootstrap scripts) now lives in
cameleer/cameleer-saas-installer (public repo). Added as a git submodule
at installer/ so compose templates remain the single source of truth.

Dev compose is now a thin overlay (ports + volume mount + dev env vars).
Production templates are chained via COMPOSE_FILE in .env:
  installer/templates/docker-compose.yml
  installer/templates/docker-compose.saas.yml
  docker-compose.yml (dev overrides)

No code duplication — fixes to compose templates go to the installer
repo and propagate to both production deployments and dev via submodule.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 12:59:44 +02:00
hsiegeln
cafd7e9369 feat: add bootstrap scripts for one-line installer download
All checks were successful
CI / build (push) Successful in 1m39s
CI / docker (push) Successful in 18s
get-cameleer.sh and get-cameleer.ps1 download just the installer
files from Gitea into a local ./installer directory. Usage:

  curl -fsSL https://gitea.siegeln.net/.../get-cameleer.sh | bash
  irm https://gitea.siegeln.net/.../get-cameleer.ps1 | iex

Supports --version=v1.2.0 to pin a specific tag, defaults to main.
Pass --run to auto-execute the installer after download.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 12:48:23 +02:00
hsiegeln
b5068250f9 fix(ui): improve onboarding page styling to match sign-in page
All checks were successful
CI / build (push) Successful in 1m51s
CI / docker (push) Successful in 1m16s
Add 32px card padding and reduce max-width to 420px for consistency
with the sign-in page. Add noValidate to prevent browser-native
required validation outlines on inputs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 12:43:11 +02:00
hsiegeln
0cfa359fc5 fix(sign-in): detect register mode from URL path, not just query param
All checks were successful
CI / build (push) Successful in 1m19s
CI / docker (push) Successful in 44s
Newer Logto versions redirect to /register?app_id=... instead of
/sign-in?first_screen=register. Check the pathname in addition to
the query param so the registration form shows correctly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 10:10:57 +02:00
hsiegeln
5cc9f8c9ef fix(spa): add /register and /onboarding to SPA forward routes
All checks were successful
CI / build (push) Successful in 1m18s
CI / docker (push) Successful in 43s
These routes were missing from SpaController, so requests to
/platform/register and /platform/onboarding had no handler. Spring
forwarded to /error, which isn't in the permitAll() list, resulting
in a 401 instead of serving the SPA.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 10:05:56 +02:00
hsiegeln
b066d1abe7 fix(sign-in): validate email format before registration attempt
All checks were successful
CI / build (push) Successful in 1m17s
CI / docker (push) Successful in 46s
Show "Please enter a valid email address" when the user enters a
username instead of an email in the sign-up form, rather than letting
it hit Logto's API and returning a cryptic 400.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 09:41:41 +02:00
hsiegeln
ae1d9fa4db fix(docker): add extra_hosts so Logto can reach itself via public hostname
All checks were successful
CI / build (push) Successful in 1m14s
CI / docker (push) Successful in 18s
Logto validates M2M tokens by fetching its own JWKS from the ENDPOINT
URL (e.g. https://app.cameleer.io/oidc/jwks). Behind a Cloudflare
tunnel, that hostname resolves to Cloudflare's IP and the container
can't route back through the tunnel — the fetch times out (ETIMEDOUT),
causing all Management API calls to return 500.

Adding extra_hosts maps AUTH_HOST to host-gateway so the request goes
to the Docker host, which has Traefik on :443, which routes back to
Logto internally. This hairpin works because NODE_TLS_REJECT=0 accepts
the self-signed cert.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 09:13:39 +02:00
hsiegeln
6fe10432e6 fix(installer): remove duplicate config load that kills upgrade silently
All checks were successful
CI / build (push) Successful in 1m18s
CI / docker (push) Successful in 15s
The upgrade path in handle_rerun called load_config_file a second time
(already called by detect_existing_install). On the second pass, every
variable is already set, so [ -z "$VAR" ] && VAR="$value" returns
exit code 1 (test fails, && short-circuits). With set -e, the non-zero
exit from the case clause kills the script silently after printing
"[INFO] Upgrading installation..." — no error, no further output.

Removed the redundant load_config_file and load_env_overrides calls.
Both were already executed in main() before handle_rerun is reached.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 09:03:07 +02:00
hsiegeln
9f3faf4816 fix(traefik): set Logto router priority=1 to prevent route hijacking
All checks were successful
CI / build (push) Successful in 1m17s
CI / docker (push) Successful in 18s
Traefik auto-calculates router priority from rule string length. When
deployed with a domain longer than 23 chars (e.g. app.cameleer.io),
Host(`app.cameleer.io`) (25 chars) outranks PathPrefix(`/platform`)
(23 chars), causing ALL requests — including /platform/* — to route
to Logto instead of the SaaS app. This breaks login because the sign-in
UI loads without an OIDC interaction session.

Setting priority=1 makes Logto a true catch-all, matching the intent
documented in docker/CLAUDE.md.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 08:50:16 +02:00
hsiegeln
a60095608e fix(installer): send correct Host header in Traefik routing check
All checks were successful
CI / build (push) Successful in 1m18s
CI / docker (push) Successful in 19s
The root redirect rule matches Host(`PUBLIC_HOST`), not localhost.
Curl with --resolve (bash) and Host header (PS1) so the health
check sends the right hostname when verifying Traefik routing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 08:15:18 +02:00
hsiegeln
9f9112c6a5 feat(installer): add interactive registry prompts
Some checks failed
CI / build (push) Successful in 1m19s
CI / docker (push) Successful in 16s
SonarQube Analysis / sonarqube (push) Failing after 1m47s
Both simple and expert modes now ask "Pull images from a private
registry?" with follow-up prompts for URL, username, and token.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 02:17:52 +02:00
hsiegeln
e1a9f6d225 feat(installer): add --registry, --registry-user, --registry-token
All checks were successful
CI / build (push) Successful in 1m21s
CI / docker (push) Successful in 15s
Both installers (bash + PS1) now support pulling images from a
custom Docker registry. Writes *_IMAGE env vars to .env so compose
templates use the configured registry. Runs docker login before
pull when credentials are provided. Persisted in cameleer.conf
for upgrades/reconfigure.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 02:10:48 +02:00
hsiegeln
180644f0df fix(installer): SIGPIPE crash in generate_password with pipefail
All checks were successful
CI / build (push) Successful in 1m15s
CI / docker (push) Successful in 18s
`tr | head -c 32` causes tr to receive SIGPIPE when head exits early.
With `set -eo pipefail`, exit code 141 kills the script right after
"Configuration validated" before any passwords are generated.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 01:41:47 +02:00
hsiegeln
62b74d2d06 ci: remove sync-images workflow
All checks were successful
CI / build (push) Successful in 1m16s
CI / docker (push) Successful in 16s
Remote server will pull directly from the Gitea registry instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 01:25:56 +02:00