package controllers import ( "fmt" "net/http" "clapclap/internal/apperr" "clapclap/internal/auth/tfa" "clapclap/internal/dtos" "clapclap/internal/ginctx" "clapclap/internal/utils" "clapclap/internal/uuid" "clapclap/internal/validate" "github.com/gin-gonic/gin" "golang.org/x/crypto/bcrypt" ) // RequestPasswordReset godoc // @Summary Request Password Reset // @Description Sends a password reset email if the account exists // @Tags auth // @Accept json // @Produce json // @Param request body dtos.ResetPasswordRequest true "Email Address" // @Success 200 {object} swaggMessageResponse // @Failure 500 {object} swaggError500 // @Router /auth/reset-password [post] func RequestPasswordReset(c *gin.Context) { req, _ := ginctx.GetDto[dtos.ResetPasswordRequest](c) repo, _ := ginctx.GetRepo(c, "repo") user, err := repo.FindUserByEmail(req.Email) if err != nil { // SECURITY: Do not abort with an error to prevent email enumeration c.JSON(http.StatusOK, gin.H{"message": "If that email exists, a reset link has been sent."}) return } go func(uID uuid.UUID, uEmail string) { defer func() { if r := recover(); r != nil { utils.LogError(fmt.Errorf("Recovered from panic in password reset task: %v", r)) } }() // to prevent email enumeration, we ignore the error if there is one // we just log it if err := tfa.DispatchPasswordResetEmail(uID, uEmail); err != nil { utils.LogError(fmt.Errorf("Failed to send password reset email to %s: %v", uEmail, err)) } }(user.ID, user.Email) c.JSON(http.StatusOK, gin.H{"message": "If that email exists, a reset link has been sent."}) } // ChangePassword godoc // @Summary Change Password // @Description Changes the user's password using a valid reset token // @Tags auth // @Accept json // @Produce json // @Param request body dtos.ChangePasswordRequest true "New Password" // @Success 200 {object} swaggMessageResponse // @Failure 401 {object} swaggError401 // @Failure 500 {object} swaggError500 // @Security BearerAuth // @Router /auth/change-password [put] func ChangePassword(c *gin.Context) { userID, _ := ginctx.GetUUID(c, "userID") req, _ := ginctx.GetDto[dtos.ChangePasswordRequest](c) repo, _ := ginctx.GetRepo(c, "repo") if !validate.Password(req.NewPassword) { apperr.AbortWithError(c, apperr.NewBadRequestError(nil, "Password does not match the required format")) return } hashed, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost) if err != nil { apperr.AbortWithError(c, apperr.NewInternalServerError(err, "Failed to secure password")) return } if err := repo.UpdatePassword(userID, string(hashed)); err != nil { apperr.AbortWithError(c, apperr.NewDatabaseError(err, "Failed to save new password")) return } c.JSON(http.StatusOK, gin.H{"message": "Password successfully updated"}) }