Files
wild-central/internal/api/v1/handlers_cloudflare.go
Paul Payne 68d6fde80d Show routes, TLS certs, and port-forwarding on services page
- Add Routes model to services UI (paths, headers, IP whitelisting per route)
- Show TLS cert info per service with inline provision/renew actions
- Remove TLS Certificates section from dashboard (now on services page)
- Make gateway router port list dynamic from config + VPN state
- Add TODO for header validation in HAProxy config generation
2026-07-10 06:10:40 +00:00

128 lines
3.2 KiB
Go

package v1
import (
"encoding/json"
"net/http"
)
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
}
if err := api.secrets.SetSecret("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
}