package clientapi import ( "net/http" "github.com/dariuszjaniak/warp/server/internal/httputil" "github.com/dariuszjaniak/warp/server/internal/userapi" ) // changePasswordRequest is the body of POST /account/password. type changePasswordRequest struct { NewPassword string `json:"new_password"` // LogoutDevices defaults to true, which is why it is a pointer: a // client that says nothing gets every other session logged out. LogoutDevices *bool `json:"logout_devices"` Auth *uiaRequest `json:"auth"` } // handleChangePassword implements POST /_matrix/client/v3/account/password. // // The current password is asked for again first, through the same // interactive authentication as deleting a device, and for the same // reason: a stolen access token must not be enough to lock the owner out. // The session the change is made from stays logged in; with // logout_devices, every other one is logged out. // // An application service acting as one of its users skips the // re-authentication: its users have no password to ask for, and the // service's token is already the strongest credential involved. func (c *ClientAPI) handleChangePassword(w http.ResponseWriter, r *http.Request, auth userapi.Authenticated) error { var req changePasswordRequest if err := httputil.DecodeJSON(w, r, &req); err != nil { return err } sessionID := "" if auth.AppService == "" { flows := []uiaFlow{{Stages: []string{stagePassword}}} var done bool var err error sessionID, done, err = c.beginOrAdvanceUIA(w, flows, req.Auth, func(_ string, stage *uiaRequest) (*httputil.MatrixError, error) { return c.passwordStage(r, auth, stage) }) if err != nil || !done { return err } } if req.NewPassword == "" { return httputil.MissingParam("new_password is required") } if len(req.NewPassword) > userapi.MaxPasswordLength { return httputil.NewError(http.StatusBadRequest, httputil.ErrCodeWeakPassword, "Password must be at most %d bytes", userapi.MaxPasswordLength) } logout := req.LogoutDevices == nil || *req.LogoutDevices if err := c.users.ChangePassword(r.Context(), auth.Localpart, req.NewPassword, logout, auth.DeviceID); err != nil { return err } c.uia.remove(sessionID) httputil.WriteJSON(w, http.StatusOK, struct{}{}) return nil } // deactivateRequest is the body of POST /account/deactivate. type deactivateRequest struct { // Erase asks for the account's profile to be forgotten as well. Erase bool `json:"erase"` Auth *uiaRequest `json:"auth"` } // deactivateResponse is its answer. Warp keeps no third-party identifiers // and talks to no identity server, so there was nothing to unbind. type deactivateResponse struct { IDServerUnbindResult string `json:"id_server_unbind_result"` } // handleDeactivate implements POST /_matrix/client/v3/account/deactivate: // the user closing their own account, behind a re-entered password. func (c *ClientAPI) handleDeactivate(w http.ResponseWriter, r *http.Request, auth userapi.Authenticated) error { var req deactivateRequest if err := httputil.DecodeJSON(w, r, &req); err != nil { return err } sessionID := "" if auth.AppService == "" { flows := []uiaFlow{{Stages: []string{stagePassword}}} var done bool var err error sessionID, done, err = c.beginOrAdvanceUIA(w, flows, req.Auth, func(_ string, stage *uiaRequest) (*httputil.MatrixError, error) { return c.passwordStage(r, auth, stage) }) if err != nil || !done { return err } } if err := c.users.DeactivateAccount(r.Context(), auth.Localpart, req.Erase); err != nil { return err } c.uia.remove(sessionID) httputil.WriteJSON(w, http.StatusOK, deactivateResponse{IDServerUnbindResult: "no-support"}) return nil }