From feaf5eea030349ede4b1b93aa5488eb194e6ecc7 Mon Sep 17 00:00:00 2001 From: Shubham Saxena <136057998+shubham7saxena7@users.noreply.github.com> Date: Sun, 17 Sep 2023 17:00:34 +0530 Subject: [PATCH] feat: email notification (#26692) update email sending flow for inviting users to workspace and forgot password with new templates fixes: https://github.com/appsmithorg/appsmith/issues/19462 code changes: **new code** * introduces new EmailService **refactoring** * moves all email sending flows to the new email service * moves all email constants and templates to the Email constants **code deleted** * removed code for welcome email invite --- .../ce/AuthenticationSuccessHandlerCE.java | 26 +- .../server/constants/ce/EmailConstantsCE.java | 32 + .../ce/InstanceAdminControllerCE.java | 13 +- .../controllers/ce/UserControllerCE.java | 11 +- .../server/services/EmailService.java | 5 + .../server/services/EmailServiceImpl.java | 12 + .../server/services/UserServiceImpl.java | 5 +- .../server/services/ce/EmailServiceCE.java | 23 + .../services/ce/EmailServiceCEImpl.java | 159 +++ .../server/services/ce/UserServiceCE.java | 15 +- .../server/services/ce/UserServiceCEImpl.java | 161 +-- .../UserServiceCECompatibleImpl.java | 6 +- .../server/solutions/EnvManagerImpl.java | 7 +- .../UserAndAccessManagementServiceImpl.java | 13 +- .../server/solutions/UserSignupImpl.java | 7 +- .../server/solutions/ce/EnvManagerCE.java | 4 +- .../server/solutions/ce/EnvManagerCEImpl.java | 61 +- .../UserAndAccessManagementServiceCEImpl.java | 95 +- .../server/solutions/ce/UserSignupCE.java | 5 +- .../server/solutions/ce/UserSignupCEImpl.java | 31 +- ...cessManagementServiceCECompatibleImpl.java | 13 +- .../email/ce/forgotPasswordTemplate.html | 542 ++++++++++ .../email/ce/instanceAdminInviteTemplate.html | 545 ++++++++++ .../inviteWorkspaceExistingUserTemplate.html | 547 ++++++++++ .../ce/inviteWorkspaceNewUserTemplate.html | 547 ++++++++++ .../resources/email/commentAddedTemplate.html | 286 ----- .../email/emailVerificationTemplate.html | 982 +++++++++++++----- .../email/forgotPasswordTemplate.html | 123 --- .../resources/email/inviteUserTemplate.html | 123 --- .../resources/email/welcomeUserTemplate.html | 302 ------ .../server/services/UserServiceTest.java | 61 +- .../services/ce/EmailServiceCEImplTest.java | 192 ++++ .../server/solutions/EnvManagerTest.java | 7 +- .../server/solutions/UserSignupTest.java | 7 +- 34 files changed, 3549 insertions(+), 1419 deletions(-) create mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/constants/ce/EmailConstantsCE.java create mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/services/EmailService.java create mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/services/EmailServiceImpl.java create mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/EmailServiceCE.java create mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/EmailServiceCEImpl.java create mode 100644 app/server/appsmith-server/src/main/resources/email/ce/forgotPasswordTemplate.html create mode 100644 app/server/appsmith-server/src/main/resources/email/ce/instanceAdminInviteTemplate.html create mode 100644 app/server/appsmith-server/src/main/resources/email/ce/inviteWorkspaceExistingUserTemplate.html create mode 100644 app/server/appsmith-server/src/main/resources/email/ce/inviteWorkspaceNewUserTemplate.html delete mode 100644 app/server/appsmith-server/src/main/resources/email/commentAddedTemplate.html delete mode 100644 app/server/appsmith-server/src/main/resources/email/forgotPasswordTemplate.html delete mode 100644 app/server/appsmith-server/src/main/resources/email/inviteUserTemplate.html delete mode 100644 app/server/appsmith-server/src/main/resources/email/welcomeUserTemplate.html create mode 100644 app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/EmailServiceCEImplTest.java diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/authentication/handlers/ce/AuthenticationSuccessHandlerCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/authentication/handlers/ce/AuthenticationSuccessHandlerCE.java index 700c771cda..fcb70ddc0c 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/authentication/handlers/ce/AuthenticationSuccessHandlerCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/authentication/handlers/ce/AuthenticationSuccessHandlerCE.java @@ -214,10 +214,9 @@ public class AuthenticationSuccessHandlerCE implements ServerAuthenticationSucce .flatMap(defaultApplication -> postVerificationRequiredHandler( webFilterExchange, user, defaultApplication, TRUE)); } else { - return Mono.zip( - userService.sendWelcomeEmail(user, originHeader), - createDefaultApplication(defaultWorkspaceId, authentication)) - .flatMap(obj -> redirectHelper.handleRedirect(webFilterExchange, obj.getT2(), true)); + return createDefaultApplication(defaultWorkspaceId, authentication) + .flatMap(application -> + redirectHelper.handleRedirect(webFilterExchange, application, true)); } }); } else { @@ -226,9 +225,7 @@ public class AuthenticationSuccessHandlerCE implements ServerAuthenticationSucce if (TRUE.equals(isVerificationRequired)) { return postVerificationRequiredHandler(webFilterExchange, user, null, TRUE); } else { - return userService - .sendWelcomeEmail(user, originHeader) - .then(redirectHelper.handleRedirect(webFilterExchange, null, true)); + return redirectHelper.handleRedirect(webFilterExchange, null, true); } }); } @@ -289,17 +286,12 @@ public class AuthenticationSuccessHandlerCE implements ServerAuthenticationSucce redirectionMono = workspaceService .isCreateWorkspaceAllowed(TRUE) .flatMap(isCreateWorkspaceAllowed -> { - if (isCreateWorkspaceAllowed) { - - return Mono.zip( - userService.sendWelcomeEmail(user, originHeader), - createDefaultApplication(defaultWorkspaceId, authentication)) - .flatMap(objects -> handleOAuth2Redirect( - webFilterExchange, objects.getT2(), finalIsFromSignup)); + if (isCreateWorkspaceAllowed.equals(Boolean.TRUE)) { + return createDefaultApplication(defaultWorkspaceId, authentication) + .flatMap(application -> handleOAuth2Redirect( + webFilterExchange, application, finalIsFromSignup)); } - return userService - .sendWelcomeEmail(user, originHeader) - .then(handleOAuth2Redirect(webFilterExchange, null, finalIsFromSignup)); + return handleOAuth2Redirect(webFilterExchange, null, finalIsFromSignup); }); } else { redirectionMono = handleOAuth2Redirect(webFilterExchange, null, isFromSignup); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/ce/EmailConstantsCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/ce/EmailConstantsCE.java new file mode 100644 index 0000000000..f0774c0ec0 --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/ce/EmailConstantsCE.java @@ -0,0 +1,32 @@ +package com.appsmith.server.constants.ce; + +import com.appsmith.server.constants.FieldName; + +public class EmailConstantsCE { + public static final String INSTANCE_NAME = "instanceName"; + public static final String RESET_URL = "resetUrl"; + public static final String EMAIL_ROLE_ADMINISTRATOR_TEXT = "an " + FieldName.ADMINISTRATOR.toLowerCase(); + public static final String EMAIL_ROLE_DEVELOPER_TEXT = "a " + FieldName.DEVELOPER.toLowerCase(); + public static final String EMAIL_ROLE_VIEWER_TEXT = "an " + FieldName.VIEWER.toLowerCase(); + public static final String PRIMARY_LINK_URL = "primaryLinkUrl"; + public static final String PRIMARY_LINK_TEXT = "primaryLinkText"; + public static final String PRIMARY_LINK_TEXT_USER_SIGNUP = "Sign up now"; + public static final String PRIMARY_LINK_TEXT_INVITE_TO_INSTANCE = "Go to instance"; + public static final String PRIMARY_LINK_TEXT_WORKSPACE_REDIRECTION = "Go to your Appsmith workspace"; + + public static final String INVITE_USER_CLIENT_URL_FORMAT = "%s/user/signup?email=%s"; + public static final String WORKSPACE_EMAIL_SUBJECT_FOR_NEW_USER = "Youโ€™re invited to the workspace %s."; + public static final String FORGOT_PASSWORD_EMAIL_SUBJECT = "Reset your Appsmith password"; + public static final String EMAIL_VERIFICATION_EMAIL_SUBJECT = "Verify your account"; + public static final String INSTANCE_ADMIN_INVITE_EMAIL_SUBJECT = "You're invited to an Appsmith instance"; + public static final String INVITE_WORKSPACE_TEMPLATE_EXISTING_USER_CE = + "email/ce/inviteWorkspaceExistingUserTemplate.html"; + public static final String INVITE_WORKSPACE_TEMPLATE_NEW_USER_CE = "email/ce/inviteWorkspaceNewUserTemplate.html"; + public static final String FORGOT_PASSWORD_TEMPLATE_CE = "email/ce/forgotPasswordTemplate.html"; + public static final String INSTANCE_ADMIN_INVITE_EMAIL_TEMPLATE = "email/ce/instanceAdminInviteTemplate.html"; + public static final String EMAIL_VERIFICATION_EMAIL_TEMPLATE = "email/emailVerificationTemplate.html"; + public static final String WORKSPACE_URL = "%s/applications#%s"; + public static final String INVITER_FIRST_NAME = "inviterFirstName"; + public static final String INVITER_WORKSPACE_NAME = "inviterWorkspaceName"; + public static final String EMAIL_VERIFICATION_URL = "verificationUrl"; +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/InstanceAdminControllerCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/InstanceAdminControllerCE.java index c1f12b85e3..868418d752 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/InstanceAdminControllerCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/InstanceAdminControllerCE.java @@ -15,6 +15,7 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; @@ -47,19 +48,23 @@ public class InstanceAdminControllerCE { @PutMapping( value = "/env", consumes = {MediaType.APPLICATION_JSON_VALUE}) - public Mono> saveEnvChangesJSON(@Valid @RequestBody Map changes) { + public Mono> saveEnvChangesJSON( + @Valid @RequestBody Map changes, @RequestHeader("Origin") String originHeader) { log.debug("Applying env updates {}", changes.keySet()); - return envManager.applyChanges(changes).thenReturn(new ResponseDTO<>(HttpStatus.OK.value(), null, null)); + return envManager + .applyChanges(changes, originHeader) + .thenReturn(new ResponseDTO<>(HttpStatus.OK.value(), null, null)); } @JsonView(Views.Public.class) @PutMapping( value = "/env", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE}) - public Mono> saveEnvChangesMultipartFormData(ServerWebExchange exchange) { + public Mono> saveEnvChangesMultipartFormData( + @RequestHeader("Origin") String originHeader, ServerWebExchange exchange) { log.debug("Applying env updates from form data"); return exchange.getMultipartData() - .flatMap(envManager::applyChangesFromMultipartFormData) + .flatMap(formData -> envManager.applyChangesFromMultipartFormData(formData, originHeader)) .thenReturn(new ResponseDTO<>(HttpStatus.OK.value(), null, null)); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/UserControllerCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/UserControllerCE.java index a64e5520cf..bc32f57586 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/UserControllerCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/UserControllerCE.java @@ -91,9 +91,11 @@ public class UserControllerCE extends BaseController value = "/super", consumes = {MediaType.APPLICATION_JSON_VALUE}) public Mono> createSuperUser( - @Valid @RequestBody UserSignupRequestDTO resource, ServerWebExchange exchange) { + @Valid @RequestBody UserSignupRequestDTO resource, + @RequestHeader("Origin") String originHeader, + ServerWebExchange exchange) { return userSignup - .signupAndLoginSuper(resource, exchange) + .signupAndLoginSuper(resource, originHeader, exchange) .map(created -> new ResponseDTO<>(HttpStatus.CREATED.value(), created, null)); } @@ -101,8 +103,9 @@ public class UserControllerCE extends BaseController @PostMapping( value = "/super", consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE}) - public Mono createSuperUserFromFormData(ServerWebExchange exchange) { - return userSignup.signupAndLoginSuperFromFormData(exchange); + public Mono createSuperUserFromFormData( + @RequestHeader("Origin") String originHeader, ServerWebExchange exchange) { + return userSignup.signupAndLoginSuperFromFormData(originHeader, exchange); } @JsonView(Views.Public.class) diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/EmailService.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/EmailService.java new file mode 100644 index 0000000000..69c491bb27 --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/EmailService.java @@ -0,0 +1,5 @@ +package com.appsmith.server.services; + +import com.appsmith.server.services.ce.EmailServiceCE; + +public interface EmailService extends EmailServiceCE {} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/EmailServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/EmailServiceImpl.java new file mode 100644 index 0000000000..6fa983232c --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/EmailServiceImpl.java @@ -0,0 +1,12 @@ +package com.appsmith.server.services; + +import com.appsmith.server.notifications.EmailSender; +import com.appsmith.server.services.ce.EmailServiceCEImpl; +import org.springframework.stereotype.Service; + +@Service +public class EmailServiceImpl extends EmailServiceCEImpl implements EmailService { + public EmailServiceImpl(EmailSender emailSender, TenantService tenantService) { + super(emailSender, tenantService); + } +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserServiceImpl.java index 9aef37ed7e..81ca44347f 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserServiceImpl.java @@ -3,7 +3,6 @@ package com.appsmith.server.services; import com.appsmith.external.services.EncryptionService; import com.appsmith.server.configurations.CommonConfig; import com.appsmith.server.configurations.EmailConfig; -import com.appsmith.server.helpers.RedirectHelper; import com.appsmith.server.helpers.UserUtils; import com.appsmith.server.notifications.EmailSender; import com.appsmith.server.ratelimiting.RateLimitService; @@ -49,7 +48,7 @@ public class UserServiceImpl extends UserServiceCECompatibleImpl implements User PermissionGroupService permissionGroupService, UserUtils userUtils, EmailVerificationTokenRepository emailVerificationTokenRepository, - RedirectHelper redirectHelper, + EmailService emailService, RateLimitService rateLimitService) { super( scheduler, @@ -74,7 +73,7 @@ public class UserServiceImpl extends UserServiceCECompatibleImpl implements User permissionGroupService, userUtils, emailVerificationTokenRepository, - redirectHelper, + emailService, rateLimitService); } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/EmailServiceCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/EmailServiceCE.java new file mode 100644 index 0000000000..4bbbbf0d1d --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/EmailServiceCE.java @@ -0,0 +1,23 @@ +package com.appsmith.server.services.ce; + +import com.appsmith.server.domains.PermissionGroup; +import com.appsmith.server.domains.User; +import com.appsmith.server.domains.Workspace; +import reactor.core.publisher.Mono; + +public interface EmailServiceCE { + Mono sendForgotPasswordEmail(String email, String resetUrl, String originHeader); + + Mono sendInviteUserToWorkspaceEmail( + User invitingUser, + User invitedUser, + Workspace workspaceInvitedTo, + PermissionGroup assignedPermissionGroup, + String originHeader, + boolean isNewUser); + + Mono sendEmailVerificationEmail(User user, String verificationUrl); + + Mono sendInstanceAdminInviteEmail( + User invitedUser, User invitingUser, String originHeader, boolean isNewUser); +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/EmailServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/EmailServiceCEImpl.java new file mode 100644 index 0000000000..573faeb50a --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/EmailServiceCEImpl.java @@ -0,0 +1,159 @@ +package com.appsmith.server.services.ce; + +import com.appsmith.server.constants.FieldName; +import com.appsmith.server.domains.PermissionGroup; +import com.appsmith.server.domains.TenantConfiguration; +import com.appsmith.server.domains.User; +import com.appsmith.server.domains.Workspace; +import com.appsmith.server.notifications.EmailSender; +import com.appsmith.server.services.TenantService; +import org.apache.commons.lang3.StringUtils; +import reactor.core.publisher.Mono; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +import static com.appsmith.server.constants.ce.EmailConstantsCE.*; + +public class EmailServiceCEImpl implements EmailServiceCE { + private final EmailSender emailSender; + private final TenantService tenantService; + + public EmailServiceCEImpl(EmailSender emailSender, TenantService tenantService) { + this.emailSender = emailSender; + this.tenantService = tenantService; + } + + @Override + public Mono sendForgotPasswordEmail(String email, String resetUrl, String originHeader) { + Map params = new HashMap<>(); + params.put(RESET_URL, resetUrl); + return this.enrichParams(params) + .flatMap(enrichedParams -> this.enrichWithBrandParams(enrichedParams, originHeader) + .flatMap(updatedParams -> emailSender.sendMail( + email, + String.format(FORGOT_PASSWORD_EMAIL_SUBJECT, updatedParams.get(INSTANCE_NAME)), + getForgotPasswordTemplate(), + updatedParams))); + } + + @Override + public Mono sendInviteUserToWorkspaceEmail( + User invitingUser, + User invitedUser, + Workspace workspaceInvitedTo, + PermissionGroup assignedPermissionGroup, + String originHeader, + boolean isNewUser) { + String inviteUrl = isNewUser + ? String.format( + INVITE_USER_CLIENT_URL_FORMAT, + originHeader, + URLEncoder.encode(invitedUser.getUsername().toLowerCase(), StandardCharsets.UTF_8)) + : originHeader; + String emailSubject = String.format(WORKSPACE_EMAIL_SUBJECT_FOR_NEW_USER, workspaceInvitedTo.getName()); + Map params = getInviteToWorkspaceEmailParams( + workspaceInvitedTo, invitingUser, inviteUrl, assignedPermissionGroup.getName(), isNewUser); + return this.enrichParams(params).flatMap(enrichedParams -> this.enrichWithBrandParams( + enrichedParams, originHeader) + .flatMap(updatedParams -> emailSender.sendMail( + invitedUser.getEmail(), emailSubject, getWorkspaceInviteTemplate(isNewUser), updatedParams))); + } + + @Override + public Mono sendEmailVerificationEmail(User user, String verificationURL) { + Map params = new HashMap<>(); + params.put(EMAIL_VERIFICATION_URL, verificationURL); + return this.enrichParams(params) + .flatMap(updatedParams -> emailSender.sendMail( + user.getEmail(), + EMAIL_VERIFICATION_EMAIL_SUBJECT, + EMAIL_VERIFICATION_EMAIL_TEMPLATE, + updatedParams)); + } + + @Override + public Mono sendInstanceAdminInviteEmail( + User invitedUser, User invitingUser, String originHeader, boolean isNewUser) { + Map params = new HashMap<>(); + String inviteUrl = isNewUser + ? String.format( + INVITE_USER_CLIENT_URL_FORMAT, + originHeader, + URLEncoder.encode(invitedUser.getUsername().toLowerCase(), StandardCharsets.UTF_8)) + : originHeader; + params.put(PRIMARY_LINK_URL, inviteUrl); + + String primaryLinkText = isNewUser ? PRIMARY_LINK_TEXT_USER_SIGNUP : PRIMARY_LINK_TEXT_INVITE_TO_INSTANCE; + params.put(PRIMARY_LINK_TEXT, primaryLinkText); + + if (invitingUser != null) { + params.put(INVITER_FIRST_NAME, StringUtils.defaultIfEmpty(invitingUser.getName(), invitingUser.getEmail())); + } + return this.enrichParams(params) + .flatMap(enrichedParams -> this.enrichWithBrandParams(enrichedParams, originHeader)) + .flatMap(updatedParams -> emailSender.sendMail( + invitedUser.getEmail(), + String.format(INSTANCE_ADMIN_INVITE_EMAIL_SUBJECT), + getAdminInstanceInviteTemplate(), + updatedParams)); + } + + protected Mono> enrichParams(Map params) { + return tenantService.getDefaultTenant().map(tenant -> { + final TenantConfiguration tenantConfiguration = tenant.getTenantConfiguration(); + params.put(INSTANCE_NAME, StringUtils.defaultIfEmpty(tenantConfiguration.getInstanceName(), "Appsmith")); + return params; + }); + } + + protected Mono> enrichWithBrandParams(Map params, String origin) { + return Mono.just(params); + } + + protected String getForgotPasswordTemplate() { + return FORGOT_PASSWORD_TEMPLATE_CE; + } + + protected String getWorkspaceInviteTemplate(boolean isNewUser) { + if (isNewUser) return INVITE_WORKSPACE_TEMPLATE_NEW_USER_CE; + + return INVITE_WORKSPACE_TEMPLATE_EXISTING_USER_CE; + } + + protected String getAdminInstanceInviteTemplate() { + return INSTANCE_ADMIN_INVITE_EMAIL_TEMPLATE; + } + + private Map getInviteToWorkspaceEmailParams( + Workspace workspace, User inviter, String inviteUrl, String roleType, boolean isNewUser) { + Map params = new HashMap<>(); + if (workspace != null) { + params.put(INVITER_WORKSPACE_NAME, workspace.getName()); + } + if (inviter != null) { + params.put(INVITER_FIRST_NAME, StringUtils.defaultIfEmpty(inviter.getName(), inviter.getEmail())); + } + if (roleType != null) { + if (roleType.startsWith(FieldName.ADMINISTRATOR)) { + params.put(FieldName.ROLE, EMAIL_ROLE_ADMINISTRATOR_TEXT); + } else if (roleType.startsWith(FieldName.DEVELOPER)) { + params.put(FieldName.ROLE, EMAIL_ROLE_DEVELOPER_TEXT); + } else if (roleType.startsWith(FieldName.VIEWER)) { + params.put(FieldName.ROLE, EMAIL_ROLE_VIEWER_TEXT); + } + } + if (isNewUser) { + params.put(PRIMARY_LINK_URL, inviteUrl); + params.put(PRIMARY_LINK_TEXT, PRIMARY_LINK_TEXT_USER_SIGNUP); + } else { + if (workspace != null) { + params.put(PRIMARY_LINK_URL, String.format(WORKSPACE_URL, inviteUrl, workspace.getId())); + } + params.put(PRIMARY_LINK_TEXT, PRIMARY_LINK_TEXT_WORKSPACE_REDIRECTION); + } + return params; + } +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserServiceCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserServiceCE.java index bdade5284b..145c487093 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserServiceCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserServiceCE.java @@ -2,7 +2,6 @@ package com.appsmith.server.services.ce; import com.appsmith.server.acl.AclPermission; import com.appsmith.server.domains.User; -import com.appsmith.server.domains.Workspace; import com.appsmith.server.dtos.ResendEmailVerificationDTO; import com.appsmith.server.dtos.ResetUserPasswordDTO; import com.appsmith.server.dtos.UserProfileDTO; @@ -13,7 +12,6 @@ import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import java.util.Map; import java.util.Set; public interface UserServiceCE extends CrudService { @@ -30,29 +28,18 @@ public interface UserServiceCE extends CrudService { Mono resetPasswordAfterForgotPassword(String token, User user); - Mono createUser(User user, String originHeader); - - Mono createUserAndSendEmail(User user, String originHeader); + Mono createUser(User user); Mono userCreate(User user, boolean isAdminUser); - Mono createNewUserAndSendInviteEmail( - String email, String originHeader, Workspace workspace, User inviter, String role); - Mono updateCurrentUser(UserUpdateDTO updates, ServerWebExchange exchange); - Map getEmailParams(Workspace workspace, User inviterUser, String inviteUrl, boolean isNewUser); - Mono isUsersEmpty(); Mono buildUserProfileDTO(User user); Flux getAllByEmails(Set emails, AclPermission permission); - Mono> updateTenantLogoInParams(Map params, String origin); - - Mono sendWelcomeEmail(User user, String originHeader); - Mono updateWithoutPermission(String id, User update); Mono resendEmailVerification(ResendEmailVerificationDTO resendEmailVerificationDTO, String redirectUrl); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserServiceCEImpl.java index a828b92d92..1c8c21294d 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserServiceCEImpl.java @@ -6,7 +6,6 @@ import com.appsmith.external.services.EncryptionService; import com.appsmith.server.acl.AclPermission; import com.appsmith.server.configurations.CommonConfig; import com.appsmith.server.configurations.EmailConfig; -import com.appsmith.server.constants.Appsmith; import com.appsmith.server.constants.FieldName; import com.appsmith.server.constants.RateLimitConstants; import com.appsmith.server.domains.EmailVerificationToken; @@ -27,7 +26,6 @@ import com.appsmith.server.dtos.UserSignupDTO; import com.appsmith.server.dtos.UserUpdateDTO; import com.appsmith.server.exceptions.AppsmithError; import com.appsmith.server.exceptions.AppsmithException; -import com.appsmith.server.helpers.RedirectHelper; import com.appsmith.server.helpers.UserUtils; import com.appsmith.server.helpers.ValidationUtils; import com.appsmith.server.notifications.EmailSender; @@ -38,6 +36,7 @@ import com.appsmith.server.repositories.PasswordResetTokenRepository; import com.appsmith.server.repositories.UserRepository; import com.appsmith.server.services.AnalyticsService; import com.appsmith.server.services.BaseService; +import com.appsmith.server.services.EmailService; import com.appsmith.server.services.PermissionGroupService; import com.appsmith.server.services.SessionUserService; import com.appsmith.server.services.TenantService; @@ -68,7 +67,6 @@ import org.springframework.util.StringUtils; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebFilterChain; import org.springframework.web.server.WebSession; -import reactor.core.Exceptions; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.core.scheduler.Scheduler; @@ -106,34 +104,26 @@ public class UserServiceCEImpl extends BaseService private final PasswordResetTokenRepository passwordResetTokenRepository; private final PasswordEncoder passwordEncoder; - private final EmailSender emailSender; - private final ApplicationRepository applicationRepository; + private final PolicySolution policySolution; private final CommonConfig commonConfig; - private final EmailConfig emailConfig; private final UserChangedHandler userChangedHandler; private final EncryptionService encryptionService; private final UserDataService userDataService; private final TenantService tenantService; private final PermissionGroupService permissionGroupService; private final UserUtils userUtils; + private final EmailService emailService; private final RateLimitService rateLimitService; - private final RedirectHelper redirectHelper; private static final WebFilterChain EMPTY_WEB_FILTER_CHAIN = serverWebExchange -> Mono.empty(); - - private static final String WELCOME_USER_EMAIL_TEMPLATE = "email/welcomeUserTemplate.html"; - private static final String FORGOT_PASSWORD_EMAIL_TEMPLATE = "email/forgotPasswordTemplate.html"; private static final String FORGOT_PASSWORD_CLIENT_URL_FORMAT = "%s/user/resetPassword?token=%s"; - private static final String INVITE_USER_CLIENT_URL_FORMAT = "%s/user/signup?email=%s"; - public static final String INVITE_USER_EMAIL_TEMPLATE = "email/inviteUserTemplate.html"; private static final Pattern ALLOWED_ACCENTED_CHARACTERS_PATTERN = Pattern.compile("^[\\p{L} 0-9 .\'\\-]+$"); private static final String EMAIL_VERIFICATION_CLIENT_URL_FORMAT = "%s/user/verify?token=%s&email=%s&redirectUrl=%s"; private static final String EMAIL_VERIFICATION_ERROR_URL_FORMAT = "/user/verify-error?code=%s&message=%s&email=%s"; - private static final String USER_EMAIL_VERIFICATION_EMAIL_TEMPLATE = "email/emailVerificationTemplate.html"; private final EmailVerificationTokenRepository emailVerificationTokenRepository; private final ServerRedirectStrategy redirectStrategy = new DefaultServerRedirectStrategy(); @@ -162,18 +152,16 @@ public class UserServiceCEImpl extends BaseService PermissionGroupService permissionGroupService, UserUtils userUtils, EmailVerificationTokenRepository emailVerificationTokenRepository, - RedirectHelper redirectHelper, + EmailService emailService, RateLimitService rateLimitService) { + super(scheduler, validator, mongoConverter, reactiveMongoTemplate, repository, analyticsService); this.workspaceService = workspaceService; this.sessionUserService = sessionUserService; this.passwordResetTokenRepository = passwordResetTokenRepository; this.passwordEncoder = passwordEncoder; - this.emailSender = emailSender; - this.applicationRepository = applicationRepository; this.policySolution = policySolution; this.commonConfig = commonConfig; - this.emailConfig = emailConfig; this.userChangedHandler = userChangedHandler; this.encryptionService = encryptionService; this.userDataService = userDataService; @@ -182,7 +170,7 @@ public class UserServiceCEImpl extends BaseService this.userUtils = userUtils; this.rateLimitService = rateLimitService; this.emailVerificationTokenRepository = emailVerificationTokenRepository; - this.redirectHelper = redirectHelper; + this.emailService = emailService; } @Override @@ -302,12 +290,7 @@ public class UserServiceCEImpl extends BaseService log.debug("Password reset url for email: {}: {}", passwordResetToken.getEmail(), resetUrl); - Map params = new HashMap<>(); - params.put("resetUrl", resetUrl); - - return updateTenantLogoInParams(params, resetUserPasswordDTO.getBaseUrl()) - .flatMap(updatedParams -> emailSender.sendMail( - email, "Appsmith Password Reset", FORGOT_PASSWORD_EMAIL_TEMPLATE, updatedParams)); + return emailService.sendForgotPasswordEmail(email, resetUrl, resetUserPasswordDTO.getBaseUrl()); }) .thenReturn(true); } @@ -446,7 +429,7 @@ public class UserServiceCEImpl extends BaseService @Override public Mono create(User user) { // This is the path that is taken when a new user signs up on its own - return createUser(user, null).map(UserSignupDTO::getUser); + return createUser(user).map(UserSignupDTO::getUser); } @Override @@ -510,12 +493,7 @@ public class UserServiceCEImpl extends BaseService } @Override - public Mono createUser(User user, String originHeader) { - if (originHeader == null || originHeader.isBlank()) { - // Default to the production link - originHeader = Appsmith.DEFAULT_ORIGIN_HEADER; - } - + public Mono createUser(User user) { // Only encode the password if it's a form signup. For OAuth signups, we don't need password if (LoginSource.FORM.equals(user.getSource())) { if (user.getPassword() == null || user.getPassword().isBlank()) { @@ -597,17 +575,6 @@ public class UserServiceCEImpl extends BaseService * @param user User object representing the user to be created/enabled. * @return Publishes the user object, after having been saved. */ - @Override - public Mono createUserAndSendEmail(User user, String originHeader) { - return createUser(user, originHeader).flatMap(userSignupDTO -> { - User savedUser = userSignupDTO.getUser(); - Mono userMono = emailConfig.isWelcomeEmailEnabled() - ? sendWelcomeEmail(savedUser, originHeader) - : Mono.just(savedUser); - return userMono.thenReturn(userSignupDTO); - }); - } - private Mono signupIfAllowed(User user) { boolean isAdminUser = false; @@ -642,30 +609,6 @@ public class UserServiceCEImpl extends BaseService }); } - @Override - public Mono sendWelcomeEmail(User user, String originHeader) { - Map params = new HashMap<>(); - params.put("primaryLinkUrl", originHeader); - - return updateTenantLogoInParams(params, originHeader) - .flatMap(updatedParams -> emailSender.sendMail( - user.getEmail(), "Welcome to Appsmith", WELCOME_USER_EMAIL_TEMPLATE, updatedParams)) - .elapsed() - .map(pair -> { - log.debug("UserServiceCEImpl::Time taken to send email: {} ms", pair.getT1()); - return pair.getT2(); - }) - .onErrorResume(error -> { - // Swallowing this exception because we don't want this to affect the rest of the flow. - log.error( - "Ignoring error: Unable to send welcome email to the user {}. Cause: ", - user.getEmail(), - Exceptions.unwrap(error)); - return Mono.just(TRUE); - }) - .thenReturn(user); - } - @Override public Mono update(String id, User userUpdate) { Mono userFromRepository = repository @@ -702,41 +645,6 @@ public class UserServiceCEImpl extends BaseService return repository.save(existingUser).map(userChangedHandler::publish); } - @Override - public Mono createNewUserAndSendInviteEmail( - String email, String originHeader, Workspace workspace, User inviter, String role) { - User newUser = new User(); - newUser.setEmail(email.toLowerCase()); - - // This is a new user. Till the user signs up, this user would be disabled. - newUser.setIsEnabled(false); - - // The invite token is not used today and doesn't need to be verified. We still save the invite token with the - // role information to classify the user persona. - newUser.setInviteToken(role + ":" + UUID.randomUUID()); - - boolean isAdminUser = commonConfig.getAdminEmails().contains(email.toLowerCase()); - - // Call user service's userCreate function so that the default workspace, etc are also created along with - // assigning basic permissions. - return userCreate(newUser, isAdminUser).flatMap(createdUser -> { - log.debug("Going to send email for invite user to {}", createdUser.getEmail()); - String inviteUrl = String.format( - INVITE_USER_CLIENT_URL_FORMAT, - originHeader, - URLEncoder.encode(createdUser.getEmail(), StandardCharsets.UTF_8)); - - // Email template parameters initialization below. - Map params = getEmailParams(workspace, inviter, inviteUrl, true); - - // We have sent out the emails. Just send back the saved user. - return updateTenantLogoInParams(params, originHeader) - .flatMap(updatedParams -> emailSender.sendMail( - createdUser.getEmail(), "Invite for Appsmith", INVITE_USER_EMAIL_TEMPLATE, updatedParams)) - .thenReturn(createdUser); - }); - } - @Override public Flux get(MultiValueMap params) { // Get All Users should not be supported. Return an error @@ -806,30 +714,6 @@ public class UserServiceCEImpl extends BaseService }); } - @Override - public Map getEmailParams(Workspace workspace, User inviter, String inviteUrl, boolean isNewUser) { - Map params = new HashMap<>(); - - if (inviter != null) { - params.put( - "inviterFirstName", - org.apache.commons.lang3.StringUtils.defaultIfEmpty(inviter.getName(), inviter.getEmail())); - } - if (workspace != null) { - params.put("inviterWorkspaceName", workspace.getName()); - } - if (isNewUser) { - params.put("primaryLinkUrl", inviteUrl); - params.put("primaryLinkText", "Sign up now"); - } else { - if (workspace != null) { - params.put("primaryLinkUrl", inviteUrl + "/applications#" + workspace.getId()); - } - params.put("primaryLinkText", "Go to workspace"); - } - return params; - } - @Override public Mono isUsersEmpty() { return repository.isUsersEmpty(); @@ -892,11 +776,6 @@ public class UserServiceCEImpl extends BaseService return repository.findAllByEmails(emails); } - @Override - public Mono> updateTenantLogoInParams(Map params, String origin) { - return Mono.just(params); - } - @Override public Mono resendEmailVerification( ResendEmailVerificationDTO resendEmailVerificationDTO, String redirectUrl) { @@ -971,21 +850,7 @@ public class UserServiceCEImpl extends BaseService URLEncoder.encode(emailVerificationToken.getEmail(), StandardCharsets.UTF_8), redirectUrlCopy); - log.debug( - "Email Verification url for email: {}: {}", - emailVerificationToken.getEmail(), - verificationUrl); - - Map params = new HashMap<>(); - - params.put("verificationUrl", verificationUrl); - - return updateTenantLogoInParams(params, resendEmailVerificationDTO.getBaseUrl()) - .flatMap(updatedParams -> emailSender.sendMail( - email, - "Verify your account", - USER_EMAIL_VERIFICATION_EMAIL_TEMPLATE, - updatedParams)); + return emailService.sendEmailVerificationEmail(user, verificationUrl); }) .thenReturn(true); } @@ -1092,11 +957,7 @@ public class UserServiceCEImpl extends BaseService user.setEmailVerified(TRUE); Mono redirectionMono = redirectStrategy.sendRedirect( webFilterExchange.getExchange(), URI.create(postVerificationRedirectUrl)); - Mono welcomeEmailMono = sendWelcomeEmail(user, baseUrl); - return repository - .save(user) - .then(Mono.zip(welcomeEmailMono, redirectionMono)) - .flatMap(objects -> Mono.just(objects.getT2())); + return repository.save(user).then(redirectionMono); }); }); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce_compatible/UserServiceCECompatibleImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce_compatible/UserServiceCECompatibleImpl.java index d55011c3f4..d27345aa6b 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce_compatible/UserServiceCECompatibleImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce_compatible/UserServiceCECompatibleImpl.java @@ -3,7 +3,6 @@ package com.appsmith.server.services.ce_compatible; import com.appsmith.external.services.EncryptionService; import com.appsmith.server.configurations.CommonConfig; import com.appsmith.server.configurations.EmailConfig; -import com.appsmith.server.helpers.RedirectHelper; import com.appsmith.server.helpers.UserUtils; import com.appsmith.server.notifications.EmailSender; import com.appsmith.server.ratelimiting.RateLimitService; @@ -12,6 +11,7 @@ import com.appsmith.server.repositories.EmailVerificationTokenRepository; import com.appsmith.server.repositories.PasswordResetTokenRepository; import com.appsmith.server.repositories.UserRepository; import com.appsmith.server.services.AnalyticsService; +import com.appsmith.server.services.EmailService; import com.appsmith.server.services.PermissionGroupService; import com.appsmith.server.services.SessionUserService; import com.appsmith.server.services.TenantService; @@ -52,7 +52,7 @@ public class UserServiceCECompatibleImpl extends UserServiceCEImpl implements Us PermissionGroupService permissionGroupService, UserUtils userUtils, EmailVerificationTokenRepository emailVerificationTokenRepository, - RedirectHelper redirectHelper, + EmailService emailService, RateLimitService rateLimitService) { super( scheduler, @@ -77,7 +77,7 @@ public class UserServiceCECompatibleImpl extends UserServiceCEImpl implements Us permissionGroupService, userUtils, emailVerificationTokenRepository, - redirectHelper, + emailService, rateLimitService); } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/EnvManagerImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/EnvManagerImpl.java index d90b3f874b..865c9bf2dd 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/EnvManagerImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/EnvManagerImpl.java @@ -9,6 +9,7 @@ import com.appsmith.server.notifications.EmailSender; import com.appsmith.server.repositories.UserRepository; import com.appsmith.server.services.AnalyticsService; import com.appsmith.server.services.ConfigService; +import com.appsmith.server.services.EmailService; import com.appsmith.server.services.PermissionGroupService; import com.appsmith.server.services.SessionUserService; import com.appsmith.server.services.TenantService; @@ -38,7 +39,8 @@ public class EnvManagerImpl extends EnvManagerCEImpl implements EnvManager { ConfigService configService, UserUtils userUtils, TenantService tenantService, - ObjectMapper objectMapper) { + ObjectMapper objectMapper, + EmailService emailService) { super( sessionUserService, @@ -55,6 +57,7 @@ public class EnvManagerImpl extends EnvManagerCEImpl implements EnvManager { configService, userUtils, tenantService, - objectMapper); + objectMapper, + emailService); } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/UserAndAccessManagementServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/UserAndAccessManagementServiceImpl.java index 6fb9a28d4c..ecdd3fa12a 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/UserAndAccessManagementServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/UserAndAccessManagementServiceImpl.java @@ -1,8 +1,9 @@ package com.appsmith.server.solutions; -import com.appsmith.server.notifications.EmailSender; +import com.appsmith.server.configurations.CommonConfig; import com.appsmith.server.repositories.UserRepository; import com.appsmith.server.services.AnalyticsService; +import com.appsmith.server.services.EmailService; import com.appsmith.server.services.PermissionGroupService; import com.appsmith.server.services.SessionUserService; import com.appsmith.server.services.UserService; @@ -23,8 +24,9 @@ public class UserAndAccessManagementServiceImpl extends UserAndAccessManagementS UserRepository userRepository, AnalyticsService analyticsService, UserService userService, - EmailSender emailSender, - PermissionGroupPermission permissionGroupPermission) { + PermissionGroupPermission permissionGroupPermission, + EmailService emailService, + CommonConfig commonConfig) { super( sessionUserService, @@ -33,7 +35,8 @@ public class UserAndAccessManagementServiceImpl extends UserAndAccessManagementS userRepository, analyticsService, userService, - emailSender, - permissionGroupPermission); + permissionGroupPermission, + emailService, + commonConfig); } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/UserSignupImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/UserSignupImpl.java index cd3007004b..31befe5059 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/UserSignupImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/UserSignupImpl.java @@ -7,6 +7,7 @@ import com.appsmith.server.helpers.UserUtils; import com.appsmith.server.services.AnalyticsService; import com.appsmith.server.services.CaptchaService; import com.appsmith.server.services.ConfigService; +import com.appsmith.server.services.EmailService; import com.appsmith.server.services.UserDataService; import com.appsmith.server.services.UserService; import com.appsmith.server.solutions.ce.UserSignupCEImpl; @@ -27,7 +28,8 @@ public class UserSignupImpl extends UserSignupCEImpl implements UserSignup { EnvManager envManager, CommonConfig commonConfig, UserUtils userUtils, - NetworkUtils networkUtils) { + NetworkUtils networkUtils, + EmailService emailService) { super( userService, @@ -39,6 +41,7 @@ public class UserSignupImpl extends UserSignupCEImpl implements UserSignup { envManager, commonConfig, userUtils, - networkUtils); + networkUtils, + emailService); } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/EnvManagerCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/EnvManagerCE.java index 3531cba174..6fdf4275fc 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/EnvManagerCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/EnvManagerCE.java @@ -14,9 +14,9 @@ public interface EnvManagerCE { List transformEnvContent(String envContent, Map changes); - Mono applyChanges(Map changes); + Mono applyChanges(Map changes, String originHeader); - Mono applyChangesFromMultipartFormData(MultiValueMap formData); + Mono applyChangesFromMultipartFormData(MultiValueMap formData, String originHeader); void setAnalyticsEventAction( Map properties, String newVariable, String originalVariable, String authEnv); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/EnvManagerCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/EnvManagerCEImpl.java index d52441def1..dc3f25e740 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/EnvManagerCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/EnvManagerCEImpl.java @@ -21,6 +21,7 @@ import com.appsmith.server.notifications.EmailSender; import com.appsmith.server.repositories.UserRepository; import com.appsmith.server.services.AnalyticsService; import com.appsmith.server.services.ConfigService; +import com.appsmith.server.services.EmailService; import com.appsmith.server.services.PermissionGroupService; import com.appsmith.server.services.SessionUserService; import com.appsmith.server.services.TenantService; @@ -115,6 +116,8 @@ public class EnvManagerCEImpl implements EnvManagerCE { private final ObjectMapper objectMapper; + private final EmailService emailService; + /** * This regex pattern matches environment variable declarations like `VAR_NAME=value` or `VAR_NAME="value"` or just * `VAR_NAME=`. It also defines two named capture groups, `name` and `value`, for the variable's name and value @@ -140,7 +143,8 @@ public class EnvManagerCEImpl implements EnvManagerCE { ConfigService configService, UserUtils userUtils, TenantService tenantService, - ObjectMapper objectMapper) { + ObjectMapper objectMapper, + EmailService emailService) { this.sessionUserService = sessionUserService; this.userService = userService; @@ -157,6 +161,7 @@ public class EnvManagerCEImpl implements EnvManagerCE { this.userUtils = userUtils; this.tenantService = tenantService; this.objectMapper = objectMapper; + this.emailService = emailService; } /** @@ -335,7 +340,7 @@ public class EnvManagerCEImpl implements EnvManagerCE { } @Override - public Mono applyChanges(Map changes) { + public Mono applyChanges(Map changes, String originHeader) { // This flow is pertinent for any variables that need to change in the .env file or be saved in the tenant // configuration return verifyCurrentUserIsSuper() @@ -396,7 +401,7 @@ public class EnvManagerCEImpl implements EnvManagerCE { commonConfig.setAdminEmails(changesCopy.remove(APPSMITH_ADMIN_EMAILS.name())); String oldAdminEmailsCsv = originalValues.get(APPSMITH_ADMIN_EMAILS.name()); dependentTasks = dependentTasks - .then(updateAdminUserPolicies(oldAdminEmailsCsv)) + .then(updateAdminUserPolicies(oldAdminEmailsCsv, originHeader)) .then(); } @@ -449,7 +454,7 @@ public class EnvManagerCEImpl implements EnvManagerCE { } @Override - public Mono applyChangesFromMultipartFormData(MultiValueMap formData) { + public Mono applyChangesFromMultipartFormData(MultiValueMap formData, String originHeader) { return Flux.fromIterable(formData.entrySet()) .flatMap(entry -> { final String key = entry.getKey(); @@ -474,7 +479,7 @@ public class EnvManagerCEImpl implements EnvManagerCE { }); }) .collectMap(Map.Entry::getKey, Map.Entry::getValue) - .flatMap(this::applyChanges); + .flatMap(changesMap -> this.applyChanges(changesMap, originHeader)); } @Override @@ -571,7 +576,7 @@ public class EnvManagerCEImpl implements EnvManagerCE { * * @param oldAdminEmailsCsv comma separated email addresses that was set as admin email earlier */ - private Mono updateAdminUserPolicies(String oldAdminEmailsCsv) { + private Mono updateAdminUserPolicies(String oldAdminEmailsCsv, String originHeader) { Set oldAdminEmails = TextUtils.csvToSet(oldAdminEmailsCsv); Set newAdminEmails = commonConfig.getAdminEmails(); @@ -584,14 +589,48 @@ public class EnvManagerCEImpl implements EnvManagerCE { Mono removedUsersMono = Flux.fromIterable(removedUsers) .flatMap(userService::findByEmail) .collectList() - .flatMap(users -> userUtils.removeSuperUser(users)); + .flatMap(userUtils::removeSuperUser); - Mono newUsersMono = Flux.fromIterable(newUsers) - .flatMap(userService::findByEmail) + Flux usersFlux = Flux.fromIterable(newUsers) + .flatMap(email -> userService.findByEmail(email).map(existingUser -> { + if (existingUser == null) { + User newUser = new User(); + newUser.setEmail(email); + newUser.setIsEnabled(false); + return newUser; + } + return existingUser; + })) + .cache(); + + Flux newUsersFlux = usersFlux.filter(user -> !user.isEnabled()); + Flux existingUsersFlux = usersFlux.filter(User::isEnabled); + + // we are sending email to existing users who are not already super-users + Mono> existingUsersWhichAreNotAlreadySuperUsersMono = existingUsersFlux + .filterWhen(user -> userUtils.isSuperUser(user).map(isSuper -> !isSuper)) + .collectList(); + + Mono newUsersMono = newUsersFlux + .flatMap(newUsersFluxUser -> sessionUserService + .getCurrentUser() + .flatMap(invitingUser -> emailService.sendInstanceAdminInviteEmail( + newUsersFluxUser, invitingUser, originHeader, true))) .collectList() - .flatMap(users -> userUtils.makeSuperUser(users)); + .map(results -> results.stream().allMatch(result -> result)); - return Mono.when(removedUsersMono, newUsersMono).then(Mono.just(TRUE)); + Mono existingUsersMono = existingUsersWhichAreNotAlreadySuperUsersMono.flatMap(users -> userUtils + .makeSuperUser(users) + .flatMap( + success -> Flux.fromIterable(users) + .flatMap(user -> sessionUserService + .getCurrentUser() + .flatMap(invitingUser -> emailService.sendInstanceAdminInviteEmail( + user, invitingUser, originHeader, false))) + .then(Mono.just(success)) // Emit 'success' as the result + )); + + return Mono.when(removedUsersMono, newUsersMono, existingUsersMono).map(tuple -> TRUE); } @Override diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/UserAndAccessManagementServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/UserAndAccessManagementServiceCEImpl.java index e011cc0121..4d4c963f71 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/UserAndAccessManagementServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/UserAndAccessManagementServiceCEImpl.java @@ -1,6 +1,7 @@ package com.appsmith.server.solutions.ce; import com.appsmith.external.constants.AnalyticsEvents; +import com.appsmith.server.configurations.CommonConfig; import com.appsmith.server.constants.FieldName; import com.appsmith.server.domains.PermissionGroup; import com.appsmith.server.domains.User; @@ -8,9 +9,9 @@ import com.appsmith.server.domains.Workspace; import com.appsmith.server.dtos.InviteUsersDTO; import com.appsmith.server.exceptions.AppsmithError; import com.appsmith.server.exceptions.AppsmithException; -import com.appsmith.server.notifications.EmailSender; import com.appsmith.server.repositories.UserRepository; import com.appsmith.server.services.AnalyticsService; +import com.appsmith.server.services.EmailService; import com.appsmith.server.services.PermissionGroupService; import com.appsmith.server.services.SessionUserService; import com.appsmith.server.services.UserService; @@ -21,6 +22,8 @@ import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import reactor.util.function.Tuple2; +import reactor.util.function.Tuples; import java.util.ArrayList; import java.util.HashMap; @@ -28,7 +31,6 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; -import static com.appsmith.server.services.ce.UserServiceCEImpl.INVITE_USER_EMAIL_TEMPLATE; import static java.lang.Boolean.TRUE; @Slf4j @@ -40,8 +42,9 @@ public class UserAndAccessManagementServiceCEImpl implements UserAndAccessManage private final UserRepository userRepository; private final AnalyticsService analyticsService; private final UserService userService; - private final EmailSender emailSender; private final PermissionGroupPermission permissionGroupPermission; + private final EmailService emailService; + private final CommonConfig commonConfig; public UserAndAccessManagementServiceCEImpl( SessionUserService sessionUserService, @@ -50,8 +53,9 @@ public class UserAndAccessManagementServiceCEImpl implements UserAndAccessManage UserRepository userRepository, AnalyticsService analyticsService, UserService userService, - EmailSender emailSender, - PermissionGroupPermission permissionGroupPermission) { + PermissionGroupPermission permissionGroupPermission, + EmailService emailService, + CommonConfig commonConfig) { this.sessionUserService = sessionUserService; this.permissionGroupService = permissionGroupService; @@ -59,8 +63,9 @@ public class UserAndAccessManagementServiceCEImpl implements UserAndAccessManage this.userRepository = userRepository; this.analyticsService = analyticsService; this.userService = userService; - this.emailSender = emailSender; + this.emailService = emailService; this.permissionGroupPermission = permissionGroupPermission; + this.commonConfig = commonConfig; } /** @@ -125,57 +130,38 @@ public class UserAndAccessManagementServiceCEImpl implements UserAndAccessManage // Check if the invited user exists. If yes, return the user, else create a new user by triggering // createNewUserAndSendInviteEmail. In both the cases, send the appropriate emails - Mono> inviteUsersMono = Flux.fromIterable(usernames) - .flatMap(username -> Mono.zip( - Mono.just(username), - workspaceMono, - currentUserMono, - permissionGroupMono, - defaultPermissionGroupsMono)) + Mono>> inviteUsersMonoWithUserCreationMapping = Flux.fromIterable(usernames) + .flatMap(username -> Mono.zip(Mono.just(username), workspaceMono, defaultPermissionGroupsMono)) .flatMap(tuple -> { String username = tuple.getT1(); Workspace workspace = tuple.getT2(); eventData.put(FieldName.WORKSPACE, workspace); - User currentUser = tuple.getT3(); - PermissionGroup permissionGroup = tuple.getT4(); - List defaultPermissionGroups = tuple.getT5(); + List defaultPermissionGroups = tuple.getT3(); Mono getUserFromDbAndCheckIfUserExists = userRepository .findByEmail(username) - .flatMap(user -> { - return throwErrorIfUserAlreadyExistsInWorkspace(user, defaultPermissionGroups) - // If no errors, proceed forward - .thenReturn(user); - }); + .flatMap(user -> throwErrorIfUserAlreadyExistsInWorkspace(user, defaultPermissionGroups) + .thenReturn(user)); return getUserFromDbAndCheckIfUserExists - .flatMap(existingUser -> { - // The user already existed, just send an email informing that the user has been added - // to a new workspace - log.debug( - "Going to send email to user {} informing that the user has been added to new workspace {}", - existingUser.getEmail(), - workspace.getName()); - - // Email template parameters initialization below. - Map params = - userService.getEmailParams(workspace, currentUser, originHeader, false); - + .flatMap(existingUser -> Mono.just(Tuples.of(existingUser, false))) + .switchIfEmpty(Mono.defer(() -> { + User newUser = new User(); + newUser.setEmail(username.toLowerCase()); + newUser.setIsEnabled(false); + boolean isAdminUser = false; return userService - .updateTenantLogoInParams(params, originHeader) - .flatMap(updatedParams -> emailSender.sendMail( - existingUser.getEmail(), - "Appsmith: You have been added to a new workspace", - INVITE_USER_EMAIL_TEMPLATE, - updatedParams)) - .thenReturn(existingUser); - }) - .switchIfEmpty(userService.createNewUserAndSendInviteEmail( - username, originHeader, workspace, currentUser, permissionGroup.getName())); + .userCreate(newUser, isAdminUser) + .flatMap(newCreatedUser -> Mono.just(Tuples.of(newCreatedUser, true))); + })); }) .collectList() .cache(); + Mono> inviteUsersMono = inviteUsersMonoWithUserCreationMapping + .map(tuple2s -> tuple2s.stream().map(Tuple2::getT1).collect(Collectors.toList())) + .cache(); + // assign permission group to the invited users. Mono bulkAddUserResultMono = Mono.zip(permissionGroupMono, inviteUsersMono) .flatMap(tuple -> { @@ -203,7 +189,28 @@ public class UserAndAccessManagementServiceCEImpl implements UserAndAccessManage AnalyticsEvents.EXECUTE_INVITE_USERS, currentUser, analyticsProperties); }); - return bulkAddUserResultMono.then(sendAnalyticsEventMono).then(inviteUsersMono); + Mono sendEmailsMono = Mono.zip( + inviteUsersMonoWithUserCreationMapping, workspaceMono, currentUserMono, permissionGroupMono) + .flatMap(tuple -> { + List> users = tuple.getT1(); + Workspace workspace = tuple.getT2(); + User currentUser = tuple.getT3(); + PermissionGroup permissionGroup = tuple.getT4(); + + return Flux.fromIterable(users) + .flatMap(userTuple -> { + User user = userTuple.getT1(); + boolean isNewUser = userTuple.getT2(); + return emailService.sendInviteUserToWorkspaceEmail( + currentUser, user, workspace, permissionGroup, originHeader, isNewUser); + }) + .all(emailSent -> emailSent); + }); + + return bulkAddUserResultMono + .then(sendAnalyticsEventMono) + .then(sendEmailsMono) + .then(inviteUsersMono); } private Mono throwErrorIfUserAlreadyExistsInWorkspace( diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/UserSignupCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/UserSignupCE.java index d017011155..e6068de603 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/UserSignupCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/UserSignupCE.java @@ -27,7 +27,8 @@ public interface UserSignupCE { */ Mono signupAndLoginFromFormData(ServerWebExchange exchange); - Mono signupAndLoginSuper(UserSignupRequestDTO userFromRequest, ServerWebExchange exchange); + Mono signupAndLoginSuper( + UserSignupRequestDTO userFromRequest, String originHeader, ServerWebExchange exchange); - Mono signupAndLoginSuperFromFormData(ServerWebExchange exchange); + Mono signupAndLoginSuperFromFormData(String originHeader, ServerWebExchange exchange); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/UserSignupCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/UserSignupCEImpl.java index f28cbbb8ca..b0ff0c30ec 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/UserSignupCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/UserSignupCEImpl.java @@ -17,6 +17,7 @@ import com.appsmith.server.helpers.UserUtils; import com.appsmith.server.services.AnalyticsService; import com.appsmith.server.services.CaptchaService; import com.appsmith.server.services.ConfigService; +import com.appsmith.server.services.EmailService; import com.appsmith.server.services.UserDataService; import com.appsmith.server.services.UserService; import com.appsmith.server.solutions.EnvManager; @@ -75,6 +76,7 @@ public class UserSignupCEImpl implements UserSignupCE { private final CommonConfig commonConfig; private final UserUtils userUtils; private final NetworkUtils networkUtils; + private final EmailService emailService; private static final ServerRedirectStrategy redirectStrategy = new DefaultServerRedirectStrategy(); @@ -90,7 +92,8 @@ public class UserSignupCEImpl implements UserSignupCE { EnvManager envManager, CommonConfig commonConfig, UserUtils userUtils, - NetworkUtils networkUtils) { + NetworkUtils networkUtils, + EmailService emailService) { this.userService = userService; this.userDataService = userDataService; @@ -102,6 +105,7 @@ public class UserSignupCEImpl implements UserSignupCE { this.commonConfig = commonConfig; this.userUtils = userUtils; this.networkUtils = networkUtils; + this.emailService = emailService; } /** @@ -126,15 +130,15 @@ public class UserSignupCEImpl implements UserSignupCE { } // only creating user, welcome email will be sent post user email verification - Mono createUserAndSendEmailMono = userService - .createUser(user, exchange.getRequest().getHeaders().getOrigin()) + Mono createUserMono = userService + .createUser(user) .elapsed() .map(pair -> { log.debug("UserSignupCEImpl::Time taken for create user and send email: {} ms", pair.getT1()); return pair.getT2(); }); - return Mono.zip(createUserAndSendEmailMono, exchange.getSession(), ReactiveSecurityContextHolder.getContext()) + return Mono.zip(createUserMono, exchange.getSession(), ReactiveSecurityContextHolder.getContext()) .switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.INTERNAL_SERVER_ERROR))) .flatMap(tuple -> { final User savedUser = tuple.getT1().getUser(); @@ -231,7 +235,8 @@ public class UserSignupCEImpl implements UserSignupCE { }); } - public Mono signupAndLoginSuper(UserSignupRequestDTO userFromRequest, ServerWebExchange exchange) { + public Mono signupAndLoginSuper( + UserSignupRequestDTO userFromRequest, String originHeader, ServerWebExchange exchange) { Mono userMono = userService .isUsersEmpty() .flatMap(isEmpty -> { @@ -280,11 +285,13 @@ public class UserSignupCEImpl implements UserSignupCE { }); Mono applyEnvManagerChangesMono = envManager - .applyChanges(Map.of( - APPSMITH_DISABLE_TELEMETRY.name(), - String.valueOf(!userFromRequest.isAllowCollectingAnonymousData()), - APPSMITH_ADMIN_EMAILS.name(), - user.getEmail())) + .applyChanges( + Map.of( + APPSMITH_DISABLE_TELEMETRY.name(), + String.valueOf(!userFromRequest.isAllowCollectingAnonymousData()), + APPSMITH_ADMIN_EMAILS.name(), + user.getEmail()), + originHeader) // We need a non-empty value for `.elapsed` to work. .thenReturn(true) .elapsed() @@ -331,7 +338,7 @@ public class UserSignupCEImpl implements UserSignupCE { }); } - public Mono signupAndLoginSuperFromFormData(ServerWebExchange exchange) { + public Mono signupAndLoginSuperFromFormData(String originHeader, ServerWebExchange exchange) { return exchange.getFormData() .map(formData -> { final UserSignupRequestDTO user = new UserSignupRequestDTO(); @@ -358,7 +365,7 @@ public class UserSignupCEImpl implements UserSignupCE { } return user; }) - .flatMap(user -> signupAndLoginSuper(user, exchange)) + .flatMap(user -> signupAndLoginSuper(user, originHeader, exchange)) .then() .onErrorResume(error -> { String referer = exchange.getRequest().getHeaders().getFirst("referer"); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce_compatible/UserAndAccessManagementServiceCECompatibleImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce_compatible/UserAndAccessManagementServiceCECompatibleImpl.java index a8fe6bb470..8e6b655657 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce_compatible/UserAndAccessManagementServiceCECompatibleImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce_compatible/UserAndAccessManagementServiceCECompatibleImpl.java @@ -1,8 +1,9 @@ package com.appsmith.server.solutions.ce_compatible; -import com.appsmith.server.notifications.EmailSender; +import com.appsmith.server.configurations.CommonConfig; import com.appsmith.server.repositories.UserRepository; import com.appsmith.server.services.AnalyticsService; +import com.appsmith.server.services.EmailService; import com.appsmith.server.services.PermissionGroupService; import com.appsmith.server.services.SessionUserService; import com.appsmith.server.services.UserService; @@ -21,8 +22,9 @@ public class UserAndAccessManagementServiceCECompatibleImpl extends UserAndAcces UserRepository userRepository, AnalyticsService analyticsService, UserService userService, - EmailSender emailSender, - PermissionGroupPermission permissionGroupPermission) { + PermissionGroupPermission permissionGroupPermission, + EmailService emailService, + CommonConfig commonConfig) { super( sessionUserService, permissionGroupService, @@ -30,7 +32,8 @@ public class UserAndAccessManagementServiceCECompatibleImpl extends UserAndAcces userRepository, analyticsService, userService, - emailSender, - permissionGroupPermission); + permissionGroupPermission, + emailService, + commonConfig); } } diff --git a/app/server/appsmith-server/src/main/resources/email/ce/forgotPasswordTemplate.html b/app/server/appsmith-server/src/main/resources/email/ce/forgotPasswordTemplate.html new file mode 100644 index 0000000000..13aac09904 --- /dev/null +++ b/app/server/appsmith-server/src/main/resources/email/ce/forgotPasswordTemplate.html @@ -0,0 +1,542 @@ + + + + Reset your password for {{instanceName}} + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + + + + + +
+ +
+
+
+

+ + Hey, + +

+
+
+
+

+ You recently requested to reset your + Appsmith instance password. Let’s + get you a new one in two clicks. +

+
+
+
+

+ The link expires in two days’ + time. Better not wait. +

+
+
+ + + + + + +
+ + Set a new password now +
+
+
+

+ 1390, Market Street, Suite 200, San + Francisco, California 94102, United + States +

+
+
+
+
+ +
+
+
+ + diff --git a/app/server/appsmith-server/src/main/resources/email/ce/instanceAdminInviteTemplate.html b/app/server/appsmith-server/src/main/resources/email/ce/instanceAdminInviteTemplate.html new file mode 100644 index 0000000000..1e42fcb63b --- /dev/null +++ b/app/server/appsmith-server/src/main/resources/email/ce/instanceAdminInviteTemplate.html @@ -0,0 +1,545 @@ + + + + You're invited to the appsmith instance {{instanceName}} + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + + + + + +
+ +
+
+
+

+ + Hey, + +

+
+
+
+

+ {{inviterFirstName}} has invited you to + the Appsmith instance + {{instanceName}} +

+
+
+
+

+ Join your Appsmith instance to get + access to fast, usable, beautiful + apps. +

+
+
+ + + + + + +
+ + {{primaryLinkText}} +
+
+
+

+ 1390, Market Street, Suite 200, San + Francisco, California 94102, United + States +

+
+
+
+
+ +
+
+
+ + diff --git a/app/server/appsmith-server/src/main/resources/email/ce/inviteWorkspaceExistingUserTemplate.html b/app/server/appsmith-server/src/main/resources/email/ce/inviteWorkspaceExistingUserTemplate.html new file mode 100644 index 0000000000..70c1b13141 --- /dev/null +++ b/app/server/appsmith-server/src/main/resources/email/ce/inviteWorkspaceExistingUserTemplate.html @@ -0,0 +1,547 @@ + + + + You're invited to the workspace {{inviterWorkspaceName}} + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + + + + + +
+ +
+
+
+

+ + Hey, + +

+
+
+
+

+ {{inviterFirstName}} has invited you as + {{role}} to the Appsmith + workspace + {{inviterWorkspaceName}} +

+
+
+
+

+ Join your Appsmith workspace to get + access to fast, usable, beautiful + apps. +

+
+
+ + + + + + +
+ + Go to your Appsmith + workspace +
+
+
+

+ 1390, Market Street, Suite 200, San + Francisco, California 94102, United + States +

+
+
+
+
+ +
+
+
+ + diff --git a/app/server/appsmith-server/src/main/resources/email/ce/inviteWorkspaceNewUserTemplate.html b/app/server/appsmith-server/src/main/resources/email/ce/inviteWorkspaceNewUserTemplate.html new file mode 100644 index 0000000000..b3b487fbba --- /dev/null +++ b/app/server/appsmith-server/src/main/resources/email/ce/inviteWorkspaceNewUserTemplate.html @@ -0,0 +1,547 @@ + + + + You're invited to the workspace {{inviterWorkspaceName}} + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + + + + + +
+ +
+
+
+

+ + Hey, + +

+
+
+
+

+ {{inviterFirstName}} has invited you as + {{role}} access to the Appsmith + workspace + {{inviterWorkspaceName}} +

+
+
+
+

+ Join your Appsmith workspace to get + access to fast, usable, beautiful + apps. +

+
+
+ + + + + + +
+ + Join your Appsmith + workspace +
+
+
+

+ 1390, Market Street, Suite 200, San + Francisco, California 94102, United + States +

+
+
+
+
+ +
+
+
+ + diff --git a/app/server/appsmith-server/src/main/resources/email/commentAddedTemplate.html b/app/server/appsmith-server/src/main/resources/email/commentAddedTemplate.html deleted file mode 100644 index f5f3d0212d..0000000000 --- a/app/server/appsmith-server/src/main/resources/email/commentAddedTemplate.html +++ /dev/null @@ -1,286 +0,0 @@ - - - - - - - - - - - - - - -
-
- - - - - - -
- - - - - - -
- - - - - - -
- - - - - - - -
- - - - - - - - - - - - - - - - -
-
- -
-
- -
- - Commenting is deprecated
- We will be removing comments from Appsmith in v1.7.12
- Learn More -
-
- -
- - - -
- - - - - - -
-
- {{Commenter_Name}}, - {{#NewComment}} added {{/NewComment}} - {{#Resolved}} resolved {{/Resolved}} - {{#Replied}} replied to {{/Replied}} - {{#Mentioned}} mentioned you in {{/Mentioned}} - {{#Resolved}} a comment thread {{/Resolved}} - {{^Resolved}} a comment on {{/Resolved}} -
{{Application_Name}}, {{Page_Name}}: -
- {{#Comment_Body}} -
- {{ . }} -
- {{/Comment_Body}} - - -
- -
- Appsmith is an open source framework to build admin panels, CRUD apps and workflows. Build everything you need, 10x faster. -
-
- - - - - - -
-
- 2261 Market Street #4147, United States, San Francisco, California, 94114, US -
-
-
- -
-
-
-
-
- - - - diff --git a/app/server/appsmith-server/src/main/resources/email/emailVerificationTemplate.html b/app/server/appsmith-server/src/main/resources/email/emailVerificationTemplate.html index 3567d90488..17e9cc2a5c 100644 --- a/app/server/appsmith-server/src/main/resources/email/emailVerificationTemplate.html +++ b/app/server/appsmith-server/src/main/resources/email/emailVerificationTemplate.html @@ -1,15 +1,15 @@ - + - - Verify email for your Appsmith account + + Verify email for your Appsmith account - + - - + + - + - - + + - - -
- -
- - +
+
+ -
- -
- - - -
-

- +
+ + + + + + +
+

+ +
+
+ -
- - - - - - -
- - -
-
-
- -
- +
+
+ + + + - +
+ - - + - + + + + + + + + + + + + + + + + + + + -
- - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - -
- -
-
-
-
-
-

- Verify your email address -

-
-
-
-

-Hello,
-
Thank you for signing up! Before we get started, we just need to confirm that this is you. Click below to verify your email address:
-

-
-
-
-
- - - - - - -
- - Verify - -
-
-
-

- The link expires in two daysโ€™ time. Better not wait. -
- P.S.: You will see a welcome e-mail soon after you join to help get started with Appsmith. -

-
-
+
+ + + + + + +
+ +
+
+   +
+
+
+

+ Verify your email address +

+
+
+
+

+ Hello,
+
+ Thank you for signing up! Before we get + started, we just need to confirm that + this is you. Click below to verify your + email address: +
+

+
+
+
+   +
+
+ + + + + + +
+ + Verify + +
+
+
+

+ The link expires in two daysโ€™ time. + Better not wait. +
+ P.S.: You will see a welcome e-mail + soon after you join to help get started + with Appsmith. +

+
+
- - -
-
- -
- - +
+
+ -
- -
- - - -
-

- +
+ + + + + + +
+

+ +
+
+ -
- - - - - - -
- - -
-
-
- -
- +
+
+ + + + - +
+ - - + - + + + + -
- - - - - - - - - -
- - - - - - -
- -
-
-
-

- Appsmith is an open source framework that helps developer and companies to build powerful internal tools. -

-
-
+
+ + + + + + +
+ +
+
+

+ Appsmith is an open source framework + that helps developer and companies to + build powerful internal tools. +

+
+
- - -
-
- - - - \ No newline at end of file + + + diff --git a/app/server/appsmith-server/src/main/resources/email/forgotPasswordTemplate.html b/app/server/appsmith-server/src/main/resources/email/forgotPasswordTemplate.html deleted file mode 100644 index c4ad264012..0000000000 --- a/app/server/appsmith-server/src/main/resources/email/forgotPasswordTemplate.html +++ /dev/null @@ -1,123 +0,0 @@ - - - - Reset password for your Appsmith account - - - - - - - - - - - - - - - - - - - - -
-
-
-
-

- -
-
-
-
-
-
-
-
-
-
-
-

Password reset link for Appsmith apps

-

Forgotten your password? No worries, we’ve got you covered. This link will expire in 48 hours.

-
-
-
Reset Password -
-
-
- -
-
-
-
-

- -
-
-
-
-
-
-
-
-
-
-

Appsmith is an open source framework that helps developer and companies to build powerful internal tools.

-
-
- -
-
- - diff --git a/app/server/appsmith-server/src/main/resources/email/inviteUserTemplate.html b/app/server/appsmith-server/src/main/resources/email/inviteUserTemplate.html deleted file mode 100644 index 485e19b1bc..0000000000 --- a/app/server/appsmith-server/src/main/resources/email/inviteUserTemplate.html +++ /dev/null @@ -1,123 +0,0 @@ - - - - {{inviterFirstName}} has invited you to collaborate - - - - - - - - - - - - - - - - - - - - -
-
-
-
-

- -
-
-
-
-
-
-
-
-
-
-
-

{{inviterFirstName}} has invited you to collaborate on the workspace "{{inviterWorkspaceName}}"

-

You have been invited to join the workspace "{{inviterWorkspaceName}}" in Appsmith. Accept the invite to create your account and get started.

-
-
-
{{primaryLinkText}} -
-
-
- -
-
-
-
-

- -
-
-
-
-
-
-
-
-
-
-

Appsmith is an open source framework that helps developer and companies to build powerful internal tools.

-
-
- -
-
- - diff --git a/app/server/appsmith-server/src/main/resources/email/welcomeUserTemplate.html b/app/server/appsmith-server/src/main/resources/email/welcomeUserTemplate.html deleted file mode 100644 index 42f9fe8ab0..0000000000 --- a/app/server/appsmith-server/src/main/resources/email/welcomeUserTemplate.html +++ /dev/null @@ -1,302 +0,0 @@ - - - - - - - - - - - - - - - -
-
- - - - -
- - - - -
- - - - -
- - - - - -
- - - - - - - -
- - - Welcome to Appsmith! - - - - - - - - - - - - - - - - - - - - -
-
-
-
-

- -
-
-
-
-
-
-
- - - -
-
-
-
-

Welcome to Appsmith

-

You're now part of a developer community using appsmith to save hundreds of hours building internal tools! ๐ŸŽ‰ -

If you'd like some help getting started, I'd be happy to get on a call & walk you through building your first app! Schedule a Call ๐Ÿ“…

-

We're a fun, active group of developers, and we'd love for you to join us on Discord

-
-
-
Go To Dashboard -
-
-
- -
-
-
-
-

- -
-
-
-
-
-
-
-
-
-
-

Appsmith is an open source framework that helps developer and companies to build powerful internal tools.

-
-
- -
-
- -
- -
-
-
-
-
- - diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/UserServiceTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/UserServiceTest.java index d5e1801728..175e7ac796 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/UserServiceTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/UserServiceTest.java @@ -55,10 +55,8 @@ import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.Set; -import java.util.UUID; import static com.appsmith.server.acl.AclPermission.MANAGE_USERS; import static com.appsmith.server.acl.AclPermission.RESET_PASSWORD_USERS; @@ -125,43 +123,6 @@ public class UserServiceTest { public void setup() { userMono = userService.findByEmail("usertest@usertest.com"); } - - // Test if email params are updating correctly - @Test - public void checkEmailParamsForExistingUser() { - Workspace workspace = new Workspace(); - workspace.setName("UserServiceTest Update Org"); - workspace.setId(UUID.randomUUID().toString()); - - User inviter = new User(); - inviter.setName("inviterUserToApplication"); - - String inviteUrl = "http://localhost:8080"; - String expectedUrl = inviteUrl + "/applications#" + workspace.getId(); - - Map params = userService.getEmailParams(workspace, inviter, inviteUrl, false); - assertEquals(expectedUrl, params.get("primaryLinkUrl")); - assertEquals("inviterUserToApplication", params.get("inviterFirstName")); - assertEquals("UserServiceTest Update Org", params.get("inviterWorkspaceName")); - } - - @Test - public void checkEmailParamsForNewUser() { - Workspace workspace = new Workspace(); - workspace.setId(UUID.randomUUID().toString()); - workspace.setName("UserServiceTest Update Org"); - - User inviter = new User(); - inviter.setName("inviterUserToApplication"); - - String inviteUrl = "http://localhost:8080"; - - Map params = userService.getEmailParams(workspace, inviter, inviteUrl, true); - assertEquals(inviteUrl, params.get("primaryLinkUrl")); - assertEquals("inviterUserToApplication", params.get("inviterFirstName")); - assertEquals("UserServiceTest Update Org", params.get("inviterWorkspaceName")); - } - // Test the update workspace flow. @Test public void updateInvalidUserWithAnything() { @@ -388,9 +349,7 @@ public class UserServiceTest { signUpUser.setEmail(newUserEmail); signUpUser.setPassword("123456"); - Mono invitedUserSignUpMono = userService - .createUserAndSendEmail(signUpUser, "http://localhost:8080") - .map(UserSignupDTO::getUser); + Mono invitedUserSignUpMono = userService.createUser(signUpUser).map(UserSignupDTO::getUser); StepVerifier.create(invitedUserSignUpMono) .assertNext(user -> { @@ -563,24 +522,6 @@ public class UserServiceTest { .verifyComplete(); } - @Test - public void createUserAndSendEmail_WhenUserExistsWithEmailInOtherCase_ThrowsException() { - User existingUser = new User(); - existingUser.setEmail("abcd@gmail.com"); - userRepository.save(existingUser).block(); - - User newUser = new User(); - newUser.setEmail("abCd@gmail.com"); // same as above except c in uppercase - newUser.setSource(LoginSource.FORM); - newUser.setPassword("abcdefgh"); - Mono userAndSendEmail = - userService.createUserAndSendEmail(newUser, null).map(UserSignupDTO::getUser); - - StepVerifier.create(userAndSendEmail) - .expectErrorMessage(AppsmithError.USER_ALREADY_EXISTS_SIGNUP.getMessage(existingUser.getEmail())) - .verify(); - } - @Test public void forgotPasswordTokenGenerate_AfterTrying3TimesIn24Hours_ThrowsException() { String testEmail = "test-email-for-password-reset"; diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/EmailServiceCEImplTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/EmailServiceCEImplTest.java new file mode 100644 index 0000000000..d6ccc8e0d5 --- /dev/null +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/EmailServiceCEImplTest.java @@ -0,0 +1,192 @@ +package com.appsmith.server.services.ce; + +import com.appsmith.server.constants.FieldName; +import com.appsmith.server.domains.PermissionGroup; +import com.appsmith.server.domains.Tenant; +import com.appsmith.server.domains.TenantConfiguration; +import com.appsmith.server.domains.User; +import com.appsmith.server.domains.Workspace; +import com.appsmith.server.notifications.EmailSender; +import com.appsmith.server.services.TenantService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import reactor.core.publisher.Mono; + +import java.util.HashMap; +import java.util.Map; + +import static com.appsmith.server.constants.ce.EmailConstantsCE.EMAIL_ROLE_ADMINISTRATOR_TEXT; +import static com.appsmith.server.constants.ce.EmailConstantsCE.FORGOT_PASSWORD_EMAIL_SUBJECT; +import static com.appsmith.server.constants.ce.EmailConstantsCE.FORGOT_PASSWORD_TEMPLATE_CE; +import static com.appsmith.server.constants.ce.EmailConstantsCE.INSTANCE_ADMIN_INVITE_EMAIL_SUBJECT; +import static com.appsmith.server.constants.ce.EmailConstantsCE.INSTANCE_ADMIN_INVITE_EMAIL_TEMPLATE; +import static com.appsmith.server.constants.ce.EmailConstantsCE.INSTANCE_NAME; +import static com.appsmith.server.constants.ce.EmailConstantsCE.INVITER_FIRST_NAME; +import static com.appsmith.server.constants.ce.EmailConstantsCE.INVITER_WORKSPACE_NAME; +import static com.appsmith.server.constants.ce.EmailConstantsCE.PRIMARY_LINK_TEXT; +import static com.appsmith.server.constants.ce.EmailConstantsCE.PRIMARY_LINK_URL; +import static com.appsmith.server.constants.ce.EmailConstantsCE.RESET_URL; +import static com.appsmith.server.constants.ce.EmailConstantsCE.WORKSPACE_EMAIL_SUBJECT_FOR_NEW_USER; +import static graphql.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.anyMap; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doAnswer; + +@SpringBootTest +@ExtendWith(SpringExtension.class) +class EmailServiceCEImplTest { + + @Autowired + TenantService tenantService; + + @Mock + EmailSender mockEmailSender; + + private EmailServiceCE emailService; + + @BeforeEach + public void setup() { + MockitoAnnotations.openMocks(this); + TenantConfiguration tenantConfiguration = new TenantConfiguration(); + + Tenant tenant = tenantService.getDefaultTenant().block(); + assert tenant != null; + tenant.setTenantConfiguration(tenantConfiguration); + + this.emailService = new EmailServiceCEImpl(mockEmailSender, tenantService); + } + + @Test + void sendForgotPasswordEmail() { + String email = "test@example.com"; + String resetUrl = "http://example.com/reset"; + String originHeader = "http://example.com"; + + Map expectedParams = new HashMap<>(); + expectedParams.put(RESET_URL, resetUrl); + expectedParams.put(INSTANCE_NAME, "Appsmith"); + + doAnswer(invocation -> { + String to = invocation.getArgument(0); + String subject = invocation.getArgument(1); + String text = invocation.getArgument(2); + Map params = invocation.getArgument(3); + + assertEquals(email, to); + assertEquals( + String.format(FORGOT_PASSWORD_EMAIL_SUBJECT, expectedParams.get(INSTANCE_NAME)), subject); + assertEquals(FORGOT_PASSWORD_TEMPLATE_CE, text); + assertEquals(expectedParams, params); + + return Mono.just(true); + }) + .when(mockEmailSender) + .sendMail(anyString(), anyString(), anyString(), anyMap()); + + emailService.sendForgotPasswordEmail(email, resetUrl, originHeader).block(); + } + + @Test + void sendInviteUserToWorkspaceEmail() { + User invitingUser = new User(); + String invitingUserMail = "a@abc.com"; + invitingUser.setEmail(invitingUserMail); + + User invitedUser = new User(); + String invitedUserMail = "b@abc.com"; + invitedUser.setEmail(invitedUserMail); + + Workspace workspace = new Workspace(); + String workspaceName = "testWorkspace"; + String workspaceId = "testWorkspaceId"; + workspace.setName(workspaceName); + workspace.setId(workspaceId); + + PermissionGroup permissionGroup = new PermissionGroup(); + String permissionGroupName = FieldName.ADMINISTRATOR + "role"; + permissionGroup.setName(permissionGroupName); + + boolean isNewUser = true; + + String originHeader = "http://example.com"; + + Map expectedParams = new HashMap<>(); + expectedParams.put(INVITER_WORKSPACE_NAME, workspaceName); + expectedParams.put(INVITER_FIRST_NAME, invitingUserMail); + expectedParams.put(FieldName.ROLE, permissionGroupName); + expectedParams.put(INSTANCE_NAME, "Appsmith"); + + doAnswer(invocation -> { + String to = invocation.getArgument(0); + String subject = invocation.getArgument(1); + String text = invocation.getArgument(2); + Map params = invocation.getArgument(3); + + assertEquals(invitedUser.getEmail(), to); + assertEquals(String.format(WORKSPACE_EMAIL_SUBJECT_FOR_NEW_USER, workspace.getName()), subject); + assertTrue(params.containsKey(PRIMARY_LINK_URL)); + assertTrue(params.containsKey(PRIMARY_LINK_TEXT)); + assertEquals(expectedParams.get(INSTANCE_NAME), params.get(INSTANCE_NAME)); + assertEquals(expectedParams.get(INVITER_WORKSPACE_NAME), params.get(INVITER_WORKSPACE_NAME)); + assertEquals(expectedParams.get(INVITER_FIRST_NAME), params.get(INVITER_FIRST_NAME)); + assertEquals(EMAIL_ROLE_ADMINISTRATOR_TEXT, params.get(FieldName.ROLE)); + + return Mono.just(true); + }) + .when(mockEmailSender) + .sendMail(anyString(), anyString(), anyString(), anyMap()); + + emailService + .sendInviteUserToWorkspaceEmail( + invitingUser, invitedUser, workspace, permissionGroup, originHeader, isNewUser) + .block(); + } + + @Test + void testSendInstanceAdminInviteEmail() { + User invitingUser = new User(); + String invitingUserMail = "a@abc.com"; + invitingUser.setEmail(invitingUserMail); + + User invitedUser = new User(); + String invitedUserMail = "b@abc.com"; + invitedUser.setEmail(invitedUserMail); + + String originHeader = "http://example.com"; + + Map expectedParams = new HashMap<>(); + expectedParams.put(INSTANCE_NAME, "Appsmith"); + expectedParams.put(INVITER_FIRST_NAME, invitingUserMail); + + doAnswer(invocation -> { + String to = invocation.getArgument(0); + String subject = invocation.getArgument(1); + String text = invocation.getArgument(2); + Map params = invocation.getArgument(3); + + assertEquals(invitedUser.getEmail(), to); + assertEquals( + String.format(INSTANCE_ADMIN_INVITE_EMAIL_SUBJECT, expectedParams.get(INSTANCE_NAME)), + subject); + assertEquals(INSTANCE_ADMIN_INVITE_EMAIL_TEMPLATE, text); + assertEquals(expectedParams.get(INVITER_FIRST_NAME), params.get(INVITER_FIRST_NAME)); + assertTrue(params.containsKey(PRIMARY_LINK_URL)); + assertTrue(params.containsKey(PRIMARY_LINK_TEXT)); + + return Mono.just(true); + }) + .when(mockEmailSender) + .sendMail(anyString(), anyString(), anyString(), anyMap()); + + emailService + .sendInstanceAdminInviteEmail(invitedUser, invitingUser, originHeader, true) + .block(); + } +} diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/EnvManagerTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/EnvManagerTest.java index 82e8c62161..3458e4610e 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/EnvManagerTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/EnvManagerTest.java @@ -12,6 +12,7 @@ import com.appsmith.server.notifications.EmailSender; import com.appsmith.server.repositories.UserRepository; import com.appsmith.server.services.AnalyticsService; import com.appsmith.server.services.ConfigService; +import com.appsmith.server.services.EmailService; import com.appsmith.server.services.PermissionGroupService; import com.appsmith.server.services.SessionUserService; import com.appsmith.server.services.TenantService; @@ -90,6 +91,9 @@ public class EnvManagerTest { @MockBean private ObjectMapper objectMapper; + @MockBean + private EmailService emailService; + @BeforeEach public void setup() { envManager = new EnvManagerImpl( @@ -107,7 +111,8 @@ public class EnvManagerTest { configService, userUtils, tenantService, - objectMapper); + objectMapper, + emailService); } @Test diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/UserSignupTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/UserSignupTest.java index ded3ff7621..ccd06e56d4 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/UserSignupTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/UserSignupTest.java @@ -10,6 +10,7 @@ import com.appsmith.server.helpers.UserUtils; import com.appsmith.server.services.AnalyticsService; import com.appsmith.server.services.CaptchaService; import com.appsmith.server.services.ConfigService; +import com.appsmith.server.services.EmailService; import com.appsmith.server.services.UserDataService; import com.appsmith.server.services.UserService; import org.junit.jupiter.api.BeforeEach; @@ -57,6 +58,9 @@ public class UserSignupTest { @MockBean private NetworkUtils networkUtils; + @MockBean + private EmailService emailService; + private UserSignup userSignup; @BeforeEach @@ -71,7 +75,8 @@ public class UserSignupTest { envManager, commonConfig, userUtils, - networkUtils); + networkUtils, + emailService); } private String createRandomString(int length) {