mirror of
https://github.com/matrix-org/dendrite.git
synced 2025-12-28 17:23:09 -06:00
Can login with 3pid and password using m.login.password
This commit is contained in:
parent
f290e722c3
commit
433bc321ae
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue