Alternativa al ADMIN_API_KEY único de entorno, que sigue funcionando como llave maestra para no romper integraciones existentes. - Modelo ApiKey: token hasheado, IP/CIDR obligatoria (fail-closed sin IP), scopes habilitados, último uso (fecha + IP). - AdminApiAuth() acepta ahora tanto la llave maestra como una ApiKey; nuevo middleware RequireScope(scope) para gatear grupos de rutas. - Fase 1: scopes aplicados a lo más sensible — oss (archivos), query_runner (SQL arbitrario), usuarios (usuarios/roles/módulos), pasarelas (credenciales de pago). El resto de /api/v2 sigue con la llave maestra hasta una fase 2. - Panel /app/api-keys: crear/editar/revocar, token visible solo al crear, scopes por checkbox, IP obligatoria.
66 lines
2.0 KiB
Go
66 lines
2.0 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 !key.IPValida(c.IP()) {
|
|
return c.Status(403).JSON(fiber.Map{"error": "IP no autorizada para esta API key"})
|
|
}
|
|
|
|
go models.RegistrarUsoApiKey(key.ID, c.IP())
|
|
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()
|
|
}
|
|
}
|