diff --git a/src/service/ep_reset_password.go b/src/service/ep_reset_password.go index cb58894..babdea0 100644 --- a/src/service/ep_reset_password.go +++ b/src/service/ep_reset_password.go @@ -1,10 +1,12 @@ package service import ( + "apiboy/backend/src/errors" "context" "strings" + "time" - "apiboy/backend/src/errors" + "encoding/base64" "github.com/go-kit/kit/endpoint" "github.com/google/uuid" @@ -31,11 +33,14 @@ func (s *Service) ResetPassword(ctx context.Context, input *ResetPasswordInput) if user == nil { return nil, errors.Unauthorized{Msg: "Invalid user"} } + timeNow := time.Now().UTC() + + decode := user.ID + "|" + timeNow.Format(time.UnixDate) + "|" + uuid.New().String() - user.TempCode = uuid.New().String() + user.TempCode = base64.StdEncoding.EncodeToString([]byte(decode)) if err = s.Store.UpdateUser(ctx, user.ID, user); err != nil { - return nil, errors.InternalServer{Msg: "Could not generate temp password", Err: err} + return nil, errors.InternalServer{Msg: "Could not generate temp code", Err: err} } return &ResetPasswordOutput{}, nil diff --git a/src/service/ep_set_new_password.go b/src/service/ep_set_new_password.go new file mode 100644 index 0000000..82beda4 --- /dev/null +++ b/src/service/ep_set_new_password.go @@ -0,0 +1,96 @@ +package service + +import ( + "apiboy/backend/src/authutils" + "apiboy/backend/src/errors" + "context" + "encoding/base64" + "strings" + "time" + + "github.com/go-kit/kit/endpoint" +) + +// SetNewPasswordInput is the input of the endpoint +type SetNewPasswordInput struct { + Password string `json:"password" validate:"omitempty,min=6"` + TempCode string `json:"temp_code" validate:"required"` +} + +// SetNewPasswordOutput is the output of the endpoint +type SetNewPasswordOutput struct{} + +// SetNewPassword implements the business logic for the endpoint +func (s *Service) SetNewPassword(ctx context.Context, input *SetNewPasswordInput) (*SetNewPasswordOutput, error) { + password := strings.TrimSpace(input.Password) + tempCode := strings.TrimSpace(input.TempCode) + + decode, err := base64.StdEncoding.DecodeString(tempCode) + if err != nil { + return nil, errors.InternalServer{Msg: "Could not format temp code", Err: err} + } + + elements := strings.Split(string(decode), "|") + if len(elements) != 3 { + return nil, errors.Unauthorized{Msg: "Invalid code"} + } + + strDateTimeCode := elements[1] + dateTimeCode, err := time.Parse(time.UnixDate, strDateTimeCode) + if err != nil { // Always check errors even if they should not happen. + return nil, errors.InternalServer{Msg: "Could not format date time", Err: err} + } + + timeNow := time.Now().UTC() + hrs := timeNow.Sub(dateTimeCode) + + if hrs.Hours() > 24 { + return nil, errors.Unauthorized{Msg: "Invalid code"} + } + + userID := elements[0] + // get user + user, err := s.Store.GetUserByID(ctx, userID) + if err != nil { + return nil, errors.InternalServer{Msg: "Could not get user", Err: err} + } else if user == nil { + return nil, errors.NotFound{Obj: "User"} + } + + if user.TempCode != tempCode { + return nil, errors.Unauthorized{Msg: "Invalid code"} + } + + // hash new password + hashedPassword, err := authutils.HashPassword(password) + if err != nil { + return nil, errors.InternalServer{Msg: "Could not hash password", Err: err} + } + + user.Password = hashedPassword + user.TempCode = "" + + if err = s.Store.UpdateUser(ctx, user.ID, user); err != nil { + return nil, errors.InternalServer{Msg: "Could not update user", Err: err} + } + + return &SetNewPasswordOutput{}, nil +} + +// MakeSetNewPasswordEndpoint creates the endpoint +func MakeSetNewPasswordEndpoint(s *Service, m ...endpoint.Middleware) endpoint.Endpoint { + e := func(ctx context.Context, request interface{}) (response interface{}, err error) { + input, ok := request.(*SetNewPasswordInput) + if !ok { + return nil, errors.BadRequest{} + } + + return s.SetNewPassword(ctx, input) + } + + for _, mw := range m { + e = mw(e) + } + + return e +} diff --git a/src/service/http_endpoints.go b/src/service/http_endpoints.go index efbc2ff..3ec8849 100644 --- a/src/service/http_endpoints.go +++ b/src/service/http_endpoints.go @@ -11,6 +11,7 @@ type HTTPEndpoints struct { LogoutEndpoint endpoint.Endpoint SignupEndpoint endpoint.Endpoint ResetPasswordEndpoint endpoint.Endpoint + SetNewPasswordEndpoint endpoint.Endpoint UpdateUserEndpoint endpoint.Endpoint DeleteUserEndpoint endpoint.Endpoint CreateProjectEndpoint endpoint.Endpoint @@ -46,6 +47,7 @@ func MakeHTTPEndpoints(s *Service) HTTPEndpoints { LogoutEndpoint: MakeLogoutEndpoint(s, vm, am), SignupEndpoint: MakeSignupEndpoint(s, vm), ResetPasswordEndpoint: MakeResetPasswordEndpoint(s, vm), + SetNewPasswordEndpoint: MakeSetNewPasswordEndpoint(s, vm), UpdateUserEndpoint: MakeUpdateUserEndpoint(s, vm, am), DeleteUserEndpoint: MakeDeleteUserEndpoint(s, vm, am), CreateProjectEndpoint: MakeCreateProjectEndpoint(s, vm, am), diff --git a/src/service/http_handler.go b/src/service/http_handler.go index 4ea53dd..3284667 100644 --- a/src/service/http_handler.go +++ b/src/service/http_handler.go @@ -65,6 +65,13 @@ func MakeHTTPHandler(ctx context.Context, log *logger.Logger, e HTTPEndpoints) h defaultOptions..., )).Name("ResetPassword") + r.Methods("POST").Path("/auth/set_new_password").Handler(kithttp.NewServer( + e.SetNewPasswordEndpoint, + httputils.DecodeRPCRequest(&SetNewPasswordInput{}), + httputils.ResponseEncoder(log), + defaultOptions..., + )).Name("SetNewPassword") + r.Methods("POST").Path("/users/update").Handler(kithttp.NewServer( e.UpdateUserEndpoint, httputils.DecodeRPCRequest(&UpdateUserInput{}),