<commit_before>package handler

import (
	"encoding/json"
	"net/http"

	"github.com/skygeario/skygear-server/pkg/auth"
	"github.com/skygeario/skygear-server/pkg/core/auth/authz"
	"github.com/skygeario/skygear-server/pkg/core/auth/authz/policy"
	"github.com/skygeario/skygear-server/pkg/core/db"
	"github.com/skygeario/skygear-server/pkg/core/handler"
	"github.com/skygeario/skygear-server/pkg/core/inject"
	"github.com/skygeario/skygear-server/pkg/core/server"
)

func AttachChangePasswordHandler(
	server *server.Server,
	authDependency auth.DependencyMap,
) *server.Server {
	server.Handle("/change_password", &ChangePasswordHandlerFactory{
		authDependency,
	}).Methods("OPTIONS", "POST")
	return server
}

// ChangePasswordHandlerFactory creates ChangePasswordHandler
type ChangePasswordHandlerFactory struct {
	Dependency auth.DependencyMap
}

// NewHandler creates new handler
func (f ChangePasswordHandlerFactory) NewHandler(request *http.Request) http.Handler {
	h := &ChangePasswordHandler{}
	inject.DefaultInject(h, f.Dependency, request)
	return handler.APIHandlerToHandler(h, h.TxContext)
}

// ProvideAuthzPolicy provides authorization policy of handler
func (f ChangePasswordHandlerFactory) ProvideAuthzPolicy() authz.Policy {
	return policy.AllOf(
		authz.PolicyFunc(policy.DenyNoAccessKey),
		authz.PolicyFunc(policy.RequireAuthenticated),
		authz.PolicyFunc(policy.DenyDisabledUser),
	)
}

type ChangePasswordRequestPayload struct {
	Password    string `json:"password"`
	OldPassword string `json:"old_password"`
}

func (p ChangePasswordRequestPayload) Validate() error {
	return nil
}

// ChangePasswordHandler handles change password request
type ChangePasswordHandler struct {
	TxContext db.TxContext `dependency:"TxContext"`
}

func (h ChangePasswordHandler) WithTx() bool {
	return true
}

// DecodeRequest decode the request payload
func (h ChangePasswordHandler) DecodeRequest(request *http.Request) (handler.RequestPayload, error) {
	payload := ChangePasswordRequestPayload{}
	err := json.NewDecoder(request.Body).Decode(&payload)
	return payload, err
}

// Handle function handles the request
func (h ChangePasswordHandler) Handle(req interface{}) (resp interface{}, err error) {
	return
}<commit_msg>Add change password implementation<commit_after>package handler

import (
	"encoding/json"
	"net/http"

	"github.com/skygeario/skygear-server/pkg/auth"
	"github.com/skygeario/skygear-server/pkg/auth/dependency"
	"github.com/skygeario/skygear-server/pkg/auth/dependency/provider/password"
	"github.com/skygeario/skygear-server/pkg/auth/dependency/userprofile"
	"github.com/skygeario/skygear-server/pkg/auth/response"
	"github.com/skygeario/skygear-server/pkg/core/audit"
	coreAuth "github.com/skygeario/skygear-server/pkg/core/auth"
	"github.com/skygeario/skygear-server/pkg/core/auth/authtoken"
	"github.com/skygeario/skygear-server/pkg/core/auth/authz"
	"github.com/skygeario/skygear-server/pkg/core/auth/authz/policy"
	"github.com/skygeario/skygear-server/pkg/core/db"
	"github.com/skygeario/skygear-server/pkg/core/handler"
	"github.com/skygeario/skygear-server/pkg/core/inject"
	"github.com/skygeario/skygear-server/pkg/core/server"
	"github.com/skygeario/skygear-server/pkg/server/skydb"
	"github.com/skygeario/skygear-server/pkg/server/skyerr"
)

func AttachChangePasswordHandler(
	server *server.Server,
	authDependency auth.DependencyMap,
) *server.Server {
	server.Handle("/change_password", &ChangePasswordHandlerFactory{
		authDependency,
	}).Methods("OPTIONS", "POST")
	return server
}

// ChangePasswordHandlerFactory creates ChangePasswordHandler
type ChangePasswordHandlerFactory struct {
	Dependency auth.DependencyMap
}

// NewHandler creates new handler
func (f ChangePasswordHandlerFactory) NewHandler(request *http.Request) http.Handler {
	h := &ChangePasswordHandler{}
	inject.DefaultInject(h, f.Dependency, request)
	return handler.APIHandlerToHandler(h, h.TxContext)
}

// ProvideAuthzPolicy provides authorization policy of handler
func (f ChangePasswordHandlerFactory) ProvideAuthzPolicy() authz.Policy {
	return policy.AllOf(
		authz.PolicyFunc(policy.DenyNoAccessKey),
		authz.PolicyFunc(policy.RequireAuthenticated),
		authz.PolicyFunc(policy.DenyDisabledUser),
	)
}

type ChangePasswordRequestPayload struct {
	NewPassword string `json:"password"`
	OldPassword string `json:"old_password"`
}

func (p ChangePasswordRequestPayload) Validate() error {
	if p.OldPassword == "" {
		return skyerr.NewInvalidArgument("empty old password", []string{"old_password"})
	}
	if p.NewPassword == "" {
		return skyerr.NewInvalidArgument("empty password", []string{"password"})
	}
	return nil
}

// ChangePasswordHandler change the current user password
//
// ChangePasswordHandler receives old and new password:
//
// * old_password (string, required)
// * password (string, required)
//
// If user is not logged in, an 401 unauthorized will return.
//
//  Current implementation
//  curl -X POST -H "Content-Type: application/json" \
//    -d @- http://localhost:3000/change_password <<EOF
//  {
//      "old_password": "<PASSWORD>",
//      "password": "<PASSWORD>"
//  }
//  EOF
// Response
// return auth response with new access token
type ChangePasswordHandler struct {
	AuditTrail           audit.Trail                `dependency:"AuditTrail"`
	AuthContext          coreAuth.ContextGetter     `dependency:"AuthContextGetter"`
	PasswordAuthProvider password.Provider          `dependency:"PasswordAuthProvider"`
	PasswordChecker      dependency.PasswordChecker `dependency:"PasswordChecker"`
	TokenStore           authtoken.Store            `dependency:"TokenStore"`
	TxContext            db.TxContext               `dependency:"TxContext"`
	UserProfileStore     userprofile.Store          `dependency:"UserProfileStore"`
}

func (h ChangePasswordHandler) WithTx() bool {
	return true
}

// DecodeRequest decode the request payload
func (h ChangePasswordHandler) DecodeRequest(request *http.Request) (handler.RequestPayload, error) {
	payload := ChangePasswordRequestPayload{}
	err := json.NewDecoder(request.Body).Decode(&payload)
	return payload, err
}

func (h ChangePasswordHandler) Handle(req interface{}) (resp interface{}, err error) {
	payload := req.(ChangePasswordRequestPayload)
	authinfo := h.AuthContext.AuthInfo()

	if err = h.PasswordChecker.ValidatePassword(audit.ValidatePasswordPayload{
		PlainPassword: payload.NewPassword,
	}); err != nil {
		return
	}

	principals, err := h.PasswordAuthProvider.GetPrincipalsByUserID(authinfo.ID)
	if err != nil {
		if err == skydb.ErrUserNotFound {
			err = skyerr.NewError(skyerr.ResourceNotFound, "user not found")
			return
		}
		return
	}

	for _, p := range principals {
		if !p.IsSamePassword(payload.OldPassword) {
			err = skyerr.NewError(skyerr.InvalidCredentials, "Incorrect old password")
			return
		}
		p.PlainPassword = payload.NewPassword
		err = h.PasswordAuthProvider.UpdatePrincipal(*p)
		if err != nil {
			return
		}
	}

	// generate access-token
	token, err := h.TokenStore.NewToken(authinfo.ID)
	if err != nil {
		panic(err)
	}

	if err = h.TokenStore.Put(&token); err != nil {
		panic(err)
	}

	// Get Profile
	var userProfile userprofile.UserProfile
	if userProfile, err = h.UserProfileStore.GetUserProfile(authinfo.ID, token.AccessToken); err != nil {
		// TODO:
		// return proper error
		err = skyerr.NewError(skyerr.UnexpectedError, "Unable to fetch user profile")
		return
	}

	resp = response.NewAuthResponse(*authinfo, userProfile, token.AccessToken)
	h.AuditTrail.Log(audit.Entry{
		AuthID: authinfo.ID,
		Event:  audit.EventChangePassword,
	})

	return
}
{'fraction_non_alphanumeric': 0.11736037934668071, 'fraction_numerical': 0.0009220231822971549, 'mean_word_length': 4.886046511627907, 'pattern_counts': {'":': 2, '<': 7, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1062948', 'n_tokens_mistral': 2414, 'n_tokens_neox': 2352, 'n_words': 455}