feat: Add Cloudflare token verification endpoint
Add /api/v1/cloudflare/verify, /api/v1/cloudflare/zone endpoints so the Central UI's Cloudflare page can verify tokens and manage zone configuration. This was a missing endpoint from the extraction. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -212,6 +212,11 @@ func (api *API) RegisterRoutes(r *mux.Router) {
|
||||
r.HandleFunc("/api/v1/crowdsec/bouncers", api.CrowdSecGetBouncers).Methods("GET")
|
||||
r.HandleFunc("/api/v1/crowdsec/bouncers/{name}", api.CrowdSecDeleteBouncer).Methods("DELETE")
|
||||
|
||||
// Cloudflare management
|
||||
r.HandleFunc("/api/v1/cloudflare/verify", api.CloudflareVerify).Methods("GET")
|
||||
r.HandleFunc("/api/v1/cloudflare/zone", api.CloudflareGetZoneID).Methods("GET")
|
||||
r.HandleFunc("/api/v1/cloudflare/zone", api.CloudflareSetZoneID).Methods("POST")
|
||||
|
||||
// Service registration
|
||||
r.HandleFunc("/api/v1/services", api.ServicesListAll).Methods("GET")
|
||||
r.HandleFunc("/api/v1/services", api.ServicesRegister).Methods("POST")
|
||||
|
||||
129
internal/api/v1/handlers_cloudflare.go
Normal file
129
internal/api/v1/handlers_cloudflare.go
Normal file
@@ -0,0 +1,129 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type cloudflareZone struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type cloudflareVerifyResponse struct {
|
||||
TokenConfigured bool `json:"tokenConfigured"`
|
||||
TokenValid bool `json:"tokenValid"`
|
||||
TokenStatus string `json:"tokenStatus"`
|
||||
Zones []cloudflareZone `json:"zones"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// CloudflareVerify checks whether the stored Cloudflare API token is valid
|
||||
// and returns the zones it can access.
|
||||
func (api *API) CloudflareVerify(w http.ResponseWriter, r *http.Request) {
|
||||
token := api.getCloudflareToken()
|
||||
if token == "" {
|
||||
respondJSON(w, http.StatusOK, cloudflareVerifyResponse{
|
||||
TokenConfigured: false,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
resp := cloudflareVerifyResponse{TokenConfigured: true}
|
||||
|
||||
tokenStatus, err := verifyCloudflareToken(token)
|
||||
if err != nil {
|
||||
resp.Error = err.Error()
|
||||
respondJSON(w, http.StatusOK, resp)
|
||||
return
|
||||
}
|
||||
resp.TokenStatus = tokenStatus
|
||||
resp.TokenValid = tokenStatus == "active"
|
||||
|
||||
zones, err := listCloudflareZones(token)
|
||||
if err != nil {
|
||||
resp.Error = "Token valid but failed to list zones: " + err.Error()
|
||||
respondJSON(w, http.StatusOK, resp)
|
||||
return
|
||||
}
|
||||
resp.Zones = zones
|
||||
|
||||
respondJSON(w, http.StatusOK, resp)
|
||||
}
|
||||
|
||||
// CloudflareGetZoneID returns the stored zone ID from secrets.
|
||||
func (api *API) CloudflareGetZoneID(w http.ResponseWriter, r *http.Request) {
|
||||
zoneID := api.getCloudflareZoneID()
|
||||
respondJSON(w, http.StatusOK, map[string]any{
|
||||
"zoneId": zoneID,
|
||||
"configured": zoneID != "",
|
||||
})
|
||||
}
|
||||
|
||||
// CloudflareSetZoneID stores the zone ID in global secrets.
|
||||
func (api *API) CloudflareSetZoneID(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
ZoneID string `json:"zoneId"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
respondError(w, http.StatusBadRequest, "Invalid request body")
|
||||
return
|
||||
}
|
||||
|
||||
secretsPath := filepath.Join(api.dataDir, "secrets.yaml")
|
||||
if err := api.secrets.SetSecret(secretsPath, "cloudflare.zoneId", req.ZoneID); err != nil {
|
||||
respondError(w, http.StatusInternalServerError, "Failed to save zone ID")
|
||||
return
|
||||
}
|
||||
|
||||
respondMessage(w, http.StatusOK, "Zone ID saved")
|
||||
}
|
||||
|
||||
func verifyCloudflareToken(apiToken string) (string, error) {
|
||||
req, err := http.NewRequest(http.MethodGet,
|
||||
"https://api.cloudflare.com/client/v4/user/tokens/verify", nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
req.Header.Set("Authorization", "Bearer "+apiToken)
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var result struct {
|
||||
Result struct {
|
||||
Status string `json:"status"`
|
||||
} `json:"result"`
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return result.Result.Status, nil
|
||||
}
|
||||
|
||||
func listCloudflareZones(apiToken string) ([]cloudflareZone, error) {
|
||||
req, err := http.NewRequest(http.MethodGet,
|
||||
"https://api.cloudflare.com/client/v4/zones", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Authorization", "Bearer "+apiToken)
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var result struct {
|
||||
Result []cloudflareZone `json:"result"`
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result.Result, nil
|
||||
}
|
||||
Reference in New Issue
Block a user