Skip to content

Commit

Permalink
Pass error codes to user. (#30)
Browse files Browse the repository at this point in the history
  • Loading branch information
ezzarghili authored Jul 1, 2020
1 parent d227bbb commit 30047cb
Show file tree
Hide file tree
Showing 3 changed files with 29 additions and 17 deletions.
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@ Google reCAPTCHA v2 & v3 form submission verification in golang.

The API has changed form last version hence the new major version change.
Old API is still available using the package `gopkg.in/ezzarghili/recaptcha-go.v2` although it does not provide all options available in this version.
As always install the package in your environment by using a stable API version, see latest version in release page.
As always install the package in your environment by using a stable API version, see latest version in [releases page](https://github.com/ezzarghili/recaptcha-go/releases).

```bash
go get -u gopkg.in/ezzarghili/recaptcha-go.v4
go get -u gopkg.in/ezzarghili/recaptcha-go.v4
```

### recaptcha v2 API

```go
import "gopkg.in/ezzarghili/recaptcha-go.v4"
func main(){
captcha, _ := recaptcha.NewReCAPTCHA(recaptchaSecret, recaptcha.V2, 10*time.Second) // for v2 API get your secret from https://www.google.com/recaptcha/admin
captcha, _ := recaptcha.NewReCAPTCHA(recaptchaSecret, recaptcha.V2, 10 * time.Second) // for v2 API get your secret from https://www.google.com/recaptcha/admin
}
```

Expand All @@ -29,6 +29,7 @@ Now everytime you need to verify a V2 API client with no special options request
err := captcha.Verify(recaptchaResponse)
if err != nil {
// do something with err (log?)
// Example check error codes array if they exist: (err.(*recaptcha.Error)).ErrorCodes
}
// proceed
```
Expand All @@ -49,6 +50,7 @@ Other v3 options are ignored and method will return `nil` when succeeded.
err := captcha.VerifyWithOptions(recaptchaResponse, VerifyOption{RemoteIP: "123.123.123.123"})
if err != nil {
// do something with err (log?)
// Example check error codes array if they exist: (err.(*recaptcha.Error)).ErrorCodes
}
// proceed
```
Expand All @@ -58,7 +60,7 @@ if err != nil {
```go
import "gopkg.in/ezzarghili/recaptcha-go.v4"
func main(){
captcha, _ := recaptcha.NewReCAPTCHA(recaptchaSecret, recaptcha.V3, 10*time.Second) // for v3 API use https://g.co/recaptcha/v3 (apperently the same admin UI at the time of writing)
captcha, _ := recaptcha.NewReCAPTCHA(recaptchaSecret, recaptcha.V3, 10 * time.Second) // for v3 API use https://g.co/recaptcha/v3 (apperently the same admin UI at the time of writing)
}
```

Expand Down
34 changes: 21 additions & 13 deletions recaptcha.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func (realClock) Since(t time.Time) time.Duration {
return time.Since(t)
}

// ReCAPTCHA recpatcha holder struct, make adding mocking code simpler
// ReCAPTCHA recpatcha holder struct, make adding mocking code simpler.
type ReCAPTCHA struct {
client netClient
Secret string
Expand All @@ -66,6 +66,14 @@ type ReCAPTCHA struct {
horloge clock
}

// Error custom error to pass ErrorCodes to user.
type Error struct {
msg string
ErrorCodes []string
}

func (e *Error) Error() string { return e.msg }

// NewReCAPTCHA new ReCAPTCHA instance if version is set to V2 uses recatpcha v2 API, get your secret from https://www.google.com/recaptcha/admin
// if version is set to V2 uses recatpcha v2 API, get your secret from https://g.co/recaptcha/v3
func NewReCAPTCHA(ReCAPTCHASecret string, version VERSION, timeout time.Duration) (ReCAPTCHA, error) {
Expand Down Expand Up @@ -122,61 +130,61 @@ func (r *ReCAPTCHA) confirm(recaptcha reCHAPTCHARequest, options VerifyOption) (
}
response, err := r.client.PostForm(r.ReCAPTCHALink, formValues)
if err != nil {
Err = fmt.Errorf("error posting to recaptcha endpoint: '%s'", err)
Err = &Error{msg: fmt.Sprintf("error posting to recaptcha endpoint: '%s'", err)}
return
}
defer response.Body.Close()
resultBody, err := ioutil.ReadAll(response.Body)
if err != nil {
Err = fmt.Errorf("couldn't read response body: '%s'", err)
Err = &Error{msg: fmt.Sprintf("couldn't read response body: '%s'", err)}
return
}
var result reCHAPTCHAResponse
err = json.Unmarshal(resultBody, &result)
if err != nil {
Err = fmt.Errorf("invalid response body json: '%s'", err)
Err = &Error{msg: fmt.Sprintf("invalid response body json: '%s'", err)}
return
}

if options.Hostname != "" && options.Hostname != result.Hostname {
Err = fmt.Errorf("invalid response hostname '%s', while expecting '%s'", result.Hostname, options.Hostname)
Err = &Error{msg: fmt.Sprintf("invalid response hostname '%s', while expecting '%s'", result.Hostname, options.Hostname)}
return
}

if options.ApkPackageName != "" && options.ApkPackageName != result.ApkPackageName {
Err = fmt.Errorf("invalid response ApkPackageName '%s', while expecting '%s'", result.ApkPackageName, options.ApkPackageName)
Err = &Error{msg: fmt.Sprintf("invalid response ApkPackageName '%s', while expecting '%s'", result.ApkPackageName, options.ApkPackageName)}
return
}

if options.ResponseTime != 0 {
duration := r.horloge.Since(result.ChallengeTS)
if options.ResponseTime < duration {
Err = fmt.Errorf("time spent in resolving challenge '%fs', while expecting maximum '%fs'", duration.Seconds(), options.ResponseTime.Seconds())
Err = &Error{msg: fmt.Sprintf("time spent in resolving challenge '%fs', while expecting maximum '%fs'", duration.Seconds(), options.ResponseTime.Seconds())}
return
}
}
if r.Version == V3 {
if options.Action != "" && options.Action != result.Action {
Err = fmt.Errorf("invalid response action '%s', while expecting '%s'", result.Action, options.Action)
Err = &Error{msg: fmt.Sprintf("invalid response action '%s', while expecting '%s'", result.Action, options.Action)}
return
}
if options.Threshold != 0 && options.Threshold > result.Score {
Err = fmt.Errorf("received score '%f', while expecting minimum '%f'", result.Score, options.Threshold)
Err = &Error{msg: fmt.Sprintf("received score '%f', while expecting minimum '%f'", result.Score, options.Threshold)}
return
}
if options.Threshold == 0 && DefaultThreshold > result.Score {
Err = fmt.Errorf("received score '%f', while expecting minimum '%f'", result.Score, DefaultThreshold)
Err = &Error{msg: fmt.Sprintf("received score '%f', while expecting minimum '%f'", result.Score, DefaultThreshold)}
return
}
}
if result.ErrorCodes != nil {
Err = fmt.Errorf("remote error codes: %v", result.ErrorCodes)
Err = &Error{msg: fmt.Sprintf("remote error codes: %v", result.ErrorCodes), ErrorCodes: result.ErrorCodes}
return
}
if !result.Success && recaptcha.RemoteIP != "" {
Err = fmt.Errorf("invalid challenge solution or remote IP")
Err = &Error{msg: fmt.Sprintf("invalid challenge solution or remote IP")}
} else if !result.Success {
Err = fmt.Errorf("invalid challenge solution")
Err = &Error{msg: fmt.Sprintf("invalid challenge solution")}
}
return
}
2 changes: 2 additions & 0 deletions recaptcha_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ func (s *ReCaptchaSuite) TestVerifyInvalidSolutionNoRemoteIp(c *C) {
err := captcha.Verify("mycode")
c.Assert(err, NotNil)
c.Check(err, ErrorMatches, "invalid challenge solution")
c.Check((err.(*Error)).ErrorCodes, IsNil)
}

type mockSuccessClientNoOptions struct{}
Expand Down Expand Up @@ -140,6 +141,7 @@ func (s *ReCaptchaSuite) TestVerifyWithoutOptions(c *C) {
err = captcha.Verify("mycode")
c.Assert(err, NotNil)
c.Check(err, ErrorMatches, "remote error codes:.*")
c.Check((err.(*Error)).ErrorCodes, DeepEquals, []string{"invalid-input-response", "bad-request"})

}

Expand Down

0 comments on commit 30047cb

Please sign in to comment.