diff --git a/Makefile b/Makefile
index bc1266f..81164a9 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,5 @@
run:
- go run *.go
+ SKIP_CAPTCHA=1 go run *.go
test:
jasmine-node .
diff --git a/assets/public/style.css b/assets/public/style.css
index 701e1d9..1fc9bed 100644
--- a/assets/public/style.css
+++ b/assets/public/style.css
@@ -264,3 +264,7 @@ form {
flex: 1 0;
flex-direction: column;
}
+
+#captcha {
+ display: none;
+}
diff --git a/assets/templates/form.html b/assets/templates/form.html
index a44de7d..90efeef 100644
--- a/assets/templates/form.html
+++ b/assets/templates/form.html
@@ -3,22 +3,24 @@
NoteHub — {{if .ID}}Edit{{else}}Add{{end}} note
-
-
+
+
-
+
+
diff --git a/render.go b/render.go
index 0ca3ccd..3b34410 100644
--- a/render.go
+++ b/render.go
@@ -17,6 +17,7 @@ var (
statuses = map[int]string{
400: "Bad request",
401: "Unauthorized",
+ 403: "Forbidden",
404: "Not found",
412: "Precondition failed",
429: "Too many requests",
diff --git a/server.go b/server.go
index e2d0ccb..90b7151 100644
--- a/server.go
+++ b/server.go
@@ -2,11 +2,13 @@ package main
import (
"bytes"
+ "encoding/json"
"html/template"
"io"
"io/ioutil"
"math"
"net/http"
+ "net/url"
"os"
"time"
@@ -36,6 +38,8 @@ func main() {
}
defer db.Close()
+ skipCaptcha := os.Getenv("SKIP_CAPTCHA") != ""
+
var ads []byte
adsFName := os.Getenv("ADS")
if adsFName != "" {
@@ -102,6 +106,11 @@ func main() {
e.POST("/note", func(c echo.Context) error {
c.Logger().Debug("POST /note requested")
+ if !skipCaptcha && !checkRecaptcha(c, c.FormValue("g-recaptcha-response")) {
+ c.Logger().Warnf("captcha validation failed for %s", c.Request().RemoteAddr)
+ code := http.StatusForbidden
+ return c.Render(code, "Note", responsePage(code))
+ }
if !legitAccess(c) {
code := http.StatusTooManyRequests
c.Logger().Errorf("rate limit exceeded for %s", c.Request().RemoteAddr)
@@ -169,3 +178,31 @@ func fraudelent(n *Note) bool {
return n.Views > 100 &&
int(math.Ceil(100*float64(l1-l2)/float64(l1))) > fraudThreshold
}
+
+func checkRecaptcha(c echo.Context, captchaResp string) bool {
+ resp, err := http.PostForm("https://www.google.com/recaptcha/api/siteverify", url.Values{
+ "secret": []string{os.Getenv("RECAPTCHA_SECRET")},
+ "response": []string{captchaResp},
+ "remoteip": []string{c.Request().RemoteAddr},
+ })
+ if err != nil {
+ c.Logger().Errorf("captcha response verification failed: %v", err)
+ return false
+ }
+ defer resp.Body.Close()
+ respJson := &struct {
+ Success bool `json:"success"`
+ ErrorCodes []string `json:"error-codes"`
+ }{}
+ s, err := ioutil.ReadAll(resp.Body)
+ err = json.Unmarshal(s, respJson)
+ if err != nil {
+ c.Logger().Errorf("captcha response parse recaptcha response: %v", err)
+ return false
+ }
+ if !respJson.Success {
+ c.Logger().Errorf("captcha response validation failed: %v", respJson.ErrorCodes)
+ }
+ return respJson.Success
+
+}