Ir para o conteúdo

Backend - Middlewares

Este documento descreve todos os middlewares do backend, suas responsabilidades, uso e como criar novos middlewares.

Índice

  1. Visão Geral
  2. AuthMiddleware
  3. RoleMiddleware
  4. ProfileApprovalMiddleware
  5. RateLimitMiddleware
  6. Criando Novos Middlewares

Visão Geral

Middlewares são funções que interceptam requisições HTTP antes de chegarem aos handlers. Eles são executados em sequência e podem:

  • Validar autenticação
  • Verificar permissões
  • Adicionar dados ao contexto
  • Modificar requisições/respostas
  • Interromper o fluxo (retornar erro)

Localização: internal/middleware/

Framework: Echo v4

Ordem de Execução:

Request → Middleware 1 → Middleware 2 → ... → Handler → Response


AuthMiddleware

Responsabilidade

Valida tokens JWT e adiciona informações do usuário ao contexto do Echo.

Localização: internal/middleware/auth/auth.go

Funcionalidade

  1. Extrai token do header Authorization: Bearer <token>
  2. Valida formato do token
  3. Valida assinatura do token
  4. Verifica expiração
  5. Adiciona claims ao contexto:
  6. user_id (uint)
  7. user_type (string)
  8. email (string)

Uso

api := e.Group("/api")
api.Use(authMiddleware.AuthMiddleware())

Dados no Contexto

Após passar pelo middleware, o handler pode acessar:

userID := c.Get("user_id").(uint)
userType := c.Get("user_type").(string)
email := c.Get("email").(string)

Verificação Segura

userID, ok := c.Get("user_id").(uint)
if !ok {
    return echo.NewHTTPError(http.StatusUnauthorized, "User ID not found")
}

Rotas Públicas

Rotas que não devem passar pelo middleware devem ser registradas antes:

// Rotas públicas (antes do middleware)
e.POST("/api/auth/login", loginHandler)
e.GET("/api/plans", plansHandler)

// Rotas protegidas (depois do middleware)
api := e.Group("/api")
api.Use(authMiddleware.AuthMiddleware())
api.GET("/cycles", cyclesHandler)

Erros

  • 401 Unauthorized - Token ausente, inválido ou expirado
  • Mensagem: "Invalid or expired token"

RoleMiddleware

Responsabilidade

Verifica se o user_type do token está na lista de roles permitidas.

Localização: internal/middleware/role/role.go

Funcionalidade

  1. Extrai user_type do contexto (adicionado por AuthMiddleware)
  2. Verifica se está na lista de roles permitidas
  3. Se não estiver, retorna 403 Forbidden

Uso

Apenas Admin:

adminRoutes := api.Group("")
adminRoutes.Use(role.RoleMiddleware([]string{"admin"}))
adminRoutes.GET("/users", getUsersHandler)

Admin ou Professional:

usersRoutes := api.Group("")
usersRoutes.Use(role.RoleMiddleware([]string{"admin", "professional"}))
usersRoutes.GET("/users", getUsersHandler)

Apenas Client:

clientRoutes := api.Group("")
clientRoutes.Use(role.RoleMiddleware([]string{"client"}))
clientRoutes.GET("/cycles", getCyclesHandler)

Erros

  • 403 Forbidden - Usuário não tem role permitida
  • Mensagem: "Insufficient permissions"

Dependência

Requer: AuthMiddleware (deve ser aplicado antes)

api := e.Group("/api")
api.Use(authMiddleware.AuthMiddleware()) // Primeiro
api.Use(role.RoleMiddleware([]string{"admin"})) // Depois

ProfileApprovalMiddleware

Responsabilidade

Verifica se profissional tem perfil aprovado. Aplica apenas a rotas de profissionais que requerem aprovação.

Localização: internal/middleware/profile/profile.go

Funcionalidade

  1. Verifica se user_type == "professional"
  2. Se for admin, permite acesso (admin não precisa de perfil)
  3. Busca perfil profissional no banco
  4. Verifica se status == "approved"
  5. Se não estiver aprovado, retorna 403 Forbidden

Uso

approvedRoutes := api.Group("")
approvedRoutes.Use(role.RoleMiddleware([]string{"professional"}))
approvedRoutes.Use(profile.ProfileApprovalMiddleware())
approvedRoutes.GET("/patients", getPatientsHandler)

Erros

  • 403 Forbidden - Profissional não tem perfil aprovado
  • Mensagem: "Professional profile not approved"

Dependências

Requer: 1. AuthMiddleware (deve ser aplicado antes) 2. RoleMiddleware com "professional" (deve ser aplicado antes)

Rotas que NÃO Requerem Aprovação

Algumas rotas de profissional não requerem aprovação (ex: criar/atualizar perfil):

// Sem ProfileApprovalMiddleware
api.GET("/profile", getProfileHandler)
api.POST("/profile", createProfileHandler)

RateLimitMiddleware

Responsabilidade

Limita taxa de requisições por IP ou usuário para prevenir abuso.

Localização: internal/middleware/ratelimit/ratelimit.go

Funcionalidade

  1. Identifica origem da requisição (IP ou user_id)
  2. Verifica limite de requisições no período
  3. Se exceder limite, retorna 429 Too Many Requests
  4. Adiciona headers de rate limit:
  5. X-RateLimit-Limit - Limite de requisições
  6. X-RateLimit-Remaining - Requisições restantes
  7. X-RateLimit-Reset - Timestamp de reset

Uso

api := e.Group("/api")
api.Use(ratelimit.RateLimitMiddleware(100, time.Minute)) // 100 req/min

Configuração

Parâmetros: - limit - Número máximo de requisições - window - Janela de tempo (ex: time.Minute)

Exemplos:

// 100 requisições por minuto
api.Use(ratelimit.RateLimitMiddleware(100, time.Minute))

// 10 requisições por segundo
api.Use(ratelimit.RateLimitMiddleware(10, time.Second))

// 1000 requisições por hora
api.Use(ratelimit.RateLimitMiddleware(1000, time.Hour))

Erros

  • 429 Too Many Requests - Limite excedido
  • Mensagem: "Rate limit exceeded"

Headers de Resposta

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1640995200

Criando Novos Middlewares

Estrutura Básica

package middleware

import (
    "github.com/labstack/echo/v4"
)

func MyMiddleware() echo.MiddlewareFunc {
    return func(next echo.HandlerFunc) echo.HandlerFunc {
        return func(c echo.Context) error {
            // Lógica do middleware

            // Continuar para próximo middleware/handler
            return next(c)

            // OU interromper e retornar erro
            // return echo.NewHTTPError(http.StatusForbidden, "Access denied")
        }
    }
}

Exemplo: Logging Middleware

func LoggingMiddleware() echo.MiddlewareFunc {
    return func(next echo.HandlerFunc) echo.HandlerFunc {
        return func(c echo.Context) error {
            start := time.Now()

            // Executar próximo handler
            err := next(c)

            // Log após execução
            duration := time.Since(start)
            log.Printf(
                "%s %s %d %v",
                c.Request().Method,
                c.Request().URL.Path,
                c.Response().Status,
                duration,
            )

            return err
        }
    }
}

Exemplo: CORS Middleware

func CORSMiddleware() echo.MiddlewareFunc {
    return func(next echo.HandlerFunc) echo.HandlerFunc {
        return func(c echo.Context) error {
            origin := c.Request().Header.Get("Origin")

            // Verificar se origem é permitida
            if isAllowedOrigin(origin) {
                c.Response().Header().Set("Access-Control-Allow-Origin", origin)
                c.Response().Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
                c.Response().Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
                c.Response().Header().Set("Access-Control-Allow-Credentials", "true")
            }

            // Tratar OPTIONS (preflight)
            if c.Request().Method == "OPTIONS" {
                return c.NoContent(http.StatusNoContent)
            }

            return next(c)
        }
    }
}

Exemplo: Request ID Middleware

func RequestIDMiddleware() echo.MiddlewareFunc {
    return func(next echo.HandlerFunc) echo.HandlerFunc {
        return func(c echo.Context) error {
            // Gerar ou usar ID existente
            requestID := c.Request().Header.Get("X-Request-ID")
            if requestID == "" {
                requestID = uuid.New().String()
            }

            // Adicionar ao contexto
            c.Set("request_id", requestID)

            // Adicionar ao header de resposta
            c.Response().Header().Set("X-Request-ID", requestID)

            return next(c)
        }
    }
}

Ordem de Aplicação

Ordem Recomendada

api := e.Group("/api")

// 1. CORS (se necessário)
api.Use(corsMiddleware.CORSMiddleware())

// 2. Logging (opcional)
api.Use(loggingMiddleware.LoggingMiddleware())

// 3. Rate Limiting (opcional)
api.Use(ratelimit.RateLimitMiddleware(100, time.Minute))

// 4. Autenticação (obrigatório para rotas protegidas)
api.Use(authMiddleware.AuthMiddleware())

// 5. Role (se necessário)
api.Use(role.RoleMiddleware([]string{"admin"}))

// 6. Profile Approval (se necessário)
api.Use(profile.ProfileApprovalMiddleware())

// Handlers
api.GET("/users", getUsersHandler)

Exemplo Completo

func RegisterRoutes(e *echo.Echo) {
    // Rotas públicas
    e.GET("/health", HealthCheck)
    authRoutes.RegisterRoutes(e)

    // Rotas protegidas
    api := e.Group("/api")

    // Middlewares globais
    api.Use(authMiddleware.AuthMiddleware())

    // Rotas gerais (qualquer autenticado)
    generalRoutes.RegisterRoutes(api)

    // Rotas de admin
    adminRoutes := api.Group("")
    adminRoutes.Use(role.RoleMiddleware([]string{"admin"}))
    adminRoutes.GET("/users", adminRoutes.GetUsers)

    // Rotas de professional aprovado
    approvedRoutes := api.Group("")
    approvedRoutes.Use(role.RoleMiddleware([]string{"professional"}))
    approvedRoutes.Use(profile.ProfileApprovalMiddleware())
    approvedRoutes.GET("/patients", professionalRoutes.GetPatients)
}

Boas Práticas

1. Verificar Dependências

Sempre verificar se dados necessários existem no contexto:

func MyMiddleware() echo.MiddlewareFunc {
    return func(next echo.HandlerFunc) echo.HandlerFunc {
        return func(c echo.Context) error {
            userID, ok := c.Get("user_id").(uint)
            if !ok {
                return echo.NewHTTPError(http.StatusUnauthorized, "User ID not found")
            }
            // Usar userID
            return next(c)
        }
    }
}

2. Retornar Erros Apropriados

Use códigos HTTP corretos:

  • 401 Unauthorized - Não autenticado
  • 403 Forbidden - Autenticado mas sem permissão
  • 429 Too Many Requests - Rate limit excedido

3. Adicionar Dados ao Contexto

Use c.Set() para adicionar dados que handlers podem usar:

c.Set("request_id", requestID)
c.Set("ip_address", ip)

4. Não Modificar Request Body

Evite ler o body da requisição no middleware, pois ele só pode ser lido uma vez:

// ❌ ERRADO
body, _ := io.ReadAll(c.Request().Body)
c.Request().Body = io.NopCloser(bytes.NewBuffer(body))

// ✅ CORRETO - Deixar handler ler o body

5. Performance

Middlewares são executados para cada requisição. Mantenha-os leves:

  • Evite queries pesadas no middleware
  • Use cache quando apropriado
  • Evite operações síncronas lentas

Testes

Testando Middleware

func TestAuthMiddleware(t *testing.T) {
    e := echo.New()
    req := httptest.NewRequest(http.MethodGet, "/", nil)
    req.Header.Set("Authorization", "Bearer valid-token")
    rec := httptest.NewRecorder()
    c := e.NewContext(req, rec)

    // Executar middleware
    handler := authMiddleware.AuthMiddleware()(func(c echo.Context) error {
        userID := c.Get("user_id").(uint)
        assert.Equal(t, uint(1), userID)
        return c.String(http.StatusOK, "OK")
    })

    handler(c)
    assert.Equal(t, http.StatusOK, rec.Code)
}

Referências