feat: vendor admin management and shared account settings #59

Merged
hsiegeln merged 19 commits from feature/vendor-admin-account-settings into main 2026-04-27 15:20:23 +02:00
Showing only changes of commit b63e5e9c81 - Show all commits

View File

@@ -0,0 +1,114 @@
package net.siegeln.cameleer.saas.account;
import net.siegeln.cameleer.saas.account.AccountService.*;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/account")
public class AccountController {
private final AccountService accountService;
public AccountController(AccountService accountService) {
this.accountService = accountService;
}
// --- Profile ---
@GetMapping("/profile")
public ProfileData getProfile(@AuthenticationPrincipal Jwt jwt) {
return accountService.getProfile(jwt.getSubject());
}
@PatchMapping("/profile")
public ResponseEntity<Void> updateProfile(@AuthenticationPrincipal Jwt jwt,
@RequestBody Map<String, String> body) {
String name = body.get("name");
accountService.updateDisplayName(jwt.getSubject(), name);
return ResponseEntity.noContent().build();
}
// --- Password ---
record PasswordChangeRequest(String currentPassword, String newPassword) {}
@PostMapping("/password")
public ResponseEntity<Void> changePassword(@AuthenticationPrincipal Jwt jwt,
@RequestBody PasswordChangeRequest request) {
accountService.changePassword(jwt.getSubject(), request.currentPassword(), request.newPassword());
return ResponseEntity.noContent().build();
}
// --- MFA ---
@GetMapping("/mfa/status")
public MfaStatusData getMfaStatus(@AuthenticationPrincipal Jwt jwt) {
return accountService.getMfaStatus(jwt.getSubject());
}
@PostMapping("/mfa/totp/setup")
public MfaSetupData setupTotp(@AuthenticationPrincipal Jwt jwt) {
return accountService.setupTotp(jwt.getSubject());
}
record TotpVerifyRequest(String secret, String code) {}
@PostMapping("/mfa/totp/verify")
public Map<String, Boolean> verifyTotp(@AuthenticationPrincipal Jwt jwt,
@RequestBody TotpVerifyRequest request) {
boolean ok = accountService.verifyTotpCode(request.secret(), request.code());
return Map.of("verified", ok);
}
@PostMapping("/mfa/backup-codes")
public BackupCodesData generateBackupCodes(@AuthenticationPrincipal Jwt jwt) {
return accountService.generateBackupCodes(jwt.getSubject());
}
@DeleteMapping("/mfa/totp")
public ResponseEntity<Void> removeTotp(@AuthenticationPrincipal Jwt jwt) {
accountService.removeMfa(jwt.getSubject());
return ResponseEntity.noContent().build();
}
// --- Passkeys ---
@GetMapping("/mfa/webauthn")
public List<PasskeyCredential> listPasskeys(@AuthenticationPrincipal Jwt jwt) {
return accountService.listPasskeys(jwt.getSubject());
}
@PatchMapping("/mfa/webauthn/{id}/name")
public ResponseEntity<Void> renamePasskey(@AuthenticationPrincipal Jwt jwt,
@PathVariable String id,
@RequestBody Map<String, String> body) {
String name = body.get("name");
if (name == null || name.isBlank()) {
return ResponseEntity.badRequest().build();
}
accountService.renamePasskey(jwt.getSubject(), id, name.trim());
return ResponseEntity.noContent().build();
}
@DeleteMapping("/mfa/webauthn/{id}")
public ResponseEntity<Void> deletePasskey(@AuthenticationPrincipal Jwt jwt,
@PathVariable String id) {
accountService.deletePasskey(jwt.getSubject(), id);
return ResponseEntity.noContent().build();
}
// --- MFA Preference ---
@PostMapping("/mfa/method-preference")
public ResponseEntity<Void> setMfaPreference(@AuthenticationPrincipal Jwt jwt,
@RequestBody Map<String, String> body) {
accountService.setMfaMethodPreference(jwt.getSubject(), body.get("preference"));
return ResponseEntity.noContent().build();
}
}