Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 762812649c | |||
| 6fd8f8ecc8 | |||
| b4fbc972f2 | |||
| 21c594445f | |||
| 035d603504 | |||
| 08e5e2b089 | |||
| f2db7f286b | |||
| d255c7b4db |
@@ -49,7 +49,7 @@ public class SecurityConfig {
|
|||||||
@Bean
|
@Bean
|
||||||
public UserDetailsService userDetailsService(UserRepository userRepo) {
|
public UserDetailsService userDetailsService(UserRepository userRepo) {
|
||||||
return username -> {
|
return username -> {
|
||||||
User user = userRepo.findByUsername(username)
|
User user = userRepo.findByUsernameIgnoreCase(username)
|
||||||
.orElseThrow(() -> new UsernameNotFoundException("Benutzer nicht gefunden"));
|
.orElseThrow(() -> new UsernameNotFoundException("Benutzer nicht gefunden"));
|
||||||
|
|
||||||
return org.springframework.security.core.userdetails.User
|
return org.springframework.security.core.userdetails.User
|
||||||
|
|||||||
@@ -39,10 +39,17 @@ public class AdminUserController {
|
|||||||
|
|
||||||
@PostMapping("/users/save")
|
@PostMapping("/users/save")
|
||||||
public String saveUser(@ModelAttribute("user") User user) {
|
public String saveUser(@ModelAttribute("user") User user) {
|
||||||
if (user.getId() != null && (user.getPassword() == null || user.getPassword().isBlank())) {
|
if (user.getId() != null) {
|
||||||
User existingUser = userService.findById(user.getId())
|
User existingUser = userService.findById(user.getId())
|
||||||
.orElseThrow(() -> new IllegalArgumentException("Ungültige User-ID: " + user.getId()));
|
.orElseThrow(() -> new IllegalArgumentException("Ungültige User-ID: " + user.getId()));
|
||||||
user.setPassword(existingUser.getPassword());
|
// Passwort: leer = unverändert übernehmen, sonst neu verschlüsseln (Admin-Reset)
|
||||||
|
if (user.getPassword() == null || user.getPassword().isBlank()) {
|
||||||
|
user.setPassword(existingUser.getPassword());
|
||||||
|
} else {
|
||||||
|
user.setPassword(passwordEncoder.encode(user.getPassword()));
|
||||||
|
}
|
||||||
|
// Profilbild steht nicht im Formular -> bestehendes erhalten, nicht überschreiben
|
||||||
|
user.setProfilePicture(existingUser.getProfilePicture());
|
||||||
} else {
|
} else {
|
||||||
user.setPassword(passwordEncoder.encode(user.getPassword()));
|
user.setPassword(passwordEncoder.encode(user.getPassword()));
|
||||||
}
|
}
|
||||||
@@ -51,6 +58,18 @@ public class AdminUserController {
|
|||||||
return "redirect:/admin/users";
|
return "redirect:/admin/users";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Direktes Zurücksetzen des Passworts eines Nutzers (Admin-Recovery, ohne E-Mail). */
|
||||||
|
@PostMapping("/users/{id}/reset-password")
|
||||||
|
public String resetPassword(@PathVariable Long id, @RequestParam String newPassword) {
|
||||||
|
if (newPassword != null && !newPassword.isBlank()) {
|
||||||
|
User user = userService.findById(id)
|
||||||
|
.orElseThrow(() -> new IllegalArgumentException("Ungültige User-ID: " + id));
|
||||||
|
user.setPassword(passwordEncoder.encode(newPassword.trim()));
|
||||||
|
userService.save(user);
|
||||||
|
}
|
||||||
|
return "redirect:/admin/users";
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/users/edit/{id}")
|
@GetMapping("/users/edit/{id}")
|
||||||
public String editUserForm(@PathVariable Long id, Model model) {
|
public String editUserForm(@PathVariable Long id, Model model) {
|
||||||
User user = userService.findById(id).orElseThrow(() -> new IllegalArgumentException("Ungültige User-ID: " + id));
|
User user = userService.findById(id).orElseThrow(() -> new IllegalArgumentException("Ungültige User-ID: " + id));
|
||||||
|
|||||||
@@ -24,17 +24,24 @@ public class RegisterController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/register")
|
@PostMapping("/register")
|
||||||
public String processRegistration(@ModelAttribute("user") User user, Model model) {
|
public String processRegistration(@ModelAttribute("user") User user,
|
||||||
// Benutzername klein schreiben, um Doppelungen zu vermeiden
|
@RequestParam(value = "confirmPassword", required = false) String confirmPassword) {
|
||||||
user.setUsername(user.getUsername().toLowerCase());
|
// Schreibweise des Benutzernamens beibehalten, nur trimmen
|
||||||
|
String username = user.getUsername() == null ? "" : user.getUsername().trim();
|
||||||
|
user.setUsername(username);
|
||||||
|
|
||||||
// Prüfen, ob Benutzername schon existiert
|
// Passwörter müssen übereinstimmen (Schutz vor Tippfehlern bei der Eingabe)
|
||||||
if (userService.findByUsername(user.getUsername()) != null) {
|
if (user.getPassword() == null || user.getPassword().isBlank()
|
||||||
// Zurück zur kombinierten Login/Signup-Seite mit geöffnetem Signup-Panel
|
|| !user.getPassword().equals(confirmPassword)) {
|
||||||
|
return "redirect:/?passwordMismatch";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Benutzername schon vergeben? (case-insensitiv, damit "Alice" und "alice" nicht kollidieren)
|
||||||
|
if (username.isBlank() || userService.findByUsernameIgnoreCase(username) != null) {
|
||||||
return "redirect:/?registerError";
|
return "redirect:/?registerError";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Passwort verschlüsseln
|
// Passwort verschlüsseln und speichern
|
||||||
user.setPassword(passwordEncoder.encode(user.getPassword()));
|
user.setPassword(passwordEncoder.encode(user.getPassword()));
|
||||||
user.setRole("USER"); // Standardrolle
|
user.setRole("USER"); // Standardrolle
|
||||||
userService.save(user);
|
userService.save(user);
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ public interface UserRepository extends JpaRepository<User, Long> {
|
|||||||
// Benutzer anhand des Benutzernamens suchen
|
// Benutzer anhand des Benutzernamens suchen
|
||||||
Optional<User> findByUsername(String username);
|
Optional<User> findByUsername(String username);
|
||||||
|
|
||||||
|
// Case-insensitive Suche: Login und Registrierung sollen unabhängig von Groß-/Kleinschreibung greifen
|
||||||
|
Optional<User> findByUsernameIgnoreCase(String username);
|
||||||
|
|
||||||
// Benutzer anhand des Benutzernamens löschen
|
// Benutzer anhand des Benutzernamens löschen
|
||||||
void deleteByUsername(String username);
|
void deleteByUsername(String username);
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,7 @@ import com.example.mindflux.model.User;
|
|||||||
|
|
||||||
public interface UserService {
|
public interface UserService {
|
||||||
User findByUsername(String username);
|
User findByUsername(String username);
|
||||||
|
User findByUsernameIgnoreCase(String username);
|
||||||
void save(User user);
|
void save(User user);
|
||||||
void deleteByUsername(String username); // <-- Nur die Methodensignatur, ohne {}
|
void deleteByUsername(String username); // <-- Nur die Methodensignatur, ohne {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,11 @@ public class UserServiceImpl implements UserService {
|
|||||||
return userRepository.findByUsername(username).orElse(null); // <-- Optional aufgelöst
|
return userRepository.findByUsername(username).orElse(null); // <-- Optional aufgelöst
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public User findByUsernameIgnoreCase(String username) {
|
||||||
|
return userRepository.findByUsernameIgnoreCase(username).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void save(User user) {
|
public void save(User user) {
|
||||||
userRepository.save(user);
|
userRepository.save(user);
|
||||||
|
|||||||
@@ -34,8 +34,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mf-field">
|
<div class="mf-field">
|
||||||
<label class="mf-label">Passwort</label>
|
<label class="mf-label" th:text="${user.id == null} ? 'Passwort' : 'Neues Passwort (leer lassen = unverändert)'">Passwort</label>
|
||||||
<input class="mf-input" type="password" name="password" th:required="${user.id == null}" autocomplete="new-password"/>
|
<input class="mf-input" type="password" name="password" th:required="${user.id == null}" autocomplete="new-password"
|
||||||
|
th:placeholder="${user.id == null} ? 'Passwort' : 'leer lassen = unverändert'"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mf-field">
|
<div class="mf-field">
|
||||||
|
|||||||
@@ -43,9 +43,17 @@
|
|||||||
<td th:text="${user.id}">1</td>
|
<td th:text="${user.id}">1</td>
|
||||||
<td th:text="${user.username}">Max</td>
|
<td th:text="${user.username}">Max</td>
|
||||||
<td th:text="${user.email}">max@mindflux.de</td>
|
<td th:text="${user.email}">max@mindflux.de</td>
|
||||||
<td style="display:flex; gap:.5rem; flex-wrap:wrap;">
|
<td style="display:flex; gap:.5rem; flex-wrap:wrap; align-items:center;">
|
||||||
<a th:href="@{'/admin/users/edit/' + ${user.id}}" class="mf-btn outline">Bearbeiten</a>
|
<a th:href="@{'/admin/users/edit/' + ${user.id}}" class="mf-btn outline">Bearbeiten</a>
|
||||||
<form th:action="@{'/admin/users/delete/' + ${user.id}}" method="post" style="display:inline;">
|
<form th:action="@{'/admin/users/' + ${user.id} + '/reset-password'}" method="post"
|
||||||
|
style="display:flex; gap:.35rem; align-items:center;"
|
||||||
|
onsubmit="return confirm('Passwort für diesen Nutzer wirklich neu setzen?');">
|
||||||
|
<input class="mf-input" type="text" name="newPassword" placeholder="Neues Passwort"
|
||||||
|
required minlength="4" style="width:150px; padding:.45rem .6rem;">
|
||||||
|
<button type="submit" class="mf-btn">PW zurücksetzen</button>
|
||||||
|
</form>
|
||||||
|
<form th:action="@{'/admin/users/delete/' + ${user.id}}" method="post" style="display:inline;"
|
||||||
|
onsubmit="return confirm('Benutzer wirklich löschen?');">
|
||||||
<button type="submit" class="mf-btn danger">Löschen</button>
|
<button type="submit" class="mf-btn danger">Löschen</button>
|
||||||
</form>
|
</form>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -58,6 +58,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div th:if="${param.registerError}" class="mf-alert err">Benutzername bereits vergeben.</div>
|
<div th:if="${param.registerError}" class="mf-alert err">Benutzername bereits vergeben.</div>
|
||||||
|
<div th:if="${param.passwordMismatch}" class="mf-alert err">Die Passwörter stimmen nicht überein. Bitte erneut eingeben.</div>
|
||||||
|
|
||||||
<form th:action="@{/register}" method="post">
|
<form th:action="@{/register}" method="post">
|
||||||
<div class="mf-field">
|
<div class="mf-field">
|
||||||
@@ -72,6 +73,11 @@
|
|||||||
<label class="mf-label" for="reg-password">Passwort</label>
|
<label class="mf-label" for="reg-password">Passwort</label>
|
||||||
<input id="reg-password" class="mf-input" type="password" name="password" placeholder="Passwort" required>
|
<input id="reg-password" class="mf-input" type="password" name="password" placeholder="Passwort" required>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mf-field">
|
||||||
|
<label class="mf-label" for="reg-password2">Passwort wiederholen</label>
|
||||||
|
<input id="reg-password2" class="mf-input" type="password" name="confirmPassword" placeholder="Passwort wiederholen" required>
|
||||||
|
</div>
|
||||||
|
<p id="reg-pw-msg" class="mf-alert err" style="display:none">Die Passwörter stimmen nicht überein.</p>
|
||||||
<button class="neon-btn" type="submit"><span class="lock">🚀</span> Account erstellen</button>
|
<button class="neon-btn" type="submit"><span class="lock">🚀</span> Account erstellen</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
@@ -128,10 +134,30 @@
|
|||||||
el.addEventListener('click', function (e) { e.preventDefault(); c.classList.remove('active'); });
|
el.addEventListener('click', function (e) { e.preventDefault(); c.classList.remove('active'); });
|
||||||
});
|
});
|
||||||
|
|
||||||
// Bei Registrierungsfehler direkt das Signup-Panel öffnen
|
// Bei Registrierungs-/Passwortfehler direkt das Signup-Panel öffnen
|
||||||
if (new URLSearchParams(window.location.search).has('registerError')) {
|
var qp = new URLSearchParams(window.location.search);
|
||||||
|
if (qp.has('registerError') || qp.has('passwordMismatch')) {
|
||||||
c.classList.add('active');
|
c.classList.add('active');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Client-seitiger Passwort-Abgleich (sofortiges Feedback; der Server prüft zusätzlich)
|
||||||
|
var pw = document.getElementById('reg-password');
|
||||||
|
var pw2 = document.getElementById('reg-password2');
|
||||||
|
var pwMsg = document.getElementById('reg-pw-msg');
|
||||||
|
var signupForm = pw2 ? pw2.closest('form') : null;
|
||||||
|
function pwCheck() {
|
||||||
|
var mismatch = pw2.value.length > 0 && pw.value !== pw2.value;
|
||||||
|
if (pwMsg) pwMsg.style.display = mismatch ? 'block' : 'none';
|
||||||
|
pw2.setCustomValidity(pw.value !== pw2.value ? 'Passwörter stimmen nicht überein' : '');
|
||||||
|
return !mismatch;
|
||||||
|
}
|
||||||
|
if (pw && pw2) {
|
||||||
|
pw.addEventListener('input', pwCheck);
|
||||||
|
pw2.addEventListener('input', pwCheck);
|
||||||
|
if (signupForm) signupForm.addEventListener('submit', function (e) {
|
||||||
|
if (pw.value !== pw2.value) { e.preventDefault(); pwCheck(); c.classList.add('active'); }
|
||||||
|
});
|
||||||
|
}
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user