Added change password page.

This commit is contained in:
Andrew Lalis 2022-02-25 22:18:05 +01:00
parent be9edf18e4
commit 506a474819
8 changed files with 79 additions and 7 deletions

View File

@ -10,7 +10,7 @@
</parent>
<groupId>nl.andrewl</groupId>
<artifactId>coyotecredit</artifactId>
<version>1.3.1</version>
<version>1.3.2</version>
<name>coyotecredit</name>
<description>Banking and stock trading application to teach students.</description>
<properties>

View File

@ -1,15 +1,13 @@
package nl.andrewl.coyotecredit.ctl.user;
import lombok.RequiredArgsConstructor;
import nl.andrewl.coyotecredit.ctl.user.dto.ChangePasswordPayload;
import nl.andrewl.coyotecredit.model.User;
import nl.andrewl.coyotecredit.service.UserService;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.*;
@Controller
@RequestMapping(path = "/users/{userId}")
@ -22,4 +20,16 @@ public class UserPage {
model.addAttribute("user", userService.getUser(userId, user));
return "user";
}
@GetMapping(path = "/changePassword")
public String getChangePasswordPage(@PathVariable long userId, @AuthenticationPrincipal User user) {
userService.ensureCanChangePassword(user, userId);
return "user/change_password";
}
@PostMapping(path = "/changePassword")
public String postChangePassword(@PathVariable long userId, @AuthenticationPrincipal User user, @ModelAttribute ChangePasswordPayload payload) {
userService.changePassword(user, userId, payload);
return "redirect:/users/" + userId;
}
}

View File

@ -0,0 +1,6 @@
package nl.andrewl.coyotecredit.ctl.user.dto;
public record ChangePasswordPayload(
String currentPassword,
String newPassword
) {}

View File

@ -30,7 +30,7 @@ public class User implements UserDetails {
@Column(nullable = false, unique = true)
private String username;
@Column(nullable = false)
@Column(nullable = false) @Setter
private String passwordHash;
@Column(nullable = false)

View File

@ -3,6 +3,7 @@ package nl.andrewl.coyotecredit.service;
import lombok.RequiredArgsConstructor;
import nl.andrewl.coyotecredit.ctl.exchange.dto.InvitationData;
import nl.andrewl.coyotecredit.ctl.dto.RegisterPayload;
import nl.andrewl.coyotecredit.ctl.user.dto.ChangePasswordPayload;
import nl.andrewl.coyotecredit.ctl.user.dto.UserData;
import nl.andrewl.coyotecredit.ctl.user.dto.UserNotificationData;
import nl.andrewl.coyotecredit.dao.*;
@ -164,4 +165,31 @@ public class UserService {
}
notificationRepository.saveAll(notifications);
}
@Transactional(readOnly = true)
public void ensureCanChangePassword(User user, long userId) {
User changingUser = userRepository.findById(userId)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
if (!(user.isAdmin() || user.getId().equals(changingUser.getId()))) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND);
}
}
@Transactional
public void changePassword(User user, long userId, ChangePasswordPayload payload) {
User changingUser = userRepository.findById(userId)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
if (!(user.isAdmin() || user.getId().equals(changingUser.getId()))) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND);
}
if (!passwordEncoder.matches(payload.currentPassword(), changingUser.getPasswordHash())) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Current password is incorrect.");
}
if (payload.currentPassword().equals(payload.newPassword())) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "New password is the same as the current password.");
}
changingUser.setPasswordHash(passwordEncoder.encode(payload.newPassword()));
userRepository.save(changingUser);
notificationRepository.save(new UserNotification(changingUser, "Your password has just been updated."));
}
}

View File

@ -2,7 +2,7 @@
<html
lang="en"
xmlns:th="http://www.thymeleaf.org"
th:replace="~{layout/basic_page :: layout (title='Exchange Accounts', content=~{::#content})}"
th:replace="~{layout/basic_page :: layout (title='Create Account', content=~{::#content})}"
>
<div id="content" class="container">
<h1 class="display-4">Create Account</h1>

View File

@ -62,4 +62,8 @@
</li>
</ul>
</div>
<div>
<a class="btn btn-primary" th:href="@{/users/{uId}/changePassword(uId=${userId})}">Change Password</a>
</div>
</div>

View File

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html
lang="en"
xmlns:th="http://www.thymeleaf.org"
th:replace="~{layout/basic_page :: layout (title='Change Password', content=~{::#content})}"
>
<div id="content" class="container">
<h1 class="display-4">Change Password</h1>
<p class="lead">
Change the password to your account.
</p>
<form th:action="@{/users/{uId}/changePassword(uId=${userId})}" method="post">
<div class="mb-3">
<label for="currentPasswordInput" class="form-label">Current Password</label>
<input id="currentPasswordInput" name="currentPassword" class="form-control" type="password" required/>
<small class="text-muted">You must enter your current password to confirm your access to the account.</small>
</div>
<div class="mb-3">
<label for="newPasswordInput" class="form-label">New Password</label>
<input id="newPasswordInput" name="newPassword" class="form-control" type="password" minlength="6" required/>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>