Aula 18 – Golang – Fiber – Reset Password

Aula 18 – Golang – Fiber – Reset Password

Voltar para página principal do site

Todas as aulas desse curso

Aula 17                                  Aula 19

Fiber

Fiber

Pacote Programador Fullstack

Pacote Programador Fullstack

Redes Sociais:

facebook        

Link para a Digital Innovation

Quer aprender python3 de graça e com certificado? Acesse então:

workover

Meus link de afiliados:

Hostinger

Digital Ocean

One.com

Código da aula: Github

Melhore seu NETWORKING

Participe de comunidades de desenvolvedores:

Fiquem a vontade para me adicionar ao linkedin.

E também para me seguir no GITHUB.

Canais do Youtube

Toti

Lofi Music Zone Beats

Backing Track / Play-Along

Código Fluente

Putz!

Vocal Techniques and Exercises

PIX para doações

PIX Nubank

PIX Nubank


Aula 18 – Golang – Fiber – Reset Password

Nesta aula, avançamos ainda mais em nosso curso de Golang com o framework Fiber, abordando a poderosa funcionalidade de redefinição de senhas e envio de emails.

Esses são aspectos cruciais em qualquer aplicação web, pois garantem a segurança dos usuários e a capacidade de recuperar o acesso em caso de esquecimento de senhas.

Nessa aula, vamos começar mexendo em dois arquivos do projeto Fiber:

1. fiber-project/controllers/forgotController.go

Neste arquivo, exploramos como implementar a redefinição de senha e o envio de emails para os usuários.

Primeiro, vamos criar a função `Reset` que lida com a redefinição de senhas.

Veja o passo a passo:

– Validar e confirmar senhas fornecidas pelo usuário.
– Consultar a base de dados para validar o token associado ao pedido de redefinição.
– Gerar uma nova senha segura usando bcrypt.
– Atualizar a senha do usuário na base de dados.
– Retornar uma resposta de sucesso.

2. fiber-project/routes/routes.go

Além disso, vamos conectar essa função no arquivo de rotas.

fiber-project/controllers/forgotController.go 


package controllers

import (
	"fiber-project/database"
	"fiber-project/models"
	"math/rand"
        "net/smtp"
	"github.com/gofiber/fiber/v2"
        "golang.org/x/crypto/bcrypt"
)

func Forgot(c *fiber.Ctx) error {
	var data map[string]string

	if err := c.BodyParser(&data); err != nil {
		return err
	}

	token := RandStringRunes(12)
	passwordReset := models.PasswordReset{
		Email: data["email"],
		Token: token,
	}

	database.DB.Create(&passwordReset)

        from := "fluentcode@exemple.com"

        to := []string{
            data["email"],
        }

        url := "http://localhost:3000/reset/" + token

        message := []byte("Clique <a href=\"" + url + "\">aqui</a> para redefinir sua senha!")

        err := smtp.SendMail("0.0.0.0:1025", nil, from, to, message)

        if err != nil {
            return err
        }
	return c.JSON(fiber.Map{
		"message": "success",
	})
}

func Reset(c *fiber.Ctx) error {
    var data map[string]string

    if err := c.BodyParser(&data); err != nil {
        return err
    }

    if data["password"] != data["confirm_password"] {
        c.Status(400)
        return c.JSON(fiber.Map{
            "message": "Passwords do not match!",
        })
    }

    var passwordReset = models.PasswordReset{}
    if err := database.DB.Where("token = ?", data["token"]).Last(&passwordReset); err.Error != nil {
        c.Status(400)
        return c.JSON(fiber.Map{
            "message": "Invalid token!",
        })
    }
    password, _ := bcrypt.GenerateFromPassword([]byte(data["password"]), 14)

    database.DB.Model(&models.User{}).Where("email = ?", passwordReset.Email).Update("password", password)

    return c.JSON(fiber.Map{
        "message": "success",
    })
}

func RandStringRunes(n int) string {
	var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

	b := make([]rune, n)
	for i := range b {
		b[i] = letterRunes[rand.Intn(len(letterRunes))]
	}
	return string(b)
}

return c.JSON(fiber.Map{
"message": "success",
})
}

func RandStringRunes(n int) string {
var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

b := make([]rune, n)
for i := range b {
b[i] = letterRunes[rand.Intn(len(letterRunes))]
}
return string(b)
}

Aqui, configuramos a rota para a função `Reset` no arquivo de rotas.

Isso permite que as solicitações de redefinição de senha sejam devidamente roteadas para o controlador apropriado.

O código mostra como definir a rota e a conexão entre o endpoint e a função `Reset` no controlador.

fiber-project/routes/routes.go

package routes

import (
	"fiber-project/controllers"

	"github.com/gofiber/fiber/v2"
)

func Setup(app *fiber.App) {
	app.Post("/api/register", controllers.Register)
	app.Post("/api/login", controllers.Login)
	app.Get("/api/user", controllers.User)
	app.Post("/api/logout", controllers.Logout)
	app.Post("/api/forgot", controllers.Forgot)
        app.Post("/api/reset", controllers.Reset)
}

Para testar, vamos fazer uma request POST no: http://localhost:8000/api/reset

Para passar o token correto, copie o último token, no banco e cole na query, como esse exemplo logo abaixo:
{
    "token": "copiarotoken",
    "password": "1234",
    "confirm_password": "1234"
}

Agora, com essa funcionalidade implementada, nosso aplicativo está mais completo e pronto para atender às necessidades dos usuários de forma mais segura e confiável.

Refatorando

A arquitetura de software é uma disciplina crucial para construir sistemas robustos, escaláveis e de fácil manutenção.

Uma abordagem inteligente para essa tarefa é a refatoração, que envolve reestruturar o código existente sem alterar a sua funcionalidade.

No contexto de um aplicativo Go baseado no framework Fiber, desacoplar o código do framework é uma forma poderosa de melhorar a manutenibilidade, testabilidade e escalabilidade.

Vamos explorar como essa abordagem se encaixa nas boas práticas de arquitetura de software.

Desacoplando do Framework:

Ao desenvolver um aplicativo em um framework como o Fiber, é fácil que as preocupações relacionadas ao framework se misturem com a lógica de negócio.

Entretanto, essa abordagem pode resultar em um código difícil de testar, inflexível a mudanças e altamente acoplado ao framework.

A refatoração visa justamente separar essas preocupações.

Vantagens do Desacoplamento:

  1. Flexibilidade: Ao remover dependências diretas do framework do código de negócio, você ganha a liberdade de trocar ou atualizar o framework sem afetar o núcleo da aplicação.
  2. Testabilidade: Isolar a lógica de negócios torna mais fácil escrever testes unitários. Os testes se concentram na funcionalidade em si, em vez de interações complexas com o framework.
  3. Manutenibilidade: O código desacoplado é mais modular e fácil de entender. Mudanças em um aspecto não afetarão os outros, facilitando a manutenção.
  4. Escalabilidade: O desacoplamento simplifica a escalabilidade, permitindo que você ajuste componentes individuais sem redesenhar todo o sistema.
  5. Reusabilidade: O código desacoplado é mais reutilizável, pois pode ser transferido para outros projetos sem as dependências do framework.

Boas Práticas de Arquitetura:

  1. Arquitetura Hexagonal: A abordagem de desacoplamento do framework é congruente com a arquitetura hexagonal, que coloca a lógica de negócios no centro, independente das tecnologias externas.
  2. SOLID: Ao separar preocupações e criar serviços com responsabilidades únicas, você adere aos princípios SOLID. Isso resulta em código mais coeso, flexível e fácil de estender.
  3. Arquitetura Limpa: A separação do código do framework e a criação de serviços refletem os princípios da arquitetura limpa, mantendo a lógica de negócio isolada das ferramentas externas.

Processo de Refatoração:

  1. Identificação: Identifique as partes do código fortemente acopladas ao framework. Isso inclui as rotas, manipuladores, middleware e outros elementos específicos de um framework como o Fiber, por exemplo.
  2. Criação de Serviços: Crie serviços independentes para encapsular a lógica de negócio. Esses serviços definem interfaces claras para operações específicas.
  3. Camadas de Aplicação: Organize a aplicação em camadas, como controladores, serviços e repositórios. Os controladores tratam da interação com o cliente, os serviços implementam a lógica de negócio e os repositórios lidam com o armazenamento de dados.
  4. Injeção de Dependência: Use injeção de dependência para conectar os serviços aos controladores. Isso permite que você substitua implementações facilmente para testes e evoluções futuras.
  5. Testes: Escreva testes unitários para cada serviço isoladamente. Isso verifica a funcionalidade da lógica de negócio sem a necessidade de simular interações complexas com o framework.
  6. Iteração: Continue refinando os serviços à medida que o projeto evolui. Os serviços podem ser estendidos, sem modificar o código existente.

Em resumo, a refatoração para desacoplar do framework Fiber e a adesão a boas práticas de arquitetura de software proporcionam um código mais flexível, testável e manutenível.

O que vamos fazer?

O que vamos fazer é atribuir as responsabilidades de Login, Registro, Logout e Recuperação de Usuário aos serviços.

Essa é uma abordagem que fortalece a modularidade, a testabilidade e a flexibilidade do código.

Ela se alinha com os princípios de arquitetura limpa, SOLID e separação de preocupações, tornando o código mais limpo, mais adaptável e mais fácil de manter ao longo do tempo.

Então vamos lá!

fiber-project/services/auth_service.go

package services

import (
	"fiber-project/database"
	"fiber-project/models"
	"strconv"
	"time"

	"github.com/gofiber/fiber/v2"
	"github.com/golang-jwt/jwt/v4"
	"golang.org/x/crypto/bcrypt"
)

type Claims struct {
	jwt.StandardClaims
}

func Register(c *fiber.Ctx) error {
	var data map[string]string

	if err := c.BodyParser(&data); err != nil {
		return err
	}
	if data["password"] != data["confirm_password"] {
		c.Status(400)
		return c.JSON(fiber.Map{
			"message": "Passwords do not match!",
		})
	}

	password, _ := bcrypt.GenerateFromPassword([]byte(data["password"]), 14)

	user := models.User{
		FirstName: data["first_name"],
		LastName:  data["last_name"],
		Email:     data["email"],
		Password:  password,
	}

	database.DB.Create(&user)
	return c.JSON(user)
}

func Login(c *fiber.Ctx) error {
	//get the request parameter
	var data map[string]string

	if err := c.BodyParser(&data); err != nil {
		return err
	}

	var user models.User
	//get user by email
	database.DB.Where("email = ?", data["email"]).First(&user)

	//user not found
	if user.Id == 0 {
		c.Status(404)
		return c.JSON(fiber.Map{
			"message": "User not found!",
		})
	}

	//incorrect password
	if err := bcrypt.CompareHashAndPassword(user.Password, []byte(data["password"])); err != nil {
		c.Status(400)
		return c.JSON(fiber.Map{
			"message": "Incorrect password!",
		})
	}

	claims := jwt.RegisteredClaims{
		Issuer:    strconv.Itoa(int(user.Id)),
		ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
	}

	jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	token, err := jwtToken.SignedString([]byte("secret"))
	if err != nil {
		return c.SendStatus(fiber.StatusInternalServerError)
	}

	cookie := fiber.Cookie{
		Name:     "jwt",
		Value:    token,
		Expires:  time.Now().Add(24 * time.Hour),
		HTTPOnly: true,
	}
	c.Cookie(&cookie)
	return c.JSON(fiber.Map{
		"jwt": token,
	})

}

func User(c *fiber.Ctx) error {
	cookie := c.Cookies("jwt")
	token, err := jwt.ParseWithClaims(cookie, &Claims{}, func(token *jwt.Token) (interface{}, error) {
		return []byte("secret"), nil
	})

	if err != nil || !token.Valid {
		c.Status(fiber.StatusUnauthorized)
		return c.JSON(fiber.Map{
			"message": "unauthenticated",
		})
	}
	claims := token.Claims.(*Claims)
	id := claims.Issuer
	var user models.User
	database.DB.Where("id = ?", id).First(&user)
	return c.JSON(user)
}

func Logout(c *fiber.Ctx) error {
	cookie := fiber.Cookie{
		//Definir o valor do cookie como vazio e adicionar uma data de expiração no passado.
		Name:  "jwt",
		Value: "",
		//No código da função de logout, remover o cookie definindo o mesmo cookie no passado ( '-' ).
		Expires:  time.Now().Add(-time.Hour),
		HTTPOnly: true,
	}
	c.Cookie(&cookie)
	//Retornar uma resposta de sucesso em formato JSON.
	return c.JSON(fiber.Map{
		"message": "success",
	})
}

A implementação do Forgot e do Reset agora está no: fiber-project/services/forgot_service.go

fiber-project/services/forgot_service.go

package services

import (
	"fiber-project/database"
	"fiber-project/models"
	"math/rand"
	"net/smtp"

	"github.com/gofiber/fiber/v2"
	"golang.org/x/crypto/bcrypt"
)

func Forgot(c *fiber.Ctx) error {
	var data map[string]string

	if err := c.BodyParser(&data); err != nil {
		return err
	}

	token := RandStringRunes(12)
	passwordReset := models.PasswordReset{
		Email: data["email"],
		Token: token,
	}

	database.DB.Create(&passwordReset)

	from := "fluentcode@exemple.com"

	to := []string{
		data["email"],
	}

	message := []byte("Clique <a href=\"http://localhost:3000/reset/" + token + "\">aqui</a> para redefinir sua senha!")

	err := smtp.SendMail("localhost:1025", nil, from, to, message)

	if err != nil {
		return err
	}

	return c.JSON(fiber.Map{
		"message": "success",
	})
}

func Reset(c *fiber.Ctx) error {
	var data map[string]string

	if err := c.BodyParser(&data); err != nil {
		return err
	}

	if data["password"] != data["confirm_password"] {
		c.Status(400)
		return c.JSON(fiber.Map{
			"message": "Passwords do not match!",
		})
	}

	var passwordReset = models.PasswordReset{}
	if err := database.DB.Where("token = ?", data["token"]).Last(&passwordReset); err.Error != nil {
		c.Status(400)
		return c.JSON(fiber.Map{
			"message": "Invalid token!",
		})
	}
	password, _ := bcrypt.GenerateFromPassword([]byte(data["password"]), 14)

	database.DB.Model(&models.User{}).Where("email = ?", passwordReset.Email).Update("password", password)

	return c.JSON(fiber.Map{
		"message": "success",
	})
}

func RandStringRunes(n int) string {
	var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

	b := make([]rune, n)
	for i := range b {
		b[i] = letterRunes[rand.Intn(len(letterRunes))]
	}
	return string(b)
}

Agora o forgotController chama o service Forgot e Reset, ao invés dele mesmo ficar com essa responsabilidade.

fiber-project/controllers/forgotController.go

package controllers

import (
	"fiber-project/services"

	"github.com/gofiber/fiber/v2"
)

func Forgot(c *fiber.Ctx) error {
	return services.Forgot(c)
}

func Reset(c *fiber.Ctx) error {
	return services.Reset(c)
}

A mesma coisa, o authController não tem mais a responsablidade dessas lógicas de negócio: Login, Register, Logout…

Essas responsabilidades agora são dos services.

fiber-project/controllers/authController.go

package controllers

import (
	"fiber-project/services"

	"github.com/gofiber/fiber/v2"
)

func Register(c *fiber.Ctx) error {
	return services.Register(c)
}

func Login(c *fiber.Ctx) error {
	return services.Login(c)
}

func User(c *fiber.Ctx) error {
	return services.User(c)
}

func Logout(c *fiber.Ctx) error {
	return services.Logout(c)
}

E para finalizar, vamos fazer uma pequena refatoração no fiber-project/routes/routes.go

fiber-project/routes/routes.go

package routes

import (
	"fiber-project/controllers"

	"github.com/gofiber/fiber/v2"
)

func Setup(app *fiber.App) {
	api := app.Group("/api")

	api.Post("/register", controllers.Register)
	api.Post("/login", controllers.Login)
	api.Get("/user", controllers.User)
	api.Post("/logout", controllers.Logout)
	api.Post("/forgot", controllers.Forgot)
	api.Post("/reset", controllers.Reset)
}

Com isso, encerramos a parte do Back End.

Na próxima aula, vamos começar o Front End usando React.

Código da aula: Github

Voltar para página principal do blog

Todas as aulas desse curso

Aula 17                                 Aula 19

Redes Sociais:

facebook        

Novamente deixo meus link de afiliados:

Hostinger

Digital Ocean

One.com

É isso!

A gente se vê na próxima. 😉

Até lá!

\o/

Bons estudos. 😉

About The Author
-

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>