From 62d496fc9794cdf6ef3916992105883b54038c6d Mon Sep 17 00:00:00 2001 From: Brutus5000 Date: Thu, 12 Oct 2017 23:14:40 +0200 Subject: [PATCH] #35 allow password reset by username OR email --- .../com/faforever/api/error/ErrorCode.java | 3 +- .../faforever/api/user/UserController.java | 6 ++-- .../com/faforever/api/user/UserService.java | 12 ++++--- .../faforever/api/user/UserServiceTest.java | 33 +++++++++++++++++-- 4 files changed, 43 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/faforever/api/error/ErrorCode.java b/src/main/java/com/faforever/api/error/ErrorCode.java index 8f4d2d642..485dac9a5 100644 --- a/src/main/java/com/faforever/api/error/ErrorCode.java +++ b/src/main/java/com/faforever/api/error/ErrorCode.java @@ -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 entered username is currently reserved: {0} (Maximum reservation time is {1} months)"), + 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; diff --git a/src/main/java/com/faforever/api/user/UserController.java b/src/main/java/com/faforever/api/user/UserController.java index 1a78a3f1c..8484516eb 100644 --- a/src/main/java/com/faforever/api/user/UserController.java +++ b/src/main/java/com/faforever/api/user/UserController.java @@ -47,10 +47,10 @@ public void changeLogin(@RequestParam("newLogin") String newLogin, Authenticatio userService.changeLogin(newLogin, 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.") diff --git a/src/main/java/com/faforever/api/user/UserService.java b/src/main/java/com/faforever/api/user/UserService.java index 79e7edc99..ca3f38adf 100644 --- a/src/main/java/com/faforever/api/user/UserService.java +++ b/src/main/java/com/faforever/api/user/UserService.java @@ -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()); diff --git a/src/test/java/com/faforever/api/user/UserServiceTest.java b/src/test/java/com/faforever/api/user/UserServiceTest.java index dc390e18a..5039b9959 100644 --- a/src/test/java/com/faforever/api/user/UserServiceTest.java +++ b/src/test/java/com/faforever/api/user/UserServiceTest.java @@ -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 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 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); @@ -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);