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>
Introduces ApiError class in client.ts that parses Spring Boot error
bodies to extract human-readable messages (message, error, detail fields).
Adds errorMessage() helper used by all toast descriptions instead of
raw String(err) which dumped JSON blobs to the user.
Affected: all 10 page components that display error toasts.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The actions/cache restored a stale ~/.m2/repository with the old
cameleer-license-minter SNAPSHOT (pre-license-api extraction).
Adding -U forces re-resolution of SNAPSHOT dependencies.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The BuildKit cache mount for ~/.m2/repository persists the old
cameleer-license-minter SNAPSHOT (which depended on server-core).
Adding -U forces Maven to re-resolve SNAPSHOTs from the Gitea
registry, picking up the updated minter that depends on license-api.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
CI needs to resolve com.cameleer:cameleer-license-minter from the
Gitea package registry. Without this repository declaration, the
dependency only resolved from the local Maven cache.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Vendor UI:
- TenantDetailPage: full minting form with tier presets, 13 configurable
limits, expiry/grace period, label. Mint & Push or Mint & Copy actions.
License bundle display with all three env vars for standalone deployment.
- LicenseVerifyPage: paste token to decode + validate signature, shows
envelope details and state badge. Public key viewer with copy button.
- Layout: added "License Tools" nav item under Vendor section.
- vendor-hooks: useMintLicense, useLicensePresets, useVerifyLicense, usePublicKey
Tenant UI:
- TenantLicensePage: replaced features card with full 13-key limits display,
added grace period and label fields
- TenantDashboardPage: fixed limit keys (agents→max_agents, environments→max_environments)
Common:
- Updated types (dropped features, added label/gracePeriodDays/bundle types)
- Updated tier colors for STARTER/TEAM/BUSINESS/ENTERPRISE
- Updated CreateTenantPage tier dropdown
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
DockerTenantProvisioner now injects CAMELEER_SERVER_LICENSE_PUBLICKEY
env var on provisioned server containers, enabling cryptographic
license validation. SigningKeyService passed through auto-config.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- POST /vendor/tenants/{id}/license now accepts MintLicenseRequest body
with custom limits, expiresAt, gracePeriodDays, label, pushToServer
- Returns LicenseBundleResponse with token + public key + tenant slug
- GET /vendor/license-presets returns tier preset limits
- POST /vendor/license/verify decodes and validates signed tokens
- GET /vendor/signing-key/public returns the Ed25519 public key
- VendorTenantService.mintLicense() supports configurable minting
- Updated portal DTOs to drop features, add label + gracePeriodDays
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaces UUID token generation with LicenseMinter.mint() from
cameleer-license-minter. Adds full-control generateLicense() overload
accepting custom limits, expiry, grace period, and label.
Adds verifyToken() and verifyTokenSignature() using LicenseValidator.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Entity, repository, and service for generating and storing Ed25519
signing keys. Lazy-initializes on first call — generates keypair
and persists to signing_keys table.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Tier enum: LOW→STARTER, MID→TEAM, HIGH→BUSINESS, BUSINESS→ENTERPRISE
- LicenseDefaults: 13-key limits per tier matching server handoff cap matrix
- Drop features concept from LicenseEntity, LicenseResponse, portal DTOs
- Add label and gracePeriodDays to LicenseEntity
- Fix agent limit key from 'agents' to 'max_agents' in VendorTenantController
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds testSmtpConnection() that performs EHLO + auth via JavaMailSender
before persisting to Logto — saves fail fast with a clear error if
SMTP credentials are wrong. Password is now optional when editing:
if left blank the backend fetches the existing password from Logto's
connector config, so users can update host/port/fromEmail without
re-entering the password every time.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
GMX webmail broke after adding <!DOCTYPE html><html><head><body>
wrappers — the Logto SMTP connector sets these as nodemailer's html
field, and GMX's sanitizer chokes on a full document inside its own
page shell. Reverts to bare HTML fragments (the format that worked
before 12:17 commit 484a388) while keeping the extra text paragraphs
added for mail checker text-to-HTML ratio compliance.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>