Skip to content
Open
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
51 changes: 32 additions & 19 deletions recaptcha.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ type reCHAPTCHARequest struct {
RemoteIP string `json:"remoteip,omitempty"`
}

type reCHAPTCHAResponse struct {
// ReCHAPTCHAResponse holds a response from recaptcha
type ReCHAPTCHAResponse struct {
Success bool `json:"success"`
ChallengeTS time.Time `json:"challenge_ts"`
Hostname string `json:"hostname,omitempty"`
Expand Down Expand Up @@ -97,6 +98,12 @@ func NewReCAPTCHA(ReCAPTCHASecret string, version VERSION, timeout time.Duration

// Verify returns `nil` if no error and the client solved the challenge correctly
func (r *ReCAPTCHA) Verify(challengeResponse string) error {
_, err := r.VerifyWithResponse(challengeResponse)
return err
}

// VerifyWithResponse returns the same as Verify, except with the addition of the ReCAPTCHAResponse.
func (r *ReCAPTCHA) VerifyWithResponse(challengeResponse string) (*ReCHAPTCHAResponse, error) {
body := reCHAPTCHARequest{Secret: r.Secret, Response: challengeResponse}
return r.confirm(body, VerifyOption{})
}
Expand All @@ -114,6 +121,12 @@ type VerifyOption struct {
// VerifyWithOptions returns `nil` if no error and the client solved the challenge correctly and all options are matching
// `Threshold` and `Action` are ignored when using V2 version
func (r *ReCAPTCHA) VerifyWithOptions(challengeResponse string, options VerifyOption) error {
_, err := r.VerifyWithOptionsAndResponse(challengeResponse, options)
return err
}

// VerifyWithOptionsAndResponse returns the same as VerifyWithOptions, except with the addition of the ReCAPTCHAResponse.
func (r *ReCAPTCHA) VerifyWithOptionsAndResponse(challengeResponse string, options VerifyOption) (*ReCHAPTCHAResponse, error) {
var body reCHAPTCHARequest
if options.RemoteIP == "" {
body = reCHAPTCHARequest{Secret: r.Secret, Response: challengeResponse}
Expand All @@ -123,7 +136,7 @@ func (r *ReCAPTCHA) VerifyWithOptions(challengeResponse string, options VerifyOp
return r.confirm(body, options)
}

func (r *ReCAPTCHA) confirm(recaptcha reCHAPTCHARequest, options VerifyOption) (Err error) {
func (r *ReCAPTCHA) confirm(recaptcha reCHAPTCHARequest, options VerifyOption) (recaptchaResponse *ReCHAPTCHAResponse, Err error) {
Err = nil
var formValues url.Values
if recaptcha.RemoteIP != "" {
Expand All @@ -142,56 +155,56 @@ func (r *ReCAPTCHA) confirm(recaptcha reCHAPTCHARequest, options VerifyOption) (
Err = &Error{msg: fmt.Sprintf("couldn't read response body: '%s'", err), RequestError: true}
return
}
var result reCHAPTCHAResponse
err = json.Unmarshal(resultBody, &result)
recaptchaResponse = &ReCHAPTCHAResponse{}
err = json.Unmarshal(resultBody, recaptchaResponse)
if err != nil {
Err = &Error{msg: fmt.Sprintf("invalid response body json: '%s'", err), RequestError: true}
return
}

if result.ErrorCodes != nil {
Err = &Error{msg: fmt.Sprintf("remote error codes: %v", result.ErrorCodes), ErrorCodes: result.ErrorCodes}
if recaptchaResponse.ErrorCodes != nil {
Err = &Error{msg: fmt.Sprintf("remote error codes: %v", recaptchaResponse.ErrorCodes), ErrorCodes: recaptchaResponse.ErrorCodes}
return
}

if !result.Success && recaptcha.RemoteIP != "" {
if !recaptchaResponse.Success && recaptcha.RemoteIP != "" {
Err = &Error{msg: fmt.Sprintf("invalid challenge solution or remote IP")}
return
}

if !result.Success {
if !recaptchaResponse.Success {
Err = &Error{msg: fmt.Sprintf("invalid challenge solution")}
return
}

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

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

if options.ResponseTime != 0 {
duration := r.horloge.Since(result.ChallengeTS)
duration := r.horloge.Since(recaptchaResponse.ChallengeTS)
if options.ResponseTime < duration {
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 = &Error{msg: fmt.Sprintf("invalid response action '%s', while expecting '%s'", result.Action, options.Action)}
if options.Action != "" && options.Action != recaptchaResponse.Action {
Err = &Error{msg: fmt.Sprintf("invalid response action '%s', while expecting '%s'", recaptchaResponse.Action, options.Action)}
return
}
if options.Threshold != 0 && options.Threshold > result.Score {
Err = &Error{msg: fmt.Sprintf("received score '%f', while expecting minimum '%f'", result.Score, options.Threshold)}
if options.Threshold != 0 && options.Threshold > recaptchaResponse.Score {
Err = &Error{msg: fmt.Sprintf("received score '%f', while expecting minimum '%f'", recaptchaResponse.Score, options.Threshold)}
return
}
if options.Threshold == 0 && DefaultThreshold > result.Score {
Err = &Error{msg: fmt.Sprintf("received score '%f', while expecting minimum '%f'", result.Score, DefaultThreshold)}
if options.Threshold == 0 && DefaultThreshold > recaptchaResponse.Score {
Err = &Error{msg: fmt.Sprintf("received score '%f', while expecting minimum '%f'", recaptchaResponse.Score, DefaultThreshold)}
return
}
}
Expand Down
6 changes: 3 additions & 3 deletions recaptcha_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,23 +79,23 @@ func (s *ReCaptchaSuite) TestConfirm(c *C) {
}
body := reCHAPTCHARequest{Secret: "", Response: ""}

err := captcha.confirm(body, VerifyOption{})
_, err := captcha.confirm(body, VerifyOption{})
c.Assert(err, NotNil)
recaptchaErr, ok := err.(*Error)
c.Check(ok, Equals, true)
c.Check(recaptchaErr.RequestError, Equals, true)
c.Check(err, ErrorMatches, "invalid response body json:.*")

captcha.client = &mockUnavailableClient{}
err = captcha.confirm(body, VerifyOption{})
_, err = captcha.confirm(body, VerifyOption{})
c.Assert(err, NotNil)
recaptchaErr, ok = err.(*Error)
c.Check(ok, Equals, true)
c.Check(recaptchaErr.RequestError, Equals, true)
c.Check(err, ErrorMatches, "error posting to recaptcha endpoint:.*")

captcha.client = &mockInvalidReaderClient{}
err = captcha.confirm(body, VerifyOption{})
_, err = captcha.confirm(body, VerifyOption{})
c.Assert(err, NotNil)
recaptchaErr, ok = err.(*Error)
c.Check(ok, Equals, true)
Expand Down