mirror of https://github.com/cloudreve/Cloudreve
Add Cap Captcha support (#2511)
* Add Cap Captcha support - Add CaptchaCap type constant in types.go - Add Cap struct with InstanceURL, KeyID, and KeySecret fields - Add CapCaptcha method in provider.go to return Cap settings - Add default settings for Cap captcha in setting.go - Implement Cap captcha verification logic in middleware - Expose Cap captcha settings in site API This adds support for Cap captcha service as an alternative captcha option alongside existing reCAPTCHA, Turnstile and built-in captcha options. * update cap json tagspull/2557/head
parent
9a216cd09e
commit
9f9796f2f3
|
@ -143,6 +143,9 @@ var DefaultSettings = map[string]string{
|
||||||
"captcha_ReCaptchaSecret": "defaultSecret",
|
"captcha_ReCaptchaSecret": "defaultSecret",
|
||||||
"captcha_turnstile_site_key": "",
|
"captcha_turnstile_site_key": "",
|
||||||
"captcha_turnstile_site_secret": "",
|
"captcha_turnstile_site_secret": "",
|
||||||
|
"captcha_cap_instance_url": "",
|
||||||
|
"captcha_cap_key_id": "",
|
||||||
|
"captcha_cap_key_secret": "",
|
||||||
"thumb_width": "400",
|
"thumb_width": "400",
|
||||||
"thumb_height": "300",
|
"thumb_height": "300",
|
||||||
"thumb_entity_suffix": "._thumb",
|
"thumb_entity_suffix": "._thumb",
|
||||||
|
|
|
@ -38,6 +38,9 @@ type (
|
||||||
turnstileResponse struct {
|
turnstileResponse struct {
|
||||||
Success bool `json:"success"`
|
Success bool `json:"success"`
|
||||||
}
|
}
|
||||||
|
capResponse struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// CaptchaRequired 验证请求签名
|
// CaptchaRequired 验证请求签名
|
||||||
|
@ -127,6 +130,61 @@ func CaptchaRequired(enabled func(c *gin.Context) bool) gin.HandlerFunc {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
case setting.CaptchaCap:
|
||||||
|
captchaSetting := settings.CapCaptcha(c)
|
||||||
|
if captchaSetting.InstanceURL == "" || captchaSetting.KeyID == "" || captchaSetting.KeySecret == "" {
|
||||||
|
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"}}),
|
||||||
|
)
|
||||||
|
|
||||||
|
capEndpoint := strings.TrimSuffix(captchaSetting.InstanceURL, "/") + "/" + captchaSetting.KeyID + "/siteverify"
|
||||||
|
requestBody := map[string]string{
|
||||||
|
"secret": captchaSetting.KeySecret,
|
||||||
|
"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
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,8 @@ type (
|
||||||
TcCaptcha(ctx context.Context) *TcCaptcha
|
TcCaptcha(ctx context.Context) *TcCaptcha
|
||||||
// TurnstileCaptcha returns the Cloudflare Turnstile settings.
|
// TurnstileCaptcha returns the Cloudflare Turnstile settings.
|
||||||
TurnstileCaptcha(ctx context.Context) *Turnstile
|
TurnstileCaptcha(ctx context.Context) *Turnstile
|
||||||
|
// CapCaptcha returns the Cap settings.
|
||||||
|
CapCaptcha(ctx context.Context) *Cap
|
||||||
// EmailActivationEnabled returns true if email activation is required.
|
// EmailActivationEnabled returns true if email activation is required.
|
||||||
EmailActivationEnabled(ctx context.Context) bool
|
EmailActivationEnabled(ctx context.Context) bool
|
||||||
// DefaultGroup returns the default group ID for new users.
|
// DefaultGroup returns the default group ID for new users.
|
||||||
|
@ -638,6 +640,14 @@ func (s *settingProvider) TurnstileCaptcha(ctx context.Context) *Turnstile {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *settingProvider) CapCaptcha(ctx context.Context) *Cap {
|
||||||
|
return &Cap{
|
||||||
|
InstanceURL: s.getString(ctx, "captcha_cap_instance_url", ""),
|
||||||
|
KeyID: s.getString(ctx, "captcha_cap_key_id", ""),
|
||||||
|
KeySecret: s.getString(ctx, "captcha_cap_key_secret", ""),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *settingProvider) ReCaptcha(ctx context.Context) *ReCaptcha {
|
func (s *settingProvider) ReCaptcha(ctx context.Context) *ReCaptcha {
|
||||||
return &ReCaptcha{
|
return &ReCaptcha{
|
||||||
Secret: s.getString(ctx, "captcha_ReCaptchaSecret", ""),
|
Secret: s.getString(ctx, "captcha_ReCaptchaSecret", ""),
|
||||||
|
|
|
@ -28,6 +28,7 @@ const (
|
||||||
CaptchaReCaptcha = CaptchaType("recaptcha")
|
CaptchaReCaptcha = CaptchaType("recaptcha")
|
||||||
CaptchaTcaptcha = CaptchaType("tcaptcha")
|
CaptchaTcaptcha = CaptchaType("tcaptcha")
|
||||||
CaptchaTurnstile = CaptchaType("turnstile")
|
CaptchaTurnstile = CaptchaType("turnstile")
|
||||||
|
CaptchaCap = CaptchaType("cap")
|
||||||
)
|
)
|
||||||
|
|
||||||
type ReCaptcha struct {
|
type ReCaptcha struct {
|
||||||
|
@ -47,6 +48,12 @@ type Turnstile struct {
|
||||||
Secret string
|
Secret string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Cap struct {
|
||||||
|
InstanceURL string
|
||||||
|
KeyID string
|
||||||
|
KeySecret string
|
||||||
|
}
|
||||||
|
|
||||||
type SMTP struct {
|
type SMTP struct {
|
||||||
FromName string
|
FromName string
|
||||||
From string
|
From string
|
||||||
|
|
|
@ -28,6 +28,8 @@ type SiteConfig struct {
|
||||||
ReCaptchaKey string `json:"captcha_ReCaptchaKey,omitempty"`
|
ReCaptchaKey string `json:"captcha_ReCaptchaKey,omitempty"`
|
||||||
CaptchaType setting.CaptchaType `json:"captcha_type,omitempty"`
|
CaptchaType setting.CaptchaType `json:"captcha_type,omitempty"`
|
||||||
TurnstileSiteID string `json:"turnstile_site_id,omitempty"`
|
TurnstileSiteID string `json:"turnstile_site_id,omitempty"`
|
||||||
|
CapInstanceURL string `json:"captcha_cap_instance_url,omitempty"`
|
||||||
|
CapKeyID string `json:"captcha_cap_key_id,omitempty"`
|
||||||
RegisterEnabled bool `json:"register_enabled,omitempty"`
|
RegisterEnabled bool `json:"register_enabled,omitempty"`
|
||||||
TosUrl string `json:"tos_url,omitempty"`
|
TosUrl string `json:"tos_url,omitempty"`
|
||||||
PrivacyPolicyUrl string `json:"privacy_policy_url,omitempty"`
|
PrivacyPolicyUrl string `json:"privacy_policy_url,omitempty"`
|
||||||
|
@ -119,6 +121,7 @@ func (s *GetSettingService) GetSiteConfig(c *gin.Context) (*SiteConfig, error) {
|
||||||
userRes := user.BuildUser(u, dep.HashIDEncoder())
|
userRes := user.BuildUser(u, dep.HashIDEncoder())
|
||||||
logo := settings.Logo(c)
|
logo := settings.Logo(c)
|
||||||
reCaptcha := settings.ReCaptcha(c)
|
reCaptcha := settings.ReCaptcha(c)
|
||||||
|
capCaptcha := settings.CapCaptcha(c)
|
||||||
appSetting := settings.AppSetting(c)
|
appSetting := settings.AppSetting(c)
|
||||||
|
|
||||||
return &SiteConfig{
|
return &SiteConfig{
|
||||||
|
@ -132,6 +135,8 @@ func (s *GetSettingService) GetSiteConfig(c *gin.Context) (*SiteConfig, error) {
|
||||||
CaptchaType: settings.CaptchaType(c),
|
CaptchaType: settings.CaptchaType(c),
|
||||||
TurnstileSiteID: settings.TurnstileCaptcha(c).Key,
|
TurnstileSiteID: settings.TurnstileCaptcha(c).Key,
|
||||||
ReCaptchaKey: reCaptcha.Key,
|
ReCaptchaKey: reCaptcha.Key,
|
||||||
|
CapInstanceURL: capCaptcha.InstanceURL,
|
||||||
|
CapKeyID: capCaptcha.KeyID,
|
||||||
AppPromotion: appSetting.Promotion,
|
AppPromotion: appSetting.Promotion,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue