diff --git a/src/github.com/matrix-org/dendrite/clientapi/auth/auth.go b/src/github.com/matrix-org/dendrite/clientapi/auth/auth.go index 56ee31ecb..3a65232ba 100644 --- a/src/github.com/matrix-org/dendrite/clientapi/auth/auth.go +++ b/src/github.com/matrix-org/dendrite/clientapi/auth/auth.go @@ -47,7 +47,7 @@ type DeviceDatabase interface { // and returns the device it corresponds to. Returns resErr (an error response which can be // sent to the client) if the token is invalid or there was a problem querying the database. func VerifyAccessToken(req *http.Request, deviceDB DeviceDatabase) (device *authtypes.Device, resErr *util.JSONResponse) { - token, err := extractAccessToken(req) + token, err := ExtractAccessToken(req) if err != nil { resErr = &util.JSONResponse{ Code: 401, @@ -93,9 +93,9 @@ func GenerateDeviceID() (string, error) { return base64.RawURLEncoding.EncodeToString(b), nil } -// extractAccessToken from a request, or return an error detailing what went wrong. The +// ExtractAccessToken from a request, or return an error detailing what went wrong. The // error message MUST be human-readable and comprehensible to the client. -func extractAccessToken(req *http.Request) (string, error) { +func ExtractAccessToken(req *http.Request) (string, error) { // cf https://github.com/matrix-org/synapse/blob/v0.19.2/synapse/api/auth.py#L631 authBearer := req.Header.Get("Authorization") queryToken := req.URL.Query().Get("access_token") diff --git a/src/github.com/matrix-org/dendrite/clientapi/auth/storage/accounts/accounts_table.go b/src/github.com/matrix-org/dendrite/clientapi/auth/storage/accounts/accounts_table.go index a29d616e9..5c380220b 100644 --- a/src/github.com/matrix-org/dendrite/clientapi/auth/storage/accounts/accounts_table.go +++ b/src/github.com/matrix-org/dendrite/clientapi/auth/storage/accounts/accounts_table.go @@ -49,12 +49,14 @@ const selectAccountByLocalpartSQL = "" + const selectPasswordHashSQL = "" + "SELECT password_hash FROM account_accounts WHERE localpart = $1" -// TODO: Update password +const updatePasswordHashSQL = "" + + "UPDATE account_accounts SET password_hash = $1 WHERE localpart = $2" type accountsStatements struct { insertAccountStmt *sql.Stmt selectAccountByLocalpartStmt *sql.Stmt selectPasswordHashStmt *sql.Stmt + updatePasswordHashStmt *sql.Stmt serverName gomatrixserverlib.ServerName } @@ -72,6 +74,9 @@ func (s *accountsStatements) prepare(db *sql.DB, server gomatrixserverlib.Server if s.selectPasswordHashStmt, err = db.Prepare(selectPasswordHashSQL); err != nil { return } + if s.updatePasswordHashStmt, err = db.Prepare(updatePasswordHashSQL); err != nil { + return + } s.serverName = server return } @@ -123,6 +128,14 @@ func (s *accountsStatements) selectAccountByLocalpart( return &acc, err } +func (s *accountsStatements) updatePasswordHash( + ctx context.Context, hash, localpart string, +) error { + stmt := s.updatePasswordHashStmt + _, err := stmt.ExecContext(ctx, hash, localpart) + return err +} + func makeUserID(localpart string, server gomatrixserverlib.ServerName) string { return fmt.Sprintf("@%s:%s", localpart, string(server)) } diff --git a/src/github.com/matrix-org/dendrite/clientapi/auth/storage/accounts/storage.go b/src/github.com/matrix-org/dendrite/clientapi/auth/storage/accounts/storage.go index 571482739..7b9ecf3a3 100644 --- a/src/github.com/matrix-org/dendrite/clientapi/auth/storage/accounts/storage.go +++ b/src/github.com/matrix-org/dendrite/clientapi/auth/storage/accounts/storage.go @@ -142,6 +142,20 @@ func (d *Database) CreateAccount( return d.accounts.insertAccount(ctx, localpart, hash, appserviceID) } +// UpdatePassword Implements /password +func (d *Database) UpdatePassword( + ctx context.Context, plaintextPassword, localpart string, +) (err error) { + // Generate a password hash + var hash string + hash, err = hashPassword(plaintextPassword) + if err != nil { + return + } + err = d.accounts.updatePasswordHash(ctx, hash, localpart) + return +} + // SaveMembership saves the user matching a given localpart as a member of a given // room. It also stores the ID of the membership event and a flag on whether the user // is still in the room. diff --git a/src/github.com/matrix-org/dendrite/clientapi/routing/password.go b/src/github.com/matrix-org/dendrite/clientapi/routing/password.go new file mode 100644 index 000000000..9c78e4cdf --- /dev/null +++ b/src/github.com/matrix-org/dendrite/clientapi/routing/password.go @@ -0,0 +1,177 @@ +// Copyright 2017 Vector Creations Ltd +// Copyright 2017 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package routing + +import ( + "context" + "github.com/matrix-org/dendrite/common/config" + "net/http" + "strings" + + "github.com/matrix-org/dendrite/clientapi/auth" + "github.com/matrix-org/dendrite/clientapi/auth/authtypes" + "github.com/matrix-org/dendrite/clientapi/auth/storage/accounts" + "github.com/matrix-org/dendrite/clientapi/httputil" + "github.com/matrix-org/dendrite/clientapi/jsonerror" + "github.com/matrix-org/util" + log "github.com/sirupsen/logrus" +) + +// registerRequest represents the submitted registration request. +// It can be broken down into 2 sections: the auth dictionary and registration parameters. +// Registration parameters vary depending on the request, and will need to remembered across +// sessions. If no parameters are supplied, the server should use the parameters previously +// remembered. If ANY parameters are supplied, the server should REPLACE all knowledge of +// previous parameters with the ones supplied. This mean you cannot "build up" request params. + +// a generic flowRequest type for any auth call to UIAA handler to be included in UIAA.go Todo referenced in (#413) +type userInteractiveFlowRequest struct { + // parameters + Password string `json:"password"` + Username string `json:"username"` + Admin bool `json:"admin"` + + // user-interactive auth params + Auth authDict `json:"auth"` + + // Application Services place Type in the root of their registration + // request, whereas clients place it in the authDict struct. + Type authtypes.LoginType `json:"type"` +} + +type changePasswordRequest struct { + // user-interactive auth params (necessary to include to make call to UIAA handler) + userInteractiveFlowRequest + + // password change parameters + NewPassword string `json:"new_password"` +} + +// ChangePassword implements a /password request. +func ChangePassword( + req *http.Request, + accountDB *accounts.Database, + cfg *config.Dendrite, +) util.JSONResponse { + + // Todo username and password aren't in request as per spec. Use whoami referenced in #411 + var r changePasswordRequest + resErr := httputil.UnmarshalJSONRequest(req, &r) + if resErr != nil { + return *resErr + } + + // Retrieve or generate the sessionID + sessionID := r.Auth.Session + if sessionID == "" { + // Generate a new, random session ID + sessionID = util.RandomString(sessionIDLength) + } + + // If no auth type is specified by the client, send back the list of available flows + if r.Auth.Type == "" { + return util.JSONResponse{ + Code: 401, + // Todo replace by password.Flows when available + JSON: newUserInteractiveResponse(sessionID, + cfg.Derived.Registration.Flows, cfg.Derived.Registration.Params), + } + } + + // Squash username to all lowercase letters + r.Username = strings.ToLower(r.Username) + + // validate new password + if resErr = validatePassword(r.NewPassword); resErr != nil { + return *resErr + } + + logger := util.GetLogger(req.Context()) + logger.WithFields(log.Fields{ + "username": r.Username, + "auth.type": r.Auth.Type, + "session_id": r.Auth.Session, + }).Info("Processing password request") + + // Todo go through User Interactive Auth uncomment references in #413 and + // authflows defined in cfg.derieved like that for registration + + //jsonRes := HandleUserInteractiveFlow(req, r.userInteractiveFlowRequest, sessionID, cfg, + // userInteractiveResponse{ + // // passing the list of allowed Flows and Params + // cfg.Derived.Password.Flows, + // nil, + // cfg.Derived.Password.Params, + // "", + // }) + //if jsonRes.Code == 200 { + // // cast JSON to userInteractiveHandlerResponse + // res := jsonRes.JSON.(userInteractiveHandlerResponse) + // + // return completeUpdation( + // req.Context(), + // accountDB, + // r.Username, + // r.Password, + // res.AppserviceID, + // r.InitialDisplayName) + //} + //return jsonRes + + //// Todo appservice ID is to be returned by UIAA handler use that. + token, err := auth.ExtractAccessToken(req) + if err != nil { + return httputil.LogThenError(req, err) + } + + appServiceID := "" + for _, as := range cfg.Derived.ApplicationServices { + if as.ASToken == token { + appServiceID = as.ID + break + } + } + // return the final updation function after auth flow + return completeUpdation(req.Context(), accountDB, r.Username, r.NewPassword, appServiceID) +} + +func completeUpdation( + ctx context.Context, + accountDB *accounts.Database, + username, password, appserviceID string, +) util.JSONResponse { + + // Blank passwords are only allowed by registered application services + if password == "" && appserviceID == "" { + return util.JSONResponse{ + Code: 400, + JSON: jsonerror.BadJSON("missing password"), + } + } + + err := accountDB.UpdatePassword(ctx, password, username) + + if err != nil { + return util.JSONResponse{ + Code: 500, + JSON: jsonerror.Unknown("failed to update password: " + err.Error()), + } + } + + return util.JSONResponse{ + Code: 200, + } +} diff --git a/src/github.com/matrix-org/dendrite/clientapi/routing/routing.go b/src/github.com/matrix-org/dendrite/clientapi/routing/routing.go index a7999bf49..bcd15fcd8 100644 --- a/src/github.com/matrix-org/dendrite/clientapi/routing/routing.go +++ b/src/github.com/matrix-org/dendrite/clientapi/routing/routing.go @@ -121,6 +121,10 @@ func Setup( }), ).Methods("PUT", "OPTIONS") + r0mux.Handle("/password", common.MakeExternalAPI("password", func(req *http.Request) util.JSONResponse { + return ChangePassword(req, accountDB, &cfg) + })).Methods("POST", "OPTIONS") + r0mux.Handle("/register", common.MakeExternalAPI("register", func(req *http.Request) util.JSONResponse { return Register(req, accountDB, deviceDB, &cfg) })).Methods("POST", "OPTIONS")