github.com/jlevesy/mattermost-server@v5.3.2-0.20181003190404-7468f35cb0c8+incompatible/app/email.go (about)

     1  // Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
     2  // See License.txt for license information.
     3  
     4  package app
     5  
     6  import (
     7  	"fmt"
     8  	"net/url"
     9  
    10  	"net/http"
    11  
    12  	"github.com/nicksnyder/go-i18n/i18n"
    13  	"github.com/pkg/errors"
    14  	"github.com/throttled/throttled"
    15  	"github.com/throttled/throttled/store/memstore"
    16  
    17  	"github.com/mattermost/mattermost-server/mlog"
    18  	"github.com/mattermost/mattermost-server/model"
    19  	"github.com/mattermost/mattermost-server/services/mailservice"
    20  	"github.com/mattermost/mattermost-server/utils"
    21  )
    22  
    23  const (
    24  	emailRateLimitingMemstoreSize = 65536
    25  	emailRateLimitingPerHour      = 20
    26  	emailRateLimitingMaxBurst     = 20
    27  )
    28  
    29  func (a *App) SetupInviteEmailRateLimiting() error {
    30  	store, err := memstore.New(emailRateLimitingMemstoreSize)
    31  	if err != nil {
    32  		return errors.Wrap(err, "Unable to setup email rate limiting memstore.")
    33  	}
    34  
    35  	quota := throttled.RateQuota{
    36  		MaxRate:  throttled.PerHour(emailRateLimitingPerHour),
    37  		MaxBurst: emailRateLimitingMaxBurst,
    38  	}
    39  
    40  	rateLimiter, err := throttled.NewGCRARateLimiter(store, quota)
    41  	if err != nil || rateLimiter == nil {
    42  		return errors.Wrap(err, "Unable to setup email rate limiting GCRA rate limiter.")
    43  	}
    44  
    45  	a.EmailRateLimiter = rateLimiter
    46  	return nil
    47  }
    48  
    49  func (a *App) SendChangeUsernameEmail(oldUsername, newUsername, email, locale, siteURL string) *model.AppError {
    50  	T := utils.GetUserTranslations(locale)
    51  
    52  	subject := T("api.templates.username_change_subject",
    53  		map[string]interface{}{"SiteName": a.ClientConfig()["SiteName"],
    54  			"TeamDisplayName": a.Config().TeamSettings.SiteName})
    55  
    56  	bodyPage := a.NewEmailTemplate("email_change_body", locale)
    57  	bodyPage.Props["SiteURL"] = siteURL
    58  	bodyPage.Props["Title"] = T("api.templates.username_change_body.title")
    59  	bodyPage.Props["Info"] = T("api.templates.username_change_body.info",
    60  		map[string]interface{}{"TeamDisplayName": a.Config().TeamSettings.SiteName, "NewUsername": newUsername})
    61  	bodyPage.Props["Warning"] = T("api.templates.email_warning")
    62  
    63  	if err := a.SendMail(email, subject, bodyPage.Render()); err != nil {
    64  		return model.NewAppError("SendChangeUsernameEmail", "api.user.send_email_change_username_and_forget.error", nil, err.Error(), http.StatusInternalServerError)
    65  	}
    66  
    67  	return nil
    68  }
    69  
    70  func (a *App) SendEmailChangeVerifyEmail(newUserEmail, locale, siteURL, token string) *model.AppError {
    71  	T := utils.GetUserTranslations(locale)
    72  
    73  	link := fmt.Sprintf("%s/do_verify_email?token=%s&email=%s", siteURL, token, url.QueryEscape(newUserEmail))
    74  
    75  	subject := T("api.templates.email_change_verify_subject",
    76  		map[string]interface{}{"SiteName": a.ClientConfig()["SiteName"],
    77  			"TeamDisplayName": a.Config().TeamSettings.SiteName})
    78  
    79  	bodyPage := a.NewEmailTemplate("email_change_verify_body", locale)
    80  	bodyPage.Props["SiteURL"] = siteURL
    81  	bodyPage.Props["Title"] = T("api.templates.email_change_verify_body.title")
    82  	bodyPage.Props["Info"] = T("api.templates.email_change_verify_body.info",
    83  		map[string]interface{}{"TeamDisplayName": a.Config().TeamSettings.SiteName})
    84  	bodyPage.Props["VerifyUrl"] = link
    85  	bodyPage.Props["VerifyButton"] = T("api.templates.email_change_verify_body.button")
    86  
    87  	if err := a.SendMail(newUserEmail, subject, bodyPage.Render()); err != nil {
    88  		return model.NewAppError("SendEmailChangeVerifyEmail", "api.user.send_email_change_verify_email_and_forget.error", nil, err.Error(), http.StatusInternalServerError)
    89  	}
    90  
    91  	return nil
    92  }
    93  
    94  func (a *App) SendEmailChangeEmail(oldEmail, newEmail, locale, siteURL string) *model.AppError {
    95  	T := utils.GetUserTranslations(locale)
    96  
    97  	subject := T("api.templates.email_change_subject",
    98  		map[string]interface{}{"SiteName": a.ClientConfig()["SiteName"],
    99  			"TeamDisplayName": a.Config().TeamSettings.SiteName})
   100  
   101  	bodyPage := a.NewEmailTemplate("email_change_body", locale)
   102  	bodyPage.Props["SiteURL"] = siteURL
   103  	bodyPage.Props["Title"] = T("api.templates.email_change_body.title")
   104  	bodyPage.Props["Info"] = T("api.templates.email_change_body.info",
   105  		map[string]interface{}{"TeamDisplayName": a.Config().TeamSettings.SiteName, "NewEmail": newEmail})
   106  	bodyPage.Props["Warning"] = T("api.templates.email_warning")
   107  
   108  	if err := a.SendMail(oldEmail, subject, bodyPage.Render()); err != nil {
   109  		return model.NewAppError("SendEmailChangeEmail", "api.user.send_email_change_email_and_forget.error", nil, err.Error(), http.StatusInternalServerError)
   110  	}
   111  
   112  	return nil
   113  }
   114  
   115  func (a *App) SendVerifyEmail(userEmail, locale, siteURL, token string) *model.AppError {
   116  	T := utils.GetUserTranslations(locale)
   117  
   118  	link := fmt.Sprintf("%s/do_verify_email?token=%s&email=%s", siteURL, token, url.QueryEscape(userEmail))
   119  
   120  	url, _ := url.Parse(siteURL)
   121  
   122  	subject := T("api.templates.verify_subject",
   123  		map[string]interface{}{"SiteName": a.ClientConfig()["SiteName"]})
   124  
   125  	bodyPage := a.NewEmailTemplate("verify_body", locale)
   126  	bodyPage.Props["SiteURL"] = siteURL
   127  	bodyPage.Props["Title"] = T("api.templates.verify_body.title", map[string]interface{}{"ServerURL": url.Host})
   128  	bodyPage.Props["Info"] = T("api.templates.verify_body.info")
   129  	bodyPage.Props["VerifyUrl"] = link
   130  	bodyPage.Props["Button"] = T("api.templates.verify_body.button")
   131  
   132  	if err := a.SendMail(userEmail, subject, bodyPage.Render()); err != nil {
   133  		return model.NewAppError("SendVerifyEmail", "api.user.send_verify_email_and_forget.failed.error", nil, err.Error(), http.StatusInternalServerError)
   134  	}
   135  
   136  	return nil
   137  }
   138  
   139  func (a *App) SendSignInChangeEmail(email, method, locale, siteURL string) *model.AppError {
   140  	T := utils.GetUserTranslations(locale)
   141  
   142  	subject := T("api.templates.signin_change_email.subject",
   143  		map[string]interface{}{"SiteName": a.ClientConfig()["SiteName"]})
   144  
   145  	bodyPage := a.NewEmailTemplate("signin_change_body", locale)
   146  	bodyPage.Props["SiteURL"] = siteURL
   147  	bodyPage.Props["Title"] = T("api.templates.signin_change_email.body.title")
   148  	bodyPage.Props["Info"] = T("api.templates.signin_change_email.body.info",
   149  		map[string]interface{}{"SiteName": a.ClientConfig()["SiteName"], "Method": method})
   150  	bodyPage.Props["Warning"] = T("api.templates.email_warning")
   151  
   152  	if err := a.SendMail(email, subject, bodyPage.Render()); err != nil {
   153  		return model.NewAppError("SendSignInChangeEmail", "api.user.send_sign_in_change_email_and_forget.error", nil, err.Error(), http.StatusInternalServerError)
   154  	}
   155  
   156  	return nil
   157  }
   158  
   159  func (a *App) SendWelcomeEmail(userId string, email string, verified bool, locale, siteURL string) *model.AppError {
   160  	T := utils.GetUserTranslations(locale)
   161  
   162  	rawUrl, _ := url.Parse(siteURL)
   163  
   164  	subject := T("api.templates.welcome_subject",
   165  		map[string]interface{}{"SiteName": a.ClientConfig()["SiteName"],
   166  			"ServerURL": rawUrl.Host})
   167  
   168  	bodyPage := a.NewEmailTemplate("welcome_body", locale)
   169  	bodyPage.Props["SiteURL"] = siteURL
   170  	bodyPage.Props["Title"] = T("api.templates.welcome_body.title", map[string]interface{}{"ServerURL": rawUrl.Host})
   171  	bodyPage.Props["Info"] = T("api.templates.welcome_body.info")
   172  	bodyPage.Props["Button"] = T("api.templates.welcome_body.button")
   173  	bodyPage.Props["Info2"] = T("api.templates.welcome_body.info2")
   174  	bodyPage.Props["Info3"] = T("api.templates.welcome_body.info3")
   175  	bodyPage.Props["SiteURL"] = siteURL
   176  
   177  	if *a.Config().NativeAppSettings.AppDownloadLink != "" {
   178  		bodyPage.Props["AppDownloadInfo"] = T("api.templates.welcome_body.app_download_info")
   179  		bodyPage.Props["AppDownloadLink"] = *a.Config().NativeAppSettings.AppDownloadLink
   180  	}
   181  
   182  	if !verified {
   183  		token, err := a.CreateVerifyEmailToken(userId)
   184  		if err != nil {
   185  			return err
   186  		}
   187  		link := fmt.Sprintf("%s/do_verify_email?token=%s&email=%s", siteURL, token.Token, url.QueryEscape(email))
   188  		bodyPage.Props["VerifyUrl"] = link
   189  	}
   190  
   191  	if err := a.SendMail(email, subject, bodyPage.Render()); err != nil {
   192  		return model.NewAppError("SendWelcomeEmail", "api.user.send_welcome_email_and_forget.failed.error", nil, err.Error(), http.StatusInternalServerError)
   193  	}
   194  
   195  	return nil
   196  }
   197  
   198  func (a *App) SendPasswordChangeEmail(email, method, locale, siteURL string) *model.AppError {
   199  	T := utils.GetUserTranslations(locale)
   200  
   201  	subject := T("api.templates.password_change_subject",
   202  		map[string]interface{}{"SiteName": a.ClientConfig()["SiteName"],
   203  			"TeamDisplayName": a.Config().TeamSettings.SiteName})
   204  
   205  	bodyPage := a.NewEmailTemplate("password_change_body", locale)
   206  	bodyPage.Props["SiteURL"] = siteURL
   207  	bodyPage.Props["Title"] = T("api.templates.password_change_body.title")
   208  	bodyPage.Props["Info"] = T("api.templates.password_change_body.info",
   209  		map[string]interface{}{"TeamDisplayName": a.Config().TeamSettings.SiteName, "TeamURL": siteURL, "Method": method})
   210  	bodyPage.Props["Warning"] = T("api.templates.email_warning")
   211  
   212  	if err := a.SendMail(email, subject, bodyPage.Render()); err != nil {
   213  		return model.NewAppError("SendPasswordChangeEmail", "api.user.send_password_change_email_and_forget.error", nil, err.Error(), http.StatusInternalServerError)
   214  	}
   215  
   216  	return nil
   217  }
   218  
   219  func (a *App) SendUserAccessTokenAddedEmail(email, locale, siteURL string) *model.AppError {
   220  	T := utils.GetUserTranslations(locale)
   221  
   222  	subject := T("api.templates.user_access_token_subject",
   223  		map[string]interface{}{"SiteName": a.ClientConfig()["SiteName"]})
   224  
   225  	bodyPage := a.NewEmailTemplate("password_change_body", locale)
   226  	bodyPage.Props["SiteURL"] = siteURL
   227  	bodyPage.Props["Title"] = T("api.templates.user_access_token_body.title")
   228  	bodyPage.Props["Info"] = T("api.templates.user_access_token_body.info",
   229  		map[string]interface{}{"SiteName": a.ClientConfig()["SiteName"], "SiteURL": siteURL})
   230  	bodyPage.Props["Warning"] = T("api.templates.email_warning")
   231  
   232  	if err := a.SendMail(email, subject, bodyPage.Render()); err != nil {
   233  		return model.NewAppError("SendUserAccessTokenAddedEmail", "api.user.send_user_access_token.error", nil, err.Error(), http.StatusInternalServerError)
   234  	}
   235  
   236  	return nil
   237  }
   238  
   239  func (a *App) SendPasswordResetEmail(email string, token *model.Token, locale, siteURL string) (bool, *model.AppError) {
   240  
   241  	T := utils.GetUserTranslations(locale)
   242  
   243  	link := fmt.Sprintf("%s/reset_password_complete?token=%s", siteURL, url.QueryEscape(token.Token))
   244  
   245  	subject := T("api.templates.reset_subject",
   246  		map[string]interface{}{"SiteName": a.ClientConfig()["SiteName"]})
   247  
   248  	bodyPage := a.NewEmailTemplate("reset_body", locale)
   249  	bodyPage.Props["SiteURL"] = siteURL
   250  	bodyPage.Props["Title"] = T("api.templates.reset_body.title")
   251  	bodyPage.Props["Info1"] = utils.TranslateAsHtml(T, "api.templates.reset_body.info1", nil)
   252  	bodyPage.Props["Info2"] = T("api.templates.reset_body.info2")
   253  	bodyPage.Props["ResetUrl"] = link
   254  	bodyPage.Props["Button"] = T("api.templates.reset_body.button")
   255  
   256  	if err := a.SendMail(email, subject, bodyPage.Render()); err != nil {
   257  		return false, model.NewAppError("SendPasswordReset", "api.user.send_password_reset.send.app_error", nil, "err="+err.Message, http.StatusInternalServerError)
   258  	}
   259  
   260  	return true, nil
   261  }
   262  
   263  func (a *App) SendMfaChangeEmail(email string, activated bool, locale, siteURL string) *model.AppError {
   264  	T := utils.GetUserTranslations(locale)
   265  
   266  	subject := T("api.templates.mfa_change_subject",
   267  		map[string]interface{}{"SiteName": a.ClientConfig()["SiteName"]})
   268  
   269  	bodyPage := a.NewEmailTemplate("mfa_change_body", locale)
   270  	bodyPage.Props["SiteURL"] = siteURL
   271  
   272  	if activated {
   273  		bodyPage.Props["Info"] = T("api.templates.mfa_activated_body.info", map[string]interface{}{"SiteURL": siteURL})
   274  		bodyPage.Props["Title"] = T("api.templates.mfa_activated_body.title")
   275  	} else {
   276  		bodyPage.Props["Info"] = T("api.templates.mfa_deactivated_body.info", map[string]interface{}{"SiteURL": siteURL})
   277  		bodyPage.Props["Title"] = T("api.templates.mfa_deactivated_body.title")
   278  	}
   279  	bodyPage.Props["Warning"] = T("api.templates.email_warning")
   280  
   281  	if err := a.SendMail(email, subject, bodyPage.Render()); err != nil {
   282  		return model.NewAppError("SendMfaChangeEmail", "api.user.send_mfa_change_email.error", nil, err.Error(), http.StatusInternalServerError)
   283  	}
   284  
   285  	return nil
   286  }
   287  
   288  func (a *App) SendInviteEmails(team *model.Team, senderName string, senderUserId string, invites []string, siteURL string) {
   289  	if a.EmailRateLimiter == nil {
   290  		a.Log.Error("Email invite not sent, rate limiting could not be setup.", mlog.String("user_id", senderUserId), mlog.String("team_id", team.Id))
   291  		return
   292  	}
   293  	rateLimited, result, err := a.EmailRateLimiter.RateLimit(senderUserId, len(invites))
   294  	if err != nil {
   295  		a.Log.Error("Error rate limiting invite email.", mlog.String("user_id", senderUserId), mlog.String("team_id", team.Id), mlog.Err(err))
   296  		return
   297  	}
   298  
   299  	if rateLimited {
   300  		a.Log.Error("Invite emails rate limited.",
   301  			mlog.String("user_id", senderUserId),
   302  			mlog.String("team_id", team.Id),
   303  			mlog.String("retry_after", result.RetryAfter.String()),
   304  			mlog.Err(err))
   305  		return
   306  	}
   307  
   308  	for _, invite := range invites {
   309  		if len(invite) > 0 {
   310  			senderRole := utils.T("api.team.invite_members.member")
   311  
   312  			subject := utils.T("api.templates.invite_subject",
   313  				map[string]interface{}{"SenderName": senderName,
   314  					"TeamDisplayName": team.DisplayName,
   315  					"SiteName":        a.ClientConfig()["SiteName"]})
   316  
   317  			bodyPage := a.NewEmailTemplate("invite_body", model.DEFAULT_LOCALE)
   318  			bodyPage.Props["SiteURL"] = siteURL
   319  			bodyPage.Props["Title"] = utils.T("api.templates.invite_body.title")
   320  			bodyPage.Html["Info"] = utils.TranslateAsHtml(utils.T, "api.templates.invite_body.info",
   321  				map[string]interface{}{"SenderStatus": senderRole, "SenderName": senderName, "TeamDisplayName": team.DisplayName})
   322  			bodyPage.Props["Info"] = map[string]interface{}{}
   323  			bodyPage.Props["Button"] = utils.T("api.templates.invite_body.button")
   324  			bodyPage.Html["ExtraInfo"] = utils.TranslateAsHtml(utils.T, "api.templates.invite_body.extra_info",
   325  				map[string]interface{}{"TeamDisplayName": team.DisplayName})
   326  			bodyPage.Props["TeamURL"] = siteURL + "/" + team.Name
   327  
   328  			token := model.NewToken(
   329  				TOKEN_TYPE_TEAM_INVITATION,
   330  				model.MapToJson(map[string]string{"teamId": team.Id, "email": invite}),
   331  			)
   332  
   333  			props := make(map[string]string)
   334  			props["email"] = invite
   335  			props["display_name"] = team.DisplayName
   336  			props["name"] = team.Name
   337  			data := model.MapToJson(props)
   338  
   339  			if result := <-a.Srv.Store.Token().Save(token); result.Err != nil {
   340  				mlog.Error(fmt.Sprintf("Failed to send invite email successfully err=%v", result.Err))
   341  				continue
   342  			}
   343  			bodyPage.Props["Link"] = fmt.Sprintf("%s/signup_user_complete/?d=%s&t=%s", siteURL, url.QueryEscape(data), url.QueryEscape(token.Token))
   344  
   345  			if !a.Config().EmailSettings.SendEmailNotifications {
   346  				mlog.Info(fmt.Sprintf("sending invitation to %v %v", invite, bodyPage.Props["Link"]))
   347  			}
   348  
   349  			if err := a.SendMail(invite, subject, bodyPage.Render()); err != nil {
   350  				mlog.Error(fmt.Sprintf("Failed to send invite email successfully err=%v", err))
   351  			}
   352  		}
   353  	}
   354  }
   355  
   356  func (a *App) NewEmailTemplate(name, locale string) *utils.HTMLTemplate {
   357  	t := utils.NewHTMLTemplate(a.HTMLTemplates(), name)
   358  
   359  	var localT i18n.TranslateFunc
   360  	if locale != "" {
   361  		localT = utils.GetUserTranslations(locale)
   362  	} else {
   363  		localT = utils.T
   364  	}
   365  
   366  	t.Props["Footer"] = localT("api.templates.email_footer")
   367  
   368  	if *a.Config().EmailSettings.FeedbackOrganization != "" {
   369  		t.Props["Organization"] = localT("api.templates.email_organization") + *a.Config().EmailSettings.FeedbackOrganization
   370  	} else {
   371  		t.Props["Organization"] = ""
   372  	}
   373  
   374  	t.Props["EmailInfo1"] = localT("api.templates.email_info1")
   375  	t.Props["EmailInfo2"] = localT("api.templates.email_info2")
   376  	t.Props["EmailInfo3"] = localT("api.templates.email_info3",
   377  		map[string]interface{}{"SiteName": a.Config().TeamSettings.SiteName})
   378  	t.Props["SupportEmail"] = *a.Config().SupportSettings.SupportEmail
   379  
   380  	return t
   381  }
   382  
   383  func (a *App) SendDeactivateAccountEmail(email string, locale, siteURL string) *model.AppError {
   384  	T := utils.GetUserTranslations(locale)
   385  
   386  	rawUrl, _ := url.Parse(siteURL)
   387  
   388  	subject := T("api.templates.deactivate_subject",
   389  		map[string]interface{}{"SiteName": a.ClientConfig()["SiteName"],
   390  			"ServerURL": rawUrl.Host})
   391  
   392  	bodyPage := a.NewEmailTemplate("deactivate_body", locale)
   393  	bodyPage.Props["SiteURL"] = siteURL
   394  	bodyPage.Props["Title"] = T("api.templates.deactivate_body.title", map[string]interface{}{"ServerURL": rawUrl.Host})
   395  	bodyPage.Props["Info"] = T("api.templates.deactivate_body.info",
   396  		map[string]interface{}{"SiteURL": siteURL})
   397  	bodyPage.Props["Warning"] = T("api.templates.deactivate_body.warning")
   398  
   399  	if err := a.SendMail(email, subject, bodyPage.Render()); err != nil {
   400  		return model.NewAppError("SendDeactivateEmail", "api.user.send_deactivate_email_and_forget.failed.error", nil, err.Error(), http.StatusInternalServerError)
   401  	}
   402  
   403  	return nil
   404  }
   405  
   406  func (a *App) SendMail(to, subject, htmlBody string) *model.AppError {
   407  	license := a.License()
   408  	return mailservice.SendMailUsingConfig(to, subject, htmlBody, a.Config(), license != nil && *license.Features.Compliance)
   409  }