implement change password endpoint

Signed-off-by: MohitKS5 <mohitkumarsingh907@gmail.com>
This commit is contained in:
MohitKS5 2018-03-09 02:14:31 +05:30
parent 6b55972183
commit eb306f05a8
5 changed files with 212 additions and 4 deletions

View file

@ -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")

View file

@ -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))
}

View file

@ -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.

View file

@ -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,
}
}

View file

@ -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")