From dca0adec81c06ea32e2f20de766f72f8fa76ccf4 Mon Sep 17 00:00:00 2001 From: ItsOnlyBinary Date: Tue, 2 Feb 2021 18:24:17 +0000 Subject: [PATCH] Add recaptcha cache so multiple networks can use the same response --- main.go | 4 ++ pkg/recaptcha/recaptcha.go | 67 +++++++++++++++++++- pkg/webircgateway/client_command_handlers.go | 2 +- 3 files changed, 70 insertions(+), 3 deletions(-) diff --git a/main.go b/main.go index efd812b..b025672 100644 --- a/main.go +++ b/main.go @@ -10,6 +10,7 @@ import ( "sync" "syscall" + "github.com/kiwiirc/webircgateway/pkg/recaptcha" "github.com/kiwiirc/webircgateway/pkg/webircgateway" ) @@ -39,6 +40,9 @@ func main() { os.Exit(1) } + // Start recaptcha cleanup function + recaptcha.Init() + runGateway(*configFile, *startSection) } diff --git a/pkg/recaptcha/recaptcha.go b/pkg/recaptcha/recaptcha.go index 2d602fc..488e2e8 100644 --- a/pkg/recaptcha/recaptcha.go +++ b/pkg/recaptcha/recaptcha.go @@ -7,9 +7,21 @@ import ( "io/ioutil" "net/http" "net/url" + "sync" "time" ) +var cacheVerified sync.Map +var cacheLife, _ = time.ParseDuration("90s") +var cacheFrequency, _ = time.ParseDuration("10m") +var cacheTicker = time.NewTicker(cacheFrequency) + +// CacheItem represents an item in response cache +type CacheItem struct { + created int64 + remoteAddr string +} + // R type represents an object of Recaptcha and has public property Secret, // which is secret obtained from google recaptcha tool admin interface type R struct { @@ -24,9 +36,55 @@ type googleResponse struct { ErrorCodes []string `json:"error-codes"` } +// Init starts recpatcha response cache cleanup +func Init() { + go func() { + for range cacheTicker.C { + cleanCache() + } + }() +} + +func cleanCache() { + cacheVerified.Range(func(key interface{}, value interface{}) bool { + cacheItem := value.(CacheItem) + expired := time.Now().Unix() - int64(cacheLife.Seconds()) + if cacheItem.created < expired { + cacheVerified.Delete(key) + } + return true + }) +} + +func verifyCached(response string, remoteAddr string) bool { + cacheInterface, ok := cacheVerified.Load(response) + if !ok { + // Not in cache + return false + } + cacheItem := cacheInterface.(CacheItem) + if cacheItem.remoteAddr != remoteAddr { + // None matching remoteAddr + return false + } + expired := time.Now().Unix() - int64(cacheLife.Seconds()) + if cacheItem.created < expired { + // Cached response expired + cacheVerified.Delete(remoteAddr) + return false + } + + // Valid response + return true +} + // VerifyResponse is a method similar to `Verify`; but doesn't parse the form for you. Useful if // you're receiving the data as a JSON object from a javascript app or similar. -func (r *R) VerifyResponse(response string) bool { +func (r *R) VerifyResponse(response string, remoteAddr string) bool { + if verifyCached(response, remoteAddr) { + // Response is cached and still valid + return true + } r.lastError = make([]string, 1) client := &http.Client{Timeout: 20 * time.Second} resp, err := client.PostForm(r.URL, @@ -47,7 +105,12 @@ func (r *R) VerifyResponse(response string) bool { r.lastError = append(r.lastError, err.Error()) return false } - if !gr.Success { + if gr.Success { + cacheVerified.Store(response, CacheItem{ + created: time.Now().Unix(), + remoteAddr: remoteAddr, + }) + } else { r.lastError = append(r.lastError, gr.ErrorCodes...) } return gr.Success diff --git a/pkg/webircgateway/client_command_handlers.go b/pkg/webircgateway/client_command_handlers.go index d895673..fa6dcf3 100644 --- a/pkg/webircgateway/client_command_handlers.go +++ b/pkg/webircgateway/client_command_handlers.go @@ -193,7 +193,7 @@ func (c *Client) ProcessLineFromClient(line string) (string, error) { Secret: c.Gateway.Config.ReCaptchaSecret, } - verified = captcha.VerifyResponse(message.Params[0]) + verified = captcha.VerifyResponse(message.Params[0], c.RemoteAddr) } if !verified {