From e8904b9667a2aa063d2e0c78646152c2634b475b Mon Sep 17 00:00:00 2001 From: Sven Carstensen Date: Mon, 26 Jan 2026 10:55:59 +0100 Subject: [PATCH] Erweiterungen --- pom.xml | 2 +- .../de/assecutor/votianlt/model/User.java | 3 +++ .../votianlt/pages/view/AddAppUserView.java | 5 +++++ .../votianlt/pages/view/EditProfileView.java | 10 +++++++++- .../votianlt/pages/view/LoginView.java | 14 +++++++++++-- .../votianlt/pages/view/ShowJobsView.java | 20 +++++++++++++++---- .../votianlt/pages/view/StartView.java | 13 ++++++++++-- src/main/resources/application.properties | 4 ++-- 8 files changed, 59 insertions(+), 12 deletions(-) diff --git a/pom.xml b/pom.xml index 730b6c2..e303a17 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ de.assecutor.votianlt votianlt - 0.8.1 + 0.8.2 jar diff --git a/src/main/java/de/assecutor/votianlt/model/User.java b/src/main/java/de/assecutor/votianlt/model/User.java index 79c9be1..8835f39 100644 --- a/src/main/java/de/assecutor/votianlt/model/User.java +++ b/src/main/java/de/assecutor/votianlt/model/User.java @@ -60,4 +60,7 @@ public class User { // Digitale Abwicklung und App-Nutzer Ortung private boolean digitalProcessingEnabled = true; // Digitale Abwicklung per App private boolean locationTrackingEnabled = true; // App-Nutzer orten + + // 2-Faktor-Authentifizierung (standardmäßig aktiviert für neue Nutzer) + private boolean twoFactorEnabled = true; } \ No newline at end of file diff --git a/src/main/java/de/assecutor/votianlt/pages/view/AddAppUserView.java b/src/main/java/de/assecutor/votianlt/pages/view/AddAppUserView.java index 5fefa4b..2729961 100644 --- a/src/main/java/de/assecutor/votianlt/pages/view/AddAppUserView.java +++ b/src/main/java/de/assecutor/votianlt/pages/view/AddAppUserView.java @@ -25,6 +25,7 @@ import org.springframework.beans.factory.annotation.Autowired; @Route(value = "add-app-user", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class) @RolesAllowed({ "USER", "ADMIN" }) public class AddAppUserView extends VerticalLayout { + private final AppUserService appUserService; private final Binder binder = new Binder<>(AppUser.class); // Form fields @@ -38,6 +39,7 @@ public class AddAppUserView extends VerticalLayout { @Autowired public AddAppUserView(AppUserService appUserService) { + this.appUserService = appUserService; setSizeFull(); setPadding(true); setSpacing(true); @@ -179,6 +181,9 @@ public class AddAppUserView extends VerticalLayout { AppUser newAppUser = new AppUser(); binder.writeBean(newAppUser); + // Save to database + appUserService.createAppUser(newAppUser); + // Show success message Notification.show("App-Nutzer erfolgreich angelegt", 3000, Notification.Position.MIDDLE); diff --git a/src/main/java/de/assecutor/votianlt/pages/view/EditProfileView.java b/src/main/java/de/assecutor/votianlt/pages/view/EditProfileView.java index 1e12b4b..9eca7a3 100644 --- a/src/main/java/de/assecutor/votianlt/pages/view/EditProfileView.java +++ b/src/main/java/de/assecutor/votianlt/pages/view/EditProfileView.java @@ -394,11 +394,19 @@ public class EditProfileView extends HorizontalLayout { // 2-Faktor Auth Checkbox twoFactor = new Checkbox("2-Faktor-Authentifizierung"); + twoFactor.setValue(currentUser.isTwoFactorEnabled()); + twoFactor.addValueChangeListener(e -> currentUser.setTwoFactorEnabled(e.getValue())); Icon twoFactorInfo = VaadinIcon.QUESTION_CIRCLE_O.create(); twoFactorInfo.getStyle().set("marginLeft", "0.3em"); HorizontalLayout twoFactorLayout = new HorizontalLayout(twoFactor, twoFactorInfo); twoFactorLayout.setAlignItems(Alignment.CENTER); - securityTab.add(twoFactorLayout); + + Span twoFactorDescription = new Span("Bei Aktivierung wird bei jeder Anmeldung ein Code per E-Mail gesendet"); + twoFactorDescription.getStyle().set("font-size", "var(--lumo-font-size-s)") + .set("color", "var(--lumo-secondary-text-color)") + .set("margin-left", "var(--lumo-space-xl)"); + + securityTab.add(twoFactorLayout, twoFactorDescription); // Passwort ändern Button Button changePassword = new Button("Passwort ändern"); diff --git a/src/main/java/de/assecutor/votianlt/pages/view/LoginView.java b/src/main/java/de/assecutor/votianlt/pages/view/LoginView.java index d3269b3..0735fb8 100644 --- a/src/main/java/de/assecutor/votianlt/pages/view/LoginView.java +++ b/src/main/java/de/assecutor/votianlt/pages/view/LoginView.java @@ -19,6 +19,8 @@ import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.Route; import com.vaadin.flow.server.auth.AnonymousAllowed; import de.assecutor.votianlt.security.totp.TwoFactorService; +import de.assecutor.votianlt.repository.UserRepository; +import de.assecutor.votianlt.model.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; @@ -49,8 +51,11 @@ public class LoginView extends VerticalLayout implements BeforeEnterObserver, Af @Autowired private Environment environment; + @Autowired + private UserRepository userRepository; + @Value("${app.security.two-factor.enabled:false}") - private boolean twoFactorEnabled; + private boolean twoFactorEnabledGlobal; @Value("${app.version:unknown}") private String appVersion; @@ -133,7 +138,12 @@ public class LoginView extends VerticalLayout implements BeforeEnterObserver, Af Authentication auth = authenticationManager .authenticate(new UsernamePasswordAuthenticationToken(username, password)); - if (twoFactorEnabled) { + // Prüfe ob 2FA für diesen Nutzer aktiviert ist (global UND nutzer-spezifisch) + boolean userTwoFactorEnabled = userRepository.findByEmail(username) + .map(User::isTwoFactorEnabled) + .orElse(true); // Standardmäßig aktiviert falls Nutzer nicht gefunden + + if (twoFactorEnabledGlobal && userTwoFactorEnabled) { // 2FA aktiviert: Benutzer noch nicht in SecurityContext setzen this.pendingAuth = auth; twoFaField.setVisible(true); diff --git a/src/main/java/de/assecutor/votianlt/pages/view/ShowJobsView.java b/src/main/java/de/assecutor/votianlt/pages/view/ShowJobsView.java index 8658d7f..b5de6f0 100644 --- a/src/main/java/de/assecutor/votianlt/pages/view/ShowJobsView.java +++ b/src/main/java/de/assecutor/votianlt/pages/view/ShowJobsView.java @@ -77,8 +77,8 @@ public class ShowJobsView extends VerticalLayout { startDate.addValueChangeListener(e -> loadData()); endDate.addValueChangeListener(e -> loadData()); - // Configure grid columns: Kunde, Auftragsnummer, Auftragsdatum, Zielort - grid.addColumn(Job::getDeliveryCompany).setHeader("Kunde").setAutoWidth(true).setFlexGrow(1).setSortable(true); + // Configure grid columns: Auftraggeber, Auftragsnummer, Auftragsdatum, Zielort + grid.addColumn(job -> extractCompanyName(job.getCustomerSelection())).setHeader("Auftraggeber").setAutoWidth(true).setFlexGrow(1).setSortable(true); grid.addColumn(Job::getJobNumber).setHeader("Auftragsnummer").setAutoWidth(true).setSortable(true); grid.addColumn(Job::getCreatedAt).setHeader("Auftragsdatum").setAutoWidth(true).setSortable(true); grid.addColumn(Job::getDeliveryCity).setHeader("Zielort").setAutoWidth(true).setFlexGrow(1).setSortable(true); @@ -160,11 +160,11 @@ public class ShowJobsView extends VerticalLayout { private String generateCsv(java.util.List jobs) { StringBuilder csv = new StringBuilder(); // CSV Header - csv.append("Kunde,Auftragsnummer,Auftragsdatum,Zielort\n"); + csv.append("Auftraggeber,Auftragsnummer,Auftragsdatum,Zielort\n"); // CSV Data for (Job job : jobs) { - csv.append(escapeCsv(job.getDeliveryCompany())).append(","); + csv.append(escapeCsv(extractCompanyName(job.getCustomerSelection()))).append(","); csv.append(escapeCsv(job.getJobNumber())).append(","); csv.append(job.getCreatedAt() != null ? job.getCreatedAt().toString() : "").append(","); csv.append(escapeCsv(job.getDeliveryCity())).append("\n"); @@ -181,4 +181,16 @@ public class ShowJobsView extends VerticalLayout { } return value; } + + private String extractCompanyName(String customerSelection) { + if (customerSelection == null || customerSelection.isBlank()) { + return ""; + } + // Format: "Firmenname | Vorname Nachname" - extrahiere nur den Firmennamen + int separatorIndex = customerSelection.indexOf(" | "); + if (separatorIndex > 0) { + return customerSelection.substring(0, separatorIndex).trim(); + } + return customerSelection.trim(); + } } diff --git a/src/main/java/de/assecutor/votianlt/pages/view/StartView.java b/src/main/java/de/assecutor/votianlt/pages/view/StartView.java index 10719f1..f71aa60 100644 --- a/src/main/java/de/assecutor/votianlt/pages/view/StartView.java +++ b/src/main/java/de/assecutor/votianlt/pages/view/StartView.java @@ -17,6 +17,7 @@ import com.vaadin.flow.router.BeforeEnterEvent; import com.vaadin.flow.router.BeforeEnterObserver; import com.vaadin.flow.server.auth.AnonymousAllowed; import de.assecutor.votianlt.security.SecurityService; +import org.springframework.beans.factory.annotation.Value; @Route("") @PageTitle("VotianLT - Willkommen") @@ -24,9 +25,11 @@ import de.assecutor.votianlt.security.SecurityService; public class StartView extends VerticalLayout implements BeforeEnterObserver { private final SecurityService securityService; + private final String appVersion; - public StartView(SecurityService securityService) { + public StartView(SecurityService securityService, @Value("${app.version:unknown}") String appVersion) { this.securityService = securityService; + this.appVersion = appVersion; setSizeFull(); setPadding(false); setSpacing(false); @@ -324,7 +327,13 @@ public class StartView extends VerticalLayout implements BeforeEnterObserver { slogan.getStyle().set("font-style", "italic"); slogan.getStyle().set("color", "var(--lumo-primary-color)"); - footer.add(companyTitle, companyInfo, ctaText, slogan); + // Versionsnummer + Span versionSpan = new Span("Version " + appVersion); + versionSpan.getStyle().set("color", "var(--lumo-secondary-text-color)") + .set("font-size", "var(--lumo-font-size-s)") + .set("margin-top", "var(--lumo-space-l)"); + + footer.add(companyTitle, companyInfo, ctaText, slogan, versionSpan); return footer; } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index e6ecb06..45749b3 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -56,8 +56,8 @@ spring.servlet.multipart.max-request-size=64MB # Jackson message converter limits spring.jackson.default-property-inclusion=non_null -# 2FA Configuration -app.security.two-factor.enabled=false +# 2FA Configuration (global toggle - individual users can disable in their profile) +app.security.two-factor.enabled=true # Message Delivery Layer Configuration app.messaging.delivery.max-retries=3