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
2 changed files with 34 additions and 19 deletions
Showing only changes of commit 372d3c77a0 - Show all commits

View File

@@ -107,12 +107,8 @@ public class TenantPortalController {
@PostMapping("/password")
public ResponseEntity<Void> changeOwnPassword(@AuthenticationPrincipal Jwt jwt,
@RequestBody PasswordChangeRequest body) {
try {
portalService.changePassword(jwt.getSubject(), body.password());
return ResponseEntity.noContent().build();
} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest().build();
}
portalService.changePassword(jwt.getSubject(), body.password());
return ResponseEntity.noContent().build();
}
@PostMapping("/team/{userId}/password")
@@ -203,23 +199,15 @@ public class TenantPortalController {
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();
}
portalService.renamePasskey(jwt.getSubject(), id, name);
return ResponseEntity.noContent().build();
}
@DeleteMapping("/mfa/webauthn/{id}")
public ResponseEntity<Void> deletePasskey(@AuthenticationPrincipal Jwt jwt,
@PathVariable String id) {
try {
portalService.deletePasskey(jwt.getSubject(), id);
return ResponseEntity.noContent().build();
} catch (IllegalArgumentException e) {
return ResponseEntity.notFound().build();
}
portalService.deletePasskey(jwt.getSubject(), id);
return ResponseEntity.noContent().build();
}
@PostMapping("/mfa/method-preference")

View File

@@ -2,6 +2,7 @@ package net.siegeln.cameleer.saas.vendor;
import net.siegeln.cameleer.saas.account.AccountService;
import net.siegeln.cameleer.saas.identity.LogtoManagementClient;
import net.siegeln.cameleer.saas.notification.PasswordResetNotificationService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
@@ -20,13 +21,16 @@ public class VendorAdminService {
private final LogtoManagementClient logtoClient;
private final AccountService accountService;
private final EmailConnectorService emailConnectorService;
private final PasswordResetNotificationService passwordNotificationService;
public VendorAdminService(LogtoManagementClient logtoClient,
AccountService accountService,
EmailConnectorService emailConnectorService) {
EmailConnectorService emailConnectorService,
PasswordResetNotificationService passwordNotificationService) {
this.logtoClient = logtoClient;
this.accountService = accountService;
this.emailConnectorService = emailConnectorService;
this.passwordNotificationService = passwordNotificationService;
}
// --- Records ---
@@ -107,13 +111,36 @@ public class VendorAdminService {
}
public void resetAdminPassword(String userId, String newPassword) {
verifyIsVendorAdmin(userId);
accountService.validatePassword(newPassword);
logtoClient.updateUserPassword(userId, newPassword);
log.info("Reset password for vendor admin {}", userId);
// Send notification email
try {
var user = logtoClient.getUser(userId);
if (user != null) {
String email = String.valueOf(user.getOrDefault("primaryEmail", ""));
if (!email.isBlank()) {
passwordNotificationService.sendNotification(email);
}
}
} catch (Exception e) {
log.warn("Failed to send password reset notification: {}", e.getMessage());
}
}
public void resetAdminMfa(String userId) {
verifyIsVendorAdmin(userId);
logtoClient.deleteAllMfaVerifications(userId);
log.info("Reset MFA for vendor admin {}", userId);
}
private void verifyIsVendorAdmin(String userId) {
boolean isAdmin = listAdmins().stream()
.anyMatch(a -> userId.equals(a.userId()));
if (!isAdmin) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "User is not a platform administrator");
}
}
}