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>
|
<artifactId>hibernate-validator</artifactId>
|
||||||
<version>8.0.1.Final</version>
|
<version>8.0.1.Final</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-mail</artifactId>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ public class SecurityConfig {
|
||||||
.csrf(AbstractHttpConfigurer::disable)
|
.csrf(AbstractHttpConfigurer::disable)
|
||||||
.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||||
.authorizeHttpRequests(authz -> authz
|
.authorizeHttpRequests(authz -> authz
|
||||||
.requestMatchers("/api/login", "/api/register").permitAll()
|
.requestMatchers("/api/login", "/api/register", "/api/request", "api/confirm").permitAll()
|
||||||
.anyRequest().authenticated()
|
.anyRequest().authenticated()
|
||||||
)
|
)
|
||||||
.exceptionHandling(ex -> ex
|
.exceptionHandling(ex -> ex
|
||||||
|
|
@ -63,7 +63,7 @@ public class SecurityConfig {
|
||||||
public SecurityFilterChain webSecurity(HttpSecurity http) throws Exception {
|
public SecurityFilterChain webSecurity(HttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
.authorizeHttpRequests(authz -> authz
|
.authorizeHttpRequests(authz -> authz
|
||||||
.requestMatchers("/login", "/register", "/css/**", "/js/**").permitAll()
|
.requestMatchers("/login", "/register", "/css/**", "/js/**", "/reset-request").permitAll()
|
||||||
.anyRequest().authenticated()
|
.anyRequest().authenticated()
|
||||||
)
|
)
|
||||||
.formLogin(form -> form
|
.formLogin(form -> form
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,19 @@ package it.boergmann.tkdApp.controller.api;
|
||||||
|
|
||||||
import it.boergmann.tkdApp.dto.AppUserResponse;
|
import it.boergmann.tkdApp.dto.AppUserResponse;
|
||||||
import it.boergmann.tkdApp.domain.AppUser;
|
import it.boergmann.tkdApp.domain.AppUser;
|
||||||
|
import it.boergmann.tkdApp.dto.PasswordResetRequest;
|
||||||
import it.boergmann.tkdApp.dto.RegisterRequest;
|
import it.boergmann.tkdApp.dto.RegisterRequest;
|
||||||
|
import it.boergmann.tkdApp.dto.PasswordConfirmRequest;
|
||||||
import it.boergmann.tkdApp.service.AppUserService;
|
import it.boergmann.tkdApp.service.AppUserService;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.ui.Model;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api")
|
@RequestMapping("/api")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
|
@ -36,4 +39,16 @@ public class UserApiController {
|
||||||
appUserService.registerNewUser(request);
|
appUserService.registerNewUser(request);
|
||||||
return ResponseEntity.ok().build();
|
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;
|
package it.boergmann.tkdApp.controller.web;
|
||||||
|
|
||||||
|
import it.boergmann.tkdApp.dto.PasswordResetRequest;
|
||||||
import it.boergmann.tkdApp.dto.RegisterRequest;
|
import it.boergmann.tkdApp.dto.RegisterRequest;
|
||||||
import it.boergmann.tkdApp.domain.AppUser;
|
import it.boergmann.tkdApp.domain.AppUser;
|
||||||
import it.boergmann.tkdApp.domain.Role;
|
import it.boergmann.tkdApp.domain.Role;
|
||||||
import it.boergmann.tkdApp.repository.AppUserRepository;
|
import it.boergmann.tkdApp.repository.AppUserRepository;
|
||||||
import it.boergmann.tkdApp.service.AppUserService;
|
import it.boergmann.tkdApp.service.AppUserService;
|
||||||
|
import it.boergmann.tkdApp.service.MailService;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
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.");
|
model.addAttribute("message", "Registrierung erfolgreich! Bitte auf Freischaltung durch Admin warten.");
|
||||||
return "login";
|
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;
|
private final AppUserService appUserService;
|
||||||
|
|
||||||
@GetMapping("/login")
|
|
||||||
public String loginPage() {
|
|
||||||
return "login"; // src/main/resources/templates/login.html
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/me")
|
@GetMapping("/me")
|
||||||
public String profilePage(Model model) {
|
public String profilePage(Model model) {
|
||||||
AppUser user = appUserService.getCurrentUser();
|
AppUser user = appUserService.getCurrentUser();
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,12 @@ public class AppUser implements UserDetails{
|
||||||
|
|
||||||
private String password;
|
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)
|
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
|
||||||
@Builder.Default
|
@Builder.Default
|
||||||
private List<AppUserRole> appUserRoles = new ArrayList<>();
|
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.domain.AppUserRole;
|
||||||
import it.boergmann.tkdApp.repository.AppUserRepository;
|
import it.boergmann.tkdApp.repository.AppUserRepository;
|
||||||
import it.boergmann.tkdApp.repository.AppUserRoleRepository;
|
import it.boergmann.tkdApp.repository.AppUserRoleRepository;
|
||||||
|
import it.boergmann.tkdApp.service.MailService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.boot.ApplicationRunner;
|
import org.springframework.boot.ApplicationRunner;
|
||||||
import org.springframework.context.annotation.Bean;
|
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 existsByUsername(String username); // 🔥 hier hinzufügen!
|
||||||
boolean existsByEmail(String email);
|
boolean existsByEmail(String email);
|
||||||
Optional<AppUser> findByUsername(String username);
|
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.Role;
|
||||||
import it.boergmann.tkdApp.domain.AppUserRole;
|
import it.boergmann.tkdApp.domain.AppUserRole;
|
||||||
import it.boergmann.tkdApp.dto.RegisterRequest;
|
import it.boergmann.tkdApp.dto.RegisterRequest;
|
||||||
|
import it.boergmann.tkdApp.dto.PasswordConfirmRequest;
|
||||||
import it.boergmann.tkdApp.repository.AppUserRepository;
|
import it.boergmann.tkdApp.repository.AppUserRepository;
|
||||||
import it.boergmann.tkdApp.repository.AppUserRoleRepository;
|
import it.boergmann.tkdApp.repository.AppUserRoleRepository;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
@ -22,6 +23,7 @@ public class AppUserService {
|
||||||
private final AppUserRepository userRepository;
|
private final AppUserRepository userRepository;
|
||||||
private final PasswordEncoder passwordEncoder;
|
private final PasswordEncoder passwordEncoder;
|
||||||
private final AppUserRoleRepository roleAssignmentRepository;
|
private final AppUserRoleRepository roleAssignmentRepository;
|
||||||
|
private final MailService mailService;
|
||||||
|
|
||||||
public AppUser getCurrentUser() {
|
public AppUser getCurrentUser() {
|
||||||
String username = SecurityContextHolder.getContext().getAuthentication().getName();
|
String username = SecurityContextHolder.getContext().getAuthentication().getName();
|
||||||
|
|
@ -77,4 +79,39 @@ public class AppUserService {
|
||||||
|
|
||||||
return savedUser;
|
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:
|
hibernate:
|
||||||
dialect: org.hibernate.dialect.PostgreSQLDialect
|
dialect: org.hibernate.dialect.PostgreSQLDialect
|
||||||
open-in-view: false
|
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:
|
jwt:
|
||||||
secret: 2T5tOkcLVT8vKfi0qyS0HxYnI2DklK9Mr0BHWWQgsjaAtO3aX5QhVi93h7jVPYiY
|
secret: 2T5tOkcLVT8vKfi0qyS0HxYnI2DklK9Mr0BHWWQgsjaAtO3aX5QhVi93h7jVPYiY
|
||||||
expiration-ms: 86400000 # 1 Tag
|
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