- 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>
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>
- 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>
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>
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>
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>
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>
- 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>
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>
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>
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>
- 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>
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>
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>
- 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>
- 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>
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>
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>
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>
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 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>
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>