Skip to content

Commit 98cb80a

Browse files
committed
Add option to redirect blocked request to a configured URL
1 parent 1337822 commit 98cb80a

File tree

3 files changed

+62
-5
lines changed

3 files changed

+62
-5
lines changed

geoblock.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ type Config struct {
5454
AddCountryHeader bool `yaml:"addCountryHeader"`
5555
HTTPStatusCodeDeniedRequest int `yaml:"httpStatusCodeDeniedRequest"`
5656
LogFilePath string `yaml:"logFilePath"`
57+
RedirectURLIfDenied string `yaml:"redirectUrlIfDenied"`
5758
}
5859

5960
type ipEntry struct {
@@ -91,6 +92,7 @@ type GeoBlock struct {
9192
httpStatusCodeDeniedRequest int
9293
database *lru.LRUCache
9394
logFile *os.File
95+
redirectURLIfDenied string
9496
name string
9597
}
9698

@@ -174,6 +176,7 @@ func New(ctx context.Context, next http.Handler, config *Config, name string) (h
174176
addCountryHeader: config.AddCountryHeader,
175177
httpStatusCodeDeniedRequest: config.HTTPStatusCodeDeniedRequest,
176178
logFile: logFile,
179+
redirectURLIfDenied: config.RedirectURLIfDenied,
177180
name: name,
178181
}, nil
179182
}
@@ -194,8 +197,14 @@ func (a *GeoBlock) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
194197

195198
for _, requestIPAddress := range requestIPAddresses {
196199
if !a.allowDenyIPAddress(requestIPAddress, req) {
197-
rw.WriteHeader(a.httpStatusCodeDeniedRequest)
198-
a.next.ServeHTTP(rw, req)
200+
if len(a.redirectURLIfDenied) != 0 {
201+
rw.Header().Set("Location", a.redirectURLIfDenied)
202+
rw.WriteHeader(http.StatusMovedPermanently)
203+
a.next.ServeHTTP(rw, req)
204+
} else {
205+
rw.WriteHeader(a.httpStatusCodeDeniedRequest)
206+
a.next.ServeHTTP(rw, req)
207+
}
199208
}
200209
}
201210

@@ -553,6 +562,9 @@ func printConfiguration(config *Config, logger *log.Logger) {
553562
logger.Printf("countries: %v", config.Countries)
554563
logger.Printf("Denied request status code: %d", config.HTTPStatusCodeDeniedRequest)
555564
logger.Printf("Log file path: %s", config.LogFilePath)
565+
if len(config.RedirectURLIfDenied) != 0 {
566+
logger.Printf("Redirect URL on denied requests: %s", config.RedirectURLIfDenied)
567+
}
556568
}
557569

558570
func initializeLogFile(logFilePath string, logger *log.Logger) (*os.File, error) {

geoblock_test.go

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,35 @@ func TestDeniedCountry(t *testing.T) {
406406
assertStatusCode(t, recorder.Result(), http.StatusForbidden)
407407
}
408408

409+
func TestDeniedCountryWithRedirect(t *testing.T) {
410+
cfg := createTesterConfig()
411+
cfg.Countries = append(cfg.Countries, "CH")
412+
cfg.RedirectURLIfDenied = "https://google.com"
413+
414+
ctx := context.Background()
415+
next := http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {})
416+
417+
handler, err := geoblock.New(ctx, next, cfg, "GeoBlock")
418+
if err != nil {
419+
t.Fatal(err)
420+
}
421+
422+
recorder := httptest.NewRecorder()
423+
424+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "http://localhost", nil)
425+
if err != nil {
426+
t.Fatal(err)
427+
}
428+
429+
req.Header.Add(xForwardedFor, caExampleIP)
430+
431+
handler.ServeHTTP(recorder, req)
432+
433+
result := recorder.Result()
434+
assertStatusCode(t, result, http.StatusMovedPermanently)
435+
assertResponseHeader(t, result, "Location", cfg.RedirectURLIfDenied)
436+
}
437+
409438
func TestCustomDeniedRequestStatusCode(t *testing.T) {
410439
cfg := createTesterConfig()
411440
cfg.Countries = append(cfg.Countries, "CH")
@@ -860,7 +889,7 @@ func TestCountryHeader(t *testing.T) {
860889

861890
handler.ServeHTTP(recorder, req)
862891

863-
assertHeader(t, req, CountryHeader, "CA")
892+
assertRequestHeader(t, req, CountryHeader, "CA")
864893
}
865894

866895
func TestIpGeolocationHttpField(t *testing.T) {
@@ -891,7 +920,7 @@ func TestIpGeolocationHttpField(t *testing.T) {
891920

892921
handler.ServeHTTP(recorder, req)
893922

894-
assertHeader(t, req, CountryHeader, "CA")
923+
assertRequestHeader(t, req, CountryHeader, "CA")
895924
assertStatusCode(t, recorder.Result(), http.StatusOK)
896925
}
897926

@@ -987,14 +1016,22 @@ func assertStatusCode(t *testing.T, req *http.Response, expected int) {
9871016
}
9881017
}
9891018

990-
func assertHeader(t *testing.T, req *http.Request, key string, expected string) {
1019+
func assertRequestHeader(t *testing.T, req *http.Request, key string, expected string) {
9911020
t.Helper()
9921021

9931022
if received := req.Header.Get(key); received != expected {
9941023
t.Errorf("header value mismatch: %s: %s <> %s", key, expected, received)
9951024
}
9961025
}
9971026

1027+
func assertResponseHeader(t *testing.T, response *http.Response, key string, expected string) {
1028+
t.Helper()
1029+
1030+
if received := response.Header.Get(key); received != expected {
1031+
t.Errorf("header value mismatch: %s: %s <> %s", key, expected, received)
1032+
}
1033+
}
1034+
9981035
type CountryCodeHandler struct {
9991036
ResponseCountryCode string
10001037
}

readme.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,3 +518,11 @@ Allows customizing the HTTP status code returned if the request was denied.
518518
### Define a custom log file `logFilePath`
519519

520520
Allows to define a target for the logs of the middleware. The path must look like the following: `logFilePath: "/log/geoblock.log"`. Make sure the folder is writeable.
521+
522+
### Define a custom log file `XForwardedForReverseProxy`
523+
524+
Basically tells GeoBlock to only allow/deny a request based on the first IP address in the X-ForwardedFor HTTP header. This is useful for servers behind e.g. a Cloudflare proxy.
525+
526+
### Define a custom log file `redirectUrlIfDenied`
527+
528+
Allows returning a HTTP 301 status code, which indicates that the requested resource has been moved. The URL which can be specified is used to redirect the client to. So instead of "blocking" the client, the client will be redirected to the configured URL.

0 commit comments

Comments
 (0)