This commit is contained in:
Lizandro Guarnizo
2026-05-11 22:13:43 -05:00
parent f923e43515
commit 93568c95c3
2 changed files with 71 additions and 33 deletions
+31
View File
@@ -182,6 +182,28 @@ type CFTokenVerify struct {
ExpiresOn string `json:"expires_on"`
}
// CFTokenPolicy representa una política de permisos del token.
type CFTokenPolicy struct {
ID string `json:"id"`
Effect string `json:"effect"`
Resources map[string]string `json:"resources"`
PermGroups []CFTokenPermGroup `json:"permission_groups"`
}
// CFTokenPermGroup un grupo de permisos.
type CFTokenPermGroup struct {
ID string `json:"id"`
Name string `json:"name"`
}
// CFTokenDetail detalle completo de un token incluyendo sus políticas.
type CFTokenDetail struct {
ID string `json:"id"`
Name string `json:"name"`
Status string `json:"status"`
Policies []CFTokenPolicy `json:"policies"`
}
// VerifyToken verifica la validez del API token usando /user/tokens/verify.
func (c *CloudflareClient) VerifyToken() (*CFTokenVerify, error) {
var result CFTokenVerify
@@ -191,6 +213,15 @@ func (c *CloudflareClient) VerifyToken() (*CFTokenVerify, error) {
return &result, nil
}
// GetTokenDetail obtiene el detalle completo de un token (permisos y recursos).
func (c *CloudflareClient) GetTokenDetail(tokenID string) (*CFTokenDetail, error) {
var detail CFTokenDetail
if err := c.get("/user/tokens/"+tokenID, &detail); err != nil {
return nil, err
}
return &detail, nil
}
// GetZones lista todas las zonas de la cuenta.
func (c *CloudflareClient) GetZones() ([]CFZone, error) {
var zones []CFZone
+40 -33
View File
@@ -101,7 +101,7 @@ func cloudflareClient() (*services.CloudflareClient, error) {
return services.NewCloudflareClient(sanitizeCFToken(cfg.APIToken), sanitizeCFToken(cfg.AccountID)), nil
}
// VerifyCloudflareToken verifica que el token activo sea válido contra la API de Cloudflare.
// VerifyCloudflareToken verifica el token y devuelve sus permisos reales.
func VerifyCloudflareToken(c *fiber.Ctx) error {
cfg, err := models.GetCloudflareConfig()
if err != nil {
@@ -116,70 +116,77 @@ func VerifyCloudflareToken(c *fiber.Ctx) error {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"ok": false, "error": "Token vacío"})
}
dirty := len([]rune(raw)) != len([]rune(token))
client := services.NewCloudflareClient(token, sanitizeCFToken(cfg.AccountID))
// Primer carácter y último para diagnóstico sin exponer el token completo
runes := []rune(token)
preview := ""
if len(runes) >= 8 {
preview = string(runes[:4]) + "…" + string(runes[len(runes)-4:])
}
// 1. Verificar que el token sea válido
client := services.NewCloudflareClient(token, sanitizeCFToken(cfg.AccountID))
// 1. Verificar token
verify, err := client.VerifyToken()
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"ok": false,
"step": "verify_token",
"error": err.Error(),
"hint": "El token no es válido. Crea uno nuevo en dash.cloudflare.com → Mi Perfil → API Tokens",
"hint": "Token inválido. Crea uno nuevo en dash.cloudflare.com → Mi Perfil → API Tokens",
"token_len": len(token),
"token_prev": preview,
"was_dirty": dirty,
})
}
// 2. Probar Zone:Zone:Read (listar zonas)
_, zonesErr := client.GetZones()
// 2. Obtener políticas reales del token
detail, detailErr := client.GetTokenDetail(verify.ID)
detailErrMsg := ""
if detailErr != nil {
detailErrMsg = detailErr.Error()
}
// 3. Probar Zone:Zone:Read
zones, zonesErr := client.GetZones()
zonesOK := zonesErr == nil
zonesErrMsg := ""
if zonesErr != nil {
zonesErrMsg = zonesErr.Error()
}
// 3. Probar Zone:DNS:Read (leer DNS de la primera zona disponible)
// 4. Probar Zone:DNS:Read en la primera zona
dnsOK := false
dnsErrMsg := ""
if zonesOK {
zones, _ := client.GetZones()
if len(zones) > 0 {
_, dnsErr := client.GetDNSRecords(zones[0].ID)
dnsOK = dnsErr == nil
if dnsErr != nil {
dnsErrMsg = dnsErr.Error()
}
dnsZoneID := ""
if zonesOK && len(zones) > 0 {
dnsZoneID = zones[0].ID
_, dnsErr := client.GetDNSRecords(dnsZoneID)
dnsOK = dnsErr == nil
if dnsErr != nil {
dnsErrMsg = dnsErr.Error()
}
}
hint := "Token válido con todos los permisos necesarios"
if !zonesOK {
hint = "Falta permiso Zone:Zone:Read"
} else if !dnsOK {
hint = "Falta permiso Zone:DNS:Read — o el token tiene scope de zonas específicas y no incluye la zona consultada. En Cloudflare: edita el token → Zone Resources → cambia a 'All zones'."
}
return c.JSON(fiber.Map{
"ok": true,
"token_len": len(token),
"token_prev": preview,
"was_dirty": dirty,
"ok": true,
"token_status": verify.Status,
"permissions": fiber.Map{
"zone_read": fiber.Map{"ok": zonesOK, "error": zonesErrMsg},
"dns_read": fiber.Map{"ok": dnsOK, "error": dnsErrMsg},
"token_id": verify.ID,
"token_len": len(token),
"token_prev": preview,
"was_dirty": dirty,
"detail_error": detailErrMsg,
"policies": func() interface{} { if detail != nil { return detail.Policies }; return nil }(),
"permissions_test": fiber.Map{
"zone_read": fiber.Map{"ok": zonesOK, "error": zonesErrMsg},
"dns_read": fiber.Map{"ok": dnsOK, "error": dnsErrMsg, "zone_tested": dnsZoneID},
},
"hint": func() string {
if !zonesOK {
return "Falta permiso Zone:Zone:Read en el token"
}
if !dnsOK {
return "Falta permiso Zone:DNS:Read en el token. Edita el token en Cloudflare y agrega ese permiso."
}
return "Token válido con todos los permisos necesarios"
}(),
"hint": hint,
})
}