Class: Trackdown::Providers::CloudflareProvider

Inherits:
BaseProvider
  • Object
show all
Defined in:
lib/trackdown/providers/cloudflare_provider.rb

Overview

Provider that uses Cloudflare HTTP headers for IP geolocation This is the fastest and most lightweight option when your app is behind Cloudflare

Cloudflare must have "IP Geolocation" or "Add visitor location headers" enabled in the dashboard under Network settings or via Managed Transforms Exact header contract: https://developers.cloudflare.com/fundamentals/reference/http-headers/ Exact origin-protection guidance: https://developers.cloudflare.com/ssl/origin-configuration/authenticated-origin-pull/

Constant Summary collapse

COUNTRY_HEADER =
'HTTP_CF_IPCOUNTRY'
CITY_HEADER =
'HTTP_CF_IPCITY'
REGION_HEADER =
'HTTP_CF_REGION'
REGION_CODE_HEADER =
'HTTP_CF_REGION_CODE'
LATITUDE_HEADER =
'HTTP_CF_IPLATITUDE'
LONGITUDE_HEADER =
'HTTP_CF_IPLONGITUDE'
TIMEZONE_HEADER =
'HTTP_CF_TIMEZONE'
CONTINENT_HEADER =
'HTTP_CF_IPCONTINENT'
METRO_CODE_HEADER =
'HTTP_CF_METRO_CODE'
POSTAL_CODE_HEADER =
'HTTP_CF_POSTAL_CODE'
UNKNOWN_CODE =

Cloudflare's XX and T1 pseudo-codes do not name countries. Unicode also defines ZZ as unknown/invalid territory, so none is treated as a country. Cloudflare: https://developers.cloudflare.com/fundamentals/reference/http-headers/#cf-ipcountry Unicode ZZ semantics: https://www.unicode.org/reports/tr35/tr35-78/tr35.html#unicode_region_subtag_validity

'XX'
UNKNOWN_OR_INVALID_TERRITORY_CODE =
'ZZ'
TOR_CODE =
'T1'

Class Method Summary collapse

Methods inherited from BaseProvider

get_country_name, get_emoji_flag, parse_coordinate, request_provenance

Class Method Details

.available?(request: nil) ⇒ Boolean

Check if Cloudflare headers are available in the request

Returns:

  • (Boolean)


57
58
59
60
61
# File 'lib/trackdown/providers/cloudflare_provider.rb', line 57

def available?(request: nil)
  return false unless request

  !extract_country_code(request).nil?
end

.locate(_ip, request: nil) ⇒ LocationResult

Locate IP using Cloudflare headers

Parameters:

  • ip (String)

    The IP address (not used, as Cloudflare already resolved it)

  • request (ActionDispatch::Request) (defaults to: nil)

    Rails request object with Cloudflare headers

Returns:

Raises:



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/trackdown/providers/cloudflare_provider.rb', line 67

def locate(_ip, request: nil)
  raise Trackdown::Error, "CloudflareProvider requires a request object with Cloudflare headers" unless request

  provenance = request_provenance(request)
  country_code = extract_country_code(request)

  # If no valid country code, return unknown
  if country_code.nil? || country_code == UNKNOWN_CODE
    return LocationResult.unavailable(:provider_returned_unknown_country, **provenance)
  end

  country_name = get_country_name(country_code)
  city = extract_city(request)
  flag_emoji = get_emoji_flag(country_code)

  LocationResult.new(
    country_code, country_name, city, flag_emoji,
    region: extract_header(request, REGION_HEADER),
    region_code: extract_header(request, REGION_CODE_HEADER),
    continent: extract_header(request, CONTINENT_HEADER),
    timezone: extract_header(request, TIMEZONE_HEADER),
    latitude: parse_coordinate(request.env[LATITUDE_HEADER], range: LATITUDE_RANGE),
    longitude: parse_coordinate(request.env[LONGITUDE_HEADER], range: LONGITUDE_RANGE),
    postal_code: extract_header(request, POSTAL_CODE_HEADER),
    metro_code: extract_header(request, METRO_CODE_HEADER),
    # "T1" says the visitor came through Tor, which is precisely a country
    # Cloudflare could not determine. The code is kept, the claim is not.
    # Only Cloudflare's own two pseudo-codes are treated this way: a code
    # we simply haven't heard of (Kosovo's user-assigned "XK", say) is a
    # real answer, not an unresolved one.
    # https://developers.cloudflare.com/fundamentals/reference/http-headers/#cf-ipcountry
    unavailable_reason: (:provider_returned_unknown_country if country_code == TOR_CODE),
    **provenance
  )
end

.provider_nameObject



48
49
50
# File 'lib/trackdown/providers/cloudflare_provider.rb', line 48

def provider_name
  :cloudflare
end

.provider_sourceObject



52
53
54
# File 'lib/trackdown/providers/cloudflare_provider.rb', line 52

def provider_source
  :cloudflare_request_headers
end