diff --git a/rest/middlewares/auth_servicio_pago.go b/rest/middlewares/auth_servicio_pago.go index e97ffa5..3a7b236 100644 --- a/rest/middlewares/auth_servicio_pago.go +++ b/rest/middlewares/auth_servicio_pago.go @@ -24,7 +24,7 @@ func AuthServicioPago(c *fiber.Ctx) error { return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": true, "message": "Token inválido o servicio inactivo"}) } - if !servicio.IPPermitida(c.IP()) { + if !servicio.IPPermitida(IPDelCliente(c)) { return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": true, "message": "IP no autorizada para este servicio"}) } diff --git a/rest/middlewares/hermes_auth.go b/rest/middlewares/hermes_auth.go index 5d88fa2..b4cb288 100644 --- a/rest/middlewares/hermes_auth.go +++ b/rest/middlewares/hermes_auth.go @@ -38,11 +38,17 @@ func AdminApiAuth() fiber.Handler { if err != nil { return c.Status(401).JSON(fiber.Map{"error": "unauthorized"}) } - if !key.IPValida(c.IP()) { - return c.Status(403).JSON(fiber.Map{"error": "IP no autorizada para esta API key"}) + if ip := IPDelCliente(c); !key.IPValida(ip) { + // Decir qué IP se vio: sin eso, del otro lado no hay forma de saber + // qué cargar en la llave y se prueba a ciegas. + return c.Status(403).JSON(fiber.Map{ + "error": "IP no autorizada para esta API key", + "ip_vista": ip, + "ip_permitida": key.IPPermitida, + }) } - go models.RegistrarUsoApiKey(key.ID, c.IP()) + go models.RegistrarUsoApiKey(key.ID, IPDelCliente(c)) c.Locals("api_key", key) return c.Next() } diff --git a/rest/middlewares/ip_cliente.go b/rest/middlewares/ip_cliente.go new file mode 100644 index 0000000..82907bf --- /dev/null +++ b/rest/middlewares/ip_cliente.go @@ -0,0 +1,30 @@ +package middlewares + +import ( + "strings" + + "github.com/gofiber/fiber/v2" +) + +// IPDelCliente devuelve la IP pública desde la que se hizo la petición. +// +// c.IP() no sirve para esto en producción: detrás del proxy del hosting +// devuelve la IP interna del proxy (10.x.x.x), así que ninguna IP pública +// autorizada coincidía nunca y toda llave con restricción de IP daba 403. +// +// Se toma la ÚLTIMA entrada de X-Forwarded-For, no la primera: con un solo +// proxy adelante, esa es la que escribió el proxy y no la puede falsear quien +// llama. Si el cliente manda su propio X-Forwarded-For, el proxy le agrega la +// IP real al final — quedarse con la primera sería creerle al que llama. +func IPDelCliente(c *fiber.Ctx) string { + if xff := c.Get("X-Forwarded-For"); xff != "" { + partes := strings.Split(xff, ",") + if ip := strings.TrimSpace(partes[len(partes)-1]); ip != "" { + return ip + } + } + if real := strings.TrimSpace(c.Get("X-Real-Ip")); real != "" { + return real + } + return c.IP() +} diff --git a/rest/middlewares/ip_cliente_test.go b/rest/middlewares/ip_cliente_test.go new file mode 100644 index 0000000..baa44c4 --- /dev/null +++ b/rest/middlewares/ip_cliente_test.go @@ -0,0 +1,48 @@ +package middlewares + +import ( + "net/http/httptest" + "testing" + + "github.com/gofiber/fiber/v2" +) + +func TestIPDelCliente(t *testing.T) { + casos := []struct { + nombre string + xff string + real string + want string + }{ + {"un solo proxy", "46.202.93.92", "", "46.202.93.92"}, + // Si quien llama manda su propio X-Forwarded-For, el proxy le agrega la + // IP real al final. Quedarse con la primera sería dejar que elija su IP. + {"cliente intentando falsear", "1.2.3.4, 46.202.93.92", "", "46.202.93.92"}, + {"con espacios", " 10.0.0.1 , 46.202.93.92 ", "", "46.202.93.92"}, + {"sin xff, con x-real-ip", "", "46.202.93.92", "46.202.93.92"}, + } + + app := fiber.New() + app.Get("/ip", func(c *fiber.Ctx) error { + return c.SendString(IPDelCliente(c)) + }) + + for _, cs := range casos { + req := httptest.NewRequest("GET", "/ip", nil) + if cs.xff != "" { + req.Header.Set("X-Forwarded-For", cs.xff) + } + if cs.real != "" { + req.Header.Set("X-Real-Ip", cs.real) + } + resp, err := app.Test(req) + if err != nil { + t.Fatalf("%s: %v", cs.nombre, err) + } + buf := make([]byte, 64) + n, _ := resp.Body.Read(buf) + if got := string(buf[:n]); got != cs.want { + t.Errorf("%s: IPDelCliente = %q, want %q", cs.nombre, got, cs.want) + } + } +}