sojorn/go-backend/internal/middleware/geoip.go
Patrick Britton 5782563236 Add GeoIP middleware to block requests from outside North America
- Add GeoIP middleware that checks country codes
- Block all countries except US, CA, MX, and Central American countries
- Add setup script for GeoIP database
- Gracefully handle missing database (logs warning but continues)
2026-02-01 12:34:26 -06:00

93 lines
2.3 KiB
Go

package middleware
import (
"net"
"net/http"
"github.com/gin-gonic/gin"
"github.com/oschwald/geoip2-golang"
"github.com/rs/zerolog/log"
)
// GeoIPMiddleware blocks requests from outside North America
type GeoIPMiddleware struct {
db *geoip2.Reader
}
// NewGeoIPMiddleware creates a new GeoIP middleware
func NewGeoIPMiddleware(dbPath string) (*GeoIPMiddleware, error) {
db, err := geoip2.Open(dbPath)
if err != nil {
return nil, err
}
return &GeoIPMiddleware{db: db}, nil
}
// Close closes the GeoIP database
func (g *GeoIPMiddleware) Close() error {
return g.db.Close()
}
// Middleware returns the Gin middleware function
func (g *GeoIPMiddleware) Middleware() gin.HandlerFunc {
return func(c *gin.Context) {
// Get client IP
clientIP := c.ClientIP()
// Parse IP
ip := net.ParseIP(clientIP)
if ip == nil {
// Invalid IP, block it
log.Warn().Str("ip", clientIP).Msg("Invalid IP address, blocking request")
c.JSON(http.StatusForbidden, gin.H{"error": "Access denied"})
c.Abort()
return
}
// Skip for private/local IPs
if ip.IsPrivate() || ip.IsLoopback() || ip.IsLinkLocalUnicast() {
c.Next()
return
}
// Look up country
record, err := g.db.Country(ip)
if err != nil {
log.Warn().Str("ip", clientIP).Err(err).Msg("Failed to lookup country, blocking request")
c.JSON(http.StatusForbidden, gin.H{"error": "Access denied"})
c.Abort()
return
}
// Check if country is in North America
countryCode := record.Country.IsoCode
if !g.isNorthAmericanCountry(countryCode) {
log.Info().Str("ip", clientIP).Str("country", countryCode).Msg("Blocking request from outside North America")
c.JSON(http.StatusForbidden, gin.H{"error": "Access denied - region not supported"})
c.Abort()
return
}
c.Next()
}
}
// isNorthAmericanCountry checks if the country code is from North America
func (g *GeoIPMiddleware) isNorthAmericanCountry(countryCode string) bool {
northAmericanCountries := map[string]bool{
"US": true, // United States
"CA": true, // Canada
"MX": true, // Mexico
"GT": true, // Guatemala
"BZ": true, // Belize
"SV": true, // El Salvador
"HN": true, // Honduras
"NI": true, // Nicaragua
"CR": true, // Costa Rica
"PA": true, // Panama
}
return northAmericanCountries[countryCode]
}