Skip to content

Commit

Permalink
#35 allow password reset by username OR email
Browse files Browse the repository at this point in the history
  • Loading branch information
Brutus5000 committed Oct 18, 2017
1 parent 3882627 commit f6c5a5a
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 14 deletions.
5 changes: 3 additions & 2 deletions src/main/java/com/faforever/api/error/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public enum ErrorCode {
TOKEN_INVALID(135, "Invalid operation", "The delivered token is invalid."),
TOKEN_EXPIRED(136, "Invalid operation", "The delivered token has expired."),
PASSWORD_RESET_FAILED(137, "Password reset failed", "Username and/or email did not match."),
PASSWORD_CHANGE_FAILED(138, "Password change failed", "Your current password did not match."),
PASSWORD_CHANGE_FAILED_WRONG_PASSWORD(138, "Password change failed", "Your current password did not match."),
USERNAME_CHANGE_TOO_EARLY(139, "Username change not allowed", "Only one name change per 30 days is allowed. {0} more days to go."),
EMAIL_CHANGE_FAILED(140, "Email change failed", "An unknown error happened while updating the database."),
STEAM_ID_UNCHANGEABLE(141, "Linking to Steam failed", "Your account is already bound to another Steam ID."),
Expand All @@ -68,7 +68,8 @@ public enum ErrorCode {
MOD_UID_EXISTS(159, "Duplicate mod UID", "A mod with UID ''{0}'' already exists."),
MOD_STRUCTURE_INVALID(160, "Invalid file structure for mod", "Files in the the root level of the zip file are not allowed. Please ensure all files reside inside a folder."),
MOD_VERSION_NOT_A_NUMBER(161, "Mod version is not a number", "The mod version has to be a whole number like 123, but was ''{0}''"),
USERNAME_RESERVED(162, "Invalid account data", "The entered username is currently reserved: {0} (Maximum reservation time is {1} months)");
USERNAME_RESERVED(162, "Invalid account data", "The username ''{0}'' can only be claimed by the original owner within {1} months after it has been freed."),
UNKNOWN_IDENTIFIER(163, "Unable to resolve user", "The identifier does neither match a username nor an email: {0}");

private final int code;
private final String title;
Expand Down
6 changes: 3 additions & 3 deletions src/main/java/com/faforever/api/user/UserController.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ public void changeLogin(@RequestParam("newUsername") String newUsername, Authent
userService.changeLogin(newUsername, userService.getUser(authentication));
}

@ApiOperation("Sends a password reset to the email linked by this account.")
@ApiOperation("Sends a password reset to the username OR email linked by this account.")
@RequestMapping(path = "/resetPassword", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public void resetPassword(@RequestParam("email") String email) {
userService.resetPassword(email);
public void resetPassword(@RequestParam("identifier") String identifier) {
userService.resetPassword(identifier);
}

@ApiOperation("Sets a new password for an account.")
Expand Down
14 changes: 9 additions & 5 deletions src/main/java/com/faforever/api/user/UserService.java
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ void activate(String token) {

void changePassword(String currentPassword, String newPassword, User user) {
if (!Objects.equals(user.getPassword(), passwordEncoder.encode(currentPassword))) {
throw new ApiException(new Error(ErrorCode.PASSWORD_CHANGE_FAILED));
throw new ApiException(new Error(ErrorCode.PASSWORD_CHANGE_FAILED_WRONG_PASSWORD));
}

user.setPassword(passwordEncoder.encode(newPassword));
Expand Down Expand Up @@ -176,13 +176,17 @@ void changeLogin(String newLogin, User user) {
userRepository.save(user);
}

void resetPassword(String email) {
log.debug("Registration requested for user: {}", email);
void resetPassword(String identifier) {
log.debug("Password reset requested for user-identifier: {}", identifier);

User user = userRepository.findOneByEmailIgnoreCase(email);
User user = userRepository.findOneByLoginIgnoreCase(identifier);

if (user == null) {
throw new ApiException(new Error(ErrorCode.USERNAME_INVALID));
user = userRepository.findOneByEmailIgnoreCase(identifier);
}

if (user == null) {
throw new ApiException(new Error(ErrorCode.UNKNOWN_IDENTIFIER));
}

String token = createPasswordResetToken(user.getId());
Expand Down
35 changes: 31 additions & 4 deletions src/test/java/com/faforever/api/user/UserServiceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ public void changePassword() {

@Test
public void changePasswordInvalidPassword() {
expectedException.expect(ApiExceptionWithCode.apiExceptionWithCode(ErrorCode.PASSWORD_CHANGE_FAILED));
expectedException.expect(ApiExceptionWithCode.apiExceptionWithCode(ErrorCode.PASSWORD_CHANGE_FAILED_WRONG_PASSWORD));

User user = createUser(TEST_USERID, TEST_USERNAME, "invalid password", TEST_EMAIL);
instance.changePassword(TEST_CURRENT_PASSWORD, TEST_NEW_PASSWORD, user);
Expand Down Expand Up @@ -280,7 +280,34 @@ public void changeLoginUsernameReservedBySelf() {
@Test
@SneakyThrows
@SuppressWarnings("unchecked")
public void resetPassword() {
public void resetPasswordByLogin() {
properties.getPasswordReset().setPasswordResetUrlFormat("http://www.example.com/resetPassword/%s");

User user = createUser(TEST_USERID, TEST_USERNAME, TEST_CURRENT_PASSWORD, TEST_EMAIL);

when(userRepository.findOneByLoginIgnoreCase(TEST_USERNAME)).thenReturn(user);
instance.resetPassword(TEST_USERNAME);

verify(userRepository).findOneByLoginIgnoreCase(TEST_USERNAME);

ArgumentCaptor<String> urlCaptor = ArgumentCaptor.forClass(String.class);
verify(emailService).sendPasswordResetMail(eq(TEST_USERNAME), eq(TEST_EMAIL), urlCaptor.capture());

String passwordResetUrl = urlCaptor.getValue();
assertThat(passwordResetUrl, startsWith("http://www.example.com/resetPassword/"));

String token = passwordResetUrl.split("/")[4];
HashMap<String, String> claims = objectMapper.readValue(JwtHelper.decode(token).getClaims(), HashMap.class);

assertThat(claims.get(UserService.KEY_ACTION), is("reset_password"));
assertThat(claims.get(UserService.KEY_USER_ID), is(user.getId()));
assertThat(Instant.parse(claims.get(UserService.KEY_EXPIRY)).isAfter(Instant.now()), is(true));
}

@Test
@SneakyThrows
@SuppressWarnings("unchecked")
public void resetPasswordByEmail() {
properties.getPasswordReset().setPasswordResetUrlFormat("http://www.example.com/resetPassword/%s");

User user = createUser(TEST_USERID, TEST_USERNAME, TEST_CURRENT_PASSWORD, TEST_EMAIL);
Expand All @@ -307,8 +334,8 @@ public void resetPassword() {
@Test
@SneakyThrows
@SuppressWarnings("unchecked")
public void resetPasswordUnknownUser() {
expectedException.expect(ApiExceptionWithCode.apiExceptionWithCode(ErrorCode.USERNAME_INVALID));
public void resetPasswordUnknownUsernameAndEmail() {
expectedException.expect(ApiExceptionWithCode.apiExceptionWithCode(ErrorCode.UNKNOWN_IDENTIFIER));

when(userRepository.findOneByEmailIgnoreCase(TEST_EMAIL)).thenReturn(null);
instance.resetPassword(TEST_EMAIL);
Expand Down

0 comments on commit f6c5a5a

Please sign in to comment.