added IP Block and notification for failed logins

This commit is contained in:
ghostersk
2026-03-08 17:35:58 +00:00
parent 948e111cc6
commit ef85246806
11 changed files with 1152 additions and 11 deletions

97
internal/geo/geo.go Normal file
View File

@@ -0,0 +1,97 @@
// Package geo provides IP geolocation lookup using the free ip-api.com service.
// No API key is required. Rate limit: 45 requests/minute on the free tier.
// Results are cached in memory to reduce API calls.
package geo
import (
"encoding/json"
"fmt"
"log"
"net"
"net/http"
"strings"
"sync"
"time"
)
type GeoResult struct {
CountryCode string
Country string
Cached bool
}
type cacheEntry struct {
result GeoResult
fetchedAt time.Time
}
var (
mu sync.Mutex
cache = make(map[string]*cacheEntry)
)
const cacheTTL = 24 * time.Hour
// Lookup returns the country for an IP address.
// Returns empty strings on failure (private IPs, rate limit, etc.).
func Lookup(ip string) GeoResult {
// Skip private / loopback
parsed := net.ParseIP(ip)
if parsed == nil || isPrivate(parsed) {
return GeoResult{}
}
mu.Lock()
if e, ok := cache[ip]; ok && time.Since(e.fetchedAt) < cacheTTL {
mu.Unlock()
r := e.result
r.Cached = true
return r
}
mu.Unlock()
result := fetchFromAPI(ip)
mu.Lock()
cache[ip] = &cacheEntry{result: result, fetchedAt: time.Now()}
mu.Unlock()
return result
}
func fetchFromAPI(ip string) GeoResult {
url := fmt.Sprintf("http://ip-api.com/json/%s?fields=status,country,countryCode", ip)
client := &http.Client{Timeout: 3 * time.Second}
resp, err := client.Get(url)
if err != nil {
log.Printf("geo lookup failed for %s: %v", ip, err)
return GeoResult{}
}
defer resp.Body.Close()
var data struct {
Status string `json:"status"`
Country string `json:"country"`
CountryCode string `json:"countryCode"`
}
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil || data.Status != "success" {
return GeoResult{}
}
return GeoResult{
CountryCode: strings.ToUpper(data.CountryCode),
Country: data.Country,
}
}
func isPrivate(ip net.IP) bool {
privateRanges := []string{
"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16",
"127.0.0.0/8", "::1/128", "fc00::/7",
}
for _, cidr := range privateRanges {
_, network, _ := net.ParseCIDR(cidr)
if network != nil && network.Contains(ip) {
return true
}
}
return false
}