From e32e4d4e387170cb8088bac041b754155be67d5c Mon Sep 17 00:00:00 2001 From: Sven F <9976560+sven1103@users.noreply.github.com> Date: Thu, 27 Jun 2024 13:11:30 +0200 Subject: [PATCH] Addresses registration side effects observed on simultanous clients login (#647) Every client UI session gets their own registration layout instance, such that the observed registration failures due to race conditions are gone. --- .../user/registration/Registration.java | 114 -------------- .../registration/RegistrationSpec.groovy | 128 ---------------- .../java/life/qbic/datamanager/AppConfig.java | 14 -- .../register/UserRegistrationHandler.java | 138 ----------------- .../UserRegistrationHandlerInterface.java | 18 --- .../register/UserRegistrationLayout.java | 144 +++++++++++++++++- 6 files changed, 137 insertions(+), 419 deletions(-) delete mode 100644 identity/src/main/java/life/qbic/identity/application/user/registration/Registration.java delete mode 100644 identity/src/test/groovy/life/qbic/identity/domain/usermanagement/registration/RegistrationSpec.groovy delete mode 100644 user-interface/src/main/java/life/qbic/datamanager/views/register/UserRegistrationHandler.java delete mode 100644 user-interface/src/main/java/life/qbic/datamanager/views/register/UserRegistrationHandlerInterface.java diff --git a/identity/src/main/java/life/qbic/identity/application/user/registration/Registration.java b/identity/src/main/java/life/qbic/identity/application/user/registration/Registration.java deleted file mode 100644 index 4bb70dcd0..000000000 --- a/identity/src/main/java/life/qbic/identity/application/user/registration/Registration.java +++ /dev/null @@ -1,114 +0,0 @@ -package life.qbic.identity.application.user.registration; - -import life.qbic.application.commons.ApplicationResponse; -import life.qbic.identity.application.user.IdentityService; -import life.qbic.identity.application.user.IdentityService.EmptyUserNameException; -import life.qbic.identity.application.user.IdentityService.UserExistsException; -import life.qbic.identity.application.user.IdentityService.UserNameNotAvailableException; -import life.qbic.identity.domain.model.EmailAddress.EmailValidationException; -import life.qbic.identity.domain.model.EncryptedPassword.PasswordValidationException; -import life.qbic.identity.domain.model.FullName.FullNameValidationException; -import life.qbic.logging.api.Logger; -import life.qbic.logging.service.LoggerFactory; - -/** - * User Registration use case - * - *

Tries to register a new user and create a user account. - * - *

In case a user with the provided mail already exists, the registration will fail and calls - * the failure output method. - * - * @since 1.0.0 - */ -public class Registration implements RegisterUserInput { - - private static final Logger log = LoggerFactory.logger(Registration.class.getName()); - - private RegisterUserOutput registerUserOutput; - - private final IdentityService identityService; - - /** - * Creates the registration use case. - * - *

Upon construction, a dummy output interface is created, that needs to be overridden by - * explicitly setting it via {@link Registration#setRegisterUserOutput(RegisterUserOutput)}. - * - *

The default output implementation just prints to std out on success and std err on failure, - * after the use case has been executed via - * {@link RegisterUserInput#register(String, String, char[], String)}. - * - * @param identityService the user registration service to save the new user to. - * @since 1.0.0 - */ - public Registration(IdentityService identityService) { - this.identityService = identityService; - } - - /** - * Sets and overrides the use case output. - * - * @param registerUserOutput an output interface implementation, so the use case can trigger the - * callback methods after its execution - * @since 1.0.0 - */ - public void setRegisterUserOutput(RegisterUserOutput registerUserOutput) { - this.registerUserOutput = registerUserOutput; - } - - /** - * @inheritDocs - */ - @Override - public void register(String fullName, String email, char[] rawPassword, String userName) { - if (registerUserOutput == null) { - log.error("No use case output set."); - return; - } - try { - identityService.registerUser(fullName, userName, email, rawPassword) - .ifSuccessOrElse(this::reportSuccess, - response -> registerUserOutput.onUnexpectedFailure(build(response))); - } catch (Exception e) { - log.error("User registration failed", e); - registerUserOutput.onUnexpectedFailure("Unexpected error occurred."); - } - } - - private void reportSuccess(ApplicationResponse applicationResponse) { - registerUserOutput.onUserRegistrationSucceeded(); - } - - private UserRegistrationException build(ApplicationResponse applicationResponse) { - var builder = UserRegistrationException.builder(); - - for (RuntimeException e : applicationResponse.failures()) { - - if (e instanceof EmailValidationException emailValidationException) { - builder.withEmailFormatException(emailValidationException); - } else if (e instanceof PasswordValidationException passwordValidationException) { - builder.withInvalidPasswordException(passwordValidationException); - } else if (e instanceof FullNameValidationException fullNameValidationException) { - builder.withFullNameException(fullNameValidationException); - } else if (e instanceof UserExistsException userExistsException) { - builder.withUserExistsException(userExistsException); - } else if (e instanceof UserNameNotAvailableException userNameNotAvailableException) { - builder.withUserNameNotAvailableException(userNameNotAvailableException); - } else if (e instanceof EmptyUserNameException emptyUserNameException) { - builder.withEmptyUserNameException(emptyUserNameException); - } else { - builder.withUnexpectedException(e); - } - } - return builder.build(); - } - - /** - * @inheritDocs - */ - @Override - public void setOutput(RegisterUserOutput output) { - registerUserOutput = output; - } -} diff --git a/identity/src/test/groovy/life/qbic/identity/domain/usermanagement/registration/RegistrationSpec.groovy b/identity/src/test/groovy/life/qbic/identity/domain/usermanagement/registration/RegistrationSpec.groovy deleted file mode 100644 index 688d0aba7..000000000 --- a/identity/src/test/groovy/life/qbic/identity/domain/usermanagement/registration/RegistrationSpec.groovy +++ /dev/null @@ -1,128 +0,0 @@ -package life.qbic.identity.domain.usermanagement.registration - -import life.qbic.identity.application.user.IdentityService -import life.qbic.identity.application.user.registration.RegisterUserOutput -import life.qbic.identity.application.user.registration.Registration -import life.qbic.identity.application.user.registration.UserRegistrationException -import life.qbic.identity.domain.model.* -import life.qbic.identity.domain.registry.DomainRegistry -import life.qbic.identity.domain.repository.UserDataStorage -import life.qbic.identity.domain.repository.UserRepository -import life.qbic.identity.domain.service.UserDomainService -import org.springframework.data.domain.Pageable -import spock.lang.Shared -import spock.lang.Specification - -import java.util.stream.Collectors - -/** - * Tests for the registration use case - * - * @since 1.0.0 - */ -class RegistrationSpec extends Specification { - - @Shared - public IdentityService userRegistrationService - - @Shared - public TestStorage testStorage - private String validPassword = "0123456789012" - - def setupSpec() { - testStorage = new TestStorage() - DomainRegistry domainRegistry = DomainRegistry.instance() - domainRegistry.registerService(new UserDomainService(UserRepository.getInstance(testStorage))) - userRegistrationService = new IdentityService(UserRepository.getInstance(testStorage)) - } - - def "When a user is already registered with a given email address, abort the registration and communicate the failure"() { - given: "A repository with one user entry" - def testUser = User.create(FullName.from("Mr Somebody"), EmailAddress.from("some@body.com"), "svenipopenni", EncryptedPassword.from(validPassword.toCharArray())) - testStorage.save(testUser) - - and: - def useCaseOutput = Mock(RegisterUserOutput.class) - - and: "a new user to register" - def newUser = User.create(FullName.from("Mr Somebody"), EmailAddress.from("some@body.com"), "svenipopenni", EncryptedPassword.from(validPassword.toCharArray())) - - and: "a the use case with output" - def registration = new Registration(new IdentityService(UserRepository.getInstance(testStorage))) - registration.setOutput(useCaseOutput) - - when: "a user is registered" - registration.register(newUser.fullName().get(), newUser.emailAddress().get(), "12345678".toCharArray(), newUser.userName()) - - then: - 0 * useCaseOutput.onUserRegistrationSucceeded() - 1 * useCaseOutput.onUnexpectedFailure(_ as UserRegistrationException) - // the user has not been added to the repository - testStorage.findUsersByEmailAddress(testUser.emailAddress()).size() == 1 - } - - def "When a user is not yet registered with a given email address, register the user"() { - given: "A repository with one user entry" - def testUser = User.create(FullName.from("Mr Somebody"), EmailAddress.from("some@body.com"), "svenipopenni", EncryptedPassword.from(validPassword.toCharArray())) - testStorage.save(testUser) - - and: - def useCaseOutput = Mock(RegisterUserOutput.class) - - and: "a new user to register" - def newUser = User.create(FullName.from("Mr Nobody"), EmailAddress.from("no@body.com"), "svenipopenni2", EncryptedPassword.from(validPassword.toCharArray())) - - and: "a the use case with output" - def registration = new Registration(new IdentityService(UserRepository.getInstance(Mock(UserDataStorage)))) - registration.setOutput(useCaseOutput) - - when: "a user is registered" - registration.register(newUser.fullName().get(), newUser.emailAddress().get(), validPassword.toCharArray(), newUser.userName()) - - then: - 1 * useCaseOutput.onUserRegistrationSucceeded() - 0 * useCaseOutput.onUnexpectedFailure(_ as String) - 0 * useCaseOutput.onUnexpectedFailure(_ as UserRegistrationException) - def storedUser = testStorage.findUsersByEmailAddress(newUser.emailAddress()).get(0) - storedUser.fullName() == newUser.fullName() - !storedUser.id().get().isBlank() - - } - - private static class TestStorage implements UserDataStorage { - - private List users = [] - - @Override - List findUsersByEmailAddress(EmailAddress email) { - return users.stream() - .filter((User user) -> { user.emailAddress().equals(email) }).collect() - } - - @Override - void save(User user) { - users.add(user) - } - - @Override - Optional findUserById(UserId id) { - return users.stream().filter(user -> user.id() == id).findAny() - } - - @Override - List findAllActiveUsers() { - return users.findAll { it.active } - } - - @Override - Optional findUserByUserName(String userName) { - return Optional.ofNullable(users.find { it.userName() == userName }) - } - - @Override - List findByUserNameContainingIgnoreCaseAndActiveTrue(String username, Pageable pageable) { - return users.stream().filter { it.userName() == username }.filter { it.isActive() }.collect(Collectors.toList()) - } - } - -} diff --git a/user-interface/src/main/java/life/qbic/datamanager/AppConfig.java b/user-interface/src/main/java/life/qbic/datamanager/AppConfig.java index 5f75039dd..fdf93849c 100644 --- a/user-interface/src/main/java/life/qbic/datamanager/AppConfig.java +++ b/user-interface/src/main/java/life/qbic/datamanager/AppConfig.java @@ -20,8 +20,6 @@ import life.qbic.identity.application.user.policy.directive.WhenUserRegisteredSendConfirmationEmail; import life.qbic.identity.application.user.policy.directive.WhenUserRegisteredSubmitIntegrationEvent; import life.qbic.identity.application.user.registration.EmailAddressConfirmation; -import life.qbic.identity.application.user.registration.RegisterUserInput; -import life.qbic.identity.application.user.registration.Registration; import life.qbic.identity.domain.repository.UserDataStorage; import life.qbic.identity.domain.repository.UserRepository; import life.qbic.infrastructure.email.EmailServiceProvider; @@ -114,18 +112,6 @@ public PasswordResetInput passwordResetInput(IdentityService identityService) { return new PasswordResetRequest(identityService); } - /** - * Creates the registration use case. - * - * @param identityService the user registration services used by this use case - * @return the use case input - * @since 1.0.0 - */ - @Bean - public RegisterUserInput registerUserInput(IdentityService identityService) { - return new Registration(identityService); - } - @Bean public UserInformationService userInformationService(UserRepository userRepository) { return new BasicUserInformationService(userRepository); diff --git a/user-interface/src/main/java/life/qbic/datamanager/views/register/UserRegistrationHandler.java b/user-interface/src/main/java/life/qbic/datamanager/views/register/UserRegistrationHandler.java deleted file mode 100644 index 15d6d7f55..000000000 --- a/user-interface/src/main/java/life/qbic/datamanager/views/register/UserRegistrationHandler.java +++ /dev/null @@ -1,138 +0,0 @@ -package life.qbic.datamanager.views.register; - -import com.vaadin.flow.component.Key; -import com.vaadin.flow.component.UI; -import com.vaadin.flow.router.QueryParameters; -import java.util.Map; -import life.qbic.datamanager.views.notifications.ErrorMessage; -import life.qbic.identity.application.user.registration.RegisterUserInput; -import life.qbic.identity.application.user.registration.RegisterUserOutput; -import life.qbic.identity.application.user.registration.UserRegistrationException; -import life.qbic.logging.api.Logger; -import life.qbic.logging.service.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -/** - * Handles the {@link UserRegistrationLayout} components - * - *

This class is responsible for enabling buttons or triggering other view relevant changes on - * the view class components - */ -@Component -public class UserRegistrationHandler - implements UserRegistrationHandlerInterface, RegisterUserOutput { - - private static final Logger log = - LoggerFactory.logger(UserRegistrationHandler.class.getName()); - private final RegisterUserInput registrationUseCase; - private UserRegistrationLayout userRegistrationLayout; - - @Autowired - UserRegistrationHandler(RegisterUserInput registrationUseCase) { - this.registrationUseCase = registrationUseCase; - registrationUseCase.setOutput(this); - - } - - @Override - public void handle(UserRegistrationLayout registrationLayout) { - if (userRegistrationLayout != registrationLayout) { - this.userRegistrationLayout = registrationLayout; - initFields(); - addListener(); - } - } - - private void initFields() { - userRegistrationLayout.fullName.setPattern("\\S.*"); - userRegistrationLayout.fullName.setErrorMessage("Please provide your full name here"); - userRegistrationLayout.email.setErrorMessage("Please provide a valid mail address"); - userRegistrationLayout.password.setHelperText("A password must be at least 12 characters"); - userRegistrationLayout.password.setPattern(".{12,}"); - userRegistrationLayout.password.setErrorMessage("Password too short"); - userRegistrationLayout.username.setHelperText("Your unique username, visible to other users"); - userRegistrationLayout.username.setErrorMessage("Please provide a username"); - } - - private void addListener() { - userRegistrationLayout.registerButton.addClickShortcut(Key.ENTER); - - userRegistrationLayout.registerButton.addClickListener( - event -> { - clearNotifications(); - registrationUseCase.register( - userRegistrationLayout.fullName.getValue().strip(), - userRegistrationLayout.email.getValue().strip(), - userRegistrationLayout.password.getValue().toCharArray(), - userRegistrationLayout.username.getValue().strip()); - }); - } - - private void clearNotifications() { - userRegistrationLayout.notificationLayout.removeAll(); - } - - private void showError(String title, String description) { - clearNotifications(); - ErrorMessage errorMessage = new ErrorMessage(title, description); - userRegistrationLayout.notificationLayout.add(errorMessage); - } - - private void showAlreadyUsedEmailError() { - showError("Email address already in use", - "If you have difficulties with your password you can reset it."); - } - - private void showUnexpectedError() { - showError("Registration failed", "Please try again."); - } - - @Override - public void onUserRegistrationSucceeded() { - QueryParameters registrationParams = QueryParameters.simple(Map.of("userRegistered", "true")); - UI.getCurrent().navigate("/login", registrationParams); - } - - @Override - public void onUnexpectedFailure(UserRegistrationException userRegistrationException) { - handleRegistrationFailure(userRegistrationException); - } - - @Override - public void onUnexpectedFailure(String reason) { - showUnexpectedError(); - } - - private void handleRegistrationFailure(UserRegistrationException userRegistrationException) { - if (userRegistrationException.fullNameException().isPresent()) { - userRegistrationLayout.fullName.setInvalid(true); - } - if (userRegistrationException.passwordException().isPresent()) { - userRegistrationLayout.password.setInvalid(true); - } - if (userRegistrationException.emailFormatException().isPresent()) { - userRegistrationLayout.email.setInvalid(true); - } - if (userRegistrationException.userExistsException().isPresent()) { - showAlreadyUsedEmailError(); - } - if (userRegistrationException.userNameNotAvailableException().isPresent()) { - showUserNameNotAvailableError(); - } - if (userRegistrationException.emptyUserNameException().isPresent()) { - showEmptyUserNameError(); - } - if (userRegistrationException.unexpectedException().isPresent()) { - showUnexpectedError(); - } - } - - private void showUserNameNotAvailableError() { - showError("Username already in use", "Please try another username"); - } - - private void showEmptyUserNameError() { - showError("Username must not be empty", "Please try another username"); - } -} diff --git a/user-interface/src/main/java/life/qbic/datamanager/views/register/UserRegistrationHandlerInterface.java b/user-interface/src/main/java/life/qbic/datamanager/views/register/UserRegistrationHandlerInterface.java deleted file mode 100644 index 4f3040447..000000000 --- a/user-interface/src/main/java/life/qbic/datamanager/views/register/UserRegistrationHandlerInterface.java +++ /dev/null @@ -1,18 +0,0 @@ -package life.qbic.datamanager.views.register; - -/** - * Interface to handle the {@link UserRegistrationLayout} to the {@link - * UserRegistrationHandler}. - * - * @since 1.0.0 - */ -public interface UserRegistrationHandlerInterface { - - /** - * Registers a {@link UserRegistrationLayout} to an implementing class - * - * @param registerLayout The view that is being handled - * @since 1.0.0 - */ - void handle(UserRegistrationLayout registerLayout); -} diff --git a/user-interface/src/main/java/life/qbic/datamanager/views/register/UserRegistrationLayout.java b/user-interface/src/main/java/life/qbic/datamanager/views/register/UserRegistrationLayout.java index c1a9e67b5..90d909a1a 100644 --- a/user-interface/src/main/java/life/qbic/datamanager/views/register/UserRegistrationLayout.java +++ b/user-interface/src/main/java/life/qbic/datamanager/views/register/UserRegistrationLayout.java @@ -1,7 +1,9 @@ package life.qbic.datamanager.views.register; import com.vaadin.flow.component.HasValueAndElement; +import com.vaadin.flow.component.Key; import com.vaadin.flow.component.Text; +import com.vaadin.flow.component.UI; import com.vaadin.flow.component.button.Button; import com.vaadin.flow.component.button.ButtonVariant; import com.vaadin.flow.component.dependency.CssImport; @@ -13,15 +15,29 @@ import com.vaadin.flow.component.textfield.PasswordField; import com.vaadin.flow.component.textfield.TextField; import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.QueryParameters; import com.vaadin.flow.router.Route; import com.vaadin.flow.router.RouterLink; import com.vaadin.flow.server.auth.AnonymousAllowed; +import com.vaadin.flow.spring.annotation.UIScope; import java.io.Serial; +import java.util.Map; +import java.util.Objects; import java.util.stream.Stream; +import life.qbic.application.commons.ApplicationResponse; import life.qbic.datamanager.views.AppRoutes; import life.qbic.datamanager.views.landing.LandingPageLayout; import life.qbic.datamanager.views.login.LoginLayout; import life.qbic.datamanager.views.login.passwordreset.ResetPasswordLayout; +import life.qbic.datamanager.views.notifications.ErrorMessage; +import life.qbic.identity.application.user.IdentityService; +import life.qbic.identity.application.user.IdentityService.EmptyUserNameException; +import life.qbic.identity.application.user.IdentityService.UserExistsException; +import life.qbic.identity.application.user.IdentityService.UserNameNotAvailableException; +import life.qbic.identity.application.user.registration.UserRegistrationException; +import life.qbic.identity.domain.model.EmailAddress.EmailValidationException; +import life.qbic.identity.domain.model.EncryptedPassword.PasswordValidationException; +import life.qbic.identity.domain.model.FullName.FullNameValidationException; import org.springframework.beans.factory.annotation.Autowired; /** @@ -33,6 +49,7 @@ @Route(value = AppRoutes.REGISTER, layout = LandingPageLayout.class) @CssImport("./styles/views/login/login-view.css") @AnonymousAllowed +@UIScope public class UserRegistrationLayout extends VerticalLayout { @Serial @@ -55,19 +72,76 @@ public class UserRegistrationLayout extends VerticalLayout { private VerticalLayout fieldLayout; private final VerticalLayout contentLayout; private H2 layoutTitle; + private IdentityService identityService; - public UserRegistrationLayout(@Autowired UserRegistrationHandlerInterface registerHandler) { - + public UserRegistrationLayout(@Autowired IdentityService identityService) { + this.identityService = Objects.requireNonNull(identityService); this.addClassName("grid"); - contentLayout = new VerticalLayout(); - + this.contentLayout = new VerticalLayout(); initLayout(); styleLayout(); - registerToHandler(registerHandler); + initFields(); + addListener(); + } + + private void initFields() { + fullName.setPattern("\\S.*"); + fullName.setErrorMessage("Please provide your full name here"); + email.setErrorMessage("Please provide a valid mail address"); + password.setHelperText("A password must be at least 12 characters"); + password.setPattern(".{12,}"); + password.setErrorMessage("Password too short"); + username.setHelperText("Your unique username, visible to other users"); + username.setErrorMessage("Please provide a username"); } - private void registerToHandler(UserRegistrationHandlerInterface registerHandler) { - registerHandler.handle(this); + private void addListener() { + registerButton.addClickShortcut(Key.ENTER); + + registerButton.addClickListener( + event -> { + clearNotifications(); + var response = identityService.registerUser( + fullName.getValue().strip(), + username.getValue().strip(), + email.getValue().strip(), + password.getValue().toCharArray() + ); + handleResponse(response); + }); + } + + private void handleResponse(ApplicationResponse response) { + response.ifSuccessOrElse(this::onUserRegistrationSucceeded, this::handleRegistrationFailure); + } + + private void handleRegistrationFailure(ApplicationResponse response) { + UserRegistrationException exception = convertToRegistrationException(response); + handleRegistrationFailure(exception); + } + + private UserRegistrationException convertToRegistrationException(ApplicationResponse applicationResponse) { + var builder = UserRegistrationException.builder(); + + for (RuntimeException e : applicationResponse.failures()) { + + if (e instanceof EmailValidationException emailValidationException) { + builder.withEmailFormatException(emailValidationException); + } else if (e instanceof PasswordValidationException passwordValidationException) { + builder.withInvalidPasswordException(passwordValidationException); + } else if (e instanceof FullNameValidationException fullNameValidationException) { + builder.withFullNameException(fullNameValidationException); + } else if (e instanceof UserExistsException userExistsException) { + builder.withUserExistsException(userExistsException); + } else if (e instanceof UserNameNotAvailableException userNameNotAvailableException) { + builder.withUserNameNotAvailableException(userNameNotAvailableException); + } else if (e instanceof EmptyUserNameException emptyUserNameException) { + builder.withEmptyUserNameException(emptyUserNameException); + } else { + builder.withUnexpectedException(e); + } + } + return builder.build(); } private void initLayout() { @@ -163,4 +237,60 @@ private void styleRegisterButton() { private void setRequiredIndicatorVisible(HasValueAndElement... components) { Stream.of(components).forEach(comp -> comp.setRequiredIndicatorVisible(true)); } + + private void showUserNameNotAvailableError() { + showError("Username already in use", "Please try another username"); + } + + private void showEmptyUserNameError() { + showError("Username must not be empty", "Please try another username"); + } + + private void showError(String title, String description) { + clearNotifications(); + ErrorMessage errorMessage = new ErrorMessage(title, description); + notificationLayout.add(errorMessage); + } + + private void clearNotifications() { + notificationLayout.removeAll(); + } + + private void handleRegistrationFailure(UserRegistrationException userRegistrationException) { + if (userRegistrationException.fullNameException().isPresent()) { + fullName.setInvalid(true); + } + if (userRegistrationException.passwordException().isPresent()) { + password.setInvalid(true); + } + if (userRegistrationException.emailFormatException().isPresent()) { + email.setInvalid(true); + } + if (userRegistrationException.userExistsException().isPresent()) { + showAlreadyUsedEmailError(); + } + if (userRegistrationException.userNameNotAvailableException().isPresent()) { + showUserNameNotAvailableError(); + } + if (userRegistrationException.emptyUserNameException().isPresent()) { + showEmptyUserNameError(); + } + if (userRegistrationException.unexpectedException().isPresent()) { + showUnexpectedError(); + } + } + + public void onUserRegistrationSucceeded(ApplicationResponse response) { + QueryParameters registrationParams = QueryParameters.simple(Map.of("userRegistered", "true")); + UI.getCurrent().navigate("/login", registrationParams); + } + + private void showUnexpectedError() { + showError("Registration failed", "Please try again."); + } + + private void showAlreadyUsedEmailError() { + showError("Email address already in use", + "If you have difficulties with your password you can reset it."); + } }