From b3104dc410b14444807ebf063250dd9b52b68216 Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Mon, 27 Apr 2026 08:45:52 +0200 Subject: [PATCH] feat: add passkey and auth settings endpoints to TenantPortalController --- .../saas/portal/TenantPortalController.java | 75 ++++++++++++++++++- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/siegeln/cameleer/saas/portal/TenantPortalController.java b/src/main/java/net/siegeln/cameleer/saas/portal/TenantPortalController.java index 4fe98ea..c864aec 100644 --- a/src/main/java/net/siegeln/cameleer/saas/portal/TenantPortalController.java +++ b/src/main/java/net/siegeln/cameleer/saas/portal/TenantPortalController.java @@ -22,6 +22,7 @@ import org.springframework.web.multipart.MultipartFile; import java.time.Instant; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.UUID; @RestController @@ -185,6 +186,65 @@ public class TenantPortalController { } } + // --- Passkey endpoints --- + + @GetMapping("/mfa/webauthn") + public ResponseEntity> listPasskeys( + @AuthenticationPrincipal Jwt jwt) { + return ResponseEntity.ok(portalService.listPasskeys(jwt.getSubject())); + } + + @PatchMapping("/mfa/webauthn/{id}/name") + public ResponseEntity renamePasskey(@AuthenticationPrincipal Jwt jwt, + @PathVariable String id, + @RequestBody Map body) { + String name = body.get("name"); + if (name == null || name.isBlank()) { + return ResponseEntity.badRequest().build(); + } + try { + portalService.renamePasskey(jwt.getSubject(), id, name); + return ResponseEntity.noContent().build(); + } catch (IllegalArgumentException e) { + return ResponseEntity.notFound().build(); + } + } + + @DeleteMapping("/mfa/webauthn/{id}") + public ResponseEntity deletePasskey(@AuthenticationPrincipal Jwt jwt, + @PathVariable String id) { + try { + portalService.deletePasskey(jwt.getSubject(), id); + return ResponseEntity.noContent().build(); + } catch (IllegalArgumentException e) { + return ResponseEntity.notFound().build(); + } + } + + @PostMapping("/mfa/method-preference") + public ResponseEntity updateMfaMethodPreference(@AuthenticationPrincipal Jwt jwt, + @RequestBody Map body) { + String preference = body.get("preference"); + if (preference == null || !Set.of("totp", "webauthn").contains(preference)) { + return ResponseEntity.badRequest().build(); + } + portalService.updateMfaMethodPreference(jwt.getSubject(), preference); + return ResponseEntity.noContent().build(); + } + + // --- Auth settings endpoints --- + + @GetMapping("/auth-settings") + public ResponseEntity getAuthSettings() { + return ResponseEntity.ok(portalService.getAuthSettings()); + } + + @PutMapping("/auth-settings") + public ResponseEntity updateAuthSettings(@RequestBody Map updates) { + portalService.updateTenantSettings(updates); + return ResponseEntity.ok().build(); + } + @PatchMapping("/settings") public ResponseEntity updateSettings(@RequestBody Map updates) { portalService.updateTenantSettings(updates); @@ -199,8 +259,19 @@ public class TenantPortalController { } var tenant = tenantOpt.get(); Map settings = tenant.getSettings() != null ? tenant.getSettings() : Map.of(); - boolean mfaRequired = Boolean.TRUE.equals(settings.get("mfaRequired")); - return ResponseEntity.ok(Map.of("mfaRequired", mfaRequired)); + String mfaMode = settings.containsKey("mfaMode") + ? String.valueOf(settings.get("mfaMode")) + : (Boolean.TRUE.equals(settings.get("mfaRequired")) ? "required" : "off"); + boolean passkeyEnabled = Boolean.TRUE.equals(settings.get("passkeyEnabled")); + String passkeyMode = settings.containsKey("passkeyMode") + ? String.valueOf(settings.get("passkeyMode")) + : "optional"; + return ResponseEntity.ok(Map.of( + "mfaRequired", "required".equals(mfaMode), + "mfaMode", mfaMode, + "passkeyEnabled", passkeyEnabled, + "passkeyMode", passkeyMode + )); } // --- CA Certificate management ---