code.gitea.io/gitea@v1.19.3/modules/recaptcha/recaptcha.go (about)

     1  // Copyright 2018 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package recaptcha
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"io"
    10  	"net/http"
    11  	"net/url"
    12  	"strings"
    13  
    14  	"code.gitea.io/gitea/modules/json"
    15  	"code.gitea.io/gitea/modules/setting"
    16  	"code.gitea.io/gitea/modules/util"
    17  )
    18  
    19  // Response is the structure of JSON returned from API
    20  type Response struct {
    21  	Success     bool        `json:"success"`
    22  	ChallengeTS string      `json:"challenge_ts"`
    23  	Hostname    string      `json:"hostname"`
    24  	ErrorCodes  []ErrorCode `json:"error-codes"`
    25  }
    26  
    27  const apiURL = "api/siteverify"
    28  
    29  // Verify calls Google Recaptcha API to verify token
    30  func Verify(ctx context.Context, response string) (bool, error) {
    31  	post := url.Values{
    32  		"secret":   {setting.Service.RecaptchaSecret},
    33  		"response": {response},
    34  	}
    35  	// Basically a copy of http.PostForm, but with a context
    36  	req, err := http.NewRequestWithContext(ctx, http.MethodPost,
    37  		util.URLJoin(setting.Service.RecaptchaURL, apiURL), strings.NewReader(post.Encode()))
    38  	if err != nil {
    39  		return false, fmt.Errorf("Failed to create CAPTCHA request: %w", err)
    40  	}
    41  	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
    42  
    43  	resp, err := http.DefaultClient.Do(req)
    44  	if err != nil {
    45  		return false, fmt.Errorf("Failed to send CAPTCHA response: %s", err)
    46  	}
    47  	defer resp.Body.Close()
    48  	body, err := io.ReadAll(resp.Body)
    49  	if err != nil {
    50  		return false, fmt.Errorf("Failed to read CAPTCHA response: %s", err)
    51  	}
    52  
    53  	var jsonResponse Response
    54  	err = json.Unmarshal(body, &jsonResponse)
    55  	if err != nil {
    56  		return false, fmt.Errorf("Failed to parse CAPTCHA response: %s", err)
    57  	}
    58  	var respErr error
    59  	if len(jsonResponse.ErrorCodes) > 0 {
    60  		respErr = jsonResponse.ErrorCodes[0]
    61  	}
    62  	return jsonResponse.Success, respErr
    63  }
    64  
    65  // ErrorCode is a reCaptcha error
    66  type ErrorCode string
    67  
    68  // String fulfills the Stringer interface
    69  func (e ErrorCode) String() string {
    70  	switch e {
    71  	case "missing-input-secret":
    72  		return "The secret parameter is missing."
    73  	case "invalid-input-secret":
    74  		return "The secret parameter is invalid or malformed."
    75  	case "missing-input-response":
    76  		return "The response parameter is missing."
    77  	case "invalid-input-response":
    78  		return "The response parameter is invalid or malformed."
    79  	case "bad-request":
    80  		return "The request is invalid or malformed."
    81  	case "timeout-or-duplicate":
    82  		return "The response is no longer valid: either is too old or has been used previously."
    83  	}
    84  	return string(e)
    85  }
    86  
    87  // Error fulfills the error interface
    88  func (e ErrorCode) Error() string {
    89  	return e.String()
    90  }