mirror of
https://github.com/matrix-org/dendrite.git
synced 2025-12-29 01:33:10 -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"
|
"github.com/matrix-org/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
email = "email"
|
||||||
|
)
|
||||||
|
|
||||||
type GetAccountByPassword func(ctx context.Context, localpart, password string) (*api.Account, error)
|
type GetAccountByPassword func(ctx context.Context, localpart, password string) (*api.Account, error)
|
||||||
|
|
||||||
type PasswordRequest struct {
|
type PasswordRequest struct {
|
||||||
|
|
@ -59,9 +63,9 @@ func (t *LoginTypePassword) Login(ctx context.Context, req interface{}) (*Login,
|
||||||
if username != "" {
|
if username != "" {
|
||||||
localpart, err = userutil.ParseUsernameParam(username, &t.Config.Matrix.ServerName)
|
localpart, err = userutil.ParseUsernameParam(username, &t.Config.Matrix.ServerName)
|
||||||
} else {
|
} else {
|
||||||
if r.Medium == "email" {
|
if r.Medium == email {
|
||||||
if r.Address != "" {
|
if r.Address != "" {
|
||||||
localpart, err = t.AccountDB.GetLocalpartForThreePID(ctx, r.Address, "email")
|
localpart, err = t.AccountDB.GetLocalpartForThreePID(ctx, r.Address, email)
|
||||||
if localpart == "" {
|
if localpart == "" {
|
||||||
return nil, &util.JSONResponse{
|
return nil, &util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
|
|
|
||||||
|
|
@ -598,7 +598,7 @@ func Setup(
|
||||||
|
|
||||||
r0mux.Handle("/account/3pid",
|
r0mux.Handle("/account/3pid",
|
||||||
httputil.MakeAuthAPI("account_3pid", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
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)
|
).Methods(http.MethodPost, http.MethodOptions)
|
||||||
|
|
||||||
|
|
@ -610,10 +610,22 @@ func Setup(
|
||||||
|
|
||||||
r0mux.Handle("/{path:(?:account/3pid|register)}/email/requestToken",
|
r0mux.Handle("/{path:(?:account/3pid|register)}/email/requestToken",
|
||||||
httputil.MakeExternalAPI("account_3pid_request_token", func(req *http.Request) util.JSONResponse {
|
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)
|
).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
|
// Element logs get flooded unless this is handled
|
||||||
r0mux.Handle("/presence/{userID}/status",
|
r0mux.Handle("/presence/{userID}/status",
|
||||||
httputil.MakeExternalAPI("presence", func(req *http.Request) util.JSONResponse {
|
httputil.MakeExternalAPI("presence", func(req *http.Request) util.JSONResponse {
|
||||||
|
|
|
||||||
|
|
@ -15,14 +15,19 @@
|
||||||
package routing
|
package routing
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||||
"github.com/matrix-org/dendrite/clientapi/threepid"
|
"github.com/matrix-org/dendrite/clientapi/threepid"
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
"github.com/matrix-org/dendrite/userapi/api"
|
"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/dendrite/userapi/storage/accounts"
|
||||||
|
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
|
@ -31,6 +36,7 @@ import (
|
||||||
|
|
||||||
type reqTokenResponse struct {
|
type reqTokenResponse struct {
|
||||||
SID string `json:"sid"`
|
SID string `json:"sid"`
|
||||||
|
SumbitURL string `json:"submit_url,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type threePIDsResponse struct {
|
type threePIDsResponse struct {
|
||||||
|
|
@ -40,7 +46,7 @@ type threePIDsResponse struct {
|
||||||
// RequestEmailToken implements:
|
// RequestEmailToken implements:
|
||||||
// POST /account/3pid/email/requestToken
|
// POST /account/3pid/email/requestToken
|
||||||
// POST /register/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
|
var body threepid.EmailAssociationRequest
|
||||||
if reqErr := httputil.UnmarshalJSONRequest(req, &body); reqErr != nil {
|
if reqErr := httputil.UnmarshalJSONRequest(req, &body); reqErr != nil {
|
||||||
return *reqErr
|
return *reqErr
|
||||||
|
|
@ -66,6 +72,33 @@ func RequestEmailToken(req *http.Request, accountDB accounts.Database, cfg *conf
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
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)
|
resp.SID, err = threepid.CreateSession(req.Context(), body, cfg)
|
||||||
if err == threepid.ErrNotTrusted {
|
if err == threepid.ErrNotTrusted {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -81,12 +114,143 @@ func RequestEmailToken(req *http.Request, accountDB accounts.Database, cfg *conf
|
||||||
Code: http.StatusOK,
|
Code: http.StatusOK,
|
||||||
JSON: resp,
|
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
|
// CheckAndSave3PIDAssociation implements POST /account/3pid
|
||||||
func CheckAndSave3PIDAssociation(
|
func CheckAndSave3PIDAssociation(
|
||||||
req *http.Request, accountDB accounts.Database, device *api.Device,
|
req *http.Request, accountDB accounts.Database, device *api.Device,
|
||||||
cfg *config.ClientAPI,
|
cfg *config.ClientAPI, userAPI userapi.UserInternalAPI,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
var body threepid.EmailAssociationCheckRequest
|
var body threepid.EmailAssociationCheckRequest
|
||||||
if reqErr := httputil.UnmarshalJSONRequest(req, &body); reqErr != nil {
|
if reqErr := httputil.UnmarshalJSONRequest(req, &body); reqErr != nil {
|
||||||
|
|
@ -94,7 +258,33 @@ func CheckAndSave3PIDAssociation(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the association has been validated
|
// Check if the association has been validated
|
||||||
verified, address, medium, err := threepid.CheckAssociation(req.Context(), body.Creds, cfg)
|
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 {
|
if err == threepid.ErrNotTrusted {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
|
|
@ -104,6 +294,7 @@ func CheckAndSave3PIDAssociation(
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("threepid.CheckAssociation failed")
|
util.GetLogger(req.Context()).WithError(err).Error("threepid.CheckAssociation failed")
|
||||||
return jsonerror.InternalServerError()
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !verified {
|
if !verified {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
|
||||||
|
|
@ -112,6 +112,8 @@ type Derived struct {
|
||||||
ExclusiveApplicationServicesAliasRegexp *regexp.Regexp
|
ExclusiveApplicationServicesAliasRegexp *regexp.Regexp
|
||||||
// Note: An Exclusive Regex for room ID isn't necessary as we aren't blocking
|
// Note: An Exclusive Regex for room ID isn't necessary as we aren't blocking
|
||||||
// servers from creating RoomIDs in exclusive application service namespaces
|
// 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 {
|
type InternalAPIOptions struct {
|
||||||
|
|
@ -288,7 +290,7 @@ func (config *Dendrite) Derive() error {
|
||||||
if err := loadAppServices(&config.AppServiceAPI, &config.Derived); err != nil {
|
if err := loadAppServices(&config.AppServiceAPI, &config.Derived); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
config.Derived.SendEmails = config.UserAPI.Email.Enabled
|
||||||
return nil
|
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 {
|
func (u *testUserAPI) CreateSession(context.Context, *userapi.CreateSessionRequest, *userapi.CreateSessionResponse) error {
|
||||||
return nil
|
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
|
return nil
|
||||||
}
|
}
|
||||||
func (u *testUserAPI) GetThreePidForSession(context.Context, *userapi.SessionOwnership, *userapi.GetThreePidForSessionResponse) error {
|
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 {
|
func (u *testUserAPI) CreateSession(context.Context, *userapi.CreateSessionRequest, *userapi.CreateSessionResponse) error {
|
||||||
return nil
|
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
|
return nil
|
||||||
}
|
}
|
||||||
func (u *testUserAPI) GetThreePidForSession(context.Context, *userapi.SessionOwnership, *userapi.GetThreePidForSessionResponse) error {
|
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
|
can fetch self-signing keys over federation
|
||||||
Changing master key notifies local users
|
Changing master key notifies local users
|
||||||
Changing user-signing 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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
|
@ -34,6 +35,8 @@ const (
|
||||||
Register
|
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.
|
// UserInternalAPI is the internal API for information about users and devices.
|
||||||
type UserInternalAPI interface {
|
type UserInternalAPI interface {
|
||||||
InputAccountData(ctx context.Context, req *InputAccountDataRequest, res *InputAccountDataResponse) error
|
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
|
QuerySearchProfiles(ctx context.Context, req *QuerySearchProfilesRequest, res *QuerySearchProfilesResponse) error
|
||||||
QueryOpenIDToken(ctx context.Context, req *QueryOpenIDTokenRequest, res *QueryOpenIDTokenResponse) error
|
QueryOpenIDToken(ctx context.Context, req *QueryOpenIDTokenRequest, res *QueryOpenIDTokenResponse) error
|
||||||
CreateSession(context.Context, *CreateSessionRequest, *CreateSessionResponse) 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
|
GetThreePidForSession(context.Context, *SessionOwnership, *GetThreePidForSessionResponse) error
|
||||||
DeleteSession(context.Context, *SessionOwnership, struct{}) error
|
DeleteSession(context.Context, *SessionOwnership, struct{}) error
|
||||||
IsSessionValidated(context.Context, *SessionOwnership, *IsSessionValidatedResponse) error
|
IsSessionValidated(context.Context, *SessionOwnership, *IsSessionValidatedResponse) error
|
||||||
|
|
@ -438,7 +441,7 @@ type CreateSessionRequest struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateSessionResponse struct {
|
type CreateSessionResponse struct {
|
||||||
Sid int64
|
Sid int64 `json:"sid"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ValidateSessionRequest struct {
|
type ValidateSessionRequest struct {
|
||||||
|
|
@ -446,6 +449,10 @@ type ValidateSessionRequest struct {
|
||||||
Token string
|
Token string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ValidateSessionResponse struct {
|
||||||
|
NextLink string
|
||||||
|
}
|
||||||
|
|
||||||
type GetThreePidForSessionResponse struct {
|
type GetThreePidForSessionResponse struct {
|
||||||
ThreePid string
|
ThreePid string
|
||||||
}
|
}
|
||||||
|
|
@ -466,6 +473,7 @@ type Session struct {
|
||||||
type IsSessionValidatedResponse struct {
|
type IsSessionValidatedResponse struct {
|
||||||
Validated bool
|
Validated bool
|
||||||
ValidatedAt int
|
ValidatedAt int
|
||||||
|
ThreePid string
|
||||||
}
|
}
|
||||||
|
|
||||||
type ThreepidSessionType int
|
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))
|
util.GetLogger(ctx).Infof("CreateSession req=%+v res=%+v", js(req), js(res))
|
||||||
return err
|
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)
|
err := t.Impl.ValidateSession(ctx, req, res)
|
||||||
util.GetLogger(ctx).Infof("ValidateSession req=%+v res=%+v", js(req), js(res))
|
util.GetLogger(ctx).Infof("ValidateSession req=%+v res=%+v", js(req), js(res))
|
||||||
return err
|
return err
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ package internal
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -18,8 +17,6 @@ const (
|
||||||
tokenByteLength = 48
|
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 {
|
func (a *UserInternalAPI) CreateSession(ctx context.Context, req *api.CreateSessionRequest, res *api.CreateSessionResponse) error {
|
||||||
s, err := a.ThreePidDB.GetSessionByThreePidAndSecret(ctx, req.ThreePid, req.ClientSecret)
|
s, err := a.ThreePidDB.GetSessionByThreePidAndSecret(ctx, req.ThreePid, req.ClientSecret)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -76,15 +73,20 @@ func (a *UserInternalAPI) CreateSession(ctx context.Context, req *api.CreateSess
|
||||||
}, req.SessionType)
|
}, 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)
|
s, err := getSessionByOwnership(ctx, &req.SessionOwnership, a.ThreePidDB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if s.Token != req.Token {
|
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 {
|
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.Validated = s.Validated
|
||||||
res.ValidatedAt = int(s.ValidatedAt)
|
res.ValidatedAt = int(s.ValidatedAt)
|
||||||
|
res.ThreePid = s.ThreePid
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -118,12 +121,12 @@ func getSessionByOwnership(ctx context.Context, so *api.SessionOwnership, d thre
|
||||||
s, err := d.GetSession(ctx, so.Sid)
|
s, err := d.GetSession(ctx, so.Sid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
return nil, ErrBadSession
|
return nil, api.ErrBadSession
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if s.ClientSecret != so.ClientSecret {
|
if s.ClientSecret != so.ClientSecret {
|
||||||
return nil, ErrBadSession
|
return nil, api.ErrBadSession
|
||||||
}
|
}
|
||||||
return s, err
|
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)
|
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")
|
span, ctx := opentracing.StartSpanFromContext(ctx, "ValidateSession")
|
||||||
defer span.Finish()
|
defer span.Finish()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,14 @@ package mail
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"net/smtp"
|
"net/smtp"
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"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
|
// - https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-account-password-email-requesttoken
|
||||||
Send(*Mail, api.ThreepidSessionType) error
|
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 {
|
type SmtpMailer struct {
|
||||||
conf config.EmailConf
|
conf config.EmailConf
|
||||||
templates []*template.Template
|
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 {
|
type Mail struct {
|
||||||
|
|
@ -61,23 +73,34 @@ func (m *SmtpMailer) send(mail *Mail, t *template.Template) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return smtp.SendMail(
|
if err = validateLine(mail.To); err != nil {
|
||||||
m.conf.Smtp.Host,
|
return err
|
||||||
smtp.PlainAuth(
|
}
|
||||||
"",
|
// lock at the point when data are prepared and we are executing commands
|
||||||
m.conf.Smtp.User,
|
m.sendMutex.Lock()
|
||||||
m.conf.Smtp.Password,
|
defer m.sendMutex.Unlock()
|
||||||
m.conf.Smtp.Host,
|
if err = m.cl.Mail(m.conf.From); err != nil {
|
||||||
),
|
return err
|
||||||
m.conf.From,
|
}
|
||||||
[]string{
|
if err = m.cl.Rcpt(mail.To); err != nil {
|
||||||
mail.To,
|
return err
|
||||||
},
|
}
|
||||||
b.Bytes(),
|
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) {
|
func NewMailer(c *config.UserAPI) (Mailer, error) {
|
||||||
|
if err := validateLine(c.Email.From); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
sessionTypes := api.ThreepidSessionTypes()
|
sessionTypes := api.ThreepidSessionTypes()
|
||||||
templates := make([]*template.Template, len(sessionTypes))
|
templates := make([]*template.Template, len(sessionTypes))
|
||||||
for _, t := range sessionTypes {
|
for _, t := range sessionTypes {
|
||||||
|
|
@ -92,9 +115,47 @@ func NewMailer(c *config.UserAPI) (Mailer, error) {
|
||||||
}
|
}
|
||||||
templates[t] = template
|
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{
|
return &SmtpMailer{
|
||||||
conf: c.Email,
|
conf: c.Email,
|
||||||
templates: templates,
|
templates: templates,
|
||||||
|
auth: auth,
|
||||||
|
cl: cl,
|
||||||
|
sendMutex: sync.Mutex{},
|
||||||
}, nil
|
}, 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) {
|
func mustValidateSesson(is *is.I, i *internal.UserInternalAPI, secret, token string, sid int64) {
|
||||||
|
res := api.ValidateSessionResponse{}
|
||||||
err := i.ValidateSession(ctx, &api.ValidateSessionRequest{
|
err := i.ValidateSession(ctx, &api.ValidateSessionRequest{
|
||||||
SessionOwnership: api.SessionOwnership{
|
SessionOwnership: api.SessionOwnership{
|
||||||
Sid: sid,
|
Sid: sid,
|
||||||
|
|
@ -259,7 +260,8 @@ func mustValidateSesson(is *is.I, i *internal.UserInternalAPI, secret, token str
|
||||||
},
|
},
|
||||||
Token: token,
|
Token: token,
|
||||||
},
|
},
|
||||||
struct{}{},
|
&res,
|
||||||
)
|
)
|
||||||
is.NoErr(err)
|
is.NoErr(err)
|
||||||
|
is.Equal(res.NextLink, testReq.NextLink)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue