mirror of https://github.com/cloudreve/Cloudreve
196 lines
5.9 KiB
Go
196 lines
5.9 KiB
Go
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()
|
|
}
|
|
}
|