ip
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user