Browse Source

recaptcha implemented

master
Christian Müller 8 years ago
parent
commit
9c6ce5297e
  1. 2
      Makefile
  2. 4
      assets/public/style.css
  3. 19
      assets/templates/form.html
  4. 1
      render.go
  5. 37
      server.go

2
Makefile

@ -1,5 +1,5 @@
run: run:
go run *.go SKIP_CAPTCHA=1 go run *.go
test: test:
jasmine-node . jasmine-node .

4
assets/public/style.css

@ -264,3 +264,7 @@ form {
flex: 1 0; flex: 1 0;
flex-direction: column; flex-direction: column;
} }
#captcha {
display: none;
}

19
assets/templates/form.html

@ -3,22 +3,24 @@
<html> <html>
<head> <head>
<title>NoteHub &mdash; {{if .ID}}Edit{{else}}Add{{end}} note</title> <title>NoteHub &mdash; {{if .ID}}Edit{{else}}Add{{end}} note</title>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<meta content="width=device-width, initial-scale=1.0" name="viewport"> <meta content="width=device-width, initial-scale=1.0" name="viewport" />
<link href="/style.css" rel="stylesheet" type="text/css" /> <link href="/style.css" rel="stylesheet" type="text/css" />
<base target="_blank"> <base target="_blank" />
<script src='https://www.google.com/recaptcha/api.js'></script>
</head> </head>
<body> <body>
<form action="/note" autocomplete="off" method="POST" target="_self"> <form action="/note" autocomplete="off" method="POST" target="_self">
<textarea autofocus name="text">{{.Text}}</textarea> <textarea autofocus name="text">{{.Text}}</textarea>
<fieldset> <fieldset>
<input id="id" name="id" value="{{.ID}}" type="hidden" /> <input id="id" name="id" value="{{.ID}}" type="hidden" />
<input class="ui-elem" id="password" name="password" placeholder="Password for editing" type="text" autocomplete="off"> <input class="ui-elem" name="password" placeholder="Password for editing" type="text" autocomplete="off" />
<label style="margin-right: 1em"> <label style="margin-right: 1em">
<input id="tos" name="tos" type="checkbox" onClick="toggleButton()"> <input id="tos" name="tos" type="checkbox" onClick="toggleButton()" />
Accept <a href="/TOS.md">Terms of Service</a> Accept <a href="/TOS.md">Terms of Service</a>
</label> </label>
<input class="button ui-elem" disabled id="publish-button" name="button" type="submit" value="Publish"> <div id="captcha" class="g-recaptcha" data-sitekey="6LemnDEUAAAAAC6A4VNRefz0BSLiC343W4sXQd6I"></div>
<input class="button ui-elem" disabled id="publish-button" name="button" type="submit" value="Publish" />
</fieldset> </fieldset>
</form> </form>
<footer> <footer>
@ -28,7 +30,10 @@
</footer> </footer>
<script> <script>
function $(id) { return document.getElementById(id) } function $(id) { return document.getElementById(id) }
function toggleButton() { $('publish-button').disabled = !$('tos').checked } function toggleButton() {
$('publish-button').disabled = !$('tos').checked;
$('captcha').style.display = $('tos').checked ? 'block' : 'none';
}
</script> </script>
</body> </body>
</html> </html>

1
render.go

@ -17,6 +17,7 @@ var (
statuses = map[int]string{ statuses = map[int]string{
400: "Bad request", 400: "Bad request",
401: "Unauthorized", 401: "Unauthorized",
403: "Forbidden",
404: "Not found", 404: "Not found",
412: "Precondition failed", 412: "Precondition failed",
429: "Too many requests", 429: "Too many requests",

37
server.go

@ -2,11 +2,13 @@ package main
import ( import (
"bytes" "bytes"
"encoding/json"
"html/template" "html/template"
"io" "io"
"io/ioutil" "io/ioutil"
"math" "math"
"net/http" "net/http"
"net/url"
"os" "os"
"time" "time"
@ -36,6 +38,8 @@ func main() {
} }
defer db.Close() defer db.Close()
skipCaptcha := os.Getenv("SKIP_CAPTCHA") != ""
var ads []byte var ads []byte
adsFName := os.Getenv("ADS") adsFName := os.Getenv("ADS")
if adsFName != "" { if adsFName != "" {
@ -102,6 +106,11 @@ func main() {
e.POST("/note", func(c echo.Context) error { e.POST("/note", func(c echo.Context) error {
c.Logger().Debug("POST /note requested") 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) { if !legitAccess(c) {
code := http.StatusTooManyRequests code := http.StatusTooManyRequests
c.Logger().Errorf("rate limit exceeded for %s", c.Request().RemoteAddr) c.Logger().Errorf("rate limit exceeded for %s", c.Request().RemoteAddr)
@ -169,3 +178,31 @@ func fraudelent(n *Note) bool {
return n.Views > 100 && return n.Views > 100 &&
int(math.Ceil(100*float64(l1-l2)/float64(l1))) > fraudThreshold 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
}

Loading…
Cancel
Save