Skip to content

Commit f2ee0db

Browse files
authoredMar 5, 2025··
Merge pull request #165 from devondragon/issue-164-Add-Update-Password-API-endpoint-and-functionality
Add update password API endpoint and functionality
2 parents 542f323 + b02bb1f commit f2ee0db

File tree

5 files changed

+92
-2
lines changed

5 files changed

+92
-2
lines changed
 

‎src/main/java/com/digitalsanctuary/spring/user/api/UserAPI.java

+36
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@
1515
import org.springframework.web.bind.annotation.RequestMapping;
1616
import org.springframework.web.bind.annotation.RestController;
1717
import com.digitalsanctuary.spring.user.audit.AuditEvent;
18+
import com.digitalsanctuary.spring.user.dto.PasswordDto;
1819
import com.digitalsanctuary.spring.user.dto.UserDto;
1920
import com.digitalsanctuary.spring.user.event.OnRegistrationCompleteEvent;
21+
import com.digitalsanctuary.spring.user.exceptions.InvalidOldPasswordException;
2022
import com.digitalsanctuary.spring.user.exceptions.UserAlreadyExistException;
2123
import com.digitalsanctuary.spring.user.persistence.model.User;
2224
import com.digitalsanctuary.spring.user.service.DSUserDetails;
@@ -146,6 +148,40 @@ public ResponseEntity<JSONResponse> resetPassword(@Valid @RequestBody UserDto us
146148
return buildSuccessResponse("If account exists, password reset email has been sent!", forgotPasswordPendingURI);
147149
}
148150

151+
/**
152+
* Updates the user's password. This is used when the user is logged in and wants to change their password.
153+
*
154+
* @param userDetails the authenticated user details
155+
* @param passwordDto the password data transfer object containing the old and new passwords
156+
* @param request the HTTP servlet request
157+
* @param locale the locale
158+
* @return a ResponseEntity containing a JSONResponse with the password update result
159+
*/
160+
@PostMapping("/updatePassword")
161+
public ResponseEntity<JSONResponse> updatePassword(@AuthenticationPrincipal DSUserDetails userDetails,
162+
@Valid @RequestBody PasswordDto passwordDto, HttpServletRequest request, Locale locale) {
163+
validateAuthenticatedUser(userDetails);
164+
User user = userDetails.getUser();
165+
166+
try {
167+
if (!userService.checkIfValidOldPassword(user, passwordDto.getOldPassword())) {
168+
throw new InvalidOldPasswordException("Invalid old password");
169+
}
170+
171+
userService.changeUserPassword(user, passwordDto.getNewPassword());
172+
logAuditEvent("PasswordUpdate", "Success", "User password updated", user, request);
173+
174+
return buildSuccessResponse(messages.getMessage("message.update-password.success", null, locale), null);
175+
} catch (InvalidOldPasswordException ex) {
176+
logAuditEvent("PasswordUpdate", "Failure", "Invalid old password", user, request);
177+
return buildErrorResponse(messages.getMessage("message.update-password.invalid-old", null, locale), 1, HttpStatus.BAD_REQUEST);
178+
} catch (Exception ex) {
179+
log.error("Unexpected error during password update.", ex);
180+
logAuditEvent("PasswordUpdate", "Failure", ex.getMessage(), user, request);
181+
return buildErrorResponse("System Error!", 5, HttpStatus.INTERNAL_SERVER_ERROR);
182+
}
183+
}
184+
149185
/**
150186
* Deletes the user's account. This is used when the user wants to delete their account. This will either delete the account or disable it based
151187
* on the configuration of the actuallyDeleteAccount property. After the account is disabled or deleted, the user will be logged out.

‎src/main/resources/messages/dsspringusermessages.properties

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ email.signature=Best regards, <br /><em>The DigitalSanctuary Team</em>
1212

1313
# Messages
1414
message.update-user.success=Your profile has been successfully updated.
15+
message.update-password.success=Your password has been successfully updated.
16+
message.update-password.invalid-old=The old password is incorrect.
1517

1618
message.account.verified=Your account has been successfully verified.
1719
message.logout.success=You logged out successfully

‎src/test/java/com/digitalsanctuary/spring/user/api/UserApiTest.java

+33
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
import com.digitalsanctuary.spring.user.api.data.Response;
1818
import com.digitalsanctuary.spring.user.api.helper.AssertionsHelper;
1919
import com.digitalsanctuary.spring.user.api.provider.ApiTestRegistrationArgumentsProvider;
20+
import com.digitalsanctuary.spring.user.api.provider.ApiTestUpdatePasswordArgumentsProvider;
2021
import com.digitalsanctuary.spring.user.api.provider.holder.ApiTestArgumentsHolder;
22+
import com.digitalsanctuary.spring.user.dto.PasswordDto;
2123
import com.digitalsanctuary.spring.user.dto.UserDto;
2224
import com.digitalsanctuary.spring.user.jdbc.Jdbc;
2325
import com.digitalsanctuary.spring.user.persistence.model.User;
@@ -75,6 +77,37 @@ public void resetPassword() throws Exception {
7577
AssertionsHelper.compareResponses(actual, excepted);
7678
}
7779

80+
/**
81+
* Tests the update password functionality with valid and invalid password combinations.
82+
*
83+
* @param argumentsHolder Contains test data for password updates (valid/invalid scenarios)
84+
* @throws Exception if any error occurs during test execution
85+
*/
86+
@ParameterizedTest
87+
@ArgumentsSource(ApiTestUpdatePasswordArgumentsProvider.class)
88+
@Order(3)
89+
public void updatePassword(ApiTestArgumentsHolder argumentsHolder) throws Exception {
90+
// Register and login test user first
91+
login(baseTestUser);
92+
93+
PasswordDto passwordDto = argumentsHolder.getPasswordDto();
94+
95+
ResultActions action = perform(MockMvcRequestBuilders.post(URL + "/updatePassword")
96+
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
97+
.content(buildUrlEncodedFormEntity(passwordDto)));
98+
99+
if (argumentsHolder.getStatus() == DataStatus.VALID) {
100+
action.andExpect(status().isOk());
101+
}
102+
if (argumentsHolder.getStatus() == DataStatus.INVALID) {
103+
action.andExpect(status().isBadRequest());
104+
}
105+
106+
MockHttpServletResponse actual = action.andReturn().getResponse();
107+
Response expected = argumentsHolder.getResponse();
108+
AssertionsHelper.compareResponses(actual, expected);
109+
}
110+
78111

79112

80113
protected void login(UserDto userDto) {

‎src/test/java/com/digitalsanctuary/spring/user/api/data/ApiTestData.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -73,13 +73,13 @@ public static Response userUpdateSuccess() {
7373
}
7474
public static Response passwordUpdateSuccess() {
7575
return new Response(true, 0, null,
76-
new String[]{"Password updated successfully"}, null
76+
new String[]{"Your password has been successfully updated."}, null
7777
);
7878
}
7979

8080
public static Response passwordUpdateFailry() {
8181
return new Response(false, 1, null,
82-
new String[]{"Invalid Old Password"}, null
82+
new String[]{"The old password is incorrect."}, null
8383
);
8484
}
8585
public static Response successDeleteAccount() {

‎src/test/java/com/digitalsanctuary/spring/user/service/UserServiceTest.java

+19
Original file line numberDiff line numberDiff line change
@@ -101,5 +101,24 @@ void checkIfValidOldPassword_returnTrueIfValid() {
101101
when(passwordEncoder.matches(anyString(), anyString())).thenReturn(true);
102102
Assertions.assertTrue(userService.checkIfValidOldPassword(testUser, testUser.getPassword()));
103103
}
104+
105+
@Test
106+
void checkIfValidOldPassword_returnFalseIfInvalid() {
107+
when(passwordEncoder.matches(anyString(), anyString())).thenReturn(false);
108+
Assertions.assertFalse(userService.checkIfValidOldPassword(testUser, "wrongPassword"));
109+
}
110+
111+
@Test
112+
void changeUserPassword_encodesAndSavesNewPassword() {
113+
String newPassword = "newTestPassword";
114+
String encodedPassword = "encodedNewPassword";
115+
116+
when(passwordEncoder.encode(newPassword)).thenReturn(encodedPassword);
117+
when(userRepository.save(any(User.class))).thenReturn(testUser);
118+
119+
userService.changeUserPassword(testUser, newPassword);
120+
121+
Assertions.assertEquals(encodedPassword, testUser.getPassword());
122+
}
104123

105124
}

0 commit comments

Comments
 (0)
Please sign in to comment.