Skip to content

Commit fc9607d

Browse files
committed
Added e-mail and password updating
1 parent 5d8d6ed commit fc9607d

File tree

13 files changed

+506
-24
lines changed

13 files changed

+506
-24
lines changed

app/src/main/java/dev/blaauwendraad/recipe_book/service/UserAuthenticationService.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import dev.blaauwendraad.recipe_book.repository.RefreshTokenRepository;
66
import dev.blaauwendraad.recipe_book.repository.UserRepository;
77
import dev.blaauwendraad.recipe_book.service.exception.AccessTokenRefreshException;
8-
import dev.blaauwendraad.recipe_book.service.exception.UserLoginException;
8+
import dev.blaauwendraad.recipe_book.service.exception.UserAuthenticationException;
99
import dev.blaauwendraad.recipe_book.service.exception.UserRegistrationException;
1010
import dev.blaauwendraad.recipe_book.service.exception.UserRegistrationValidationException;
1111
import dev.blaauwendraad.recipe_book.service.model.AuthenticationDetails;
@@ -63,9 +63,9 @@ public UserAccount registerUser(String username, String emailAddress, String pas
6363
*
6464
* @param emailAddress the e-mail address of the user
6565
* @param password the password of the user
66-
* @throws UserLoginException if there is an error during user login
66+
* @throws UserAuthenticationException if there is an error during user login
6767
*/
68-
public AuthenticationDetails login(String emailAddress, String password) throws UserLoginException {
68+
public AuthenticationDetails login(String emailAddress, String password) throws UserAuthenticationException {
6969
UserAccount userAccount = validateCredentials(emailAddress, password);
7070
RefreshTokenEntity refreshTokenEntity = createAndStoreRefreshToken(userAccount);
7171
return new AuthenticationDetails(
@@ -123,13 +123,13 @@ private String generateRefreshToken() {
123123
return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
124124
}
125125

126-
private UserAccount validateCredentials(String emailAddress, String password) throws UserLoginException {
126+
private UserAccount validateCredentials(String emailAddress, String password) throws UserAuthenticationException {
127127
UserAccountEntity userAccountEntity = userRepository.findByEmail(emailAddress);
128128
if (userAccountEntity == null) {
129-
throw new UserLoginException("No user account found for the provided email address.");
129+
throw new UserAuthenticationException("No user account found for the provided email address.");
130130
}
131131
if (!BcryptUtil.matches(password, userAccountEntity.passwordHash)) {
132-
throw new UserLoginException("Invalid password provided.");
132+
throw new UserAuthenticationException("Invalid password provided.");
133133
}
134134
return UserAccountConverter.toUserAccount(userAccountEntity);
135135
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package dev.blaauwendraad.recipe_book.service;
2+
3+
import dev.blaauwendraad.recipe_book.data.model.UserAccountEntity;
4+
import dev.blaauwendraad.recipe_book.repository.UserRepository;
5+
import dev.blaauwendraad.recipe_book.service.exception.UserAuthenticationException;
6+
import io.quarkus.elytron.security.common.BcryptUtil;
7+
import jakarta.enterprise.context.ApplicationScoped;
8+
import jakarta.inject.Inject;
9+
import jakarta.transaction.Transactional;
10+
import jakarta.ws.rs.NotFoundException;
11+
12+
@ApplicationScoped
13+
public class UserService {
14+
private final UserRepository userRepository;
15+
16+
@Inject
17+
public UserService(UserRepository userRepository) {
18+
this.userRepository = userRepository;
19+
}
20+
21+
public String getEmail(Long userId) {
22+
UserAccountEntity userAccountEntity = userRepository.findById(userId);
23+
if (userAccountEntity == null) {
24+
throw new NotFoundException("User with userId " + userId + " does not exist");
25+
}
26+
return userAccountEntity.emailAddress;
27+
}
28+
29+
@Transactional
30+
public void updateEmail(Long userId, String newEmail, String currentPassword) throws UserAuthenticationException {
31+
UserAccountEntity userAccountEntity = userRepository.findById(userId);
32+
if (userAccountEntity == null) {
33+
throw new UserAuthenticationException("No user account found for the provided email address.");
34+
}
35+
if (!BcryptUtil.matches(currentPassword, userAccountEntity.passwordHash)) {
36+
throw new UserAuthenticationException("Invalid password provided.");
37+
}
38+
userAccountEntity.emailAddress = newEmail;
39+
userRepository.persist(userAccountEntity);
40+
}
41+
42+
@Transactional
43+
public void updatePassword(Long userId, String currentPassword, String newPassword, String confirmPassword)
44+
throws UserAuthenticationException {
45+
UserAccountEntity userAccountEntity = userRepository.findById(userId);
46+
if (userAccountEntity == null) {
47+
throw new UserAuthenticationException("No user account found for the provided email address.");
48+
}
49+
if (!BcryptUtil.matches(currentPassword, userAccountEntity.passwordHash)) {
50+
throw new UserAuthenticationException("Invalid password provided.");
51+
}
52+
if (!newPassword.equals(confirmPassword)) {
53+
throw new UserAuthenticationException("New password and confirmation password do not match.");
54+
}
55+
userAccountEntity.passwordHash = BcryptUtil.bcryptHash(newPassword);
56+
userRepository.persist(userAccountEntity);
57+
}
58+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package dev.blaauwendraad.recipe_book.service.exception;
2+
3+
public class UserAuthenticationException extends DetailedMessageException {
4+
5+
public UserAuthenticationException(String detailMessage) {
6+
super("Failed to login user.", detailMessage);
7+
}
8+
}

app/src/main/java/dev/blaauwendraad/recipe_book/service/exception/UserLoginExceptionMapper.java renamed to app/src/main/java/dev/blaauwendraad/recipe_book/service/exception/UserAuthenticationExceptionMapper.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,22 @@
77
import jakarta.ws.rs.ext.Provider;
88

99
@Provider
10-
public class UserLoginExceptionMapper implements ExceptionMapper<UserLoginException> {
10+
public class UserAuthenticationExceptionMapper implements ExceptionMapper<UserAuthenticationException> {
1111

1212
@Override
13-
public Response toResponse(UserLoginException exception) {
13+
public Response toResponse(UserAuthenticationException exception) {
1414
var errorResponse = toErrorResponse(exception);
1515
return Response.status(errorResponse.httpStatusCode())
1616
.type(MediaType.APPLICATION_JSON_TYPE)
1717
.entity(errorResponse)
1818
.build();
1919
}
2020

21-
private static ErrorResponse toErrorResponse(UserLoginException exception) {
21+
private static ErrorResponse toErrorResponse(UserAuthenticationException exception) {
2222
return new ErrorResponse(
2323
exception.getMessage() != null
2424
? exception.getMessage()
25-
: "An unexpected error occurred while trying to log in",
25+
: "An unexpected error occurred while trying to authenticate the user.",
2626
exception.getDetailMessage(),
2727
Response.Status.BAD_REQUEST.getStatusCode());
2828
}

app/src/main/java/dev/blaauwendraad/recipe_book/service/exception/UserLoginException.java

Lines changed: 0 additions & 8 deletions
This file was deleted.

app/src/main/java/dev/blaauwendraad/recipe_book/web/UserAuthenticationResource.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import dev.blaauwendraad.recipe_book.service.UserAuthenticationService;
44
import dev.blaauwendraad.recipe_book.service.exception.AccessTokenRefreshException;
5-
import dev.blaauwendraad.recipe_book.service.exception.UserLoginException;
5+
import dev.blaauwendraad.recipe_book.service.exception.UserAuthenticationException;
66
import dev.blaauwendraad.recipe_book.service.exception.UserRegistrationException;
77
import dev.blaauwendraad.recipe_book.service.model.AuthenticationDetails;
88
import dev.blaauwendraad.recipe_book.service.model.UserAccount;
@@ -35,7 +35,8 @@ public UserAuthenticationResource(UserAuthenticationService userAuthenticationSe
3535
@POST
3636
@PermitAll
3737
@Path("/login")
38-
public LoginResponse login(@Valid @NotNull LoginAttemptRequest loginAttemptRequest) throws UserLoginException {
38+
public LoginResponse login(@Valid @NotNull LoginAttemptRequest loginAttemptRequest)
39+
throws UserAuthenticationException {
3940
AuthenticationDetails loginDetails =
4041
userAuthenticationService.login(loginAttemptRequest.emailAddress(), loginAttemptRequest.password());
4142
return new LoginResponse(
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package dev.blaauwendraad.recipe_book.web;
2+
3+
import dev.blaauwendraad.recipe_book.service.UserService;
4+
import dev.blaauwendraad.recipe_book.service.exception.UserAuthenticationException;
5+
import dev.blaauwendraad.recipe_book.web.model.UpdateEmailRequest;
6+
import dev.blaauwendraad.recipe_book.web.model.UpdatePasswordRequest;
7+
import jakarta.annotation.security.RolesAllowed;
8+
import jakarta.enterprise.context.ApplicationScoped;
9+
import jakarta.inject.Inject;
10+
import jakarta.validation.Valid;
11+
import jakarta.validation.constraints.NotNull;
12+
import jakarta.ws.rs.Consumes;
13+
import jakarta.ws.rs.ForbiddenException;
14+
import jakarta.ws.rs.GET;
15+
import jakarta.ws.rs.PUT;
16+
import jakarta.ws.rs.Path;
17+
import jakarta.ws.rs.PathParam;
18+
import jakarta.ws.rs.Produces;
19+
import jakarta.ws.rs.core.MediaType;
20+
import jakarta.ws.rs.core.Response;
21+
import org.eclipse.microprofile.jwt.JsonWebToken;
22+
23+
@ApplicationScoped
24+
@Path("/api/users/{userId}")
25+
public class UserResource {
26+
private final UserService userService;
27+
28+
@Inject
29+
JsonWebToken jwt;
30+
31+
@Inject
32+
public UserResource(UserService userService) {
33+
this.userService = userService;
34+
}
35+
36+
@GET
37+
@Produces(MediaType.TEXT_PLAIN)
38+
@Path("/email")
39+
@RolesAllowed({"admin", "user"})
40+
public Response getEmail(@PathParam("userId") Long userId) {
41+
if (!Long.valueOf(jwt.getName()).equals(userId)) {
42+
throw new ForbiddenException("Not allowed to get e-mail of other user");
43+
}
44+
String email = userService.getEmail(userId);
45+
return Response.ok(email).build();
46+
}
47+
48+
@PUT
49+
@Produces(MediaType.APPLICATION_JSON)
50+
@Consumes(MediaType.APPLICATION_JSON)
51+
@Path("/email")
52+
@RolesAllowed({"admin", "user"})
53+
public Response updateEmail(@PathParam("userId") Long userId, @Valid @NotNull UpdateEmailRequest request)
54+
throws UserAuthenticationException {
55+
if (!Long.valueOf(jwt.getName()).equals(userId)) {
56+
throw new ForbiddenException("Not allowed to update e-mail of other user");
57+
}
58+
userService.updateEmail(userId, request.newEmail(), request.currentPassword());
59+
return Response.ok().build();
60+
}
61+
62+
@PUT
63+
@Produces(MediaType.APPLICATION_JSON)
64+
@Consumes(MediaType.APPLICATION_JSON)
65+
@Path("/password")
66+
@RolesAllowed({"admin", "user"})
67+
public Response updatePassword(@PathParam("userId") Long userId, @Valid @NotNull UpdatePasswordRequest request)
68+
throws UserAuthenticationException {
69+
if (!Long.valueOf(jwt.getName()).equals(userId)) {
70+
throw new ForbiddenException("Not allowed to update password of other user");
71+
}
72+
System.out.println("Updating password for userId: " + userId);
73+
userService.updatePassword(userId, request.currentPassword(), request.newPassword(), request.confirmPassword());
74+
return Response.ok().build();
75+
}
76+
}

app/src/main/java/dev/blaauwendraad/recipe_book/web/model/SaveRecipeRequestDto.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
public record SaveRecipeRequestDto(
1414
@NotBlank @Size(min = 5, max = 100) String title,
1515
@Nullable @Size(max = 2000) String description,
16-
@NotNull @NotNull @Positive @Max(100) Integer numServings,
16+
@NotNull @Positive @Max(100) Integer numServings,
1717
@NotNull PreparationTime preparationTime,
1818
@Size(min = 1, max = 50) List<@Valid IngredientDto> ingredients,
1919
@Size(min = 1, max = 50) List<@Valid PreparationStepDto> preparationSteps) {}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package dev.blaauwendraad.recipe_book.web.model;
2+
3+
import jakarta.validation.constraints.Email;
4+
import jakarta.validation.constraints.NotNull;
5+
6+
public record UpdateEmailRequest(@NotNull @Email String newEmail, @NotNull String currentPassword) {}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package dev.blaauwendraad.recipe_book.web.model;
2+
3+
import jakarta.validation.constraints.NotNull;
4+
import jakarta.validation.constraints.Size;
5+
6+
public record UpdatePasswordRequest(
7+
@NotNull String currentPassword, @NotNull @Size(min = 8) String newPassword, @NotNull String confirmPassword) {}

0 commit comments

Comments
 (0)