diff --git a/auth/auth.go b/auth/auth.go index 1451d181..3e9ee4a2 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -30,6 +30,7 @@ type Authentication interface { RenewAccessToken(ctx echo.Context) error VerifyEmail(ctx echo.Context) error ResetPassword(ctx echo.Context) error + ResetForgottenPassword(ctx echo.Context) error ForgotPassword(ctx echo.Context) error Invites(ctx echo.Context) error } diff --git a/auth/github.go b/auth/github.go index 2ed6a31c..0e4f7db5 100644 --- a/auth/github.go +++ b/auth/github.go @@ -101,11 +101,7 @@ func (a *auth) GithubLoginCallbackHandler(ctx echo.Context) error { sessionId := uuid.NewString() if err = a.pgStore.AddSession(ctx.Request().Context(), sessionId, refreshToken, oauthUser.Username); err != nil { - ctx.Redirect(http.StatusTemporaryRedirect, a.c.WebAppErrorRedirectURL) - echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ - "error": err.Error(), - "message": "ERR_CREATING_SESSION", - }) + echoErr := ctx.Redirect(http.StatusTemporaryRedirect, a.c.WebAppErrorRedirectPath) a.logger.Log(ctx, err) return echoErr } diff --git a/auth/jwt_middleware.go b/auth/jwt_middleware.go index 2c6ba3ff..f56b9e31 100644 --- a/auth/jwt_middleware.go +++ b/auth/jwt_middleware.go @@ -7,7 +7,6 @@ import ( "time" "github.com/containerish/OpenRegistry/types" - "github.com/fatih/color" "github.com/golang-jwt/jwt" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" @@ -45,7 +44,6 @@ func (a *auth) JWT() echo.MiddlewareFunc { ErrorHandlerWithContext: func(err error, ctx echo.Context) error { // ErrorHandlerWithContext only logs the failing requtest ctx.Set(types.HandlerStartTime, time.Now()) - color.Red(ctx.QueryParam("token")) a.logger.Log(ctx, err) return ctx.JSON(http.StatusUnauthorized, echo.Map{ "error": err.Error(), diff --git a/auth/reset_password.go b/auth/reset_password.go index 8bd9126e..1197507b 100644 --- a/auth/reset_password.go +++ b/auth/reset_password.go @@ -13,7 +13,7 @@ import ( "github.com/labstack/echo/v4" ) -func (a *auth) ResetPassword(ctx echo.Context) error { +func (a *auth) ResetForgottenPassword(ctx echo.Context) error { token, ok := ctx.Get("user").(*jwt.Token) if !ok { err := fmt.Errorf("ERR_EMPTY_TOKEN") @@ -36,6 +36,18 @@ func (a *auth) ResetPassword(ctx echo.Context) error { return echoErr } + var pwd *types.Password + err := json.NewDecoder(ctx.Request().Body).Decode(&pwd) + if err != nil { + echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ + "error": err.Error(), + "message": "request body could not be decoded", + }) + a.logger.Log(ctx, err) + return echoErr + } + _ = ctx.Request().Body.Close() + userId := c.Id user, err := a.pgStore.GetUserById(ctx.Request().Context(), userId, true) if err != nil { @@ -47,79 +59,112 @@ func (a *auth) ResetPassword(ctx echo.Context) error { return echoErr } - var pwd *types.Password - kind := ctx.QueryParam("kind") + if err = validatePassword(pwd.NewPassword); err != nil { + echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ + "error": err.Error(), + "message": `password must be alphanumeric, at least 8 chars long, must have at least one special character +and an uppercase letter`, + }) + a.logger.Log(ctx, err) + return echoErr + } - err = json.NewDecoder(ctx.Request().Body).Decode(&pwd) - if err != nil { + if a.verifyPassword(user.Password, pwd.NewPassword) { + + err = fmt.Errorf("new password can not be same as old password") + // error is already user friendly echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ "error": err.Error(), - "message": "request body could not be decoded", + "message": err.Error(), }) a.logger.Log(ctx, err) return echoErr } - _ = ctx.Request().Body.Close() - if err = verifyPassword(pwd.NewPassword); err != nil { - echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ - "error": err.Error(), - "message": `password must be alphanumeric, at least 8 chars long, must have at least one special character -and an uppercase letter`, + hashPassword, err := a.hashPassword(pwd.NewPassword) + if err != nil { + echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{ + "error": err.Error(), + "message": "ERR_HASH_NEW_PASSWORD", }) a.logger.Log(ctx, err) return echoErr } - if kind == "forgot_password_callback" { - hashPassword, err := a.hashPassword(pwd.NewPassword) - if err != nil { - echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{ - "error": err.Error(), - "message": "ERR_HASH_NEW_PASSWORD", - }) - a.logger.Log(ctx, err) - return echoErr - } + if err = a.pgStore.UpdateUserPWD(ctx.Request().Context(), userId, hashPassword); err != nil { + echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{ + "error": err.Error(), + "message": "error updating new password", + }) + a.logger.Log(ctx, err) + return echoErr + } - if user.Password == hashPassword { - err = fmt.Errorf("new password can not be same as old password") - // error is already user friendly - echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ - "error": err.Error(), - "message": err.Error(), - }) - a.logger.Log(ctx, err) - return echoErr - } + err = ctx.JSON(http.StatusAccepted, echo.Map{ + "message": "password changed successfully", + }) + a.logger.Log(ctx, err) + return err +} - if err = a.pgStore.UpdateUserPWD(ctx.Request().Context(), userId, hashPassword); err != nil { - echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{ - "error": err.Error(), - "message": "error updating new password", - }) - a.logger.Log(ctx, err) - return echoErr - } +func (a *auth) ResetPassword(ctx echo.Context) error { + token, ok := ctx.Get("user").(*jwt.Token) + if !ok { + err := fmt.Errorf("ERR_EMPTY_TOKEN") + echoErr := ctx.JSON(http.StatusUnauthorized, echo.Map{ + "error": err.Error(), + "message": "JWT token can not be empty", + }) + a.logger.Log(ctx, err) + return echoErr + } - err = ctx.JSON(http.StatusAccepted, echo.Map{ - "message": "password changed successfully", + c, ok := token.Claims.(*Claims) + if !ok { + err := fmt.Errorf("ERR_INVALID_CLAIMS") + echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{ + "error": err.Error(), + "message": "invalid claims in JWT", }) a.logger.Log(ctx, err) - return err + return echoErr } - if pwd.OldPassword == pwd.NewPassword { - err = fmt.Errorf("OLD_NEW_PWD_SAME") + var pwd *types.Password + err := json.NewDecoder(ctx.Request().Body).Decode(&pwd) + if err != nil { echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ "error": err.Error(), - "message": "new password can not be same as old password", + "message": "request body could not be decoded", }) a.logger.Log(ctx, err) return echoErr } + _ = ctx.Request().Body.Close() - newHashedPwd, err := a.hashPassword(pwd.NewPassword) + userId := c.Id + user, err := a.pgStore.GetUserById(ctx.Request().Context(), userId, true) + if err != nil { + echoErr := ctx.JSON(http.StatusNotFound, echo.Map{ + "error": err.Error(), + "message": "error getting user by ID from DB", + }) + a.logger.Log(ctx, err) + return echoErr + } + + // compare the current password with password hash from DB + if !a.verifyPassword(user.Password, pwd.OldPassword) { + err = fmt.Errorf("ERR_WRONG_PASSWORD") + echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ + "error": err.Error(), + "message": "password is wrong", + }) + a.logger.Log(ctx, err) + return echoErr + } + + hashPassword, err := a.hashPassword(pwd.NewPassword) if err != nil { echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{ "error": err.Error(), @@ -129,7 +174,28 @@ and an uppercase letter`, return echoErr } - if err = a.pgStore.UpdateUserPWD(ctx.Request().Context(), userId, newHashedPwd); err != nil { + if user.Password == hashPassword { + err = fmt.Errorf("new password can not be same as old password") + // error is already user friendly + echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ + "error": err.Error(), + "message": err.Error(), + }) + a.logger.Log(ctx, err) + return echoErr + } + + if err = validatePassword(pwd.NewPassword); err != nil { + echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ + "error": err.Error(), + "message": `password must be alphanumeric, at least 8 chars long, must have at least one special character +and an uppercase letter`, + }) + a.logger.Log(ctx, err) + return echoErr + } + + if err = a.pgStore.UpdateUserPWD(ctx.Request().Context(), userId, hashPassword); err != nil { echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{ "error": err.Error(), "message": "error updating new password", diff --git a/auth/signup.go b/auth/signup.go index 6e553be3..9c034120 100644 --- a/auth/signup.go +++ b/auth/signup.go @@ -40,7 +40,7 @@ func (a *auth) SignUp(ctx echo.Context) error { return echoErr } - if err := verifyPassword(u.Password); err != nil { + if err := validatePassword(u.Password); err != nil { // err.Error() is already user friendly echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ "error": err.Error(), @@ -155,7 +155,7 @@ func verifyEmail(email string) error { return nil } -func verifyPassword(password string) error { +func validatePassword(password string) error { var uppercasePresent bool var lowercasePresent bool var numberPresent bool diff --git a/go.mod b/go.mod index 0535ad0c..835b00b3 100644 --- a/go.mod +++ b/go.mod @@ -20,10 +20,10 @@ require ( golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 ) -require github.com/sendgrid/rest v2.6.9+incompatible // indirect require ( github.com/beorn7/perks v1.0.1 // indirect + github.com/sendgrid/rest v2.6.9+incompatible // indirect github.com/cespare/xxhash/v2 v2.1.1 // indirect github.com/fsnotify/fsnotify v1.4.9 // indirect github.com/go-playground/locales v0.14.0 // indirect diff --git a/router/helpers.go b/router/helpers.go index e981fee5..1505ce57 100644 --- a/router/helpers.go +++ b/router/helpers.go @@ -23,5 +23,6 @@ func RegisterAuthRoutes(authRouter *echo.Group, authSvc auth.Authentication) { authRouter.Add(http.MethodDelete, "/sessions", authSvc.ExpireSessions) authRouter.Add(http.MethodGet, "/renew", authSvc.RenewAccessToken) authRouter.Add(http.MethodPost, "/reset-password", authSvc.ResetPassword, authSvc.JWT()) + authRouter.Add(http.MethodPost, "/reset-forgotten-password", authSvc.ResetForgottenPassword, authSvc.JWT()) authRouter.Add(http.MethodGet, "/forgot-password", authSvc.ForgotPassword) } diff --git a/router/router.go b/router/router.go index 80ba2f41..8640a69d 100644 --- a/router/router.go +++ b/router/router.go @@ -131,5 +131,4 @@ func Extensions(group *echo.Group, reg registry.Registry, ext extensions.Extenio group.Add(http.MethodGet, Search, reg.GetImageNamespace) group.Add(http.MethodGet, CatalogDetail, ext.CatalogDetail, middlewares...) group.Add(http.MethodGet, RepositoryDetail, ext.RepositoryDetail, middlewares...) - }