Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add example #2

Merged
merged 5 commits into from
Jan 25, 2024
Merged
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
29 changes: 18 additions & 11 deletions client.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package friendlycaptcha

import (
"fmt"
"net/http"
)

type ClientOption func(*Client)
type ClientOption func(*Client) error

// A client for the Friendly Captcha API, see also the API docs at https://developer.friendlycaptcha.com
type Client struct {
Expand All @@ -31,7 +32,7 @@ const (
euSiteverifyEndpointURL = "https://eu.frcapi.com/api/v2/captcha/siteverify"
)

func NewClient(opts ...ClientOption) *Client {
func NewClient(opts ...ClientOption) (*Client, error) {
const (
defaultSiteverifyEndpoint = globalSiteverifyEndpointURL
)
Expand All @@ -43,36 +44,41 @@ func NewClient(opts ...ClientOption) *Client {

// Loop through each option
for _, opt := range opts {
// Call the option giving the instantiated
// *Client as the argument
opt(c)
// Call the option giving the instantiated *Client as the argument
err := opt(c)
if err != nil {
return nil, err
}
}

if c.APIKey == "" {
panic("You must set your Friendly Captcha APIKey using `WithAPIKey()` when creating a new client")
return nil, fmt.Errorf("you must set your Friendly Captcha API key using `WithAPIKey()` when creating a new client")
}

return c
return c, nil
}

func WithAPIKey(apiKey string) ClientOption {
return func(c *Client) {
return func(c *Client) error {
c.APIKey = apiKey
return nil
}
}

func WithSitekey(sitekey string) ClientOption {
return func(c *Client) {
return func(c *Client) error {
c.Sitekey = sitekey
return nil
}
}

// In strict mode only strictly verified captcha response are allowed. If your API key is invalid or your server can not reach the API endpoint all requests will be rejected.
//
// This defaults to `false`.
func WithStrictMode(strict bool) ClientOption {
return func(c *Client) {
return func(c *Client) error {
c.Strict = strict
return nil
}
}

Expand All @@ -84,7 +90,8 @@ func WithSiteverifyEndpoint(siteverifyEndpoint string) ClientOption {
siteverifyEndpoint = euSiteverifyEndpointURL
}

return func(c *Client) {
return func(c *Client) error {
c.SiteverifyEndpoint = siteverifyEndpoint
return nil
}
}
5 changes: 4 additions & 1 deletion client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,15 @@ func TestSDKWithMockServer(t *testing.T) {

for _, test := range testsFile.Tests {
t.Run(test.Name, func(t *testing.T) {
frcClient := NewClient(
frcClient, err := NewClient(
WithAPIKey("YOUR_API_KEY"),
WithSitekey("YOUR_SITE_KEY"),
WithSiteverifyEndpoint(MockServerURL+SiteverifyEndpoint),
WithStrictMode(test.Strict),
)
if err != nil {
t.Fatalf("failed to create Friendly Captcha client: %v", err)
}
result := frcClient.VerifyCaptchaResponse(context.TODO(), test.Response)

shouldAccept := result.ShouldAccept()
Expand Down
23 changes: 23 additions & 0 deletions example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Friendly Captcha Go Example

This application integrates Friendly Captcha for form submissions using Go.

### Requirements

- Go
- Your Friendly Captcha API key and sitekey.

### Start the application

- Setup env variables and start the application

> NOTE: `FRC_SITEVERIFY_ENDPOINT` and `FRC_WIDGET_ENDPOINT` are optional. If not set, the default values will be used. You can also use `global` or `eu` as shorthands for both.

```bash
FRC_APIKEY=<your API key> FRC_SITEKEY=<your sitekey> FRC_SITEVERIFY_ENDPOINT=<siteverify endpoint> FRC_WIDGET_ENDPOINT=<widget endpoint> go run main.go
```

# Usage

Navigate to http://localhost:8844/ in your browser.
Fill out the form and submit. The Friendly Captcha verification will protect the form from bots.
83 changes: 83 additions & 0 deletions example/demo.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Friendly Captcha Go SDK example</title>
<style>
* {
box-sizing: border-box;
}

body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
width: 100vw;
background-color: #f2f2f2;
}

h1 {
font-size: 1.4em;
}

main {
width: 100%;
max-width: 680px;
}

.message {
width: 100%;
padding: 0.5em 2em;
margin: 1.5em 0;
border-radius: 8px;
}

.message h2 {
margin-bottom: 0;
}

label {
display: block;
}
</style>

<script type="module" src="https://cdn.jsdelivr.net/npm/@friendlycaptcha/sdk/site.min.js" async defer></script>
<script nomodule src="https://cdn.jsdelivr.net/npm/@friendlycaptcha/sdk/site.compat.min.js" async defer></script>

<!-- You can change the data-api-endpoint via this tag. More info here https://developer.friendlycaptcha.com/docs/sdk/configuration -->
<!-- <meta name="frc-api-endpoint" content="."> -->
</head>

<body>
<main>
<h1>Friendly Captcha Go SDK form</h1>
{{if .Message}}
<p>{{.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}}" {{if
.WidgetEndpoint}}data-api-endpoint="{{.WidgetEndpoint}}" {{end}}></div>
<input style="margin-top: 1em" type="submit" value="Submit" />
</div>
</form>
</main>
<script>
if (window.history.replaceState) {
window.history.replaceState(null, null, window.location.href);
}
</script>
</body>

</html>
8 changes: 8 additions & 0 deletions example/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module github.com/friendlycaptcha/friendly-captcha-go/example

// TODO replace when module is published
replace github.com/friendlycaptcha/friendly-captcha-go => ../

go 1.21.6

require github.com/friendlycaptcha/friendly-captcha-go v0.0.0-00010101000000-000000000000
8 changes: 8 additions & 0 deletions example/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
115 changes: 115 additions & 0 deletions example/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package main

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

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

type formMessage struct {
Subject string
Message string
}

type templateData struct {
Message string
Sitekey string
WidgetEndpoint string
}

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

// Optionally we can pass in custom endpoints to be used, such as "eu".
siteverifyEndpoint := os.Getenv("FRC_SITEVERIFY_ENDPOINT")
widgetEndpoint := os.Getenv("FRC_WIDGET_ENDPOINT")

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 API key respectively.")
}

opts := []friendlycaptcha.ClientOption{
friendlycaptcha.WithAPIKey(apikey),
friendlycaptcha.WithSitekey(sitekey),
}
if siteverifyEndpoint != "" {
opts = append(opts, friendlycaptcha.WithSiteverifyEndpoint(siteverifyEndpoint)) // optional, defaults to "global"
}
frcClient, err := friendlycaptcha.NewClient(opts...)
if err != nil {
log.Fatalf("Failed to create Friendly Captcha client: %s", err)
}

tmpl := template.Must(template.ParseFiles("demo.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 {
err := tmpl.Execute(w, templateData{
Message: "",
Sitekey: sitekey,
WidgetEndpoint: widgetEndpoint,
})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
return
}

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

solution := r.FormValue("frc-captcha-response")
result := frcClient.VerifyCaptchaResponse(r.Context(), solution)

if !result.WasAbleToVerify() {
// In this case we were not actually able to verify the response embedded in the form, but we may still want to accept it.
// It could mean there is a network issue or that the service is down. In those cases you generally want to accept submissions anyhow.
// That's why we use `shouldAccept()` below to actually accept or reject the form submission. It will return true in these cases.

if result.IsErrorDueToClientError() {
// Something is wrong with our configuration, check your API key!
// Send yourself an alert to fix this! Your site is unprotected until you fix this.
log.Printf("CAPTCHA CONFIG ERROR: %s\n", result.RequestError())
} else {
log.Printf("Failed to verify captcha response: %s\n", result.RequestError())
}
}

if !result.ShouldAccept() {
err := tmpl.Execute(w, templateData{
Message: "❌ Anti-robot check failed, please try again.",
Sitekey: sitekey,
WidgetEndpoint: widgetEndpoint,
})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
return
}

// The captcha was OK, process the form.
_ = form // Normally we would use the form data here and submit it to our database.

err := tmpl.Execute(w, templateData{
Message: "✅ Your message has been submitted successfully.",
Sitekey: sitekey,
WidgetEndpoint: widgetEndpoint,
})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
})

log.Printf("Starting server on localhost port 8844 (http://localhost:8844)")
http.ListenAndServe(":8844", nil)
}