-
-
Notifications
You must be signed in to change notification settings - Fork 6.2k
Add new captcha: cloudflare turnstile #22369
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
a6bfdc5
8c63433
59186ab
0821c09
034dc52
c5d9c49
4ba29b3
09b7707
a152ec7
09ff015
0d185a7
4b3b828
f178ef4
46aa739
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| // Copyright 2023 The Gitea Authors. All rights reserved. | ||
| // SPDX-License-Identifier: MIT | ||
|
|
||
| package turnstile | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "io" | ||
| "net/http" | ||
| "net/url" | ||
| "strings" | ||
|
|
||
| "code.gitea.io/gitea/modules/json" | ||
| "code.gitea.io/gitea/modules/setting" | ||
| ) | ||
|
|
||
| // Response is the structure of JSON returned from API | ||
| type Response struct { | ||
| Success bool `json:"success"` | ||
| ChallengeTS string `json:"challenge_ts"` | ||
| Hostname string `json:"hostname"` | ||
| ErrorCodes []ErrorCode `json:"error-codes"` | ||
| Action string `json:"login"` | ||
| Cdata string `json:"cdata"` | ||
| } | ||
|
|
||
| // Verify calls Cloudflare Turnstile API to verify token | ||
| func Verify(ctx context.Context, response, ip string) (bool, error) { | ||
| // Cloudflare turnstile official access instruction address: https://developers.cloudflare.com/turnstile/get-started/server-side-validation/ | ||
| post := url.Values{ | ||
| "secret": {setting.Service.CfTurnstileSecret}, | ||
| "response": {response}, | ||
| "remoteip": {ip}, | ||
|
||
| } | ||
| // Basically a copy of http.PostForm, but with a context | ||
| req, err := http.NewRequestWithContext(ctx, http.MethodPost, | ||
| "https://challenges.cloudflare.com/turnstile/v0/siteverify", strings.NewReader(post.Encode())) | ||
| if err != nil { | ||
| return false, fmt.Errorf("Failed to create CAPTCHA request: %w", err) | ||
| } | ||
| req.Header.Set("Content-Type", "application/x-www-form-urlencoded") | ||
|
|
||
| resp, err := http.DefaultClient.Do(req) | ||
| if err != nil { | ||
| return false, fmt.Errorf("Failed to send CAPTCHA response: %s", err) | ||
wolfogre marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| defer resp.Body.Close() | ||
| body, err := io.ReadAll(resp.Body) | ||
| if err != nil { | ||
| return false, fmt.Errorf("Failed to read CAPTCHA response: %s", err) | ||
wolfogre marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| var jsonResponse Response | ||
| err = json.Unmarshal(body, &jsonResponse) | ||
| if err != nil { | ||
wolfogre marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return false, fmt.Errorf("Failed to parse CAPTCHA response: %s", err) | ||
wolfogre marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| var respErr error | ||
| if len(jsonResponse.ErrorCodes) > 0 { | ||
| respErr = jsonResponse.ErrorCodes[0] | ||
| } | ||
| return jsonResponse.Success, respErr | ||
| } | ||
|
|
||
| // ErrorCode is a reCaptcha error | ||
| type ErrorCode string | ||
|
|
||
| // String fulfills the Stringer interface | ||
| func (e ErrorCode) String() string { | ||
| switch e { | ||
| case "missing-input-secret": | ||
| return "The secret parameter was not passed." | ||
| case "invalid-input-secret": | ||
| return "The secret parameter was invalid or did not exist." | ||
| case "missing-input-response": | ||
| return "The response parameter was not passed." | ||
| case "invalid-input-response": | ||
| return "The response parameter is invalid or has expired." | ||
| case "bad-request": | ||
| return "The request was rejected because it was malformed." | ||
| case "timeout-or-duplicate": | ||
| return "The response parameter has already been validated before." | ||
| case "internal-error": | ||
| return "An internal error happened while validating the response. The request can be retried." | ||
| } | ||
| return string(e) | ||
| } | ||
|
|
||
| // Error fulfills the error interface | ||
| func (e ErrorCode) Error() string { | ||
| return e.String() | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.