diff --git a/clientapi/auth/password.go b/clientapi/auth/password.go index 7882804dd..f377e79d8 100644 --- a/clientapi/auth/password.go +++ b/clientapi/auth/password.go @@ -26,6 +26,10 @@ import ( "github.com/matrix-org/util" ) +const ( + email = "email" +) + type GetAccountByPassword func(ctx context.Context, localpart, password string) (*api.Account, error) type PasswordRequest struct { @@ -59,9 +63,9 @@ func (t *LoginTypePassword) Login(ctx context.Context, req interface{}) (*Login, if username != "" { localpart, err = userutil.ParseUsernameParam(username, &t.Config.Matrix.ServerName) } else { - if r.Medium == "email" { + if r.Medium == email { if r.Address != "" { - localpart, err = t.AccountDB.GetLocalpartForThreePID(ctx, r.Address, "email") + localpart, err = t.AccountDB.GetLocalpartForThreePID(ctx, r.Address, email) if localpart == "" { return nil, &util.JSONResponse{ Code: http.StatusForbidden, diff --git a/clientapi/routing/routing.go b/clientapi/routing/routing.go index 30ecc2922..8ce987167 100644 --- a/clientapi/routing/routing.go +++ b/clientapi/routing/routing.go @@ -598,7 +598,7 @@ func Setup( r0mux.Handle("/account/3pid", httputil.MakeAuthAPI("account_3pid", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { - return CheckAndSave3PIDAssociation(req, accountDB, device, cfg) + return CheckAndSave3PIDAssociation(req, accountDB, device, cfg, userAPI) }), ).Methods(http.MethodPost, http.MethodOptions) @@ -610,10 +610,22 @@ func Setup( r0mux.Handle("/{path:(?:account/3pid|register)}/email/requestToken", httputil.MakeExternalAPI("account_3pid_request_token", func(req *http.Request) util.JSONResponse { - return RequestEmailToken(req, accountDB, cfg) + return RequestEmailToken(req, accountDB, userAPI, cfg) }), ).Methods(http.MethodPost, http.MethodOptions) + r0mux.Handle("/account/password/email/requestToken", + httputil.MakeAuthAPI("account_password_request_token", userAPI, func(req *http.Request, dev *userapi.Device) util.JSONResponse { + return RequestAccountPasswordEmailToken(req, accountDB, userAPI, dev) + }), + ).Methods(http.MethodPost, http.MethodOptions) + + r0mux.Handle("/{path:(?:account/password|account/3pid|register)}/email/submitToken", + httputil.MakeExternalAPI("email_submit_token", func(req *http.Request) util.JSONResponse { + return SubmitToken(req, userAPI) + }), + ).Methods(http.MethodGet, http.MethodOptions) + // Element logs get flooded unless this is handled r0mux.Handle("/presence/{userID}/status", httputil.MakeExternalAPI("presence", func(req *http.Request) util.JSONResponse { diff --git a/clientapi/routing/threepid.go b/clientapi/routing/threepid.go index f4d233798..5104a4e4c 100644 --- a/clientapi/routing/threepid.go +++ b/clientapi/routing/threepid.go @@ -15,14 +15,19 @@ package routing import ( + "context" "net/http" + "net/url" + "strconv" + "github.com/gorilla/mux" "github.com/matrix-org/dendrite/clientapi/auth/authtypes" "github.com/matrix-org/dendrite/clientapi/httputil" "github.com/matrix-org/dendrite/clientapi/jsonerror" "github.com/matrix-org/dendrite/clientapi/threepid" "github.com/matrix-org/dendrite/setup/config" "github.com/matrix-org/dendrite/userapi/api" + userapi "github.com/matrix-org/dendrite/userapi/api" "github.com/matrix-org/dendrite/userapi/storage/accounts" "github.com/matrix-org/gomatrixserverlib" @@ -30,7 +35,8 @@ import ( ) type reqTokenResponse struct { - SID string `json:"sid"` + SID string `json:"sid"` + SumbitURL string `json:"submit_url,omitempty"` } type threePIDsResponse struct { @@ -40,7 +46,7 @@ type threePIDsResponse struct { // RequestEmailToken implements: // POST /account/3pid/email/requestToken // POST /register/email/requestToken -func RequestEmailToken(req *http.Request, accountDB accounts.Database, cfg *config.ClientAPI) util.JSONResponse { +func RequestEmailToken(req *http.Request, accountDB accounts.Database, userAPI userapi.UserInternalAPI, cfg *config.ClientAPI) util.JSONResponse { var body threepid.EmailAssociationRequest if reqErr := httputil.UnmarshalJSONRequest(req, &body); reqErr != nil { return *reqErr @@ -66,27 +72,185 @@ func RequestEmailToken(req *http.Request, accountDB accounts.Database, cfg *conf } } - resp.SID, err = threepid.CreateSession(req.Context(), body, cfg) - if err == threepid.ErrNotTrusted { - return util.JSONResponse{ - Code: http.StatusBadRequest, - JSON: jsonerror.NotTrusted(body.IDServer), + if cfg.Derived.SendEmails { + createSessionResp := userapi.CreateSessionResponse{} + ctx := req.Context() + path := mux.Vars(req)["path"] + var sessionType userapi.ThreepidSessionType + switch path { + case "account/3pid": + sessionType = userapi.AccountThreepid + case "register": + sessionType = userapi.Register } - } else if err != nil { - util.GetLogger(req.Context()).WithError(err).Error("threepid.CreateSession failed") + err = userAPI.CreateSession(ctx, &userapi.CreateSessionRequest{ + ClientSecret: body.Secret, + NextLink: body.NextLink, + ThreePid: body.Email, + SendAttempt: body.SendAttempt, + SessionType: sessionType, + }, &createSessionResp) + if err != nil { + util.GetLogger(req.Context()).WithError(err).Error("userAPI.CreateSession failed") + return jsonerror.InternalServerError() + } + return util.JSONResponse{ + Code: http.StatusOK, + JSON: createSessionResp, + } + } else { + resp.SID, err = threepid.CreateSession(req.Context(), body, cfg) + if err == threepid.ErrNotTrusted { + return util.JSONResponse{ + Code: http.StatusBadRequest, + JSON: jsonerror.NotTrusted(body.IDServer), + } + } else if err != nil { + util.GetLogger(req.Context()).WithError(err).Error("threepid.CreateSession failed") + return jsonerror.InternalServerError() + } + + return util.JSONResponse{ + Code: http.StatusOK, + JSON: resp, + } + } + +} + +func SubmitToken(req *http.Request, userAPI userapi.UserInternalAPI) util.JSONResponse { + ctx := req.Context() + validateSessionReq, matrixErr := parseSumbitTokenQuery(req.URL) + if matrixErr != nil { + util.GetLogger(req.Context()).WithError(matrixErr).Error("parseSumbitTokenQuery") + return jsonerror.InternalServerError() + } + res := userapi.ValidateSessionResponse{} + err := userAPI.ValidateSession(ctx, validateSessionReq, &res) + if err == userapi.ErrBadSession { + return util.JSONResponse{ + Code: http.StatusNotFound, + JSON: jsonerror.NotFound(err.Error()), + } + } + if res.NextLink != "" { + return util.JSONResponse{ + Code: http.StatusFound, + Headers: map[string]string{ + "Location": res.NextLink, + }, + } + } else { + return util.JSONResponse{ + Code: http.StatusOK, + } + } +} + +func parseSumbitTokenQuery(u *url.URL) (*userapi.ValidateSessionRequest, *jsonerror.MatrixError) { + q := u.Query() + sid := q["sid"] + if sid == nil { + return nil, jsonerror.MissingParam("sid param missing") + } + if len(sid) != 1 { + return nil, jsonerror.InvalidParam("sid param malformed") + } + sidParsed, err := strconv.Atoi(sid[0]) + if err != nil { + return nil, jsonerror.InvalidParam("sid is not an number") + } + clientSecret := q["client_secret"] + if clientSecret == nil { + return nil, jsonerror.MissingParam("client_secret param missing") + } + if len(clientSecret) != 1 { + return nil, jsonerror.InvalidParam("client_secret param malformed") + } + token := q["token"] + if token == nil { + return nil, jsonerror.MissingParam("token param missing") + } + if len(token) != 1 { + return nil, jsonerror.InvalidParam("token param malformed") + } + return &userapi.ValidateSessionRequest{ + SessionOwnership: userapi.SessionOwnership{ + Sid: int64(sidParsed), + ClientSecret: clientSecret[0], + }, + Token: token[0]}, nil +} + +// RequestAccountPasswordEmailToken implements: +// POST /account/password/email/requestToken +func RequestAccountPasswordEmailToken(req *http.Request, accountDB accounts.Database, userAPI userapi.UserInternalAPI, device *userapi.Device) util.JSONResponse { + var body threepid.EmailAssociationRequest + if reqErr := httputil.UnmarshalJSONRequest(req, &body); reqErr != nil { + return *reqErr + } + + var resp reqTokenResponse + var err error + + localpart, _, err := gomatrixserverlib.SplitID('@', device.UserID) + if err != nil { + util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed") + return jsonerror.InternalServerError() + } + ctx := req.Context() + associated, err := isThreePidAssociated(req.Context(), body.Email, localpart, accountDB) + if err != nil { + util.GetLogger(ctx).WithError(err).Error("isThreePidAssociated failed") return jsonerror.InternalServerError() } + if !associated { + return util.JSONResponse{ + Code: http.StatusBadRequest, + JSON: jsonerror.InvalidParam("threepid is not bound to this user"), + } + } + + res := userapi.CreateSessionResponse{} + err = userAPI.CreateSession( + ctx, + &userapi.CreateSessionRequest{ + ClientSecret: body.Secret, + NextLink: body.NextLink, + ThreePid: body.Email, + SendAttempt: body.SendAttempt, + SessionType: api.AccountPassword, + }, + &res) + if err != nil { + util.GetLogger(ctx).WithError(err).Error("userapi.CreateSessionRequest failed") + return jsonerror.InternalServerError() + } + resp.SID = strconv.Itoa(int(res.Sid)) return util.JSONResponse{ Code: http.StatusOK, JSON: resp, } } +func isThreePidAssociated(ctx context.Context, threepid, localpart string, db accounts.Database) (bool, error) { + threepids, err := db.GetThreePIDsForLocalpart(ctx, localpart) + if err != nil { + return false, err + } + for i := range threepids { + if threepid == threepids[i].Address && threepids[i].Medium == "email" { + return true, nil + } + } + return false, nil +} + // CheckAndSave3PIDAssociation implements POST /account/3pid func CheckAndSave3PIDAssociation( req *http.Request, accountDB accounts.Database, device *api.Device, - cfg *config.ClientAPI, + cfg *config.ClientAPI, userAPI userapi.UserInternalAPI, ) util.JSONResponse { var body threepid.EmailAssociationCheckRequest if reqErr := httputil.UnmarshalJSONRequest(req, &body); reqErr != nil { @@ -94,15 +258,42 @@ func CheckAndSave3PIDAssociation( } // Check if the association has been validated - verified, address, medium, err := threepid.CheckAssociation(req.Context(), body.Creds, cfg) - if err == threepid.ErrNotTrusted { - return util.JSONResponse{ - Code: http.StatusBadRequest, - JSON: jsonerror.NotTrusted(body.Creds.IDServer), + var verified bool + var err error + var address, medium string + if cfg.Derived.SendEmails { + var res userapi.IsSessionValidatedResponse + var sid int + sid, err = strconv.Atoi(body.Creds.SID) + if err != nil { + return util.JSONResponse{ + Code: http.StatusBadRequest, + JSON: jsonerror.InvalidParam("sid must be of type integer"), + } + } + err = userAPI.IsSessionValidated(req.Context(), &userapi.SessionOwnership{ + Sid: int64(sid), + ClientSecret: body.Creds.Secret, + }, &res) + if err != nil { + util.GetLogger(req.Context()).WithError(err).Error("userAPI.IsSessionValidated failed") + return jsonerror.InternalServerError() + } + verified = res.Validated + address = res.ThreePid + medium = "email" // TODO handle msisdn as well + + } else { + verified, address, medium, err = threepid.CheckAssociation(req.Context(), body.Creds, cfg) + if err == threepid.ErrNotTrusted { + return util.JSONResponse{ + Code: http.StatusBadRequest, + JSON: jsonerror.NotTrusted(body.Creds.IDServer), + } + } else if err != nil { + util.GetLogger(req.Context()).WithError(err).Error("threepid.CheckAssociation failed") + return jsonerror.InternalServerError() } - } else if err != nil { - util.GetLogger(req.Context()).WithError(err).Error("threepid.CheckAssociation failed") - return jsonerror.InternalServerError() } if !verified { diff --git a/setup/config/config.go b/setup/config/config.go index b91144078..14009c3cc 100644 --- a/setup/config/config.go +++ b/setup/config/config.go @@ -112,6 +112,8 @@ type Derived struct { ExclusiveApplicationServicesAliasRegexp *regexp.Regexp // Note: An Exclusive Regex for room ID isn't necessary as we aren't blocking // servers from creating RoomIDs in exclusive application service namespaces + // SendEmails is set to true when Email is enabled in User API. + SendEmails bool } type InternalAPIOptions struct { @@ -288,7 +290,7 @@ func (config *Dendrite) Derive() error { if err := loadAppServices(&config.AppServiceAPI, &config.Derived); err != nil { return err } - + config.Derived.SendEmails = config.UserAPI.Email.Enabled return nil } diff --git a/setup/mscs/msc2836/msc2836_test.go b/setup/mscs/msc2836/msc2836_test.go index 8aee779eb..44dec7c23 100644 --- a/setup/mscs/msc2836/msc2836_test.go +++ b/setup/mscs/msc2836/msc2836_test.go @@ -514,7 +514,7 @@ func (u *testUserAPI) QueryAccessToken(ctx context.Context, req *userapi.QueryAc func (u *testUserAPI) CreateSession(context.Context, *userapi.CreateSessionRequest, *userapi.CreateSessionResponse) error { return nil } -func (u *testUserAPI) ValidateSession(context.Context, *userapi.ValidateSessionRequest, struct{}) error { +func (u *testUserAPI) ValidateSession(context.Context, *userapi.ValidateSessionRequest, *userapi.ValidateSessionResponse) error { return nil } func (u *testUserAPI) GetThreePidForSession(context.Context, *userapi.SessionOwnership, *userapi.GetThreePidForSessionResponse) error { diff --git a/setup/mscs/msc2946/msc2946_test.go b/setup/mscs/msc2946/msc2946_test.go index ab5ffb201..8491775f8 100644 --- a/setup/mscs/msc2946/msc2946_test.go +++ b/setup/mscs/msc2946/msc2946_test.go @@ -357,7 +357,7 @@ func (u *testUserAPI) QueryAccessToken(ctx context.Context, req *userapi.QueryAc func (u *testUserAPI) CreateSession(context.Context, *userapi.CreateSessionRequest, *userapi.CreateSessionResponse) error { return nil } -func (u *testUserAPI) ValidateSession(context.Context, *userapi.ValidateSessionRequest, struct{}) error { +func (u *testUserAPI) ValidateSession(context.Context, *userapi.ValidateSessionRequest, *userapi.ValidateSessionResponse) error { return nil } func (u *testUserAPI) GetThreePidForSession(context.Context, *userapi.SessionOwnership, *userapi.GetThreePidForSessionResponse) error { diff --git a/sytest-whitelist b/sytest-whitelist index 9f3eb893a..2bd1b76a6 100644 --- a/sytest-whitelist +++ b/sytest-whitelist @@ -556,3 +556,4 @@ Fails to upload self-signing key without master key can fetch self-signing keys over federation Changing master key notifies local users Changing user-signing key notifies local users +Can login with 3pid and password using m.login.password \ No newline at end of file diff --git a/userapi/api/api.go b/userapi/api/api.go index ea6f23a9f..5ec1e6f6c 100644 --- a/userapi/api/api.go +++ b/userapi/api/api.go @@ -17,6 +17,7 @@ package api import ( "context" "encoding/json" + "errors" "github.com/matrix-org/dendrite/clientapi/auth/authtypes" "github.com/matrix-org/gomatrixserverlib" @@ -34,6 +35,8 @@ const ( Register ) +var ErrBadSession = errors.New("provided sid, client_secret and token does not point to valid session") + // UserInternalAPI is the internal API for information about users and devices. type UserInternalAPI interface { InputAccountData(ctx context.Context, req *InputAccountDataRequest, res *InputAccountDataResponse) error @@ -55,7 +58,7 @@ type UserInternalAPI interface { QuerySearchProfiles(ctx context.Context, req *QuerySearchProfilesRequest, res *QuerySearchProfilesResponse) error QueryOpenIDToken(ctx context.Context, req *QueryOpenIDTokenRequest, res *QueryOpenIDTokenResponse) error CreateSession(context.Context, *CreateSessionRequest, *CreateSessionResponse) error - ValidateSession(context.Context, *ValidateSessionRequest, struct{}) error + ValidateSession(context.Context, *ValidateSessionRequest, *ValidateSessionResponse) error GetThreePidForSession(context.Context, *SessionOwnership, *GetThreePidForSessionResponse) error DeleteSession(context.Context, *SessionOwnership, struct{}) error IsSessionValidated(context.Context, *SessionOwnership, *IsSessionValidatedResponse) error @@ -438,7 +441,7 @@ type CreateSessionRequest struct { } type CreateSessionResponse struct { - Sid int64 + Sid int64 `json:"sid"` } type ValidateSessionRequest struct { @@ -446,6 +449,10 @@ type ValidateSessionRequest struct { Token string } +type ValidateSessionResponse struct { + NextLink string +} + type GetThreePidForSessionResponse struct { ThreePid string } @@ -466,6 +473,7 @@ type Session struct { type IsSessionValidatedResponse struct { Validated bool ValidatedAt int + ThreePid string } type ThreepidSessionType int diff --git a/userapi/api/api_trace.go b/userapi/api/api_trace.go index 5ab0c885b..cd5574669 100644 --- a/userapi/api/api_trace.go +++ b/userapi/api/api_trace.go @@ -121,7 +121,7 @@ func (t *UserInternalAPITrace) CreateSession(ctx context.Context, req *CreateSes util.GetLogger(ctx).Infof("CreateSession req=%+v res=%+v", js(req), js(res)) return err } -func (t *UserInternalAPITrace) ValidateSession(ctx context.Context, req *ValidateSessionRequest, res struct{}) error { +func (t *UserInternalAPITrace) ValidateSession(ctx context.Context, req *ValidateSessionRequest, res *ValidateSessionResponse) error { err := t.Impl.ValidateSession(ctx, req, res) util.GetLogger(ctx).Infof("ValidateSession req=%+v res=%+v", js(req), js(res)) return err diff --git a/userapi/internal/threepid.go b/userapi/internal/threepid.go index 8511936b4..60cb94653 100644 --- a/userapi/internal/threepid.go +++ b/userapi/internal/threepid.go @@ -3,7 +3,6 @@ package internal import ( "context" "database/sql" - "errors" "net/url" "strconv" "time" @@ -18,8 +17,6 @@ const ( tokenByteLength = 48 ) -var ErrBadSession = errors.New("provided sid, client_secret and token does not point to valid session") - func (a *UserInternalAPI) CreateSession(ctx context.Context, req *api.CreateSessionRequest, res *api.CreateSessionResponse) error { s, err := a.ThreePidDB.GetSessionByThreePidAndSecret(ctx, req.ThreePid, req.ClientSecret) if err != nil { @@ -76,15 +73,20 @@ func (a *UserInternalAPI) CreateSession(ctx context.Context, req *api.CreateSess }, req.SessionType) } -func (a *UserInternalAPI) ValidateSession(ctx context.Context, req *api.ValidateSessionRequest, res struct{}) error { +func (a *UserInternalAPI) ValidateSession(ctx context.Context, req *api.ValidateSessionRequest, res *api.ValidateSessionResponse) error { s, err := getSessionByOwnership(ctx, &req.SessionOwnership, a.ThreePidDB) if err != nil { return err } if s.Token != req.Token { - return ErrBadSession + return api.ErrBadSession } - return a.ThreePidDB.ValidateSession(ctx, s.Sid, time.Now().Unix()) + err = a.ThreePidDB.ValidateSession(ctx, s.Sid, time.Now().Unix()) + if err != nil { + return err + } + res.NextLink = s.NextLink + return nil } func (a *UserInternalAPI) GetThreePidForSession(ctx context.Context, req *api.SessionOwnership, res *api.GetThreePidForSessionResponse) error { @@ -111,6 +113,7 @@ func (a *UserInternalAPI) IsSessionValidated(ctx context.Context, req *api.Sessi } res.Validated = s.Validated res.ValidatedAt = int(s.ValidatedAt) + res.ThreePid = s.ThreePid return nil } @@ -118,12 +121,12 @@ func getSessionByOwnership(ctx context.Context, so *api.SessionOwnership, d thre s, err := d.GetSession(ctx, so.Sid) if err != nil { if err == sql.ErrNoRows { - return nil, ErrBadSession + return nil, api.ErrBadSession } return nil, err } if s.ClientSecret != so.ClientSecret { - return nil, ErrBadSession + return nil, api.ErrBadSession } return s, err } diff --git a/userapi/inthttp/client.go b/userapi/inthttp/client.go index 116ad388e..f890cde43 100644 --- a/userapi/inthttp/client.go +++ b/userapi/inthttp/client.go @@ -242,7 +242,7 @@ func (h *httpUserInternalAPI) CreateSession(ctx context.Context, req *api.Create return httputil.PostJSON(ctx, span, h.httpClient, apiURL, req, res) } -func (h *httpUserInternalAPI) ValidateSession(ctx context.Context, req *api.ValidateSessionRequest, res struct{}) error { +func (h *httpUserInternalAPI) ValidateSession(ctx context.Context, req *api.ValidateSessionRequest, res *api.ValidateSessionResponse) error { span, ctx := opentracing.StartSpanFromContext(ctx, "ValidateSession") defer span.Finish() diff --git a/userapi/mail/mail.go b/userapi/mail/mail.go index c3f8b5abe..c55513571 100644 --- a/userapi/mail/mail.go +++ b/userapi/mail/mail.go @@ -2,8 +2,14 @@ package mail import ( "bytes" + "crypto/tls" + "errors" "fmt" + "io" "io/ioutil" + "strings" + "sync" + "net/smtp" "text/template" "time" @@ -24,9 +30,15 @@ type Mailer interface { // - https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-account-password-email-requesttoken Send(*Mail, api.ThreepidSessionType) error } + +// SmtpMailer is safe for concurrent use. It will block if email sending is in progress as long as it uses single connection. type SmtpMailer struct { conf config.EmailConf templates []*template.Template + auth smtp.Auth + cl *smtp.Client + // sendMutex guards ensures that MAIL, RCPT and DATA commands are not messed between mails. + sendMutex sync.Mutex } type Mail struct { @@ -61,23 +73,34 @@ func (m *SmtpMailer) send(mail *Mail, t *template.Template) error { if err != nil { return err } - return smtp.SendMail( - m.conf.Smtp.Host, - smtp.PlainAuth( - "", - m.conf.Smtp.User, - m.conf.Smtp.Password, - m.conf.Smtp.Host, - ), - m.conf.From, - []string{ - mail.To, - }, - b.Bytes(), - ) + if err = validateLine(mail.To); err != nil { + return err + } + // lock at the point when data are prepared and we are executing commands + m.sendMutex.Lock() + defer m.sendMutex.Unlock() + if err = m.cl.Mail(m.conf.From); err != nil { + return err + } + if err = m.cl.Rcpt(mail.To); err != nil { + return err + } + var w io.WriteCloser + w, err = m.cl.Data() + if err != nil { + return err + } + _, err = w.Write(b.Bytes()) + if err != nil { + return err + } + return w.Close() } func NewMailer(c *config.UserAPI) (Mailer, error) { + if err := validateLine(c.Email.From); err != nil { + return nil, err + } sessionTypes := api.ThreepidSessionTypes() templates := make([]*template.Template, len(sessionTypes)) for _, t := range sessionTypes { @@ -92,9 +115,47 @@ func NewMailer(c *config.UserAPI) (Mailer, error) { } templates[t] = template } + cl, err := smtp.Dial(c.Email.Smtp.Host) + if err != nil { + return nil, err + } + // defer c.Close() # TODO exit gracefully + if err = cl.Hello("localhost"); err != nil { + return nil, err + } + if ok, _ := cl.Extension("STARTTLS"); ok { + config := &tls.Config{ServerName: c.Email.Smtp.Host} + if err = cl.StartTLS(config); err != nil { + return nil, err + } + } + var auth smtp.Auth + if c.Email.Smtp.User != "" { + auth = smtp.PlainAuth( + "", + c.Email.Smtp.User, + c.Email.Smtp.Password, + c.Email.Smtp.Host, + ) + if err = cl.Auth(auth); err != nil { + return nil, err + } + + } return &SmtpMailer{ conf: c.Email, templates: templates, + auth: auth, + cl: cl, + sendMutex: sync.Mutex{}, }, nil } + +// validateLine checks to see if a line has CR or LF as per RFC 5321 +func validateLine(line string) error { + if strings.ContainsAny(line, "\n\r") { + return errors.New("smtp: A line must not contain CR or LF") + } + return nil +} diff --git a/userapi/userapi_test.go b/userapi/userapi_test.go index 66ae44abc..9f630432d 100644 --- a/userapi/userapi_test.go +++ b/userapi/userapi_test.go @@ -252,6 +252,7 @@ func mustCreateSession(is *is.I, i *internal.UserInternalAPI) (resp *api.CreateS } func mustValidateSesson(is *is.I, i *internal.UserInternalAPI, secret, token string, sid int64) { + res := api.ValidateSessionResponse{} err := i.ValidateSession(ctx, &api.ValidateSessionRequest{ SessionOwnership: api.SessionOwnership{ Sid: sid, @@ -259,7 +260,8 @@ func mustValidateSesson(is *is.I, i *internal.UserInternalAPI, secret, token str }, Token: token, }, - struct{}{}, + &res, ) is.NoErr(err) + is.Equal(res.NextLink, testReq.NextLink) }