Erweiterungen
This commit is contained in:
@@ -11,21 +11,39 @@ import com.vaadin.flow.component.textfield.PasswordField;
|
|||||||
import com.vaadin.flow.component.textfield.TextField;
|
import com.vaadin.flow.component.textfield.TextField;
|
||||||
import com.vaadin.flow.router.PageTitle;
|
import com.vaadin.flow.router.PageTitle;
|
||||||
import com.vaadin.flow.router.Route;
|
import com.vaadin.flow.router.Route;
|
||||||
|
import com.vaadin.flow.server.VaadinSession;
|
||||||
import com.vaadin.flow.server.auth.AnonymousAllowed;
|
import com.vaadin.flow.server.auth.AnonymousAllowed;
|
||||||
import de.assecutor.votianlt.pages.service.UserService;
|
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")
|
@Route("register")
|
||||||
@PageTitle("Bei VotianLT registrieren")
|
@PageTitle("Bei VotianLT registrieren")
|
||||||
@AnonymousAllowed
|
@AnonymousAllowed
|
||||||
public class RegisterView extends VerticalLayout {
|
public class RegisterView extends VerticalLayout {
|
||||||
private final UserService userService;
|
private final UserService userService;
|
||||||
|
private final MailUtil mailUtil;
|
||||||
|
|
||||||
private TextField emailField;
|
private TextField emailField;
|
||||||
private PasswordField passwordField;
|
private PasswordField passwordField;
|
||||||
private PasswordField confirmPasswordField;
|
private PasswordField confirmPasswordField;
|
||||||
|
private TextField codeField;
|
||||||
private Button submitButton;
|
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.userService = userService;
|
||||||
|
this.mailUtil = mailUtil;
|
||||||
// Layout-Konfiguration für vollständige Zentrierung
|
// Layout-Konfiguration für vollständige Zentrierung
|
||||||
setSizeFull();
|
setSizeFull();
|
||||||
setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
|
setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
|
||||||
@@ -80,22 +98,46 @@ public class RegisterView extends VerticalLayout {
|
|||||||
confirmPasswordField.setRequired(true);
|
confirmPasswordField.setRequired(true);
|
||||||
confirmPasswordField.setPlaceholder("Passwort wiederholen");
|
confirmPasswordField.setPlaceholder("Passwort wiederholen");
|
||||||
|
|
||||||
// Submit Button
|
codeField = new TextField("Bestätigungscode (6 Ziffern)");
|
||||||
submitButton = new Button("Registrieren", event -> registerUser());
|
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.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_LARGE);
|
||||||
submitButton.setWidthFull();
|
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
|
// Zurück-Link
|
||||||
Button backButton = new Button("Zurück zur Startseite", event ->
|
Button backButton = new Button("Zurück zur Startseite", event ->
|
||||||
getUI().ifPresent(ui -> ui.navigate("")));
|
getUI().ifPresent(ui -> ui.navigate("")));
|
||||||
backButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
|
backButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
|
||||||
backButton.setWidthFull();
|
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;
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void registerUser() {
|
private void onStartRegistration() {
|
||||||
var email = emailField.getValue().trim();
|
var email = emailField.getValue().trim();
|
||||||
var password = passwordField.getValue();
|
var password = passwordField.getValue();
|
||||||
var confirmPassword = confirmPasswordField.getValue();
|
var confirmPassword = confirmPasswordField.getValue();
|
||||||
@@ -106,50 +148,112 @@ public class RegisterView extends VerticalLayout {
|
|||||||
emailField.focus();
|
emailField.focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!email.contains("@") || !email.contains(".")) {
|
if (!email.contains("@") || !email.contains(".")) {
|
||||||
Notification.show("Bitte geben Sie eine gültige E-Mail-Adresse ein.", 3000, Notification.Position.MIDDLE);
|
Notification.show("Bitte geben Sie eine gültige E-Mail-Adresse ein.", 3000, Notification.Position.MIDDLE);
|
||||||
emailField.focus();
|
emailField.focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (userService.existsByEmail(email)) {
|
||||||
|
Notification.show("Ein Benutzer mit dieser E-Mail-Adresse existiert bereits.", 4000, Notification.Position.MIDDLE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (password.isEmpty()) {
|
if (password.isEmpty()) {
|
||||||
Notification.show("Bitte geben Sie ein Passwort ein.", 3000, Notification.Position.MIDDLE);
|
Notification.show("Bitte geben Sie ein Passwort ein.", 3000, Notification.Position.MIDDLE);
|
||||||
passwordField.focus();
|
passwordField.focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (password.length() < 6) {
|
if (password.length() < 6) {
|
||||||
Notification.show("Das Passwort muss mindestens 6 Zeichen lang sein.", 3000, Notification.Position.MIDDLE);
|
Notification.show("Das Passwort muss mindestens 6 Zeichen lang sein.", 3000, Notification.Position.MIDDLE);
|
||||||
passwordField.focus();
|
passwordField.focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!password.equals(confirmPassword)) {
|
if (!password.equals(confirmPassword)) {
|
||||||
Notification.show("Die Passwörter stimmen nicht überein.", 3000, Notification.Position.MIDDLE);
|
Notification.show("Die Passwörter stimmen nicht überein.", 3000, Notification.Position.MIDDLE);
|
||||||
confirmPasswordField.focus();
|
confirmPasswordField.focus();
|
||||||
return;
|
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 {
|
try {
|
||||||
// Benutzer erstellen
|
mailUtil.sendMail(email, subject, body);
|
||||||
userService.createUser(email, password, "Benutzer", "Name");
|
awaitingVerification = true;
|
||||||
|
// UI umstellen: Code-Eingabe anzeigen
|
||||||
|
codeField.clear();
|
||||||
|
codeField.setVisible(true);
|
||||||
|
verifyButton.setVisible(true);
|
||||||
|
resendButton.setVisible(true);
|
||||||
|
|
||||||
// Erfolgsmeldung und Weiterleitung
|
emailField.setReadOnly(true);
|
||||||
Notification.show("Registrierung erfolgreich! Sie werden zur Anmeldung weitergeleitet.",
|
passwordField.setReadOnly(true);
|
||||||
3000, Notification.Position.MIDDLE);
|
confirmPasswordField.setReadOnly(true);
|
||||||
|
submitButton.setEnabled(false);
|
||||||
|
|
||||||
getUI().ifPresent(ui -> ui.navigate("login"));
|
Notification.show("Ein Bestätigungscode wurde an " + email + " gesendet.", 4000, Notification.Position.MIDDLE);
|
||||||
|
} catch (MessagingException e) {
|
||||||
} catch (RuntimeException e) {
|
awaitingVerification = false;
|
||||||
if (e.getMessage().contains("already exists")) {
|
Notification.show("Fehler beim Senden der E-Mail: " + e.getMessage(), 5000, Notification.Position.MIDDLE);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user