Skip to content

Commit

Permalink
First version of SDK complete
Browse files Browse the repository at this point in the history
  • Loading branch information
gzuidhof committed Oct 27, 2021
1 parent 2e4da41 commit c6bc4e8
Show file tree
Hide file tree
Showing 4 changed files with 200 additions and 9 deletions.
69 changes: 68 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,69 @@
# friendly-captcha-go-sdk
A Go client for the Friendly Captcha verification API
A Go client for the Friendly Captcha verification API.

## Installation
```
go get github.com/friendlycaptcha/friendly-captcha-go-sdk
```

## Usage

```go
import "github.com/friendlycaptcha/friendly-captcha-go-sdk"

frcClient := friendlycaptcha.NewClient(apikey, sitekey)
```


```go
// In your middleware or request handler
solution := r.FormValue(friendlycaptcha.SolutionFormFieldName)
shouldAccept, err := frcClient.CheckCaptchaSolution(r.Context(), solution)

if err != nil {
if errors.Is(err, friendlycaptcha.ErrVerificationFailedDueToClientError) {
log.Printf("!!!!!\n\nFriendlyCaptcha is misconfigured! Check your Friendly Captcha API key and sitekey: %v\n", err)
// Send yourself an alert - the captcha won't be able to do its job to prevent spam.
} else if (errors.Is(err, friendlycaptcha.ErrVerificationRequest)) {
log.Printf("Could not talk to the Friendly Captcha API: %v\n", err)
// Perhaps the Friendly Captcha API is down?
}
}

if !shouldAccept { // The captcha was invalid
// Show the user a message that the anti-robot verification failed and that they should try again
return
}

// The captcha check was succesful, handle the request :)
```

Beware that the `CheckCaptchaSolution` function returns two values:
* Whether you should accept the request (`bool`)
* An error (or nil)

Even if the error is non-nil, the first boolean value may still be true and you should accept the request!
### Advanced, optional strictness setting
As a best practice we accept the captcha solution if we are unable to verify it: if we misconfigure our apikey or Friendly Captcha's API goes down we would rather accept all requests than lock all users out.

If you want to change this behavior you can set `client.Strict` to true, then the accept value will only be true if we were actually able to verify the captcha solution and it was valid.


## Example

Run the example
```shell
cd examples/form
FRC_SITEKEY=<my sitekey> FRC_APIKEY=<my api key> go run main.go
```

Then open your browser and head to [http://localhost:8844](http://localhost:8844)

> Note: you can create a sitekey and API key in the [Friendly Captcha dashboard](https://app.friendlycaptcha.com/account).
**Example Screenshot**

![Example screenshot](https://i.imgur.com/bsp7qDA.png)

## License
[MIT](./LICENSE)
16 changes: 8 additions & 8 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
)

type VerifyRequest struct {
Solution string `json:"solution,omitempty"`
Solution string `json:"solution"`
Secret string `json:"secret,omitempty"`
Sitekey string `json:"sitekey,omitempty"`
}
Expand All @@ -27,6 +27,10 @@ type Client struct {
APIKey string
Sitekey string
SiteverifyURL string
// If Strict is set to true only strictly verified captcha solutions will be allowed.
// By default it is false: the accept value will be true when for instance the Friendly Captcha API
// could not be reached.
Strict bool
}

const SolutionFormFieldName = "frc-captcha-solution"
Expand All @@ -44,10 +48,6 @@ var ErrVerificationRequest = errors.New("verification request failed talking to
// we were unable to verify it: we don't want to lock users out.
var ErrVerificationFailedDueToClientError = errors.New("verification request failed due to a client error (check your credentials)")

// The response from the Friendly Captcha API was not as expected. Of course this should never happen, but
// it's a good idea to still accept the captcha.
var ErrVerificationUnexpectedAPIResponse = errors.New("verification failed, the Friendly Captcha API response was not as expected")

func NewClient(apiKey string, sitekey string) Client {
return Client{
APIKey: apiKey,
Expand Down Expand Up @@ -78,21 +78,21 @@ func (frc Client) CheckCaptchaSolution(ctx context.Context, captchaSolution stri

resp, err := http.DefaultClient.Do(req)
if err != nil {
return true, fmt.Errorf("%w: %v", ErrVerificationRequest, err)
return !frc.Strict, fmt.Errorf("%w: %v", ErrVerificationRequest, err)
}

if resp.StatusCode != 200 {
b, _ := io.ReadAll(resp.Body)
// Intentionally let this through, it's probably a problem in our credentials
return true, fmt.Errorf("%w (status %d): %v", ErrVerificationFailedDueToClientError, resp.StatusCode, b)
return !frc.Strict, fmt.Errorf("%w [status %d]: %s", ErrVerificationFailedDueToClientError, resp.StatusCode, b)
}

decoder := json.NewDecoder(resp.Body)
var vr VerifyResponse
err = decoder.Decode(&vr)
if err != nil {
// Despite the error we will let this through - it must be a problem with the Friendly Captcha API.
return true, fmt.Errorf("%w: %v", ErrVerificationUnexpectedAPIResponse, err)
return !frc.Strict, fmt.Errorf("%w: %v", ErrVerificationRequest, err)
}

if !vr.Success {
Expand Down
47 changes: 47 additions & 0 deletions examples/form/form.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Friendly Captcha Go SDK example</title>

<script type="module" src="https://cdn.jsdelivr.net/npm/[email protected]/widget.module.min.js" async defer></script>
<script nomodule src="https://cdn.jsdelivr.net/npm/[email protected]/widget.min.js" async defer></script>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]" />

</head>
<body>
<main>
{{if .Submitted}}
<h1>Thanks for your message!</h1>

{{else}}
<h1>Friendly Captcha Go SDK form</h1>
{{if .Message}}
<p style="color:#ba1f1f">{{.Message}}</p>
{{end}}
<form method="POST">
<div class="form-group">
<label>Subject:</label><br />
<input type="text" name="subject"><br />
<label>Message:</label><br />
<textarea name="message"></textarea><br />

<div class="frc-captcha" data-sitekey="{{.Sitekey}}"></div>
<input style="margin-top: 1em" type="submit" value="Submit">
</div>
</form>
{{end}}
</main>

<script>
// A trick to prevent re-submission on reloading the page.
if (window.history.replaceState) {
window.history.replaceState( null, null, window.location.href );
}
</script>
</body>
</html>

77 changes: 77 additions & 0 deletions examples/form/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package main

import (
"errors"
"html/template"
"log"
"net/http"
"os"

friendlycaptcha "github.com/friendlycaptcha/friendly-captcha-go"
)

type FormMessage struct {
Subject string
Message string
}

type TemplateData struct {
Submitted bool
Message string
Sitekey string
}

func main() {
sitekey := os.Getenv("FRC_SITEKEY")
apikey := os.Getenv("FRC_APIKEY")

if sitekey == "" || apikey == "" {
log.Fatalf("Please set the FRC_SITEKEY and FRC_APIKEY environment values before running this example to your Friendly Captcha sitekey and apikey respectively.")
}

frcClient := friendlycaptcha.NewClient(apikey, sitekey)
tmpl := template.Must(template.ParseFiles("form.html"))

http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// GET - the user is requesting the form, not submitting it.
if r.Method != http.MethodPost {
tmpl.Execute(w, TemplateData{Submitted: false, Message: "", Sitekey: sitekey})
return
}

formMessage := FormMessage{
Subject: r.FormValue("subject"),
Message: r.FormValue("message"),
}

solution := r.FormValue(friendlycaptcha.SolutionFormFieldName)
shouldAccept, err := frcClient.CheckCaptchaSolution(r.Context(), solution)

if err != nil {
// Note that there can be errors but we still want to accept the form.
// The reason is that if Friendly Captcha's API ever goes down, we would rather accept
// also spammy messages than lock everybody out.

if errors.Is(err, friendlycaptcha.ErrVerificationFailedDueToClientError) {
log.Printf("!!!!!\nFriendlyCaptcha is misconfigured! Check your Friendly Captcha API key and sitekey: %s\n", err.Error())
// Send yourself an alert - the captcha won't be able to do its job to prevent spam.
} else if (errors.Is(err, friendlycaptcha.ErrVerificationRequest)) {
log.Printf("Could not talk to the Friendly Captcha API: %s\n", err.Error())
// Maybe also alert yourself, maybe the Friendly Captcha API is down?
}
}

if !shouldAccept { // The captcha was invalid
tmpl.Execute(w, TemplateData{Submitted: false, Message: "Anti-robot verification failed, please try again.", Sitekey: sitekey})
return
}

// do something with the data in the form
_ = formMessage

tmpl.Execute(w, TemplateData{Submitted: true, Message: "", Sitekey: sitekey})
})
log.Printf("Starting server on localhost port 8844 (http://localhost:8844)")

http.ListenAndServe(":8844", nil)
}

0 comments on commit c6bc4e8

Please sign in to comment.