2017-08-10 14:21:42 +02:00
|
|
|
package recaptcha
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"net/url"
|
|
|
|
"time"
|
|
|
|
|
2019-10-25 00:27:02 +02:00
|
|
|
"github.com/kataras/iris/v12/context"
|
|
|
|
"github.com/kataras/iris/v12/core/netutil"
|
2017-08-10 14:21:42 +02:00
|
|
|
)
|
|
|
|
|
2020-04-28 04:42:23 +02:00
|
|
|
func init() {
|
2020-04-28 21:34:36 +02:00
|
|
|
context.SetHandlerName("iris/middleware/recaptcha.*", "iris.reCAPTCHA")
|
2020-04-28 04:42:23 +02:00
|
|
|
}
|
|
|
|
|
2017-08-10 14:21:42 +02:00
|
|
|
const (
|
2020-02-02 15:29:06 +01:00
|
|
|
// responseFormValue = "g-recaptcha-response"
|
|
|
|
apiURL = "https://www.google.com/recaptcha/api/siteverify"
|
2017-08-10 14:21:42 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
// Response is the google's recaptcha response as JSON.
|
|
|
|
type Response struct {
|
|
|
|
ChallengeTS time.Time `json:"challenge_ts"`
|
|
|
|
Hostname string `json:"hostname"`
|
|
|
|
ErrorCodes []string `json:"error-codes"`
|
|
|
|
Success bool `json:"success"`
|
|
|
|
}
|
|
|
|
|
2017-08-10 20:34:05 +02:00
|
|
|
// Client is the default `net/http#Client` instance which
|
|
|
|
// is used to send requests to the Google API.
|
|
|
|
//
|
|
|
|
// Change Client only if you know what you're doing.
|
|
|
|
var Client = netutil.Client(time.Duration(20 * time.Second))
|
2017-08-10 14:21:42 +02:00
|
|
|
|
|
|
|
// New accepts the google's recaptcha secret key and returns
|
|
|
|
// a middleware that verifies the request by sending a response to the google's API(V2-latest).
|
|
|
|
// Secret key can be obtained by https://www.google.com/recaptcha.
|
|
|
|
//
|
|
|
|
// Used for communication between your site and Google. Be sure to keep it a secret.
|
|
|
|
//
|
|
|
|
// Use `SiteVerify` to verify a request inside another handler if needed.
|
|
|
|
func New(secret string) context.Handler {
|
2020-07-10 22:21:09 +02:00
|
|
|
return func(ctx *context.Context) {
|
2020-04-11 19:23:02 +02:00
|
|
|
if SiteVerify(ctx, secret).Success {
|
2017-08-10 14:21:42 +02:00
|
|
|
ctx.Next()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-11 19:23:02 +02:00
|
|
|
// SiteVerify accepts context and the secret key(https://www.google.com/recaptcha)
|
2017-08-10 14:21:42 +02:00
|
|
|
// and returns the google's recaptcha response, if `response.Success` is true
|
|
|
|
// then validation passed.
|
|
|
|
//
|
|
|
|
// Use `New` for middleware use instead.
|
2020-07-10 22:21:09 +02:00
|
|
|
func SiteVerify(ctx *context.Context, secret string) (response Response) {
|
2017-08-10 14:21:42 +02:00
|
|
|
generatedResponseID := ctx.FormValue("g-recaptcha-response")
|
|
|
|
if generatedResponseID == "" {
|
|
|
|
response.ErrorCodes = append(response.ErrorCodes,
|
|
|
|
"form value[g-recaptcha-response] is empty")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-08-10 20:34:05 +02:00
|
|
|
r, err := Client.PostForm(apiURL,
|
2017-08-10 14:21:42 +02:00
|
|
|
url.Values{
|
|
|
|
"secret": {secret},
|
|
|
|
"response": {generatedResponseID},
|
|
|
|
// optional: let's no track our users "remoteip": {ctx.RemoteAddr()},
|
|
|
|
},
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
response.ErrorCodes = append(response.ErrorCodes, err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
body, err := ioutil.ReadAll(r.Body)
|
|
|
|
r.Body.Close()
|
|
|
|
if err != nil {
|
|
|
|
response.ErrorCodes = append(response.ErrorCodes, err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
err = json.Unmarshal(body, &response)
|
|
|
|
if err != nil {
|
|
|
|
response.ErrorCodes = append(response.ErrorCodes, err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
return response
|
|
|
|
}
|
|
|
|
|
|
|
|
var recaptchaForm = `<form action="%s" method="POST">
|
|
|
|
<script src="https://www.google.com/recaptcha/api.js"></script>
|
|
|
|
<div class="g-recaptcha" data-sitekey="%s"></div>
|
|
|
|
<input type="submit" name="button" value="OK">
|
|
|
|
</form>`
|
|
|
|
|
|
|
|
// GetFormHTML can be used on pages where empty form
|
|
|
|
// is enough to verify that the client is not a "robot".
|
|
|
|
// i.e: GetFormHTML("public_key", "/contact")
|
|
|
|
// will return form tag which imports the google API script,
|
|
|
|
// with a simple submit button where redirects to the
|
|
|
|
// "postActionRelativePath".
|
|
|
|
//
|
|
|
|
// The "postActionRelativePath" MUST use the `SiteVerify` or
|
|
|
|
// followed by the `New()`'s context.Handler (with the secret key this time)
|
|
|
|
// in order to validate if the recaptcha verified.
|
|
|
|
//
|
|
|
|
// The majority of applications will use a custom form,
|
|
|
|
// this function is here for ridiculous simple use cases.
|
|
|
|
//
|
|
|
|
// Example Code:
|
|
|
|
//
|
|
|
|
// Method: "POST" | Path: "/contact"
|
2020-07-10 22:21:09 +02:00
|
|
|
// func postContact(ctx *context.Context) {
|
2017-08-10 14:21:42 +02:00
|
|
|
// // [...]
|
2020-04-11 19:23:02 +02:00
|
|
|
// response := recaptcha.SiteVerify(ctx, recaptchaSecret)
|
2017-08-10 14:21:42 +02:00
|
|
|
//
|
|
|
|
// if response.Success {
|
|
|
|
// // [your action here, i.e sendEmail(...)]
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// ctx.JSON(response)
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// Method: "GET" | Path: "/contact"
|
2020-07-10 22:21:09 +02:00
|
|
|
// func getContact(ctx *context.Context) {
|
2017-08-10 14:21:42 +02:00
|
|
|
// // render the recaptcha form
|
|
|
|
// ctx.HTML(recaptcha.GetFormHTML(recaptchaPublic, "/contact"))
|
|
|
|
// }
|
|
|
|
func GetFormHTML(dataSiteKey string, postActionRelativePath string) string {
|
|
|
|
return fmt.Sprintf(recaptchaForm, postActionRelativePath, dataSiteKey)
|
|
|
|
}
|