mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-10-04 00:39:04 +02:00
67 lines
2.1 KiB
Go
67 lines
2.1 KiB
Go
package iplocation
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"net/netip"
|
|
"strings"
|
|
|
|
"golang.org/x/text/language"
|
|
"golang.org/x/text/language/display"
|
|
)
|
|
|
|
type cloudflareLocationKey struct{}
|
|
|
|
type cloudflareLocation struct {
|
|
ipAddress string
|
|
country string
|
|
city string
|
|
}
|
|
|
|
// WithCloudflareLocation captures the client's location while the originating HTTP request is available
|
|
// Callers must only use this when the deployment explicitly trusts Cloudflare's location headers
|
|
func WithCloudflareLocation(ctx context.Context, ipAddress, countryCode, city string) context.Context {
|
|
location := cloudflareLocation{ipAddress: ipAddress, city: strings.TrimSpace(city)}
|
|
|
|
// Cloudflare supplies an ISO country code, while audit logs and notification emails display English country names
|
|
code := strings.ToUpper(strings.TrimSpace(countryCode))
|
|
if len(code) == 2 {
|
|
region, err := language.ParseRegion(code)
|
|
if err == nil && region.IsCountry() {
|
|
location.country = display.English.Regions().Name(region)
|
|
}
|
|
}
|
|
|
|
return context.WithValue(ctx, cloudflareLocationKey{}, location)
|
|
}
|
|
|
|
// CloudflareResolver resolves locations using trusted Cloudflare request headers
|
|
type CloudflareResolver struct{}
|
|
|
|
func NewCloudflareResolver() *CloudflareResolver {
|
|
return &CloudflareResolver{}
|
|
}
|
|
|
|
// GetLocationByIP returns the country and city for the client making the current request
|
|
func (r *CloudflareResolver) GetLocationByIP(ctx context.Context, ipAddress string) (country string, city string, err error) {
|
|
if ipAddress == "" {
|
|
return "", "", nil
|
|
}
|
|
|
|
// Keep local network labels consistent across location providers
|
|
if country, city := LocalLocation(net.ParseIP(ipAddress)); country != "" {
|
|
return country, city, nil
|
|
}
|
|
if _, err := netip.ParseAddr(ipAddress); err != nil {
|
|
return "", "", fmt.Errorf("failed to parse IP address: %w", err)
|
|
}
|
|
|
|
// Headers describe only the request's client, never another IP being inspected
|
|
location, ok := ctx.Value(cloudflareLocationKey{}).(cloudflareLocation)
|
|
if !ok || location.ipAddress != ipAddress {
|
|
return "", "", nil
|
|
}
|
|
return location.country, location.city, nil
|
|
}
|