veza/veza-backend-api/internal/services/geoip_service.go

91 lines
2.5 KiB
Go
Raw Normal View History

package services
import (
"net"
"os"
"strings"
"sync"
"go.uber.org/zap"
)
// GeoIPService provides IP geolocation using MaxMind GeoLite2 databases.
// F025: Géolocalisation connexions.
//
// If a MaxMind database is not available, falls back to a no-op resolver.
// To enable MaxMind, set GEOIP_DB_PATH to the path of a GeoLite2-City.mmdb file.
type GeoIPService struct {
logger *zap.Logger
dbPath string
mu sync.RWMutex
// In production, this would use github.com/oschwald/maxminddb-golang
// For now, we implement a basic lookup that can be extended
enabled bool
}
// NewGeoIPService creates a GeoIP service.
// Reads GEOIP_DB_PATH from environment. If not set, geolocation is disabled.
func NewGeoIPService(logger *zap.Logger) *GeoIPService {
dbPath := os.Getenv("GEOIP_DB_PATH")
enabled := false
if dbPath != "" {
if _, err := os.Stat(dbPath); err == nil {
enabled = true
logger.Info("GeoIP service enabled", zap.String("db_path", dbPath))
} else {
logger.Warn("GeoIP database not found, geolocation disabled", zap.String("db_path", dbPath))
}
} else {
logger.Info("GeoIP disabled (GEOIP_DB_PATH not set)")
}
return &GeoIPService{
logger: logger,
dbPath: dbPath,
enabled: enabled,
}
}
// Lookup resolves an IP address to country (ISO 3166-1 alpha-2) and city.
// F025: Returns empty strings if GeoIP is not available or IP is private/localhost.
func (s *GeoIPService) Lookup(ip string) (country, city string) {
if !s.enabled {
return "", ""
}
// Skip private/localhost IPs
parsed := net.ParseIP(ip)
if parsed == nil || parsed.IsLoopback() || parsed.IsPrivate() || parsed.IsUnspecified() {
return "", ""
}
// Strip IPv4-mapped IPv6 prefix
ipStr := parsed.String()
if strings.HasPrefix(ipStr, "::ffff:") {
ipStr = strings.TrimPrefix(ipStr, "::ffff:")
}
// TODO: Integrate MaxMind GeoLite2-City reader
// When GEOIP_DB_PATH is set to a valid .mmdb file, use:
// db, _ := maxminddb.Open(s.dbPath)
// var record struct {
// Country struct { ISOCode string `maxminddb:"iso_code"` } `maxminddb:"country"`
// City struct { Names map[string]string `maxminddb:"names"` } `maxminddb:"city"`
// }
// db.Lookup(parsed, &record)
// country = record.Country.ISOCode
// city = record.City.Names["en"]
//
// For now, return empty strings (geolocation column populated when MaxMind DB is installed)
return "", ""
}
// IsEnabled returns whether GeoIP resolution is active.
func (s *GeoIPService) IsEnabled() bool {
s.mu.RLock()
defer s.mu.RUnlock()
return s.enabled
}