diff --git a/src/main/java/de/assecutor/votianlt/pages/view/RegisterView.java b/src/main/java/de/assecutor/votianlt/pages/view/RegisterView.java index af9283e..09fca81 100644 --- a/src/main/java/de/assecutor/votianlt/pages/view/RegisterView.java +++ b/src/main/java/de/assecutor/votianlt/pages/view/RegisterView.java @@ -11,21 +11,39 @@ import com.vaadin.flow.component.textfield.PasswordField; import com.vaadin.flow.component.textfield.TextField; import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.Route; +import com.vaadin.flow.server.VaadinSession; import com.vaadin.flow.server.auth.AnonymousAllowed; import de.assecutor.votianlt.pages.service.UserService; +import de.assecutor.votianlt.util.MailUtil; +import jakarta.mail.MessagingException; + +import java.security.SecureRandom; +import java.time.Duration; +import java.time.LocalDateTime; @Route("register") @PageTitle("Bei VotianLT registrieren") @AnonymousAllowed public class RegisterView extends VerticalLayout { private final UserService userService; + private final MailUtil mailUtil; + private TextField emailField; private PasswordField passwordField; private PasswordField confirmPasswordField; + private TextField codeField; private Button submitButton; + private Button verifyButton; + private Button resendButton; - public RegisterView(UserService userService) { + private String pendingCode; // 6-stelliger Code im View-Zustand + private LocalDateTime codeExpiresAt; + private LocalDateTime lastSentAt; + private boolean awaitingVerification = false; + + public RegisterView(UserService userService, MailUtil mailUtil) { this.userService = userService; + this.mailUtil = mailUtil; // Layout-Konfiguration für vollständige Zentrierung setSizeFull(); setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER); @@ -80,22 +98,46 @@ public class RegisterView extends VerticalLayout { confirmPasswordField.setRequired(true); confirmPasswordField.setPlaceholder("Passwort wiederholen"); - // Submit Button - submitButton = new Button("Registrieren", event -> registerUser()); + codeField = new TextField("Bestätigungscode (6 Ziffern)"); + codeField.setWidthFull(); + codeField.setMaxLength(6); + codeField.setPattern("\\d{6}"); + codeField.setPlaceholder("z. B. 123456"); + codeField.setVisible(false); + codeField.addValueChangeListener(e -> { + String v = e.getValue(); + if (v != null && !v.matches("\\d*")) { + codeField.setValue(v.replaceAll("[^0-9]", "")); + } + }); + + // Buttons + submitButton = new Button("Registrieren", event -> onStartRegistration()); submitButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_LARGE); submitButton.setWidthFull(); + verifyButton = new Button("Code prüfen und registrieren", event -> onVerifyCode()); + verifyButton.addThemeVariants(ButtonVariant.LUMO_SUCCESS); + verifyButton.setWidthFull(); + verifyButton.setVisible(false); + + resendButton = new Button("Code erneut senden", event -> onResendCode()); + resendButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY); + resendButton.setWidthFull(); + resendButton.setVisible(false); + // Zurück-Link Button backButton = new Button("Zurück zur Startseite", event -> getUI().ifPresent(ui -> ui.navigate(""))); backButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY); backButton.setWidthFull(); - container.add(title, subtitle, emailField, passwordField, confirmPasswordField, submitButton, backButton); + container.add(title, subtitle, emailField, passwordField, confirmPasswordField, + submitButton, codeField, verifyButton, resendButton, backButton); return container; } - private void registerUser() { + private void onStartRegistration() { var email = emailField.getValue().trim(); var password = passwordField.getValue(); var confirmPassword = confirmPasswordField.getValue(); @@ -106,50 +148,112 @@ public class RegisterView extends VerticalLayout { emailField.focus(); return; } - if (!email.contains("@") || !email.contains(".")) { Notification.show("Bitte geben Sie eine gültige E-Mail-Adresse ein.", 3000, Notification.Position.MIDDLE); emailField.focus(); return; } - + if (userService.existsByEmail(email)) { + Notification.show("Ein Benutzer mit dieser E-Mail-Adresse existiert bereits.", 4000, Notification.Position.MIDDLE); + return; + } if (password.isEmpty()) { Notification.show("Bitte geben Sie ein Passwort ein.", 3000, Notification.Position.MIDDLE); passwordField.focus(); return; } - if (password.length() < 6) { Notification.show("Das Passwort muss mindestens 6 Zeichen lang sein.", 3000, Notification.Position.MIDDLE); passwordField.focus(); return; } - if (!password.equals(confirmPassword)) { Notification.show("Die Passwörter stimmen nicht überein.", 3000, Notification.Position.MIDDLE); confirmPasswordField.focus(); return; } + // Alles ok: Code erzeugen und senden + sendVerificationCode(email); + } + + private void sendVerificationCode(String email) { + // Rate-Limit: 60 Sekunden zwischen Sendungen + if (lastSentAt != null && Duration.between(lastSentAt, LocalDateTime.now()).getSeconds() < 60) { + long wait = 60 - Duration.between(lastSentAt, LocalDateTime.now()).getSeconds(); + Notification.show("Bitte warten Sie " + wait + " Sekunden, bevor Sie den Code erneut senden.", 4000, Notification.Position.MIDDLE); + return; + } + String code = generateSixDigitCode(); + pendingCode = code; + codeExpiresAt = LocalDateTime.now().plusMinutes(10); + lastSentAt = LocalDateTime.now(); + + String subject = "Ihr VotianLT Bestätigungscode"; + String body = "Ihr Bestätigungscode lautet: " + code + "\n\n" + + "Dieser Code ist 10 Minuten gültig.\n" + + "Wenn Sie diese Registrierung nicht angefragt haben, ignorieren Sie diese E-Mail."; try { - // Benutzer erstellen - userService.createUser(email, password, "Benutzer", "Name"); + mailUtil.sendMail(email, subject, body); + awaitingVerification = true; + // UI umstellen: Code-Eingabe anzeigen + codeField.clear(); + codeField.setVisible(true); + verifyButton.setVisible(true); + resendButton.setVisible(true); - // Erfolgsmeldung und Weiterleitung - Notification.show("Registrierung erfolgreich! Sie werden zur Anmeldung weitergeleitet.", - 3000, Notification.Position.MIDDLE); + emailField.setReadOnly(true); + passwordField.setReadOnly(true); + confirmPasswordField.setReadOnly(true); + submitButton.setEnabled(false); - getUI().ifPresent(ui -> ui.navigate("login")); - - } catch (RuntimeException e) { - if (e.getMessage().contains("already exists")) { - Notification.show("Ein Benutzer mit dieser E-Mail-Adresse existiert bereits.", - 5000, Notification.Position.MIDDLE); - emailField.focus(); - } else { - Notification.show("Registrierung fehlgeschlagen: " + e.getMessage(), - 5000, Notification.Position.MIDDLE); - } + Notification.show("Ein Bestätigungscode wurde an " + email + " gesendet.", 4000, Notification.Position.MIDDLE); + } catch (MessagingException e) { + awaitingVerification = false; + Notification.show("Fehler beim Senden der E-Mail: " + e.getMessage(), 5000, Notification.Position.MIDDLE); } } + + private void onVerifyCode() { + if (!awaitingVerification) { + Notification.show("Bitte starten Sie zuerst die Registrierung.", 3000, Notification.Position.MIDDLE); + return; + } + String entered = codeField.getValue() != null ? codeField.getValue().trim() : ""; + if (!entered.matches("\\d{6}")) { + Notification.show("Bitte geben Sie den 6-stelligen Code ein.", 3000, Notification.Position.MIDDLE); + return; + } + if (codeExpiresAt == null || LocalDateTime.now().isAfter(codeExpiresAt)) { + Notification.show("Der Code ist abgelaufen. Bitte senden Sie einen neuen Code.", 4000, Notification.Position.MIDDLE); + return; + } + if (!entered.equals(pendingCode)) { + Notification.show("Der eingegebene Code ist ungültig.", 3000, Notification.Position.MIDDLE); + return; + } + + // Code korrekt -> Benutzer erstellen + var email = emailField.getValue().trim(); + var password = passwordField.getValue(); + try { + userService.createUser(email, password, "Benutzer", "Name"); + VaadinSession.getCurrent().setAttribute("flashMessage", "Registrierung erfolgreich. Bitte melden Sie sich an."); + getUI().ifPresent(ui -> ui.navigate("login")); + } catch (RuntimeException e) { + Notification.show("Registrierung fehlgeschlagen: " + e.getMessage(), 5000, Notification.Position.MIDDLE); + } + } + + private void onResendCode() { + if (emailField.isReadOnly()) { + sendVerificationCode(emailField.getValue().trim()); + } + } + + private String generateSixDigitCode() { + SecureRandom random = new SecureRandom(); + int num = random.nextInt(1_000_000); // 0..999999 + return String.format("%06d", num); + } }