Files
soft_usite/rest/middlewares/hermes_auth.go
T
Lizandro GuarnizoandClaude Opus 5 c75a6d7deb fix(api-keys): la restricción por IP comparaba contra la IP del proxy, no la del cliente
c.IP() detrás del proxy del hosting devuelve la IP interna del contenedor que
reenvía (10.x.x.x). Contra eso, ninguna IP pública cargada en una llave podía
coincidir: toda API key con restricción de IP daba 403, que es exactamente lo
que le pasó a la integración de vCard.

Ahora se lee X-Forwarded-For, y se toma la ÚLTIMA entrada, no la primera: con
un proxy adelante esa es la que escribió el proxy. 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 cada uno declare su IP y la restricción no valdría
nada. El test fija ese caso.

Mismo arreglo en la autenticación de Pagos Externos, que comparaba igual.

El 403 ahora dice qué IP se vio y cuál está permitida: sin eso, del otro lado
se prueba a ciegas.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 21:02:45 -05:00

72 lines
2.2 KiB
Go

package middlewares
import (
"os"
"strings"
"github.com/gofiber/fiber/v2"
"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
)
// AdminApiAuth protege /api/v2. Acepta dos tipos de credencial:
// 1. ADMIN_API_KEY de entorno — llave maestra, acceso total, la que ya usan
// las integraciones existentes (no se retira para no romperlas).
// 2. Una ApiKey emitida desde /app/api-keys — token propio, hasheado en DB,
// restringida a una IP/CIDR obligatoria y a los scopes que se le hayan
// habilitado. RequireScope() más adelante en la cadena decide si esa
// llave puede usar el grupo de endpoints puntual.
func AdminApiAuth() fiber.Handler {
return func(c *fiber.Ctx) error {
token := ""
auth := c.Get("Authorization")
if strings.HasPrefix(auth, "Bearer ") {
token = auth[7:]
}
if token == "" {
token = c.Get("X-API-Key")
}
if token == "" {
return c.Status(401).JSON(fiber.Map{"error": "unauthorized"})
}
if masterKey := os.Getenv("ADMIN_API_KEY"); masterKey != "" && token == masterKey {
c.Locals("api_key_master", true)
return c.Next()
}
key, err := models.FindApiKeyActivaByToken(token)
if err != nil {
return c.Status(401).JSON(fiber.Map{"error": "unauthorized"})
}
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, IPDelCliente(c))
c.Locals("api_key", key)
return c.Next()
}
}
// RequireScope gatea un grupo de rutas a un scope puntual. La llave maestra
// (ADMIN_API_KEY de entorno) siempre pasa; una ApiKey scoped solo pasa si
// tiene ese scope habilitado.
func RequireScope(scope string) fiber.Handler {
return func(c *fiber.Ctx) error {
if master, _ := c.Locals("api_key_master").(bool); master {
return c.Next()
}
key, ok := c.Locals("api_key").(*models.ApiKey)
if !ok || key == nil || !key.TieneScope(scope) {
return c.Status(403).JSON(fiber.Map{"error": "Esta API key no tiene el scope '" + scope + "'"})
}
return c.Next()
}
}