فهرست منبع

finish up cleaning handlers

Alexander Belanger 4 سال پیش
والد
کامیت
63309b524c

+ 9 - 0
api/server/handlers/handler.go

@@ -22,6 +22,7 @@ type PorterHandlerWriter interface {
 type PorterHandlerReader interface {
 	PorterHandler
 	DecodeAndValidate(w http.ResponseWriter, r *http.Request, v interface{}) bool
+	DecodeAndValidateNoWrite(r *http.Request, v interface{}) error
 }
 
 type PorterHandlerReadWriter interface {
@@ -62,3 +63,11 @@ func (d *DefaultPorterHandler) WriteResult(w http.ResponseWriter, v interface{})
 func (d *DefaultPorterHandler) DecodeAndValidate(w http.ResponseWriter, r *http.Request, v interface{}) bool {
 	return d.decoderValidator.DecodeAndValidate(w, r, v)
 }
+
+func (d *DefaultPorterHandler) DecodeAndValidateNoWrite(r *http.Request, v interface{}) error {
+	return d.decoderValidator.DecodeAndValidateNoWrite(r, v)
+}
+
+func IgnoreAPIError(w http.ResponseWriter, err apierrors.RequestError) {
+	return
+}

+ 25 - 51
api/server/handlers/user/email_verify.go

@@ -5,32 +5,34 @@ import (
 	"net/http"
 	"net/url"
 
+	"github.com/porter-dev/porter/api/server/handlers"
 	"github.com/porter-dev/porter/api/server/shared"
 	"github.com/porter-dev/porter/api/server/shared/apierrors"
 	"github.com/porter-dev/porter/api/types"
-	"github.com/porter-dev/porter/internal/forms"
 	"github.com/porter-dev/porter/internal/models"
 	"github.com/porter-dev/porter/internal/notifier"
-	"golang.org/x/crypto/bcrypt"
 )
 
 type VerifyEmailInitiateHandler struct {
-	config *shared.Config
+	handlers.PorterHandler
 }
 
 func NewVerifyEmailInitiateHandler(
 	config *shared.Config,
 ) *VerifyEmailInitiateHandler {
-	return &VerifyEmailInitiateHandler{config}
+	return &VerifyEmailInitiateHandler{
+		PorterHandler: handlers.NewDefaultPorterHandler(config, nil, nil),
+	}
 }
 
 func (v *VerifyEmailInitiateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	user, _ := r.Context().Value(types.UserScope).(*models.User)
 
-	pwReset, rawToken, err := CreateTokenForEmail(v.config, user.Email)
+	pwReset, rawToken, err := CreatePWResetTokenForEmail(v.Repo().PWResetToken(), v.HandleAPIError, w, &types.InitiateResetUserPasswordRequest{
+		Email: user.Email,
+	})
 
 	if err != nil {
-		apierrors.HandleAPIError(w, v.config.Logger, apierrors.NewErrInternal(err))
 		return
 	}
 
@@ -39,29 +41,30 @@ func (v *VerifyEmailInitiateHandler) ServeHTTP(w http.ResponseWriter, r *http.Re
 		"token_id": []string{fmt.Sprintf("%d", pwReset.ID)},
 	}
 
-	err = v.config.UserNotifier.SendEmailVerification(
+	err = v.Config().UserNotifier.SendEmailVerification(
 		&notifier.SendEmailVerificationOpts{
 			Email: user.Email,
-			URL:   fmt.Sprintf("%s/api/email/verify/finalize?%s", v.config.ServerConf.ServerURL, queryVals.Encode()),
+			URL:   fmt.Sprintf("%s/api/email/verify/finalize?%s", v.Config().ServerConf.ServerURL, queryVals.Encode()),
 		},
 	)
 
 	if err != nil {
-		apierrors.HandleAPIError(w, v.config.Logger, apierrors.NewErrInternal(err))
+		v.HandleAPIError(w, apierrors.NewErrInternal(err))
 		return
 	}
 }
 
 type VerifyEmailFinalizeHandler struct {
-	config           *shared.Config
-	decoderValidator shared.RequestDecoderValidator
+	handlers.PorterHandlerReader
 }
 
 func NewVerifyEmailFinalizeHandler(
 	config *shared.Config,
 	decoderValidator shared.RequestDecoderValidator,
 ) *VerifyEmailFinalizeHandler {
-	return &VerifyEmailFinalizeHandler{config, decoderValidator}
+	return &VerifyEmailFinalizeHandler{
+		PorterHandlerReader: handlers.NewDefaultPorterHandler(config, decoderValidator, nil),
+	}
 }
 
 func (v *VerifyEmailFinalizeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
@@ -69,34 +72,27 @@ func (v *VerifyEmailFinalizeHandler) ServeHTTP(w http.ResponseWriter, r *http.Re
 
 	request := &types.VerifyEmailFinalizeRequest{}
 
-	if err := v.decoderValidator.DecodeAndValidateNoWrite(r, request); err != nil {
+	if err := v.DecodeAndValidateNoWrite(r, request); err != nil {
 		http.Redirect(w, r, "/dashboard?error="+url.QueryEscape(err.Error()), 302)
 		return
 	}
 
-	// verify the token is valid
-	token, err := v.config.Repo.PWResetToken().ReadPWResetToken(request.TokenID)
+	token, err := VerifyToken(
+		v.Repo().PWResetToken(),
+		handlers.IgnoreAPIError,
+		w,
+		&request.VerifyTokenFinalizeRequest,
+		user.Email,
+	)
 
 	if err != nil {
 		http.Redirect(w, r, "/dashboard?error="+url.QueryEscape("Email verification error: valid token required"), 302)
 		return
 	}
 
-	// make sure the token is still valid and has not expired
-	if !token.IsValid || token.IsExpired() {
-		http.Redirect(w, r, "/dashboard?error="+url.QueryEscape("Email verification error: valid token required"), 302)
-		return
-	}
-
-	// make sure the token is correct
-	if err := bcrypt.CompareHashAndPassword([]byte(token.Token), []byte(request.Token)); err != nil {
-		http.Redirect(w, r, "/dashboard?error="+url.QueryEscape("Email verification error: valid token required"), 302)
-		return
-	}
-
 	user.EmailVerified = true
 
-	user, err = v.config.Repo.User().UpdateUser(user)
+	user, err = v.Repo().User().UpdateUser(user)
 
 	if err != nil {
 		http.Redirect(w, r, "/dashboard?error="+url.QueryEscape("Could not verify email address"), 302)
@@ -106,7 +102,7 @@ func (v *VerifyEmailFinalizeHandler) ServeHTTP(w http.ResponseWriter, r *http.Re
 	// invalidate the token
 	token.IsValid = false
 
-	_, err = v.config.Repo.PWResetToken().UpdatePWResetToken(token)
+	_, err = v.Repo().PWResetToken().UpdatePWResetToken(token)
 
 	if err != nil {
 		http.Redirect(w, r, "/dashboard?error="+url.QueryEscape("Could not verify email address"), 302)
@@ -116,25 +112,3 @@ func (v *VerifyEmailFinalizeHandler) ServeHTTP(w http.ResponseWriter, r *http.Re
 	http.Redirect(w, r, "/dashboard", 302)
 	return
 }
-
-func CreateTokenForEmail(config *shared.Config, email string) (*models.PWResetToken, string, error) {
-	form := &forms.InitiateResetUserPasswordForm{
-		Email: email,
-	}
-
-	// convert the form to a pw reset token model
-	pwReset, rawToken, err := form.ToPWResetToken()
-
-	if err != nil {
-		return nil, "", err
-	}
-
-	// handle write to the database
-	pwReset, err = config.Repo.PWResetToken().CreatePWResetToken(pwReset)
-
-	if err != nil {
-		return nil, "", err
-	}
-
-	return pwReset, rawToken, nil
-}

+ 9 - 1
api/server/handlers/user/email_verify_test.go

@@ -5,6 +5,7 @@ import (
 	"net/url"
 	"testing"
 
+	"github.com/porter-dev/porter/api/server/handlers"
 	"github.com/porter-dev/porter/api/server/handlers/user"
 	"github.com/porter-dev/porter/api/server/shared"
 	"github.com/porter-dev/porter/api/server/shared/apitest"
@@ -60,7 +61,14 @@ func TestEmailVerifyFinalizeSuccessful(t *testing.T) {
 	authUser := apitest.CreateTestUser(t, config, false)
 
 	// create a token in the DB to use for testing
-	pwReset, rawToken, err := user.CreateTokenForEmail(config, authUser.Email)
+	pwReset, rawToken, err := user.CreatePWResetTokenForEmail(
+		config.Repo.PWResetToken(),
+		handlers.IgnoreAPIError,
+		nil,
+		&types.InitiateResetUserPasswordRequest{
+			Email: authUser.Email,
+		},
+	)
 
 	if err != nil {
 		t.Fatal(err)

+ 14 - 20
api/server/handlers/user/login.go

@@ -6,6 +6,7 @@ import (
 	"net/http"
 
 	"github.com/porter-dev/porter/api/server/authn"
+	"github.com/porter-dev/porter/api/server/handlers"
 	"github.com/porter-dev/porter/api/server/shared"
 	"github.com/porter-dev/porter/api/server/shared/apierrors"
 	"github.com/porter-dev/porter/api/types"
@@ -14,9 +15,7 @@ import (
 )
 
 type UserLoginHandler struct {
-	config           *shared.Config
-	decoderValidator shared.RequestDecoderValidator
-	writer           shared.ResultWriter
+	handlers.PorterHandlerReadWriter
 }
 
 func NewUserLoginHandler(
@@ -24,50 +23,45 @@ func NewUserLoginHandler(
 	decoderValidator shared.RequestDecoderValidator,
 	writer shared.ResultWriter,
 ) *UserLoginHandler {
-	return &UserLoginHandler{config, decoderValidator, writer}
+	return &UserLoginHandler{
+		PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
+	}
 }
 
 func (u *UserLoginHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	request := &types.LoginUserRequest{}
 
-	ok := u.decoderValidator.DecodeAndValidate(w, r, request)
+	ok := u.DecodeAndValidate(w, r, request)
 
 	if !ok {
 		return
 	}
 
 	// check that passwords match
-	storedUser, err := u.config.Repo.User().ReadUserByEmail(request.Email)
+	storedUser, err := u.Repo().User().ReadUserByEmail(request.Email)
 
 	// case on user not existing, send forbidden error if not exist
 	if err != nil {
 		if targetErr := gorm.ErrRecordNotFound; errors.Is(err, targetErr) {
-			apierrors.HandleAPIError(w, u.config.Logger, apierrors.NewErrForbidden(err))
+			u.HandleAPIError(w, apierrors.NewErrForbidden(err))
 			return
 		} else {
-			apierrors.HandleAPIError(w, u.config.Logger, apierrors.NewErrInternal(err))
+			u.HandleAPIError(w, apierrors.NewErrInternal(err))
 			return
 		}
 	}
 
 	if err := bcrypt.CompareHashAndPassword([]byte(storedUser.Password), []byte(request.Password)); err != nil {
-		apierrors.HandleAPIError(
-			w,
-			u.config.Logger,
-			apierrors.NewErrPassThroughToClient(
-				fmt.Errorf("incorrect password"),
-				http.StatusUnauthorized,
-			),
-		)
-
+		reqErr := apierrors.NewErrPassThroughToClient(fmt.Errorf("incorrect password"), http.StatusUnauthorized)
+		u.HandleAPIError(w, reqErr)
 		return
 	}
 
 	// save the user as authenticated in the session
-	if err := authn.SaveUserAuthenticated(w, r, u.config, storedUser); err != nil {
-		apierrors.HandleAPIError(w, u.config.Logger, apierrors.NewErrInternal(err))
+	if err := authn.SaveUserAuthenticated(w, r, u.Config(), storedUser); err != nil {
+		u.HandleAPIError(w, apierrors.NewErrInternal(err))
 		return
 	}
 
-	u.writer.WriteResult(w, storedUser.ToUserType())
+	u.WriteResult(w, storedUser.ToUserType())
 }

+ 7 - 4
api/server/handlers/user/logout.go

@@ -4,23 +4,26 @@ import (
 	"net/http"
 
 	"github.com/porter-dev/porter/api/server/authn"
+	"github.com/porter-dev/porter/api/server/handlers"
 	"github.com/porter-dev/porter/api/server/shared"
 	"github.com/porter-dev/porter/api/server/shared/apierrors"
 )
 
 type UserLogoutHandler struct {
-	config *shared.Config
+	handlers.PorterHandler
 }
 
 func NewUserLogoutHandler(
 	config *shared.Config,
 ) *UserLogoutHandler {
-	return &UserLogoutHandler{config}
+	return &UserLogoutHandler{
+		PorterHandler: handlers.NewDefaultPorterHandler(config, nil, nil),
+	}
 }
 
 func (u *UserLogoutHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
-	if err := authn.SaveUserUnauthenticated(w, r, u.config); err != nil {
-		apierrors.HandleAPIError(w, u.config.Logger, apierrors.NewErrInternal(err))
+	if err := authn.SaveUserUnauthenticated(w, r, u.Config()); err != nil {
+		u.HandleAPIError(w, apierrors.NewErrInternal(err))
 	}
 
 	return

+ 73 - 50
api/server/handlers/user/pw_reset.go

@@ -70,35 +70,9 @@ func (c *UserPasswordInitiateResetHandler) ServeHTTP(w http.ResponseWriter, r *h
 		return
 	}
 
-	// convert the form to a project model
-	expiry := time.Now().Add(30 * time.Minute)
-
-	rawToken, err := random.StringWithCharset(32, "")
+	pwReset, rawToken, err := CreatePWResetTokenForEmail(c.Repo().PWResetToken(), c.HandleAPIError, w, request)
 
 	if err != nil {
-		c.HandleAPIError(w, apierrors.NewErrInternal(err))
-		return
-	}
-
-	hashedToken, err := bcrypt.GenerateFromPassword([]byte(rawToken), 8)
-
-	if err != nil {
-		c.HandleAPIError(w, apierrors.NewErrInternal(err))
-		return
-	}
-
-	pwReset := &models.PWResetToken{
-		Email:   request.Email,
-		IsValid: true,
-		Expiry:  &expiry,
-		Token:   string(hashedToken),
-	}
-
-	// handle write to the database
-	pwReset, err = c.Repo().PWResetToken().CreatePWResetToken(pwReset)
-
-	if err != nil {
-		c.HandleAPIError(w, apierrors.NewErrInternal(err))
 		return
 	}
 
@@ -147,13 +121,13 @@ func (c *UserPasswordVerifyResetHandler) ServeHTTP(w http.ResponseWriter, r *htt
 		return
 	}
 
-	ok, _ = VerifyPasswordResetToken(c.Repo().PWResetToken(), c.HandleAPIError, w, request)
-
-	if ok {
-		w.WriteHeader(http.StatusOK)
-	}
-
-	return
+	VerifyToken(
+		c.Repo().PWResetToken(),
+		c.HandleAPIError,
+		w,
+		&request.VerifyTokenFinalizeRequest,
+		request.Email,
+	)
 }
 
 type UserPasswordFinalizeResetHandler struct {
@@ -179,10 +153,16 @@ func (c *UserPasswordFinalizeResetHandler) ServeHTTP(w http.ResponseWriter, r *h
 		return
 	}
 
-	ok, token := VerifyPasswordResetToken(c.Repo().PWResetToken(), c.HandleAPIError, w, &request.VerifyResetUserPasswordRequest)
+	token, err := VerifyToken(
+		c.Repo().PWResetToken(),
+		c.HandleAPIError,
+		w,
+		&request.VerifyTokenFinalizeRequest,
+		request.Email,
+	)
 
-	if ok {
-		w.WriteHeader(http.StatusOK)
+	if err != nil {
+		return
 	}
 
 	// check that the email exists
@@ -229,48 +209,91 @@ func (c *UserPasswordFinalizeResetHandler) ServeHTTP(w http.ResponseWriter, r *h
 	return
 }
 
-func VerifyPasswordResetToken(
+func VerifyToken(
 	pwResetRepo repository.PWResetTokenRepository,
 	handleErr func(w http.ResponseWriter, apiErr apierrors.RequestError),
 	w http.ResponseWriter,
-	request *types.VerifyResetUserPasswordRequest,
-) (bool, *models.PWResetToken) {
+	request *types.VerifyTokenFinalizeRequest,
+	email string,
+) (*models.PWResetToken, error) {
 	token, err := pwResetRepo.ReadPWResetToken(request.TokenID)
 
 	if err != nil {
 		if err == gorm.ErrRecordNotFound {
-			err = fmt.Errorf("verify/finalize password reset failed: token does not exist")
+			err = fmt.Errorf("verify token failed: token does not exist")
 			handleErr(w, apierrors.NewErrForbidden(err))
+			return nil, err
 		} else {
 			handleErr(w, apierrors.NewErrInternal(err))
 		}
 
-		return false, nil
+		return nil, err
 	}
 
 	// make sure the token is still valid and has not expired
 	if !token.IsValid || token.IsExpired() {
-		err = fmt.Errorf("verify password reset failed: expired %t, valid %t", token.IsExpired(), token.IsValid)
+		err = fmt.Errorf("verify token failed: expired %t, valid %t", token.IsExpired(), token.IsValid)
 		handleErr(w, apierrors.NewErrForbidden(err))
 
-		return false, nil
+		return nil, err
 	}
 
 	// check that the email matches
-	if token.Email != request.Email {
-		err = fmt.Errorf("verify password reset failed: token email does not match request email")
+	if token.Email != email {
+		err = fmt.Errorf("verify token failed: token email does not match request email")
 		handleErr(w, apierrors.NewErrForbidden(err))
 
-		return false, nil
+		return nil, err
 	}
 
 	// make sure the token is correct
 	if err := bcrypt.CompareHashAndPassword([]byte(token.Token), []byte(request.Token)); err != nil {
-		err = fmt.Errorf("verify password reset failed: %s", err)
+		err = fmt.Errorf("verify token failed: %s", err)
 		handleErr(w, apierrors.NewErrForbidden(err))
 
-		return false, nil
+		return nil, err
+	}
+
+	return token, nil
+}
+
+func CreatePWResetTokenForEmail(
+	pwResetRepo repository.PWResetTokenRepository,
+	handleErr func(w http.ResponseWriter, apiErr apierrors.RequestError),
+	w http.ResponseWriter,
+	request *types.InitiateResetUserPasswordRequest,
+) (*models.PWResetToken, string, error) {
+	// convert the form to a project model
+	expiry := time.Now().Add(30 * time.Minute)
+
+	rawToken, err := random.StringWithCharset(32, "")
+
+	if err != nil {
+		handleErr(w, apierrors.NewErrInternal(err))
+		return nil, "", err
+	}
+
+	hashedToken, err := bcrypt.GenerateFromPassword([]byte(rawToken), 8)
+
+	if err != nil {
+		handleErr(w, apierrors.NewErrInternal(err))
+		return nil, "", err
+	}
+
+	pwReset := &models.PWResetToken{
+		Email:   request.Email,
+		IsValid: true,
+		Expiry:  &expiry,
+		Token:   string(hashedToken),
+	}
+
+	// handle write to the database
+	pwReset, err = pwResetRepo.CreatePWResetToken(pwReset)
+
+	if err != nil {
+		handleErr(w, apierrors.NewErrInternal(err))
+		return nil, "", err
 	}
 
-	return true, token
+	return pwReset, rawToken, nil
 }

+ 12 - 8
api/types/user.go

@@ -22,11 +22,6 @@ type LoginUserRequest struct {
 
 type LoginUserResponse User
 
-type VerifyEmailFinalizeRequest struct {
-	TokenID uint   `schema:"token_id" form:"required"`
-	Token   string `schema:"token" form:"required"`
-}
-
 type CLILoginUserRequest struct {
 	Redirect string `schema:"redirect" form:"required"`
 }
@@ -43,10 +38,19 @@ type InitiateResetUserPasswordRequest struct {
 	Email string `json:"email" form:"required"`
 }
 
+type VerifyTokenFinalizeRequest struct {
+	TokenID uint   `schema:"token_id" form:"required"`
+	Token   string `schema:"token" form:"required"`
+}
+
+type VerifyEmailFinalizeRequest struct {
+	VerifyTokenFinalizeRequest
+}
+
 type VerifyResetUserPasswordRequest struct {
-	Email   string `json:"email" form:"required,max=255,email"`
-	TokenID uint   `json:"token_id" form:"required"`
-	Token   string `json:"token" form:"required"`
+	VerifyTokenFinalizeRequest
+
+	Email string `json:"email" form:"required,max=255,email"`
 }
 
 type FinalizeResetUserPasswordRequest struct {