Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"sync"
"syscall"

"github.com/kiwiirc/webircgateway/pkg/recaptcha"
"github.com/kiwiirc/webircgateway/pkg/webircgateway"
)

Expand Down Expand Up @@ -39,6 +40,9 @@ func main() {
os.Exit(1)
}

// Start recaptcha cleanup function
recaptcha.Init()

runGateway(*configFile, *startSection)
}

Expand Down
67 changes: 65 additions & 2 deletions pkg/recaptcha/recaptcha.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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,
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion pkg/webircgateway/client_command_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down