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 }