package controllers import ( "math" "strconv" "strings" "github.com/gofiber/fiber/v2" "github.com/sujit-baniya/fiber-boilerplate/pkg/models" ) // scopesValidos son los grupos de /api/v2 que hoy sí respetan RequireScope. // El resto de endpoints de /api/v2 sigue funcionando solo con la llave // maestra (ADMIN_API_KEY) mientras se migran en una fase posterior. var scopesValidos = map[string]bool{ "oss": true, "query_runner": true, "usuarios": true, "pasarelas": true, } // ApiKeysIndex renderiza el panel de administración de API keys. func ApiKeysIndex(c *fiber.Ctx) error { return c.Render("api_keys", fiber.Map{ "user": c.Locals("user"), "modules": c.Locals("userModules"), }, "layouts/main") } func GetApiKeys(c *fiber.Ctx) error { page, _ := strconv.Atoi(c.Query("page", "1")) if page < 1 { page = 1 } limit := 20 offset := (page - 1) * limit items, total, err := models.GetAllApiKeys(limit, offset) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } return c.JSON(fiber.Map{ "items": items, "total": total, "totalPages": int(math.Ceil(float64(total) / float64(limit))), "page": page, }) } type apiKeyReq struct { Nombre string `json:"nombre"` IPPermitida string `json:"ip_permitida"` Scopes []string `json:"scopes"` Activa bool `json:"activa"` } func (r apiKeyReq) validar() (scopes []string, err error) { if strings.TrimSpace(r.Nombre) == "" { return nil, fiber.NewError(fiber.StatusBadRequest, "nombre es requerido") } if strings.TrimSpace(r.IPPermitida) == "" { return nil, fiber.NewError(fiber.StatusBadRequest, "ip_permitida es requerida (IP exacta o CIDR)") } for _, s := range r.Scopes { s = strings.ToLower(strings.TrimSpace(s)) if s == "" { continue } if !scopesValidos[s] { return nil, fiber.NewError(fiber.StatusBadRequest, "scope inválido: "+s) } scopes = append(scopes, s) } if len(scopes) == 0 { return nil, fiber.NewError(fiber.StatusBadRequest, "selecciona al menos un scope") } return scopes, nil } // CreateApiKeyHandler crea una nueva API key y devuelve el token en texto // plano — es la única respuesta donde vendrá completo. func CreateApiKeyHandler(c *fiber.Ctx) error { var req apiKeyReq if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "body inválido"}) } scopes, err := req.validar() if err != nil { return c.Status(err.(*fiber.Error).Code).JSON(fiber.Map{"error": err.Error()}) } item := &models.ApiKey{ Nombre: strings.TrimSpace(req.Nombre), IPPermitida: strings.TrimSpace(req.IPPermitida), Scopes: models.JoinModulos(scopes), Activa: true, CreadoPorID: extraerUserID(c), } tokenPlano, err := models.CreateApiKey(item) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } return c.Status(fiber.StatusCreated).JSON(fiber.Map{ "ok": true, "id": item.ID, "token": tokenPlano, "aviso": "Guarda este token ahora: no se volverá a mostrar completo.", }) } func UpdateApiKeyHandler(c *fiber.Ctx) error { id, err := strconv.ParseUint(c.Params("id"), 10, 64) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "id inválido"}) } var req apiKeyReq if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "body inválido"}) } scopes, verr := req.validar() if verr != nil { return c.Status(verr.(*fiber.Error).Code).JSON(fiber.Map{"error": verr.Error()}) } updates := map[string]interface{}{ "nombre": strings.TrimSpace(req.Nombre), "ip_permitida": strings.TrimSpace(req.IPPermitida), "scopes": models.JoinModulos(scopes), "activa": req.Activa, } if err := models.UpdateApiKey(uint(id), updates); err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } return c.JSON(fiber.Map{"ok": true}) } func RegenerarApiKeyHandler(c *fiber.Ctx) error { id, err := strconv.ParseUint(c.Params("id"), 10, 64) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "id inválido"}) } tokenPlano, err := models.RegenerarApiKeyToken(uint(id)) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } return c.JSON(fiber.Map{"ok": true, "token": tokenPlano, "aviso": "El token anterior dejó de funcionar. Guarda este ahora, no se volverá a mostrar."}) } func DeleteApiKeyHandler(c *fiber.Ctx) error { id, err := strconv.ParseUint(c.Params("id"), 10, 64) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "id inválido"}) } if err := models.DeleteApiKey(uint(id)); err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } return c.JSON(fiber.Map{"ok": true}) }