Backend
2. Resetting Password
Moving onto the next API.
PUT on /api/reset-password, req -> otp, email, new password, res -> nocontent
// controllers/passwordReset.go
func ResetPassword(c *fiber.Ctx) error {
type Input struct {
OTP string `json:"otp"`
Email string `json:"email"`
NewPassword string `json:"new_password"`
}
var input Input
err := c.BodyParser(&input)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "invalid data",
})
}
// no input field should be empty
if input.OTP == "" || input.Email == "" || input.NewPassword == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "invalid data",
})
}
// TODO: check redis for otp and update password
return c.SendStatus(fiber.StatusNoContent)
}
Adding route for it
// routes/routes.go
api.Put("/reset-password", controllers.ResetPassword)
Now I need two functions:
-
VerifyOTP
-> input = otp, email; output = error (if any) -
UpdatePassword
-> input = email, password; output = error (if any)
// utils/passwordReset.go
func VerifyOTP(otp string, email string, c context.Context) (error, bool) {
key := otpKeyPrefix + email
// get the value for the key
value, err := config.RedisClient.Get(c, key).Result()
if err != nil {
// the following states that the key was not found
if err == redis.Nil {
return errors.New("otp expired / incorrect email"), false
}
// for other errors
return err, true
}
// compare received otp's hash with value in redis
err = bcrypt.CompareHashAndPassword([]byte(value), []byte(otp))
if err != nil {
return errors.New("incorrect otp"), false
}
// delete redis key to prevent abuse of otp
err = config.RedisClient.Del(c, key).Err()
if err != nil {
return err, true
}
return nil, false
}
func UpdatePassword(email string, password string, c context.Context) error {
users := config.DB.Collection("users")
// hash the password
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(password), 10)
// update the password
update := bson.M{
"$set": bson.M{
"password": hashedPassword,
},
}
_, err := users.UpdateByID(c, email, update)
if err != nil {
return err
}
return nil
}
Now I need to put them both together in the controller. I'm using the bool
from VerifyOTP
function to denote whether the error is an internal error or is it because of the input.
// controllers/passwordReset.go
func ResetPassword(c *fiber.Ctx) error {
type Input struct {
OTP string `json:"otp"`
Email string `json:"email"`
NewPassword string `json:"new_password"`
}
var input Input
err := c.BodyParser(&input)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "invalid data",
})
}
// no input field should be empty
if input.OTP == "" || input.Email == "" || input.NewPassword == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "invalid data",
})
}
// check redis for otp
err, isInternalErr := utils.VerifyOTP(input.OTP, input.Email, c.Context())
if err != nil {
var code int
if isInternalErr {
code = fiber.StatusInternalServerError
} else {
code = fiber.StatusUnauthorized
}
return c.Status(code).JSON(fiber.Map{
"error": err.Error(),
})
}
err = utils.UpdatePassword(input.Email, input.NewPassword, c.Context())
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": err.Error(),
})
}
return c.SendStatus(fiber.StatusNoContent)
}
The API is now built, and the testing can be done using the following cURL command
curl --location --request PUT 'localhost:3000/api/reset-password' \
--header 'Content-Type: application/json' \
--data-raw '{
"email": "yashjaiswal.cse@gmail.com",
"new_password": "tester123",
"otp": "DM4RDNF07B"
}'
In the next part, I'll start with the frontend
Top comments (0)