Erweiterungen

This commit is contained in:
2026-01-26 10:55:59 +01:00
parent 608417331b
commit e8904b9667
8 changed files with 59 additions and 12 deletions

View File

@@ -6,7 +6,7 @@
<groupId>de.assecutor.votianlt</groupId> <groupId>de.assecutor.votianlt</groupId>
<artifactId>votianlt</artifactId> <artifactId>votianlt</artifactId>
<version>0.8.1</version> <version>0.8.2</version>
<packaging>jar</packaging> <packaging>jar</packaging>

View File

@@ -60,4 +60,7 @@ public class User {
// Digitale Abwicklung und App-Nutzer Ortung // Digitale Abwicklung und App-Nutzer Ortung
private boolean digitalProcessingEnabled = true; // Digitale Abwicklung per App private boolean digitalProcessingEnabled = true; // Digitale Abwicklung per App
private boolean locationTrackingEnabled = true; // App-Nutzer orten private boolean locationTrackingEnabled = true; // App-Nutzer orten
// 2-Faktor-Authentifizierung (standardmäßig aktiviert für neue Nutzer)
private boolean twoFactorEnabled = true;
} }

View File

@@ -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) @Route(value = "add-app-user", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
@RolesAllowed({ "USER", "ADMIN" }) @RolesAllowed({ "USER", "ADMIN" })
public class AddAppUserView extends VerticalLayout { public class AddAppUserView extends VerticalLayout {
private final AppUserService appUserService;
private final Binder<AppUser> binder = new Binder<>(AppUser.class); private final Binder<AppUser> binder = new Binder<>(AppUser.class);
// Form fields // Form fields
@@ -38,6 +39,7 @@ public class AddAppUserView extends VerticalLayout {
@Autowired @Autowired
public AddAppUserView(AppUserService appUserService) { public AddAppUserView(AppUserService appUserService) {
this.appUserService = appUserService;
setSizeFull(); setSizeFull();
setPadding(true); setPadding(true);
setSpacing(true); setSpacing(true);
@@ -179,6 +181,9 @@ public class AddAppUserView extends VerticalLayout {
AppUser newAppUser = new AppUser(); AppUser newAppUser = new AppUser();
binder.writeBean(newAppUser); binder.writeBean(newAppUser);
// Save to database
appUserService.createAppUser(newAppUser);
// Show success message // Show success message
Notification.show("App-Nutzer erfolgreich angelegt", 3000, Notification.Position.MIDDLE); Notification.show("App-Nutzer erfolgreich angelegt", 3000, Notification.Position.MIDDLE);

View File

@@ -394,11 +394,19 @@ public class EditProfileView extends HorizontalLayout {
// 2-Faktor Auth // 2-Faktor Auth
Checkbox twoFactor = new Checkbox("2-Faktor-Authentifizierung"); 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(); Icon twoFactorInfo = VaadinIcon.QUESTION_CIRCLE_O.create();
twoFactorInfo.getStyle().set("marginLeft", "0.3em"); twoFactorInfo.getStyle().set("marginLeft", "0.3em");
HorizontalLayout twoFactorLayout = new HorizontalLayout(twoFactor, twoFactorInfo); HorizontalLayout twoFactorLayout = new HorizontalLayout(twoFactor, twoFactorInfo);
twoFactorLayout.setAlignItems(Alignment.CENTER); 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 // Passwort ändern Button
Button changePassword = new Button("Passwort ändern"); Button changePassword = new Button("Passwort ändern");

View File

@@ -19,6 +19,8 @@ import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route; import com.vaadin.flow.router.Route;
import com.vaadin.flow.server.auth.AnonymousAllowed; import com.vaadin.flow.server.auth.AnonymousAllowed;
import de.assecutor.votianlt.security.totp.TwoFactorService; 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.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
@@ -49,8 +51,11 @@ public class LoginView extends VerticalLayout implements BeforeEnterObserver, Af
@Autowired @Autowired
private Environment environment; private Environment environment;
@Autowired
private UserRepository userRepository;
@Value("${app.security.two-factor.enabled:false}") @Value("${app.security.two-factor.enabled:false}")
private boolean twoFactorEnabled; private boolean twoFactorEnabledGlobal;
@Value("${app.version:unknown}") @Value("${app.version:unknown}")
private String appVersion; private String appVersion;
@@ -133,7 +138,12 @@ public class LoginView extends VerticalLayout implements BeforeEnterObserver, Af
Authentication auth = authenticationManager Authentication auth = authenticationManager
.authenticate(new UsernamePasswordAuthenticationToken(username, password)); .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 // 2FA aktiviert: Benutzer noch nicht in SecurityContext setzen
this.pendingAuth = auth; this.pendingAuth = auth;
twoFaField.setVisible(true); twoFaField.setVisible(true);

View File

@@ -77,8 +77,8 @@ public class ShowJobsView extends VerticalLayout {
startDate.addValueChangeListener(e -> loadData()); startDate.addValueChangeListener(e -> loadData());
endDate.addValueChangeListener(e -> loadData()); endDate.addValueChangeListener(e -> loadData());
// Configure grid columns: Kunde, Auftragsnummer, Auftragsdatum, Zielort // Configure grid columns: Auftraggeber, Auftragsnummer, Auftragsdatum, Zielort
grid.addColumn(Job::getDeliveryCompany).setHeader("Kunde").setAutoWidth(true).setFlexGrow(1).setSortable(true); 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::getJobNumber).setHeader("Auftragsnummer").setAutoWidth(true).setSortable(true);
grid.addColumn(Job::getCreatedAt).setHeader("Auftragsdatum").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); 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<Job> jobs) { private String generateCsv(java.util.List<Job> jobs) {
StringBuilder csv = new StringBuilder(); StringBuilder csv = new StringBuilder();
// CSV Header // CSV Header
csv.append("Kunde,Auftragsnummer,Auftragsdatum,Zielort\n"); csv.append("Auftraggeber,Auftragsnummer,Auftragsdatum,Zielort\n");
// CSV Data // CSV Data
for (Job job : jobs) { 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(escapeCsv(job.getJobNumber())).append(",");
csv.append(job.getCreatedAt() != null ? job.getCreatedAt().toString() : "").append(","); csv.append(job.getCreatedAt() != null ? job.getCreatedAt().toString() : "").append(",");
csv.append(escapeCsv(job.getDeliveryCity())).append("\n"); csv.append(escapeCsv(job.getDeliveryCity())).append("\n");
@@ -181,4 +181,16 @@ public class ShowJobsView extends VerticalLayout {
} }
return value; 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();
}
} }

View File

@@ -17,6 +17,7 @@ import com.vaadin.flow.router.BeforeEnterEvent;
import com.vaadin.flow.router.BeforeEnterObserver; import com.vaadin.flow.router.BeforeEnterObserver;
import com.vaadin.flow.server.auth.AnonymousAllowed; import com.vaadin.flow.server.auth.AnonymousAllowed;
import de.assecutor.votianlt.security.SecurityService; import de.assecutor.votianlt.security.SecurityService;
import org.springframework.beans.factory.annotation.Value;
@Route("") @Route("")
@PageTitle("VotianLT - Willkommen") @PageTitle("VotianLT - Willkommen")
@@ -24,9 +25,11 @@ import de.assecutor.votianlt.security.SecurityService;
public class StartView extends VerticalLayout implements BeforeEnterObserver { public class StartView extends VerticalLayout implements BeforeEnterObserver {
private final SecurityService securityService; 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.securityService = securityService;
this.appVersion = appVersion;
setSizeFull(); setSizeFull();
setPadding(false); setPadding(false);
setSpacing(false); setSpacing(false);
@@ -324,7 +327,13 @@ public class StartView extends VerticalLayout implements BeforeEnterObserver {
slogan.getStyle().set("font-style", "italic"); slogan.getStyle().set("font-style", "italic");
slogan.getStyle().set("color", "var(--lumo-primary-color)"); 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; return footer;
} }

View File

@@ -56,8 +56,8 @@ spring.servlet.multipart.max-request-size=64MB
# Jackson message converter limits # Jackson message converter limits
spring.jackson.default-property-inclusion=non_null spring.jackson.default-property-inclusion=non_null
# 2FA Configuration # 2FA Configuration (global toggle - individual users can disable in their profile)
app.security.two-factor.enabled=false app.security.two-factor.enabled=true
# Message Delivery Layer Configuration # Message Delivery Layer Configuration
app.messaging.delivery.max-retries=3 app.messaging.delivery.max-retries=3