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 }