chore: 初始化 Cloudreve 源码仓库
This commit is contained in:
@@ -0,0 +1,195 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cloudreve/Cloudreve/v4/application/dependency"
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/logging"
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/recaptcha"
|
||||
request2 "github.com/cloudreve/Cloudreve/v4/pkg/request"
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/serializer"
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/setting"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/mojocn/base64Captcha"
|
||||
)
|
||||
|
||||
type req struct {
|
||||
Captcha string `json:"captcha"`
|
||||
Ticket string `json:"ticket"`
|
||||
Randstr string `json:"randstr"`
|
||||
}
|
||||
|
||||
const (
|
||||
captchaNotMatch = "CAPTCHA not match."
|
||||
captchaRefresh = "Verification failed, please refresh the page and retry."
|
||||
|
||||
tcCaptchaEndpoint = "captcha.tencentcloudapi.com"
|
||||
turnstileEndpoint = "https://challenges.cloudflare.com/turnstile/v0/siteverify"
|
||||
)
|
||||
|
||||
// CaptchaIDCtx defines keys for captcha ID
|
||||
type (
|
||||
CaptchaIDCtx struct{}
|
||||
turnstileResponse struct {
|
||||
Success bool `json:"success"`
|
||||
}
|
||||
capResponse struct {
|
||||
Success bool `json:"success"`
|
||||
}
|
||||
)
|
||||
|
||||
// CaptchaRequired 验证请求签名
|
||||
func CaptchaRequired(enabled func(c *gin.Context) bool) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
if enabled(c) {
|
||||
dep := dependency.FromContext(c)
|
||||
settings := dep.SettingProvider()
|
||||
l := logging.FromContext(c)
|
||||
|
||||
var service req
|
||||
bodyCopy := new(bytes.Buffer)
|
||||
_, err := io.Copy(bodyCopy, c.Request.Body)
|
||||
if err != nil {
|
||||
c.JSON(200, serializer.ErrWithDetails(c, serializer.CodeCaptchaError, captchaNotMatch, err))
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
bodyData := bodyCopy.Bytes()
|
||||
err = json.Unmarshal(bodyData, &service)
|
||||
if err != nil {
|
||||
c.JSON(200, serializer.ErrWithDetails(c, serializer.CodeCaptchaError, captchaNotMatch, err))
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
c.Request.Body = io.NopCloser(bytes.NewReader(bodyData))
|
||||
switch settings.CaptchaType(c) {
|
||||
case setting.CaptchaNormal, setting.CaptchaTcaptcha:
|
||||
if service.Ticket == "" || !base64Captcha.VerifyCaptcha(service.Ticket, service.Captcha) {
|
||||
c.JSON(200, serializer.ErrWithDetails(c, serializer.CodeCaptchaError, captchaNotMatch, err))
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
break
|
||||
case setting.CaptchaReCaptcha:
|
||||
captchaSetting := settings.ReCaptcha(c)
|
||||
reCAPTCHA, err := recaptcha.NewReCAPTCHA(captchaSetting.Secret, recaptcha.V2, 10*time.Second)
|
||||
if err != nil {
|
||||
l.Warning("reCAPTCHA verification failed, %s", err)
|
||||
c.Abort()
|
||||
break
|
||||
}
|
||||
|
||||
err = reCAPTCHA.Verify(service.Captcha)
|
||||
if err != nil {
|
||||
l.Warning("reCAPTCHA verification failed, %s", err)
|
||||
c.JSON(200, serializer.ErrWithDetails(c, serializer.CodeCaptchaError, captchaRefresh, err))
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
break
|
||||
case setting.CaptchaTurnstile:
|
||||
captchaSetting := settings.TurnstileCaptcha(c)
|
||||
r := dep.RequestClient(
|
||||
request2.WithContext(c),
|
||||
request2.WithLogger(logging.FromContext(c)),
|
||||
request2.WithHeader(http.Header{"Content-Type": []string{"application/x-www-form-urlencoded"}}),
|
||||
)
|
||||
formData := url.Values{}
|
||||
formData.Set("secret", captchaSetting.Secret)
|
||||
formData.Set("response", service.Ticket)
|
||||
res, err := r.Request("POST", turnstileEndpoint, strings.NewReader(formData.Encode())).
|
||||
CheckHTTPResponse(http.StatusOK).
|
||||
GetResponse()
|
||||
if err != nil {
|
||||
c.JSON(200, serializer.ErrWithDetails(c, serializer.CodeCaptchaError, "Captcha validation failed", err))
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
var trunstileRes turnstileResponse
|
||||
err = json.Unmarshal([]byte(res), &trunstileRes)
|
||||
if err != nil {
|
||||
l.Warning("Turnstile verification failed, %s", err)
|
||||
c.JSON(200, serializer.ErrWithDetails(c, serializer.CodeCaptchaError, "Captcha validation failed", err))
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
if !trunstileRes.Success {
|
||||
c.JSON(200, serializer.ErrWithDetails(c, serializer.CodeCaptchaError, "Captcha validation failed", err))
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
break
|
||||
case setting.CaptchaCap:
|
||||
captchaSetting := settings.CapCaptcha(c)
|
||||
if captchaSetting.InstanceURL == "" || captchaSetting.SiteKey == "" || captchaSetting.SecretKey == "" {
|
||||
l.Warning("Cap verification failed: missing configuration")
|
||||
c.JSON(200, serializer.ErrWithDetails(c, serializer.CodeCaptchaError, "Captcha configuration error", nil))
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
r := dep.RequestClient(
|
||||
request2.WithContext(c),
|
||||
request2.WithLogger(logging.FromContext(c)),
|
||||
request2.WithHeader(http.Header{"Content-Type": []string{"application/json"}}),
|
||||
)
|
||||
|
||||
// Cap 2.0 API format: /{siteKey}/siteverify
|
||||
capEndpoint := strings.TrimSuffix(captchaSetting.InstanceURL, "/") + "/" + captchaSetting.SiteKey + "/siteverify"
|
||||
requestBody := map[string]string{
|
||||
"secret": captchaSetting.SecretKey,
|
||||
"response": service.Ticket,
|
||||
}
|
||||
requestData, err := json.Marshal(requestBody)
|
||||
if err != nil {
|
||||
l.Warning("Cap verification failed: %s", err)
|
||||
c.JSON(200, serializer.ErrWithDetails(c, serializer.CodeCaptchaError, "Captcha validation failed", err))
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
res, err := r.Request("POST", capEndpoint, strings.NewReader(string(requestData))).
|
||||
CheckHTTPResponse(http.StatusOK).
|
||||
GetResponse()
|
||||
if err != nil {
|
||||
l.Warning("Cap verification failed: %s", err)
|
||||
c.JSON(200, serializer.ErrWithDetails(c, serializer.CodeCaptchaError, "Captcha validation failed", err))
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
var capRes capResponse
|
||||
err = json.Unmarshal([]byte(res), &capRes)
|
||||
if err != nil {
|
||||
l.Warning("Cap verification failed: %s", err)
|
||||
c.JSON(200, serializer.ErrWithDetails(c, serializer.CodeCaptchaError, "Captcha validation failed", err))
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
if !capRes.Success {
|
||||
l.Warning("Cap verification failed: validation returned false")
|
||||
c.JSON(200, serializer.ErrWithDetails(c, serializer.CodeCaptchaError, "Captcha validation failed", nil))
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user