carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package console
2
3// GET /api/cli/apps/{account}/{app}/geo?ip=<addr> (geoip spec §11.1): the
4// platform's GeoIP answer for an arbitrary address, for work that is not
5// the current request (batch jobs, logs, webhooks). The same fields as the
6// X-Carlos-Geo-* headers, plus the attribution the answering database's
7// licence requires. The queried address is never logged.
8
9import (
10 "net"
11 "net/http"
12
13 "github.com/carlosframework/platform/internal/geoip"
14)
15
16type geoResponse struct {
17 Found bool `json:"found"`
18 Country string `json:"country,omitempty"`
19 Subdivision string `json:"subdivision,omitempty"`
20 Lat *float64 `json:"lat,omitempty"`
21 Lon *float64 `json:"lon,omitempty"`
22 Source string `json:"source,omitempty"`
23 Attribution string `json:"attribution,omitempty"`
24 AttributionURL string `json:"attribution_url,omitempty"`
25}
26
27func (s *Server) handleAPIGeo(w http.ResponseWriter, r *http.Request) {
28 // The instance family's gate: the app's member, app and instance
29 // tokens, and its per-caller budget of 60 a minute.
30 if _, _, _, ok := s.instanceGateFor(w, r, "geo"); !ok {
31 return
32 }
33 ip := net.ParseIP(r.URL.Query().Get("ip"))
34 if ip == nil {
35 jsonError(w, http.StatusBadRequest, "ip: want an IPv4 or IPv6 address")
36 return
37 }
38 if s.cfg.GeoIPReader == nil {
39 jsonError(w, http.StatusServiceUnavailable, "geo unavailable: this deployment has no GeoIP database")
40 return
41 }
42 // One snapshot: the answer, its source and its attribution all come
43 // from the same reader (spec §5.4).
44 a := geoip.FromSnapshot(s.cfg.GeoIPReader.Lookup(ip))
45 switch a.State {
46 case geoip.StateOff:
47 jsonError(w, http.StatusServiceUnavailable, "geo unavailable: this deployment has no GeoIP database")
48 return
49 case geoip.StateMiss:
50 writeJSON(w, http.StatusOK, geoResponse{Found: false})
51 return
52 }
53 out := geoResponse{Found: true, Country: a.Country, Subdivision: a.Subdivision, Source: a.Source}
54 if a.HasPoint {
55 lat, lon := a.Lat, a.Lon
56 out.Lat, out.Lon = &lat, &lon
57 }
58 if at, ok := geoip.AttributionFor(a.Source); ok {
59 out.Attribution, out.AttributionURL = at.Text, at.URL
60 }
61 writeJSON(w, http.StatusOK, out)
62}
63