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
Redes Sociais:
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
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:
- 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.
- 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.
- 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.
- Escalabilidade: O desacoplamento simplifica a escalabilidade, permitindo que você ajuste componentes individuais sem redesenhar todo o sistema.
- 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:
- 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.
- 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.
- 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:
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.