Kyrux
Voltar

Middleware

Middlewares globais (aplicados a todas as rotas)

Registrados no bootstrap — já ativos por padrão:

Middleware Descrição
Recovery() Captura panics — mostra debug page (dev) ou 500 (prod)
MaxBodySize(32MB) Limita o tamanho do body — defesa contra payloads gigantes
SecureHeaders HSTS, X-Frame-Options, CSP (configurável — ver abaixo) — apenas em production (APP_ENV=development não ativa)
AllowedHosts() Bloqueia hosts não autorizados (ignorado em dev)
csrf.Middleware Valida token CSRF em métodos não seguros

Middlewares opcionais

Middleware Uso Descrição
Compress r.Use(compress.Compress) Compressão gzip das respostas
CORS(origins) r.Use(secmiddleware.CORS(...)) Cabeçalhos CORS para as origens permitidas
RequireAuth(a) por rota ou global Exige Bearer token JWT — APIs stateless
RequireLogin(store, url) por rota ou global Exige sessão ativa — views SSR; redireciona para url se não autenticado
RateLimit(max, janela) por rota (recomendado no login) Máximo de max requisições por IP por janela; excedentes recebem 429
LocalhostOnly por rota (ex: dashboards internos) 403 para qualquer IP que não seja loopback — é o que protege /kyrux/debug/
MaxBodySize(n) por rota, além do global de 32MB Limite específico menor (ou maior) para uma rota (ex: upload)
import (
    "kyrux/core/compress"
    secmiddleware "kyrux/core/security/middleware"
)

// Compressão gzip
r.Use(compress.Compress)

// CORS
r.Use(secmiddleware.CORS([]string{"https://meusite.com.br"}))

// Exigir sessão ativa em views SSR — redireciona para /login/ se não autenticado
r.Use(secmiddleware.RequireLogin(fw.Sessions, "/login/"))

// Exigir JWT em rotas de API
r.Use(secmiddleware.RequireAuth(fw.Auth))

// Rate limit por rota — essencial no login (Argon2 é caro por design):
router.Path("POST", "/login/",
    secmiddleware.RateLimit(10, time.Minute)(views.LoginPost(fw)), "login_post"),

Middleware personalizado

func LogMiddleware(next router.HandlerFunc) router.HandlerFunc {
    return func(ctx *router.Context) {
        log.Printf("%s %s", ctx.Request.Method, ctx.Request.URL.Path)
        next(ctx)
    }
}

// Aplicar globalmente
r.Use(LogMiddleware)

Middleware por rota (via wrapper)

func Register(r *router.Router) {
    // Rota SSR protegida por sessão
    loginRequired := secmiddleware.RequireLogin(fw.Sessions, "/login/")
    r.Handle("GET /dashboard/", loginRequired(func(ctx *router.Context) {
        render.For("painel").Render(ctx, "dashboard.html", nil)
    }))

    // Rota de API protegida por JWT
    jwtRequired := secmiddleware.RequireAuth(fw.Auth)
    r.Handle("GET /api/perfil/", jwtRequired(func(ctx *router.Context) {
        claims := ctx.Get("claims").(*auth.Claims)
        ctx.JSON(200, map[string]string{"user_id": claims.UserID})
    }))
}

Content-Security-Policy (CSP) configurável

A CSP enviada por SecureHeaders em produção tem um padrão estrito (secmiddleware.DefaultCSP: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:) — configurável em dois níveis, sem precisar editar core/:

Global — troca a política do site inteiro, normalmente a partir de CSP_POLICY no .env (o bootstrap já chama isso sozinho):

secmiddleware.SetCSP(cfg.Security.CSPPolicy) // policy vazia mantém o DefaultCSP

Por rota — sobrescreve só numa página que precisa de uma exceção pontual (um script/iframe de terceiro só ali — ex: um mapa incorporado ou um provedor de captcha), sem afrouxar a política padrão do resto do site:

mapaCSP := "default-src 'self'; frame-src https://www.google.com/maps/"

router.Path("GET", "/contato/",
    secmiddleware.CSPOverride(mapaCSP)(views.ContatoView(fw)), "contato"),

Funciona porque middleware por rota roda depois do global (SecureHeaders inclusive) na cadeia — o Set do CSPOverride substitui, nunca soma, o header que SecureHeaders já escreveu. Também funciona sem SecureHeaders ativo (development): só adiciona um header a mais, inofensivo.