Mailservice, passwort reset-Api Passwort reset anfordern web
This commit is contained in:
parent
fd3f82659f
commit
af021c7830
4
pom.xml
4
pom.xml
|
|
@ -107,6 +107,10 @@
|
|||
<artifactId>hibernate-validator</artifactId>
|
||||
<version>8.0.1.Final</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-mail</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ public class SecurityConfig {
|
|||
.csrf(AbstractHttpConfigurer::disable)
|
||||
.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||
.authorizeHttpRequests(authz -> authz
|
||||
.requestMatchers("/api/login", "/api/register").permitAll()
|
||||
.requestMatchers("/api/login", "/api/register", "/api/request", "api/confirm").permitAll()
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.exceptionHandling(ex -> ex
|
||||
|
|
@ -63,7 +63,7 @@ public class SecurityConfig {
|
|||
public SecurityFilterChain webSecurity(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeHttpRequests(authz -> authz
|
||||
.requestMatchers("/login", "/register", "/css/**", "/js/**").permitAll()
|
||||
.requestMatchers("/login", "/register", "/css/**", "/js/**", "/reset-request").permitAll()
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.formLogin(form -> form
|
||||
|
|
|
|||
|
|
@ -2,16 +2,19 @@ package it.boergmann.tkdApp.controller.api;
|
|||
|
||||
import it.boergmann.tkdApp.dto.AppUserResponse;
|
||||
import it.boergmann.tkdApp.domain.AppUser;
|
||||
import it.boergmann.tkdApp.dto.PasswordResetRequest;
|
||||
import it.boergmann.tkdApp.dto.RegisterRequest;
|
||||
import it.boergmann.tkdApp.dto.PasswordConfirmRequest;
|
||||
import it.boergmann.tkdApp.service.AppUserService;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
@RequiredArgsConstructor
|
||||
|
|
@ -36,4 +39,16 @@ public class UserApiController {
|
|||
appUserService.registerNewUser(request);
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
@PostMapping("/request")
|
||||
public ResponseEntity<?> requestReset(@RequestBody PasswordResetRequest request) {
|
||||
appUserService.requestPasswordReset(request.getEmail());
|
||||
return ResponseEntity.ok(Map.of("message", "Falls die E-Mail existiert, wurde ein Link gesendet"));
|
||||
}
|
||||
|
||||
@PostMapping("/confirm")
|
||||
public ResponseEntity<?> resetPassword(@RequestBody PasswordConfirmRequest request) {
|
||||
appUserService.confirmPasswordReset(request);
|
||||
return ResponseEntity.ok(Map.of("message", "Passwort wurde geändert"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
package it.boergmann.tkdApp.controller.web;
|
||||
|
||||
import it.boergmann.tkdApp.dto.PasswordResetRequest;
|
||||
import it.boergmann.tkdApp.dto.RegisterRequest;
|
||||
import it.boergmann.tkdApp.domain.AppUser;
|
||||
import it.boergmann.tkdApp.domain.Role;
|
||||
import it.boergmann.tkdApp.repository.AppUserRepository;
|
||||
import it.boergmann.tkdApp.service.AppUserService;
|
||||
import it.boergmann.tkdApp.service.MailService;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
|
|
@ -35,4 +37,24 @@ public class UserController {
|
|||
model.addAttribute("message", "Registrierung erfolgreich! Bitte auf Freischaltung durch Admin warten.");
|
||||
return "login";
|
||||
}
|
||||
|
||||
@GetMapping("/login")
|
||||
public String loginPage() {
|
||||
return "login"; // src/main/resources/templates/login.html
|
||||
}
|
||||
|
||||
private final MailService mailService; // oder dein eigener Service
|
||||
|
||||
@GetMapping("/reset-request")
|
||||
public String showRequestForm(Model model) {
|
||||
model.addAttribute("emailRequest", new PasswordResetRequest());
|
||||
return "password_reset_request";
|
||||
}
|
||||
|
||||
@PostMapping("/reset-request")
|
||||
public String handleRequest(@ModelAttribute PasswordResetRequest request, Model model) {
|
||||
appUserService.requestPasswordReset(request.getEmail());
|
||||
model.addAttribute("message", "Wenn ein Konto existiert, wurde eine Mail gesendet.");
|
||||
return "password_reset_request";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,11 +15,6 @@ public class WebController {
|
|||
|
||||
private final AppUserService appUserService;
|
||||
|
||||
@GetMapping("/login")
|
||||
public String loginPage() {
|
||||
return "login"; // src/main/resources/templates/login.html
|
||||
}
|
||||
|
||||
@GetMapping("/me")
|
||||
public String profilePage(Model model) {
|
||||
AppUser user = appUserService.getCurrentUser();
|
||||
|
|
|
|||
|
|
@ -29,6 +29,12 @@ public class AppUser implements UserDetails{
|
|||
|
||||
private String password;
|
||||
|
||||
private String resetToken;
|
||||
private LocalDateTime resetExpires;
|
||||
|
||||
private String emailVerificationToken;
|
||||
private LocalDateTime emailVerifiedAt;
|
||||
|
||||
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
|
||||
@Builder.Default
|
||||
private List<AppUserRole> appUserRoles = new ArrayList<>();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
package it.boergmann.tkdApp.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class PasswordConfirmRequest {
|
||||
@NotBlank(message = "Token darf nicht leer sein")
|
||||
private String token;
|
||||
@NotBlank(message = "Passwort darf nicht leer sein")
|
||||
private String newPassword;
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package it.boergmann.tkdApp.dto;
|
||||
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class PasswordResetRequest {
|
||||
@Email
|
||||
@NotBlank
|
||||
private String email;
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import it.boergmann.tkdApp.domain.Role;
|
|||
import it.boergmann.tkdApp.domain.AppUserRole;
|
||||
import it.boergmann.tkdApp.repository.AppUserRepository;
|
||||
import it.boergmann.tkdApp.repository.AppUserRoleRepository;
|
||||
import it.boergmann.tkdApp.service.MailService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.boot.ApplicationRunner;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
|
|
|||
|
|
@ -16,5 +16,8 @@ public interface AppUserRepository extends JpaRepository<AppUser, UUID> {
|
|||
boolean existsByUsername(String username); // 🔥 hier hinzufügen!
|
||||
boolean existsByEmail(String email);
|
||||
Optional<AppUser> findByUsername(String username);
|
||||
Optional<AppUser> findByResetToken(String token);
|
||||
Optional<AppUser> findByEmailVerificationToken(String token);
|
||||
Optional<AppUser> findByEmail(String email);
|
||||
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import it.boergmann.tkdApp.domain.AppUser;
|
|||
import it.boergmann.tkdApp.domain.Role;
|
||||
import it.boergmann.tkdApp.domain.AppUserRole;
|
||||
import it.boergmann.tkdApp.dto.RegisterRequest;
|
||||
import it.boergmann.tkdApp.dto.PasswordConfirmRequest;
|
||||
import it.boergmann.tkdApp.repository.AppUserRepository;
|
||||
import it.boergmann.tkdApp.repository.AppUserRoleRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
|
@ -22,6 +23,7 @@ public class AppUserService {
|
|||
private final AppUserRepository userRepository;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
private final AppUserRoleRepository roleAssignmentRepository;
|
||||
private final MailService mailService;
|
||||
|
||||
public AppUser getCurrentUser() {
|
||||
String username = SecurityContextHolder.getContext().getAuthentication().getName();
|
||||
|
|
@ -77,4 +79,39 @@ public class AppUserService {
|
|||
|
||||
return savedUser;
|
||||
}
|
||||
|
||||
public void requestPasswordReset(String email) {
|
||||
AppUser user = userRepository.findByEmail(email)
|
||||
.orElseThrow(() -> new IllegalArgumentException("E-Mail nicht gefunden"));
|
||||
|
||||
user.setResetToken(UUID.randomUUID().toString());
|
||||
user.setResetExpires(LocalDateTime.now().plusHours(1));
|
||||
userRepository.save(user);
|
||||
|
||||
String link = "https://tkdapp.de/reset-password?token=" + user.getResetToken();
|
||||
|
||||
|
||||
mailService.sendMail(
|
||||
email,
|
||||
"Passwort zurücksetzen 🔐",
|
||||
"<p>Hier ist dein Link: <a href=\"" + link + "\">Passwort ändern</a></p>",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
public void confirmPasswordReset(PasswordConfirmRequest request) {
|
||||
String token = request.getToken();
|
||||
String newPassword = request.getNewPassword();
|
||||
AppUser user = userRepository.findByResetToken(token)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Token ungültig"));
|
||||
|
||||
if (user.getResetExpires().isBefore(LocalDateTime.now())) {
|
||||
throw new IllegalArgumentException("Token abgelaufen");
|
||||
}
|
||||
|
||||
user.setPassword(passwordEncoder.encode(newPassword));
|
||||
user.setResetToken(null); // ✅ mark as used
|
||||
user.setResetExpires(null);
|
||||
userRepository.save(user);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
package it.boergmann.tkdApp.service;
|
||||
|
||||
import jakarta.mail.MessagingException;
|
||||
import jakarta.mail.internet.MimeMessage;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.mail.javamail.JavaMailSender;
|
||||
import org.springframework.mail.javamail.MimeMessageHelper;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class MailService {
|
||||
|
||||
private final JavaMailSender mailSender;
|
||||
|
||||
public void sendMail(String to, String subject, String text, boolean html) {
|
||||
try {
|
||||
MimeMessage message = mailSender.createMimeMessage();
|
||||
MimeMessageHelper helper = new MimeMessageHelper(message, "utf-8");
|
||||
|
||||
helper.setTo(to);
|
||||
helper.setSubject(subject);
|
||||
helper.setText(text, html);
|
||||
helper.setFrom("admin@boergmann.it"); // anpassen
|
||||
|
||||
mailSender.send(message);
|
||||
log.info("E-Mail gesendet an {}", to);
|
||||
} catch (MessagingException e) {
|
||||
log.error("Fehler beim Senden der E-Mail: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,7 +10,19 @@ spring:
|
|||
hibernate:
|
||||
dialect: org.hibernate.dialect.PostgreSQLDialect
|
||||
open-in-view: false
|
||||
|
||||
mail:
|
||||
host: mail.boergmann.it
|
||||
port: 587
|
||||
username: klaas@boergmann.it
|
||||
password: October7-Obsessed-Gumdrop-Remote
|
||||
protocol: smtp
|
||||
properties:
|
||||
mail:
|
||||
smtp:
|
||||
auth: true
|
||||
starttls:
|
||||
enable: true
|
||||
default-encoding: UTF-8
|
||||
jwt:
|
||||
secret: 2T5tOkcLVT8vKfi0qyS0HxYnI2DklK9Mr0BHWWQgsjaAtO3aX5QhVi93h7jVPYiY
|
||||
expiration-ms: 86400000 # 1 Tag
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<title>Passwort zurücksetzen</title>
|
||||
</head>
|
||||
<body>
|
||||
<h2>Passwort zurücksetzen</h2>
|
||||
|
||||
<form th:action="@{/reset-request}" method="post" th:object="${emailRequest}">
|
||||
<label for="email">E-Mail:</label>
|
||||
<input type="email" id="email" th:field="*{email}" required />
|
||||
<button type="submit">Zurücksetzen anfordern</button>
|
||||
</form>
|
||||
|
||||
<p th:if="${message}" th:text="${message}"></p>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in New Issue