diff --git a/go.mod b/go.mod index 67621e3..e5b1971 100644 --- a/go.mod +++ b/go.mod @@ -35,6 +35,8 @@ require ( github.com/jinzhu/now v1.1.5 // indirect github.com/mattn/go-sqlite3 v1.14.14 // indirect github.com/microcosm-cc/bluemonday v1.0.19 // indirect + github.com/oschwald/geoip2-golang v1.9.0 // indirect + github.com/oschwald/maxminddb-golang v1.11.0 // indirect github.com/russross/blackfriday v1.5.2 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd // indirect @@ -50,7 +52,7 @@ require ( github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect golang.org/x/image v0.0.0-20220722155232-062f8c9fd539 // indirect golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect - golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect + golang.org/x/sys v0.9.0 // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/tools v0.1.12 // indirect diff --git a/go.sum b/go.sum index e1cd0cc..8243f6f 100644 --- a/go.sum +++ b/go.sum @@ -109,6 +109,10 @@ github.com/mattn/go-sqlite3 v1.14.14 h1:qZgc/Rwetq+MtyE18WhzjokPD93dNqLGNT3QJuLv github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/microcosm-cc/bluemonday v1.0.19 h1:OI7hoF5FY4pFz2VA//RN8TfM0YJ2dJcl4P4APrCWy6c= github.com/microcosm-cc/bluemonday v1.0.19/go.mod h1:QNzV2UbLK2/53oIIwTOyLUSABMkjZ4tqiyC1g/DyqxE= +github.com/oschwald/geoip2-golang v1.9.0 h1:uvD3O6fXAXs+usU+UGExshpdP13GAqp4GBrzN7IgKZc= +github.com/oschwald/geoip2-golang v1.9.0/go.mod h1:BHK6TvDyATVQhKNbQBdrj9eAvuwOMi2zSFXizL3K81Y= +github.com/oschwald/maxminddb-golang v1.11.0 h1:aSXMqYR/EPNjGE8epgqwDay+P30hCBZIveY0WZbAWh0= +github.com/oschwald/maxminddb-golang v1.11.0/go.mod h1:YmVI+H0zh3ySFR3w+oz8PCfglAFj3PuCmui13+P9zDg= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -214,6 +218,8 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+R golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= +golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= diff --git a/pkg/config/geo_gate.go b/pkg/config/geo_gate.go new file mode 100644 index 0000000..c017071 --- /dev/null +++ b/pkg/config/geo_gate.go @@ -0,0 +1,20 @@ +package config + +// GeoIP gating rules. TODO: make dynamically configurable. + +// GeoIP database path (standard location on Fedora/Debian) +const GeoIPPath = "/usr/share/GeoIP/GeoLite2-City.mmdb" + +// US states to block. +var BlockUSStates = map[string]interface{}{ + "UT": nil, // Utah + "LA": nil, // Louisiana + "MS": nil, // Mississippi + "AR": nil, // Arkansas + "MT": nil, // Montana +} + +// Countries to block. +var BlockCountries = map[string]interface{}{ + // "DE": nil, +} diff --git a/pkg/middleware/geo_gate.go b/pkg/middleware/geo_gate.go new file mode 100644 index 0000000..bdf3a94 --- /dev/null +++ b/pkg/middleware/geo_gate.go @@ -0,0 +1,88 @@ +package middleware + +import ( + "fmt" + "net" + "net/http" + "strings" + + "code.nonshy.com/nonshy/website/pkg/config" + "code.nonshy.com/nonshy/website/pkg/controller/index" + "code.nonshy.com/nonshy/website/pkg/log" + "code.nonshy.com/nonshy/website/pkg/session" + "code.nonshy.com/nonshy/website/pkg/utility" + "github.com/oschwald/geoip2-golang" +) + +// GeoGate: block access to the site based on the user's location (due to local laws or regulations). +func GeoGate(handler http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + + // Flash errors to admins. + onError := func(err error) { + session.FlashError(w, r, "GeoIP: %s", err) + handler.ServeHTTP(w, r) + } + + // See where they're coming from. + db, err := geoip2.Open(config.GeoIPPath) + if err != nil { + onError(err) + return + } + defer db.Close() + + // If you are using strings that may be invalid, check that ip is not nil + addr := strings.SplitN(utility.IPAddress(r), ":", 2)[0] + ip := net.ParseIP(utility.IPAddress(r)) + log.Info("IP addr: %s (raw: %s)", ip, addr) + if ip != nil { + record, err := db.City(ip) + if err != nil { + onError(err) + return + } + + log.Info("Raw: %+v", record) + + // Blocked by US states + if record.Country.IsoCode == "US" { + for _, sub := range record.Subdivisions { + if _, ok := config.BlockUSStates[sub.IsoCode]; ok { + session.LogoutUser(w, r) + page := index.StaticTemplate("errors/geo_gate.html")() + page(w, r) + return + } + } + } + + // Blocked by country code + if _, ok := config.BlockCountries[record.Country.IsoCode]; ok { + session.LogoutUser(w, r) + page := index.StaticTemplate("errors/geo_gate.html")() + page(w, r) + return + } + + // Debug info + fmt.Printf("Portuguese (BR) city name: %v\n", record.City.Names["pt-BR"]) + if len(record.Subdivisions) > 0 { + fmt.Printf("English subdivision name: %v\n", record.Subdivisions[0].Names["en"]) + } + fmt.Printf("Russian country name: %v\n", record.Country.Names["ru"]) + fmt.Printf("ISO country code: %v\n", record.Country.IsoCode) + fmt.Printf("Time zone: %v\n", record.Location.TimeZone) + fmt.Printf("Coordinates: %v, %v\n", record.Location.Latitude, record.Location.Longitude) + // Output: + // Portuguese (BR) city name: Londres + // English subdivision name: England + // Russian country name: Великобритания + // ISO country code: GB + // Time zone: Europe/London + // Coordinates: 51.5142, -0.0931 + } + + handler.ServeHTTP(w, r) + }) +} diff --git a/pkg/router/router.go b/pkg/router/router.go index 57be1e8..6a18428 100644 --- a/pkg/router/router.go +++ b/pkg/router/router.go @@ -33,10 +33,11 @@ func New() http.Handler { mux.HandleFunc("/contact", index.Contact()) mux.HandleFunc("/login", account.Login()) mux.HandleFunc("/logout", account.Logout()) - mux.HandleFunc("/signup", account.Signup()) + mux.Handle("/signup", middleware.GeoGate(account.Signup())) mux.HandleFunc("/forgot-password", account.ForgotPassword()) mux.HandleFunc("/settings/confirm-email", account.ConfirmEmailChange()) mux.HandleFunc("/markdown", index.StaticTemplate("markdown.html")()) + mux.HandleFunc("/test/geo-gate", index.StaticTemplate("errors/geo_gate.html")()) // Login Required. Pages that non-certified users can access. mux.Handle("/me", middleware.LoginRequired(account.Dashboard())) diff --git a/pkg/utility/ip_address.go b/pkg/utility/ip_address.go new file mode 100644 index 0000000..4438001 --- /dev/null +++ b/pkg/utility/ip_address.go @@ -0,0 +1,23 @@ +package utility + +import ( + "net/http" + "strings" + + "code.nonshy.com/nonshy/website/pkg/config" +) + +/* +IPAddress returns the best guess at the user's IP address, as a string for logging. +*/ +func IPAddress(r *http.Request) string { + if config.Current.UseXForwardedFor { + if realIP := r.Header.Get("X-Real-IP"); realIP != "" { + return realIP + } + if xff := r.Header.Get("X-Forwarded-For"); xff != "" { + return strings.SplitN(xff, " ", 1)[0] + } + } + return r.RemoteAddr +} diff --git a/web/templates/errors/geo_gate.html b/web/templates/errors/geo_gate.html new file mode 100644 index 0000000..2de0bb3 --- /dev/null +++ b/web/templates/errors/geo_gate.html @@ -0,0 +1,28 @@ +{{define "content"}} +
+
+
+
+

Not Available

+
+
+
+ +
+

{{PrettyTitle}} is not available in your area

+

+ We regret to inform you that {{PrettyTitle}} will not be available in your area. + This may likely be due to local laws and regulations in your country or state of + origin regarding 'adult sites' and whether they are required to ask for your + photo ID to be on file. We do not, at this time, wish to handle such sensitive + information from our users (so that we don't risk such information being hacked + or leaked, which you can probably agree is a good call). +

+ +

+ If you already have an account with us, please contact support + for next steps (e.g., in case you would like us to delete your account). +

+
+
+{{end}}