From 895d9613b5397990c59b1b43616d159b4f31c16b Mon Sep 17 00:00:00 2001 From: kolaente Date: Sat, 18 Apr 2020 01:38:49 +0200 Subject: [PATCH] Add endpoint to disable totp auth --- pkg/routes/api/v1/user_totp.go | 46 ++++++++++++++++++++++++++++++++++ pkg/routes/routes.go | 1 + pkg/user/totp.go | 6 +++++ pkg/user/user.go | 18 ++++++++++--- 4 files changed, 67 insertions(+), 4 deletions(-) diff --git a/pkg/routes/api/v1/user_totp.go b/pkg/routes/api/v1/user_totp.go index 45431339097..935f0f46dea 100644 --- a/pkg/routes/api/v1/user_totp.go +++ b/pkg/routes/api/v1/user_totp.go @@ -93,6 +93,52 @@ func UserTOTPEnable(c echo.Context) error { return c.JSON(http.StatusOK, models.Message{Message: "TOTP was enabled successfully."}) } +// UserTOTPDisable disables totp settings for the current user. +// @Summary Disable totp settings +// @Description Disables any totp settings for the current user. +// @tags user +// @Accept json +// @Produce json +// @Security JWTKeyAuth +// @Param totp body user.Login true "The current user's password (only password is enough)." +// @Success 200 {object} models.Message "Successfully disabled" +// @Failure 400 {object} code.vikunja.io/web.HTTPError "Something's invalid." +// @Failure 404 {object} code.vikunja.io/web.HTTPError "User does not exist." +// @Failure 500 {object} models.Message "Internal server error." +// @Router /user/settings/totp/disable [post] +func UserTOTPDisable(c echo.Context) error { + login := &user.Login{} + if err := c.Bind(login); err != nil { + log.Debugf("Invalid model error. Internal error was: %s", err.Error()) + if he, is := err.(*echo.HTTPError); is { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid model provided. Error was: %s", he.Message)) + } + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid model provided.")) + } + + u, err := user.GetCurrentUser(c) + if err != nil { + return handler.HandleHTTPError(err, c) + } + + u, err = user.GetUserByID(u.ID) + if err != nil { + return handler.HandleHTTPError(err, c) + } + + err = user.CheckUserPassword(u, login.Password) + if err != nil { + return handler.HandleHTTPError(err, c) + } + + err = user.DisableTOTP(u) + if err != nil { + return handler.HandleHTTPError(err, c) + } + + return c.JSON(http.StatusOK, models.Message{Message: "TOTP was enabled successfully."}) +} + // UserTOTPQrCode is the handler to show a qr code to enroll the user into totp // @Summary Totp QR Code // @Description Returns a qr code for easier setup at end user's devices. diff --git a/pkg/routes/routes.go b/pkg/routes/routes.go index 1a11c5b3712..f6136d9a642 100644 --- a/pkg/routes/routes.go +++ b/pkg/routes/routes.go @@ -212,6 +212,7 @@ func registerAPIRoutes(a *echo.Group) { u.GET("/settings/totp", apiv1.UserTOTP) u.POST("/settings/totp/enroll", apiv1.UserTOTPEnroll) u.POST("/settings/totp/enable", apiv1.UserTOTPEnable) + u.POST("/settings/totp/disable", apiv1.UserTOTPDisable) u.GET("/settings/totp/qrcode", apiv1.UserTOTPQrCode) listHandler := &handler.WebHandler{ diff --git a/pkg/user/totp.go b/pkg/user/totp.go index dc942e54fbc..2f76a2ce00c 100644 --- a/pkg/user/totp.go +++ b/pkg/user/totp.go @@ -105,6 +105,12 @@ func EnableTOTP(passcode *TOTPPasscode) (err error) { return } +// DisableTOTP removes all totp settings for a user. +func DisableTOTP(user *User) (err error) { + _, err = x.Where("user_id = ?", user.ID).Delete(&TOTP{}) + return +} + // ValidateTOTPPasscode validated totp codes of users. func ValidateTOTPPasscode(passcode *TOTPPasscode) (t *TOTP, err error) { t, err = GetTOTPForUser(passcode.User) diff --git a/pkg/user/user.go b/pkg/user/user.go index 8f065b6c4f0..243926e2131 100644 --- a/pkg/user/user.go +++ b/pkg/user/user.go @@ -172,17 +172,27 @@ func CheckUserCredentials(u *Login) (*User, error) { } // Check the users password - err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(u.Password)) + err = CheckUserPassword(user, u.Password) if err != nil { - if err == bcrypt.ErrMismatchedHashAndPassword { - return &User{}, ErrWrongUsernameOrPassword{} - } return &User{}, err } return user, nil } +// CheckUserPassword checks and verifies a user's password. The user object needs to contain the hashed password from the database. +func CheckUserPassword(user *User, password string) error { + err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)) + if err != nil { + if err == bcrypt.ErrMismatchedHashAndPassword { + return ErrWrongUsernameOrPassword{} + } + return err + } + + return nil +} + // GetCurrentUser returns the current user based on its jwt token func GetCurrentUser(c echo.Context) (user *User, err error) { jwtinf := c.Get("user").(*jwt.Token)