Erweiterungen

This commit is contained in:
2025-08-28 11:35:43 +02:00
parent a95d579e37
commit 8b8cfb6207
3 changed files with 176 additions and 14 deletions

View File

@@ -5,6 +5,7 @@ import de.assecutor.votianlt.repository.AppUserRepository;
import de.assecutor.votianlt.security.SecurityService;
import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -16,14 +17,22 @@ public class AppUserService {
private final AppUserRepository appUserRepository;
private final SecurityService securityService;
private final BCryptPasswordEncoder passwordEncoder;
@Autowired
public AppUserService(AppUserRepository appUserRepository, SecurityService securityService) {
this.appUserRepository = appUserRepository;
this.securityService = securityService;
this.passwordEncoder = new BCryptPasswordEncoder();
}
public AppUser createAppUser(AppUser appUser) {
// Hash the password before saving
if (appUser.getPassword() != null && !appUser.getPassword().isEmpty()) {
String hashedPassword = passwordEncoder.encode(appUser.getPassword());
appUser.setPassword(hashedPassword);
}
// Set creation and update metadata
appUser.setErstelltAm(java.time.LocalDateTime.now());
appUser.setAktualisiertAm(java.time.LocalDateTime.now());
@@ -51,10 +60,29 @@ public class AppUserService {
}
public AppUser updateAppUser(AppUser appUser) {
// Hash the password if it's being updated and not empty
if (appUser.getPassword() != null && !appUser.getPassword().isEmpty()) {
// Only hash if it's not already hashed (BCrypt hashes start with $2a$, $2b$, or $2y$)
if (!appUser.getPassword().startsWith("$2")) {
String hashedPassword = passwordEncoder.encode(appUser.getPassword());
appUser.setPassword(hashedPassword);
}
}
appUser.setAktualisiertAm(java.time.LocalDateTime.now());
appUser.setAktualisiertVon(securityService.getCurrentUserId());
return appUserRepository.save(appUser);
}
/**
* Verify a plain text password against the stored hashed password
* @param plainPassword The plain text password to verify
* @param hashedPassword The stored BCrypt hashed password
* @return true if the password matches, false otherwise
*/
public boolean verifyPassword(String plainPassword, String hashedPassword) {
return passwordEncoder.matches(plainPassword, hashedPassword);
}
public void deleteById(ObjectId id) {
appUserRepository.deleteById(id);

View File

@@ -12,8 +12,11 @@ import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import de.assecutor.votianlt.model.AppUser;
import de.assecutor.votianlt.model.AppDevice;
import de.assecutor.votianlt.pages.service.AppUserService;
import de.assecutor.votianlt.pages.service.AppDeviceService;
import jakarta.annotation.security.RolesAllowed;
import org.springframework.beans.factory.annotation.Autowired;
@PageTitle("App-Nutzer")
@Route(value = "app-user", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
@@ -21,10 +24,13 @@ import jakarta.annotation.security.RolesAllowed;
public class AppUserView extends VerticalLayout {
private final AppUserService appUserService;
private final AppDeviceService appDeviceService;
private final Grid<AppUser> appUserGrid;
public AppUserView(AppUserService appUserService) {
@Autowired
public AppUserView(AppUserService appUserService, AppDeviceService appDeviceService) {
this.appUserService = appUserService;
this.appDeviceService = appDeviceService;
setSizeFull();
setPadding(true);
@@ -57,7 +63,13 @@ public class AppUserView extends VerticalLayout {
appUserGrid.addColumn(AppUser::getTelefon).setHeader("Telefon").setAutoWidth(true);
appUserGrid.addColumn(AppUser::getAppCode).setHeader("App-Code").setAutoWidth(true);
appUserGrid.addColumn(AppUser::getEmail).setHeader("E-Mail").setAutoWidth(true);
appUserGrid.addColumn(AppUser::getGeraet).setHeader("Gerät").setAutoWidth(true);
appUserGrid.addColumn(appUser -> {
if (appUser.getAppDeviceId() != null) {
AppDevice device = appDeviceService.findById(appUser.getAppDeviceId());
return device != null ? device.getName() : "Nicht gefunden";
}
return "Kein Gerät zugewiesen";
}).setHeader("Endgerät").setAutoWidth(true);
// Make grid rows clickable
appUserGrid.setSelectionMode(Grid.SelectionMode.SINGLE);

View File

@@ -11,6 +11,7 @@ import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.orderedlayout.FlexComponent;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.PasswordField;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.binder.Binder;
import com.vaadin.flow.data.binder.ValidationException;
@@ -19,17 +20,23 @@ import com.vaadin.flow.router.HasUrlParameter;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import de.assecutor.votianlt.model.AppUser;
import de.assecutor.votianlt.model.AppDevice;
import de.assecutor.votianlt.pages.service.AppUserService;
import de.assecutor.votianlt.pages.service.AppDeviceService;
import jakarta.annotation.security.RolesAllowed;
import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.ArrayList;
import java.util.List;
@PageTitle("App-Nutzer bearbeiten")
@Route(value = "edit-app-user", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
@RolesAllowed({"USER","ADMIN"})
public class EditAppUserView extends VerticalLayout implements HasUrlParameter<String> {
private final AppUserService appUserService;
private final AppDeviceService appDeviceService;
private AppUser appUser;
private final Binder<AppUser> binder = new Binder<>(AppUser.class);
@@ -40,11 +47,16 @@ public class EditAppUserView extends VerticalLayout implements HasUrlParameter<S
private final TextField phoneField = new TextField("Telefon (Mobil)");
private final TextField appCodeField = new TextField("App-Code");
private final TextField emailField = new TextField("E-Mail-Adresse");
private final ComboBox<String> deviceField = new ComboBox<>("kein Gerät");
private final PasswordField changePasswordField = new PasswordField("Passwort ändern");
private final PasswordField confirmChangePasswordField = new PasswordField("Passwort ändern wiederholen");
private final ComboBox<AppDevice> deviceComboBox = new ComboBox<>("Endgerät");
private ObjectId previousDeviceId;
@Autowired
public EditAppUserView(AppUserService appUserService) {
public EditAppUserView(AppUserService appUserService, AppDeviceService appDeviceService) {
this.appUserService = appUserService;
this.appDeviceService = appDeviceService;
setSizeFull();
setPadding(true);
setSpacing(true);
@@ -102,9 +114,16 @@ public class EditAppUserView extends VerticalLayout implements HasUrlParameter<S
appCodeField.setWidthFull();
emailField.setWidthFull();
// Configure password fields
changePasswordField.setWidthFull();
changePasswordField.setPlaceholder("Leer lassen, wenn nicht ändern");
confirmChangePasswordField.setWidthFull();
confirmChangePasswordField.setPlaceholder("Leer lassen, wenn nicht ändern");
// Configure device dropdown
deviceField.setItems("kein Gerät", "iPhone", "Android", "Tablet", "Desktop");
deviceField.setWidthFull();
deviceComboBox.setWidthFull();
deviceComboBox.setItemLabelGenerator(device -> device.getName());
deviceComboBox.setPlaceholder("Bitte wählen...");
// Add fields to form
formLayout.add(designationField);
@@ -112,7 +131,9 @@ public class EditAppUserView extends VerticalLayout implements HasUrlParameter<S
formLayout.add(phoneField);
formLayout.add(appCodeField);
formLayout.add(emailField);
formLayout.add(deviceField);
formLayout.add(changePasswordField);
formLayout.add(confirmChangePasswordField);
formLayout.add(deviceComboBox);
contentContainer.add(formLayout);
@@ -146,7 +167,11 @@ public class EditAppUserView extends VerticalLayout implements HasUrlParameter<S
binder.forField(phoneField).bind(AppUser::getTelefon, AppUser::setTelefon);
binder.forField(appCodeField).bind(AppUser::getAppCode, AppUser::setAppCode);
binder.forField(emailField).bind(AppUser::getEmail, AppUser::setEmail);
binder.forField(deviceField).bind(AppUser::getGeraet, AppUser::setGeraet);
binder.forField(deviceComboBox)
.bind(
appUser -> getCurrentDevice(appUser), // Get current device
(appUser, appDevice) -> appUser.setAppDeviceId(appDevice != null ? appDevice.getId() : null)
);
}
@Override
@@ -154,13 +179,11 @@ public class EditAppUserView extends VerticalLayout implements HasUrlParameter<S
try {
ObjectId appUserId = new ObjectId(parameter);
appUser = appUserService.findById(appUserId);
if (appUser == null) {
Notification.show("App-Nutzer nicht gefunden", 3000, Notification.Position.MIDDLE);
navigateBack();
return;
}
previousDeviceId = appUser.getAppDeviceId();
// Setup device ComboBox with available devices
setupDeviceComboBox();
// Load app user data into form
binder.readBean(appUser);
@@ -172,7 +195,51 @@ public class EditAppUserView extends VerticalLayout implements HasUrlParameter<S
private void saveAppUser() {
try {
// Validate password fields first
if (!validatePasswordFields()) {
return;
}
// Save current password to restore if not changing
String originalPassword = appUser.getPassword();
binder.writeBean(appUser);
// Handle password change logic
String newPassword = changePasswordField.getValue();
String confirmPassword = confirmChangePasswordField.getValue();
if (newPassword != null && !newPassword.trim().isEmpty()) {
// User wants to change password
if (confirmPassword != null && newPassword.equals(confirmPassword)) {
// Passwords match, set new password for hashing
appUser.setPassword(newPassword);
} else {
Notification.show("Passwörter stimmen nicht überein", 3000, Notification.Position.MIDDLE);
return;
}
} else {
// No password change requested, restore original password
appUser.setPassword(originalPassword);
}
// Handle device assignment changes
AppDevice selectedDevice = deviceComboBox.getValue();
if (selectedDevice.getId() != previousDeviceId) {
AppDevice previousDevice = appDeviceService.findById(previousDeviceId);
if (previousDevice != null) {
previousDevice.setAppUserId(null);
appDeviceService.updateAppDevice(previousDevice);
}
AppDevice newDevice = appDeviceService.findById(selectedDevice.getId());
if (newDevice != null) {
newDevice.setAppUserId(appUser.getId());
appDeviceService.updateAppDevice(newDevice);
}
}
appUserService.updateAppUser(appUser);
Notification.show("App-Nutzer erfolgreich gespeichert", 3000, Notification.Position.MIDDLE);
navigateBack();
@@ -180,6 +247,61 @@ public class EditAppUserView extends VerticalLayout implements HasUrlParameter<S
Notification.show("Bitte überprüfen Sie die Eingaben", 3000, Notification.Position.MIDDLE);
}
}
private boolean validatePasswordFields() {
String newPassword = changePasswordField.getValue();
String confirmPassword = confirmChangePasswordField.getValue();
// If one field is filled, both must be filled
boolean newPasswordFilled = newPassword != null && !newPassword.trim().isEmpty();
boolean confirmPasswordFilled = confirmPassword != null && !confirmPassword.trim().isEmpty();
if (newPasswordFilled && !confirmPasswordFilled) {
Notification.show("Bitte bestätigen Sie das neue Passwort", 3000, Notification.Position.MIDDLE);
return false;
}
if (!newPasswordFilled && confirmPasswordFilled) {
Notification.show("Bitte geben Sie das neue Passwort ein", 3000, Notification.Position.MIDDLE);
return false;
}
// If both are filled, they must match
if (newPasswordFilled && confirmPasswordFilled && !newPassword.equals(confirmPassword)) {
Notification.show("Passwörter stimmen nicht überein", 3000, Notification.Position.MIDDLE);
return false;
}
return true;
}
private AppDevice getCurrentDevice(AppUser appUser) {
if (appUser != null && appUser.getAppDeviceId() != null) {
return appDeviceService.findById(appUser.getAppDeviceId());
}
return null;
}
private void setupDeviceComboBox() {
List<AppDevice> availableDevices = new ArrayList<>();
// First, add the currently assigned device if it exists
AppDevice currentDevice = getCurrentDevice(appUser);
if (currentDevice != null) {
availableDevices.add(currentDevice);
}
// Then add all unassigned devices
List<AppDevice> unassignedDevices = appDeviceService.findUnassignedDevices();
availableDevices.addAll(unassignedDevices);
deviceComboBox.setItems(availableDevices);
// Set the current device as selected
if (currentDevice != null) {
deviceComboBox.setValue(currentDevice);
}
}
private void deleteAppUser() {
// Show confirmation dialog