diff --git a/application/src/main/java/org/thingsboard/server/controller/AuthController.java b/application/src/main/java/org/thingsboard/server/controller/AuthController.java index f41e40ef89..3c4db474ef 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AuthController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AuthController.java @@ -135,7 +135,7 @@ public class AuthController extends BaseController { } } - @RequestMapping(value = "/noauth/activate", params = { "activateToken" }, method = RequestMethod.GET) + @RequestMapping(value = "/noauth/activate", params = {"activateToken"}, method = RequestMethod.GET) public ResponseEntity checkActivateToken( @RequestParam(value = "activateToken") String activateToken) { HttpHeaders headers = new HttpHeaders(); @@ -159,7 +159,7 @@ public class AuthController extends BaseController { @RequestMapping(value = "/noauth/resetPasswordByEmail", method = RequestMethod.POST) @ResponseStatus(value = HttpStatus.OK) - public void requestResetPasswordByEmail ( + public void requestResetPasswordByEmail( @RequestBody JsonNode resetPasswordByEmailRequest, HttpServletRequest request) throws ThingsboardException { try { @@ -170,13 +170,13 @@ public class AuthController extends BaseController { String resetUrl = String.format("%s/api/noauth/resetPassword?resetToken=%s", baseUrl, userCredentials.getResetToken()); - mailService.sendResetPasswordEmail(resetUrl, email); + mailService.sendResetPasswordEmailAsync(resetUrl, email); } catch (Exception e) { - throw handleException(e); + log.warn("Error occurred: {}", e.getMessage()); } } - @RequestMapping(value = "/noauth/resetPassword", params = { "resetToken" }, method = RequestMethod.GET) + @RequestMapping(value = "/noauth/resetPassword", params = {"resetToken"}, method = RequestMethod.GET) public ResponseEntity checkResetToken( @RequestParam(value = "resetToken") String resetToken) { HttpHeaders headers = new HttpHeaders(); diff --git a/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java b/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java index 28f0c38eee..906e87d608 100644 --- a/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java +++ b/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java @@ -25,6 +25,7 @@ import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.DisabledException; import org.springframework.security.authentication.LockedException; import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @@ -152,7 +153,7 @@ public class ThingsboardErrorResponseHandler extends ResponseEntityExceptionHand private void handleAuthenticationException(AuthenticationException authenticationException, HttpServletResponse response) throws IOException { response.setStatus(HttpStatus.UNAUTHORIZED.value()); - if (authenticationException instanceof BadCredentialsException) { + if (authenticationException instanceof BadCredentialsException || authenticationException instanceof UsernameNotFoundException) { mapper.writeValue(response.getWriter(), ThingsboardErrorResponse.of("Invalid username or password", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)); } else if (authenticationException instanceof DisabledException) { mapper.writeValue(response.getWriter(), ThingsboardErrorResponse.of("User account is not active", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)); diff --git a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java index 715c99e2fb..5ef7cc1942 100644 --- a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java +++ b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java @@ -73,6 +73,9 @@ public class DefaultMailService implements MailService { @Autowired private TbApiUsageStateService apiUsageStateService; + @Autowired + private MailExecutorService mailExecutorService; + private JavaMailSenderImpl mailSender; private String mailFrom; @@ -221,6 +224,17 @@ public class DefaultMailService implements MailService { sendMail(mailSender, mailFrom, email, subject, message); } + @Override + public void sendResetPasswordEmailAsync(String passwordResetLink, String email) { + mailExecutorService.execute(() -> { + try { + this.sendResetPasswordEmail(passwordResetLink, email); + } catch (ThingsboardException e) { + log.error("Error occurred: {} ", e.getMessage()); + } + }); + } + @Override public void sendPasswordWasResetEmail(String loginLink, String email) throws ThingsboardException { diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java index 943852125c..600b9656f2 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java @@ -155,6 +155,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { doPost("/api/noauth/resetPasswordByEmail", resetPasswordByEmailRequest) .andExpect(status().isOk()); + Thread.sleep(1000); doGet("/api/noauth/resetPassword?resetToken={resetToken}", TestMailService.currentResetPasswordToken) .andExpect(status().isSeeOther()) .andExpect(header().string(HttpHeaders.LOCATION, "/login/resetPassword?resetToken=" + TestMailService.currentResetPasswordToken)); diff --git a/application/src/test/java/org/thingsboard/server/service/mail/TestMailService.java b/application/src/test/java/org/thingsboard/server/service/mail/TestMailService.java index d2e9dddbf2..b68319fe73 100644 --- a/application/src/test/java/org/thingsboard/server/service/mail/TestMailService.java +++ b/application/src/test/java/org/thingsboard/server/service/mail/TestMailService.java @@ -51,7 +51,7 @@ public class TestMailService { currentResetPasswordToken = passwordResetLink.split("=")[1]; return null; } - }).when(mailService).sendResetPasswordEmail(Mockito.anyString(), Mockito.anyString()); + }).when(mailService).sendResetPasswordEmailAsync(Mockito.anyString(), Mockito.anyString()); return mailService; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java index ada6208b6b..f406fd2a69 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java @@ -25,7 +25,10 @@ import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Lazy; +import org.springframework.security.authentication.DisabledException; +import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.Tenant; @@ -49,7 +52,6 @@ import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TenantDao; -import org.thingsboard.common.util.JacksonUtil; import java.util.HashMap; import java.util.Map; @@ -194,11 +196,11 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic DataValidator.validateEmail(email); User user = userDao.findByEmail(tenantId, email); if (user == null) { - throw new IncorrectParameterException(String.format("Unable to find user by email [%s]", email)); + throw new UsernameNotFoundException(String.format("Unable to find user by email [%s]", email)); } UserCredentials userCredentials = userCredentialsDao.findByUserId(tenantId, user.getUuidId()); if (!userCredentials.isEnabled()) { - throw new IncorrectParameterException("Unable to reset password for inactive user"); + throw new DisabledException(String.format("User credentials not enabled [%s]", email)); } userCredentials.setResetToken(RandomStringUtils.randomAlphanumeric(DEFAULT_TOKEN_LENGTH)); return saveUserCredentials(tenantId, userCredentials); @@ -365,7 +367,8 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic JsonNode userPasswordHistoryJson; if (additionalInfo.has(USER_PASSWORD_HISTORY)) { userPasswordHistoryJson = additionalInfo.get(USER_PASSWORD_HISTORY); - userPasswordHistoryMap = JacksonUtil.convertValue(userPasswordHistoryJson, new TypeReference<>(){}); + userPasswordHistoryMap = JacksonUtil.convertValue(userPasswordHistoryJson, new TypeReference<>() { + }); } if (userPasswordHistoryMap != null) { userPasswordHistoryMap.put(Long.toString(System.currentTimeMillis()), userCredentials.getPassword()); diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java index bd75d8bded..0311a5b26f 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java @@ -31,22 +31,25 @@ public interface MailService { void updateMailConfiguration(); void sendEmail(TenantId tenantId, String email, String subject, String message) throws ThingsboardException; - + void sendTestMail(JsonNode config, String email) throws ThingsboardException; - + void sendActivationEmail(String activationLink, String email) throws ThingsboardException; - + void sendAccountActivatedEmail(String loginLink, String email) throws ThingsboardException; - + void sendResetPasswordEmail(String passwordResetLink, String email) throws ThingsboardException; + void sendResetPasswordEmailAsync(String passwordResetLink, String email); + void sendPasswordWasResetEmail(String loginLink, String email) throws ThingsboardException; - void sendAccountLockoutEmail( String lockoutEmail, String email, Integer maxFailedLoginAttempts) throws ThingsboardException; + void sendAccountLockoutEmail(String lockoutEmail, String email, Integer maxFailedLoginAttempts) throws ThingsboardException; void send(TenantId tenantId, CustomerId customerId, String from, String to, String cc, String bcc, String subject, String body, boolean isHtml, Map images) throws ThingsboardException; void send(TenantId tenantId, CustomerId customerId, String from, String to, String cc, String bcc, String subject, String body, boolean isHtml, Map images, JavaMailSender javaMailSender) throws ThingsboardException; void sendApiFeatureStateEmail(ApiFeature apiFeature, ApiUsageStateValue stateValue, String email, ApiUsageStateMailMessage msg) throws ThingsboardException; + } diff --git a/ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.html b/ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.html index 8e723d15a4..72e263a802 100644 --- a/ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.html +++ b/ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.html @@ -15,7 +15,8 @@ limitations under the License. --> -
+
login.request-password-reset @@ -38,7 +39,7 @@
-