code.gitea.io/gitea@v1.19.3/modules/turnstile/turnstile.go (about) 1 // Copyright 2023 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package turnstile 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 ) 17 18 // Response is the structure of JSON returned from API 19 type Response struct { 20 Success bool `json:"success"` 21 ChallengeTS string `json:"challenge_ts"` 22 Hostname string `json:"hostname"` 23 ErrorCodes []ErrorCode `json:"error-codes"` 24 Action string `json:"login"` 25 Cdata string `json:"cdata"` 26 } 27 28 // Verify calls Cloudflare Turnstile API to verify token 29 func Verify(ctx context.Context, response string) (bool, error) { 30 // Cloudflare turnstile official access instruction address: https://developers.cloudflare.com/turnstile/get-started/server-side-validation/ 31 post := url.Values{ 32 "secret": {setting.Service.CfTurnstileSecret}, 33 "response": {response}, 34 } 35 // Basically a copy of http.PostForm, but with a context 36 req, err := http.NewRequestWithContext(ctx, http.MethodPost, 37 "https://challenges.cloudflare.com/turnstile/v0/siteverify", 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: %w", 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: %w", err) 51 } 52 53 var jsonResponse Response 54 if err := json.Unmarshal(body, &jsonResponse); err != nil { 55 return false, fmt.Errorf("Failed to parse CAPTCHA response: %w", err) 56 } 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 was not passed." 73 case "invalid-input-secret": 74 return "The secret parameter was invalid or did not exist." 75 case "missing-input-response": 76 return "The response parameter was not passed." 77 case "invalid-input-response": 78 return "The response parameter is invalid or has expired." 79 case "bad-request": 80 return "The request was rejected because it was malformed." 81 case "timeout-or-duplicate": 82 return "The response parameter has already been validated before." 83 case "internal-error": 84 return "An internal error happened while validating the response. The request can be retried." 85 } 86 return string(e) 87 } 88 89 // Error fulfills the error interface 90 func (e ErrorCode) Error() string { 91 return e.String() 92 }