GMX webmail strips <head> content including <style> blocks, rendering
emails as unstyled plain text. Reverts to inline styles (the only
reliable approach for email HTML) while keeping the proper HTML document
structure and extra text content added for mail checker compliance.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The registration flow hit a 422 on /api/experience/submit when MFA
policy is UserControlled. Adds the same trySubmit + skipMfaBinding
pattern already used in the sign-in flow — Logto confirms mfa-skipped
works for both SignIn and Register interaction events.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The previous commit incorrectly removed the watermark — only the
style extraction into <style> blocks was requested. Restores the
watermark <img>, {{watermarkUrl}} placeholder resolution in both
EmailConnectorService and PasswordResetNotificationService, and
the corresponding test assertions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extracts repeated inline styles into <head> <style> to improve the
text-to-HTML ratio flagged by mail checkers. Removes the decorative
watermark <img> (opacity 0.07, barely visible) which was the only
image element and triggered the "too many images" classification.
Cleans up the now-unused ProvisioningProperties dependency from
EmailConnectorService and PasswordResetNotificationService.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Mail checkers flagged missing <html> tag and insufficient text content.
Wraps all 5 templates in DOCTYPE/html/head/body, adds Outlook conditional
comments, and includes a descriptive paragraph to improve text-to-image ratio.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Logto returns 422 with an MFA recommendation when policy is
UserControlled. Call POST /profile/mfa/mfa-skipped to skip the
binding prompt, then re-submit. Users who already have MFA enrolled
still get the TOTP verification flow.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Change Button size="small" to size="sm" (design system API)
- Remove unsupported style prop from Card component
- Ensure qrcode.react is properly installed
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Covers JWT mfa_enrolled claim, enforcement model (APP_MFA_REQUIRED),
Logto Management API contract for TOTP enrollment and backup codes,
UX requirements, and error states.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds a Reset MFA button in the Actions column and an inline confirmation
card (with warning Alert) that calls useResetTeamMemberMfa on confirm.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds two new sections to the tenant Settings page:
- MfaSection: TOTP authenticator setup with QR code, 6-digit verification,
backup code display (2-column grid with copy/download), and MFA removal
- MfaEnforcementToggle: tenant admin control to require MFA for all members,
with confirmation dialog before enabling
Installs qrcode.react for QR code rendering. Uses existing MFA hooks from
tenant-hooks.ts and design-system components.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds POST /api/password-reset-notification (public, rate-limited 3/10min)
that sends a branded HTML security notification email via the runtime-
configured Logto SMTP connector. Uses spring-boot-starter-mail with a
programmatic JavaMailSender built from the connector's live credentials.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add 5 new methods for MFA operations via Logto Management API:
- getUserMfaVerifications: list all MFA factors for a user
- createTotpVerification: create TOTP MFA verification
- createBackupCodes: generate backup codes
- deleteMfaVerification: delete a specific MFA verification
- deleteAllMfaVerifications: delete all MFA verifications (admin reset)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add security notification email after password reset (warns MFA
was not required, recommends enabling it)
- Use distinct APP_MFA_REQUIRED error code + X-Cameleer-Error header
for MFA enforcement 403s to avoid collision with generic access denied
- Make backup code fallback prominent in MFA verification UI (visible
secondary action, not a subtle link)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Covers self-service password reset via Logto Experience API,
TOTP + backup code MFA with per-tenant enforcement via JWT claims,
and a server handoff document for cameleer-server MFA enrollment.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Email-registered users have no name field in Logto, causing empty OIDC
name claims. After adding user to org, derive display name from email
local part (john.doe@acme.com -> john.doe) if name is not already set.
Also adds updateUserProfile() to LogtoManagementClient.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove width/height HTML attributes and add border:0;outline:none to
the watermark img tag so broken-image placeholders collapse gracefully
when email clients block remote images.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
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>
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>
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>
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>
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>
- 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>
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>
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>
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>
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>
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>
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>
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>