From 292adeea4c423dee68dba39aef4c4594f62a1458 Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Mon, 27 Apr 2026 11:51:12 +0200 Subject: [PATCH] docs: update documentation for passkey MFA feature - 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) --- CLAUDE.md | 6 ++++-- .../specs/2026-04-27-passkey-mfa-design.md | 2 +- .../siegeln/cameleer/saas/config/CLAUDE.md | 19 ++++++++++++++++++- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index b7d5f2a..d6baa7f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -27,7 +27,7 @@ Agent-server protocol is defined in `cameleer/cameleer-common/PROTOCOL.md`. The |---------|---------|-------------| | `config/` | Security, tenant isolation, web config | `SecurityConfig`, `TenantIsolationInterceptor`, `TenantContext`, `PublicConfigController`, `MeController` | | `tenant/` | Tenant data model | `TenantEntity` (JPA: id, name, slug, tier, status, logto_org_id, db_password) | -| `vendor/` | Vendor console (platform:admin) | `VendorTenantService`, `VendorTenantController`, `InfrastructureService`, `EmailConnectorService`, `EmailConnectorController` | +| `vendor/` | Vendor console (platform:admin) | `VendorTenantService`, `VendorTenantController`, `InfrastructureService`, `EmailConnectorService`, `EmailConnectorController`, `VendorAuthPolicyController`, `VendorAuthPolicyEntity` | | `onboarding/` | Self-service sign-up onboarding | `OnboardingController`, `OnboardingService` | | `portal/` | Tenant admin portal (org-scoped) | `TenantPortalService`, `TenantPortalController` | | `provisioning/` | Pluggable tenant provisioning | `DockerTenantProvisioner`, `TenantDatabaseService`, `TenantDataCleanupService` | @@ -56,6 +56,8 @@ For detailed architecture docs, see the directory-scoped CLAUDE.md files (loaded PostgreSQL (Flyway): `src/main/resources/db/migration/` - V001 — consolidated baseline: tenants (with db_password, server_endpoint, provision_error, ca_applied_at), licenses, audit_log, certificates, tenant_ca_certs +- V002 — license minter: signing_keys table, tier renames, license label + grace period +- V003 — passkey MFA: vendor_auth_policy single-row config table (mfa_mode, passkey_enabled, passkey_mode) ## Related Conventions @@ -79,7 +81,7 @@ PostgreSQL (Flyway): `src/main/resources/db/migration/` # GitNexus — Code Intelligence -This project is indexed by GitNexus as **cameleer-saas** (3336 symbols, 7094 relationships, 281 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. +This project is indexed by GitNexus as **cameleer-saas** (3330 symbols, 7090 relationships, 281 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. > If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first. diff --git a/docs/superpowers/specs/2026-04-27-passkey-mfa-design.md b/docs/superpowers/specs/2026-04-27-passkey-mfa-design.md index 26a6728..4c19a4a 100644 --- a/docs/superpowers/specs/2026-04-27-passkey-mfa-design.md +++ b/docs/superpowers/specs/2026-04-27-passkey-mfa-design.md @@ -1,7 +1,7 @@ # Passkey MFA Design **Date:** 2026-04-27 -**Status:** Draft +**Status:** Implemented **Goal:** Add passkeys (WebAuthn) as an MFA factor alongside existing TOTP, with a long-term path toward passwordless sign-in. ## Motivation diff --git a/src/main/java/net/siegeln/cameleer/saas/config/CLAUDE.md b/src/main/java/net/siegeln/cameleer/saas/config/CLAUDE.md index 46c3158..1f14417 100644 --- a/src/main/java/net/siegeln/cameleer/saas/config/CLAUDE.md +++ b/src/main/java/net/siegeln/cameleer/saas/config/CLAUDE.md @@ -13,7 +13,24 @@ - Org roles: `owner` -> `server:admin` + `tenant:manage`, `operator` -> `server:operator`, `viewer` -> `server:viewer` - `saas-vendor` global role created by bootstrap Phase 12 and always assigned to the admin user — has `platform:admin` + all tenant scopes - Custom `JwtDecoder` in `SecurityConfig.java` — ES384 algorithm, `at+jwt` token type, split issuer-uri (string validation) / jwk-set-uri (Docker-internal fetch), audience validation (`https://api.cameleer.local`) -- Logto Custom JWT (Phase 7b in bootstrap) injects a `roles` claim into access tokens based on org roles and global roles — this makes role data available to the server without Logto-specific code +- Logto Custom JWT (Phase 7b in bootstrap) injects claims into access tokens: `roles` (org role mapping), `mfa_enrolled` (true if TOTP or WebAuthn factor), `passkey_enrolled` (true if WebAuthn factor), `mfa_method_preference` (from user custom data) + +## MFA & Passkey enforcement + +Two independent policy domains — **no inheritance**, vendor and tenant policies are separate scopes: + +| Policy | Scope | Who it affects | Stored in | +|--------|-------|---------------|-----------| +| **Vendor auth policy** | Platform logins (`/api/vendor/**`, `/api/portal/**`) | Tenant admins | `vendor_auth_policy` table (single-row) | +| **Tenant auth policy** | Org user logins (`/api/tenant/**`) | Org members | `tenants.settings` JSONB (`mfaMode`, `passkeyEnabled`, `passkeyMode`) | + +- `MfaEnforcementFilter` (after `BearerTokenAuthenticationFilter`) checks JWT claims `mfa_enrolled` and `passkey_enrolled` against effective policy +- Error codes: `APP_MFA_REQUIRED`, `APP_PASSKEY_REQUIRED` (returned via `X-Cameleer-Error` header) +- Exempt routes: `/api/tenant/mfa/`, `/api/config`, `/api/me`, `/api/onboarding`, `/api/vendor/auth-policy`, `/api/tenant/auth-settings` +- Backward-compatible: legacy `mfaRequired: true` in settings treated as `mfaMode: "required"` +- Policy settings: `mfa_mode` (off/optional/required), `passkey_enabled` (bool), `passkey_mode` (optional/preferred/required) +- Passkey registration only via Logto Experience API during sign-in (Approach A: Logto-native WebAuthn) +- Passkey management (list/rename/delete) via Logto Management API through SaaS backend endpoints ## Auth routing by persona