feat: add GeoIP country lookup with embedded DB-IP Lite database (PLAN.md 4.3)
Embeds a DB-IP Lite country MMDB (~5MB) in the binary via go:embed, keeping the single-binary deployment story clean. Country codes are stored alongside login attempts and sessions, shown in the dashboard (Top IPs, Top Countries card, Recent/Active Sessions, session detail). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
51
internal/geoip/geoip.go
Normal file
51
internal/geoip/geoip.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package geoip
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"net"
|
||||
|
||||
"github.com/oschwald/maxminddb-golang"
|
||||
)
|
||||
|
||||
//go:embed dbip-country-lite.mmdb
|
||||
var mmdbData []byte
|
||||
|
||||
// Reader provides country-level GeoIP lookups using an embedded DB-IP Lite database.
|
||||
type Reader struct {
|
||||
db *maxminddb.Reader
|
||||
}
|
||||
|
||||
// New opens the embedded MMDB and returns a ready-to-use Reader.
|
||||
func New() (*Reader, error) {
|
||||
db, err := maxminddb.FromBytes(mmdbData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Reader{db: db}, nil
|
||||
}
|
||||
|
||||
type countryRecord struct {
|
||||
Country struct {
|
||||
ISOCode string `maxminddb:"iso_code"`
|
||||
} `maxminddb:"country"`
|
||||
}
|
||||
|
||||
// Lookup returns the ISO 3166-1 alpha-2 country code for the given IP address,
|
||||
// or an empty string if the lookup fails or no result is found.
|
||||
func (r *Reader) Lookup(ipStr string) string {
|
||||
ip := net.ParseIP(ipStr)
|
||||
if ip == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
var record countryRecord
|
||||
if err := r.db.Lookup(ip, &record); err != nil {
|
||||
return ""
|
||||
}
|
||||
return record.Country.ISOCode
|
||||
}
|
||||
|
||||
// Close releases resources held by the reader.
|
||||
func (r *Reader) Close() error {
|
||||
return r.db.Close()
|
||||
}
|
||||
Reference in New Issue
Block a user