Refactor: captcha (#796)

pull/807/head
topjohncian 3 years ago committed by GitHub
parent c0f7214cdb
commit 233648b956
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,109 @@
package middleware
import (
"bytes"
"encoding/json"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/recaptcha"
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/gin-gonic/gin"
"github.com/mojocn/base64Captcha"
captcha "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/captcha/v20190722"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
"io"
"io/ioutil"
"strconv"
"time"
)
type req struct {
CaptchaCode string `json:"captchaCode"`
Ticket string `json:"ticket"`
Randstr string `json:"randstr"`
}
// CaptchaRequired 验证请求签名
func CaptchaRequired(configName string) gin.HandlerFunc {
return func(c *gin.Context) {
// 相关设定
options := model.GetSettingByNames(configName,
"captcha_type",
"captcha_ReCaptchaSecret",
"captcha_TCaptcha_SecretId",
"captcha_TCaptcha_SecretKey",
"captcha_TCaptcha_CaptchaAppId",
"captcha_TCaptcha_AppSecretKey")
// 检查验证码
isCaptchaRequired := model.IsTrueVal(options[configName])
if isCaptchaRequired {
var service req
bodyCopy := new(bytes.Buffer)
_, err := io.Copy(bodyCopy, c.Request.Body)
if err != nil {
c.JSON(200, serializer.ParamErr("验证码错误", err))
c.Abort()
}
bodyData := bodyCopy.Bytes()
err = json.Unmarshal(bodyData, &service)
if err != nil {
c.JSON(200, serializer.ParamErr("验证码错误", err))
c.Abort()
}
c.Request.Body = ioutil.NopCloser(bytes.NewReader(bodyData))
switch options["captcha_type"] {
case "normal":
captchaID := util.GetSession(c, "captchaID")
util.DeleteSession(c, "captchaID")
if captchaID == nil || !base64Captcha.VerifyCaptcha(captchaID.(string), service.CaptchaCode) {
c.JSON(200, serializer.ParamErr("验证码错误", nil))
c.Abort()
}
break
case "recaptcha":
reCAPTCHA, err := recaptcha.NewReCAPTCHA(options["captcha_ReCaptchaSecret"], recaptcha.V2, 10*time.Second)
if err != nil {
util.Log().Warning("reCAPTCHA 验证错误, %s", err)
}
err = reCAPTCHA.Verify(service.CaptchaCode)
if err != nil {
util.Log().Warning("reCAPTCHA 验证错误, %s", err)
c.JSON(200, serializer.ParamErr("验证失败,请刷新网页后再次验证", nil))
c.Abort()
}
break
case "tcaptcha":
credential := common.NewCredential(
options["captcha_TCaptcha_SecretId"],
options["captcha_TCaptcha_SecretKey"],
)
cpf := profile.NewClientProfile()
cpf.HttpProfile.Endpoint = "captcha.tencentcloudapi.com"
client, _ := captcha.NewClient(credential, "", cpf)
request := captcha.NewDescribeCaptchaResultRequest()
request.CaptchaType = common.Uint64Ptr(9)
appid, _ := strconv.Atoi(options["captcha_TCaptcha_CaptchaAppId"])
request.CaptchaAppId = common.Uint64Ptr(uint64(appid))
request.AppSecretKey = common.StringPtr(options["captcha_TCaptcha_AppSecretKey"])
request.Ticket = common.StringPtr(service.Ticket)
request.Randstr = common.StringPtr(service.Randstr)
request.UserIp = common.StringPtr(c.ClientIP())
response, err := client.DescribeCaptchaResult(request)
if err != nil {
util.Log().Warning("TCaptcha 验证错误, %s", err)
}
if *response.Response.CaptchaCode != int64(1) {
c.JSON(200, serializer.ParamErr("验证失败,请刷新网页后再次验证", nil))
c.Abort()
}
break
}
}
c.Next()
}
}

@ -149,6 +149,7 @@ Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; verti
{Name: "share_view_method", Value: "list", Type: "view"}, {Name: "share_view_method", Value: "list", Type: "view"},
{Name: "cron_garbage_collect", Value: "@hourly", Type: "cron"}, {Name: "cron_garbage_collect", Value: "@hourly", Type: "cron"},
{Name: "authn_enabled", Value: "0", Type: "authn"}, {Name: "authn_enabled", Value: "0", Type: "authn"},
{Name: "captcha_type", Value: "normal", Type: "captcha"},
{Name: "captcha_height", Value: "60", Type: "captcha"}, {Name: "captcha_height", Value: "60", Type: "captcha"},
{Name: "captcha_width", Value: "240", Type: "captcha"}, {Name: "captcha_width", Value: "240", Type: "captcha"},
{Name: "captcha_mode", Value: "3", Type: "captcha"}, {Name: "captcha_mode", Value: "3", Type: "captcha"},
@ -160,9 +161,12 @@ Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; verti
{Name: "captcha_IsShowSlimeLine", Value: "1", Type: "captcha"}, {Name: "captcha_IsShowSlimeLine", Value: "1", Type: "captcha"},
{Name: "captcha_IsShowSineLine", Value: "0", Type: "captcha"}, {Name: "captcha_IsShowSineLine", Value: "0", Type: "captcha"},
{Name: "captcha_CaptchaLen", Value: "6", Type: "captcha"}, {Name: "captcha_CaptchaLen", Value: "6", Type: "captcha"},
{Name: "captcha_IsUseReCaptcha", Value: "0", Type: "captcha"},
{Name: "captcha_ReCaptchaKey", Value: "defaultKey", Type: "captcha"}, {Name: "captcha_ReCaptchaKey", Value: "defaultKey", Type: "captcha"},
{Name: "captcha_ReCaptchaSecret", Value: "defaultSecret", Type: "captcha"}, {Name: "captcha_ReCaptchaSecret", Value: "defaultSecret", Type: "captcha"},
{Name: "captcha_TCaptcha_CaptchaAppId", Value: "", Type: "captcha"},
{Name: "captcha_TCaptcha_AppSecretKey", Value: "", Type: "captcha"},
{Name: "captcha_TCaptcha_SecretId", Value: "", Type: "captcha"},
{Name: "captcha_TCaptcha_SecretKey", Value: "", Type: "captcha"},
{Name: "thumb_width", Value: "400", Type: "thumb"}, {Name: "thumb_width", Value: "400", Type: "thumb"},
{Name: "thumb_height", Value: "300", Type: "thumb"}, {Name: "thumb_height", Value: "300", Type: "thumb"},
{Name: "pwa_small_icon", Value: "/static/img/favicon.ico", Type: "pwa"}, {Name: "pwa_small_icon", Value: "/static/img/favicon.ico", Type: "pwa"},

@ -4,20 +4,21 @@ import model "github.com/cloudreve/Cloudreve/v3/models"
// SiteConfig 站点全局设置序列 // SiteConfig 站点全局设置序列
type SiteConfig struct { type SiteConfig struct {
SiteName string `json:"title"` SiteName string `json:"title"`
SiteICPId string `json:"siteICPId"` SiteICPId string `json:"siteICPId"`
LoginCaptcha bool `json:"loginCaptcha"` LoginCaptcha bool `json:"loginCaptcha"`
RegCaptcha bool `json:"regCaptcha"` RegCaptcha bool `json:"regCaptcha"`
ForgetCaptcha bool `json:"forgetCaptcha"` ForgetCaptcha bool `json:"forgetCaptcha"`
EmailActive bool `json:"emailActive"` EmailActive bool `json:"emailActive"`
Themes string `json:"themes"` Themes string `json:"themes"`
DefaultTheme string `json:"defaultTheme"` DefaultTheme string `json:"defaultTheme"`
HomepageViewMethod string `json:"home_view_method"` HomepageViewMethod string `json:"home_view_method"`
ShareViewMethod string `json:"share_view_method"` ShareViewMethod string `json:"share_view_method"`
Authn bool `json:"authn"` Authn bool `json:"authn"`
User User `json:"user"` User User `json:"user"`
UseReCaptcha bool `json:"captcha_IsUseReCaptcha"` ReCaptchaKey string `json:"captcha_ReCaptchaKey"`
ReCaptchaKey string `json:"captcha_ReCaptchaKey"` CaptchaType string `json:"captcha_type"`
TCaptchaCaptchaAppId string `json:"tcaptcha_captcha_app_id"`
} }
type task struct { type task struct {
@ -64,20 +65,21 @@ func BuildSiteConfig(settings map[string]string, user *model.User) Response {
} }
res := Response{ res := Response{
Data: SiteConfig{ Data: SiteConfig{
SiteName: checkSettingValue(settings, "siteName"), SiteName: checkSettingValue(settings, "siteName"),
SiteICPId: checkSettingValue(settings, "siteICPId"), SiteICPId: checkSettingValue(settings, "siteICPId"),
LoginCaptcha: model.IsTrueVal(checkSettingValue(settings, "login_captcha")), LoginCaptcha: model.IsTrueVal(checkSettingValue(settings, "login_captcha")),
RegCaptcha: model.IsTrueVal(checkSettingValue(settings, "reg_captcha")), RegCaptcha: model.IsTrueVal(checkSettingValue(settings, "reg_captcha")),
ForgetCaptcha: model.IsTrueVal(checkSettingValue(settings, "forget_captcha")), ForgetCaptcha: model.IsTrueVal(checkSettingValue(settings, "forget_captcha")),
EmailActive: model.IsTrueVal(checkSettingValue(settings, "email_active")), EmailActive: model.IsTrueVal(checkSettingValue(settings, "email_active")),
Themes: checkSettingValue(settings, "themes"), Themes: checkSettingValue(settings, "themes"),
DefaultTheme: checkSettingValue(settings, "defaultTheme"), DefaultTheme: checkSettingValue(settings, "defaultTheme"),
HomepageViewMethod: checkSettingValue(settings, "home_view_method"), HomepageViewMethod: checkSettingValue(settings, "home_view_method"),
ShareViewMethod: checkSettingValue(settings, "share_view_method"), ShareViewMethod: checkSettingValue(settings, "share_view_method"),
Authn: model.IsTrueVal(checkSettingValue(settings, "authn_enabled")), Authn: model.IsTrueVal(checkSettingValue(settings, "authn_enabled")),
User: userRes, User: userRes,
UseReCaptcha: model.IsTrueVal(checkSettingValue(settings, "captcha_IsUseReCaptcha")), ReCaptchaKey: checkSettingValue(settings, "captcha_ReCaptchaKey"),
ReCaptchaKey: checkSettingValue(settings, "captcha_ReCaptchaKey"), CaptchaType: checkSettingValue(settings, "captcha_type"),
TCaptchaCaptchaAppId: checkSettingValue(settings, "captcha_TCaptcha_CaptchaAppId"),
}} }}
return res return res
} }

@ -24,8 +24,9 @@ func SiteConfig(c *gin.Context) {
"home_view_method", "home_view_method",
"share_view_method", "share_view_method",
"authn_enabled", "authn_enabled",
"captcha_IsUseReCaptcha",
"captcha_ReCaptchaKey", "captcha_ReCaptchaKey",
"captcha_type",
"captcha_TCaptcha_CaptchaAppId",
) )
// 如果已登录,则同时返回用户信息和标签 // 如果已登录,则同时返回用户信息和标签

@ -116,16 +116,17 @@ func InitMasterRouter() *gin.Engine {
user := v3.Group("user") user := v3.Group("user")
{ {
// 用户登录 // 用户登录
user.POST("session", controllers.UserLogin) user.POST("session", middleware.CaptchaRequired("login_captcha"), controllers.UserLogin)
// 用户注册 // 用户注册
user.POST("", user.POST("",
middleware.IsFunctionEnabled("register_enabled"), middleware.IsFunctionEnabled("register_enabled"),
middleware.CaptchaRequired("reg_captcha"),
controllers.UserRegister, controllers.UserRegister,
) )
// 用二步验证户登录 // 用二步验证户登录
user.POST("2fa", controllers.User2FALogin) user.POST("2fa", controllers.User2FALogin)
// 发送密码重设邮件 // 发送密码重设邮件
user.POST("reset", controllers.UserSendReset) user.POST("reset", middleware.CaptchaRequired("forget_captcha"), controllers.UserSendReset)
// 通过邮件里的链接重设密码 // 通过邮件里的链接重设密码
user.PATCH("reset", controllers.UserReset) user.PATCH("reset", controllers.UserReset)
// 邮件激活 // 邮件激活

@ -2,33 +2,27 @@ package user
import ( import (
"fmt" "fmt"
"net/url"
"time"
model "github.com/cloudreve/Cloudreve/v3/models" model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/cache" "github.com/cloudreve/Cloudreve/v3/pkg/cache"
"github.com/cloudreve/Cloudreve/v3/pkg/email" "github.com/cloudreve/Cloudreve/v3/pkg/email"
"github.com/cloudreve/Cloudreve/v3/pkg/hashid" "github.com/cloudreve/Cloudreve/v3/pkg/hashid"
"github.com/cloudreve/Cloudreve/v3/pkg/recaptcha"
"github.com/cloudreve/Cloudreve/v3/pkg/serializer" "github.com/cloudreve/Cloudreve/v3/pkg/serializer"
"github.com/cloudreve/Cloudreve/v3/pkg/util" "github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/mojocn/base64Captcha"
"github.com/pquerna/otp/totp" "github.com/pquerna/otp/totp"
"net/url"
) )
// UserLoginService 管理用户登录的服务 // UserLoginService 管理用户登录的服务
type UserLoginService struct { type UserLoginService struct {
//TODO 细致调整验证规则 //TODO 细致调整验证规则
UserName string `form:"userName" json:"userName" binding:"required,email"` UserName string `form:"userName" json:"userName" binding:"required,email"`
Password string `form:"Password" json:"Password" binding:"required,min=4,max=64"` Password string `form:"Password" json:"Password" binding:"required,min=4,max=64"`
CaptchaCode string `form:"captchaCode" json:"captchaCode"`
} }
// UserResetEmailService 发送密码重设邮件服务 // UserResetEmailService 发送密码重设邮件服务
type UserResetEmailService struct { type UserResetEmailService struct {
UserName string `form:"userName" json:"userName" binding:"required,email"` UserName string `form:"userName" json:"userName" binding:"required,email"`
CaptchaCode string `form:"captchaCode" json:"captchaCode"`
} }
// UserResetService 密码重设服务 // UserResetService 密码重设服务
@ -69,28 +63,6 @@ func (service *UserResetService) Reset(c *gin.Context) serializer.Response {
// Reset 发送密码重设邮件 // Reset 发送密码重设邮件
func (service *UserResetEmailService) Reset(c *gin.Context) serializer.Response { func (service *UserResetEmailService) Reset(c *gin.Context) serializer.Response {
// 检查验证码
isCaptchaRequired := model.IsTrueVal(model.GetSettingByName("forget_captcha"))
useRecaptcha := model.IsTrueVal(model.GetSettingByName("captcha_IsUseReCaptcha"))
recaptchaSecret := model.GetSettingByName("captcha_ReCaptchaSecret")
if isCaptchaRequired && !useRecaptcha {
captchaID := util.GetSession(c, "captchaID")
util.DeleteSession(c, "captchaID")
if captchaID == nil || !base64Captcha.VerifyCaptcha(captchaID.(string), service.CaptchaCode) {
return serializer.ParamErr("验证码错误", nil)
}
} else if isCaptchaRequired && useRecaptcha {
captcha, err := recaptcha.NewReCAPTCHA(recaptchaSecret, recaptcha.V2, 10*time.Second)
if err != nil {
util.Log().Error(err.Error())
}
err = captcha.Verify(service.CaptchaCode)
if err != nil {
util.Log().Error(err.Error())
return serializer.ParamErr("验证失败,请刷新网页后再次验证", nil)
}
}
// 查找用户 // 查找用户
if user, err := model.GetUserByEmail(service.UserName); err == nil { if user, err := model.GetUserByEmail(service.UserName); err == nil {
@ -151,30 +123,7 @@ func (service *Enable2FA) Login(c *gin.Context) serializer.Response {
// Login 用户登录函数 // Login 用户登录函数
func (service *UserLoginService) Login(c *gin.Context) serializer.Response { func (service *UserLoginService) Login(c *gin.Context) serializer.Response {
isCaptchaRequired := model.GetSettingByName("login_captcha")
useRecaptcha := model.GetSettingByName("captcha_IsUseReCaptcha")
recaptchaSecret := model.GetSettingByName("captcha_ReCaptchaSecret")
expectedUser, err := model.GetUserByEmail(service.UserName) expectedUser, err := model.GetUserByEmail(service.UserName)
if (model.IsTrueVal(isCaptchaRequired)) && !(model.IsTrueVal(useRecaptcha)) {
// TODO 验证码校验
captchaID := util.GetSession(c, "captchaID")
util.DeleteSession(c, "captchaID")
if captchaID == nil || !base64Captcha.VerifyCaptcha(captchaID.(string), service.CaptchaCode) {
return serializer.ParamErr("验证码错误", nil)
}
} else if (model.IsTrueVal(isCaptchaRequired)) && (model.IsTrueVal(useRecaptcha)) {
captcha, err := recaptcha.NewReCAPTCHA(recaptchaSecret, recaptcha.V2, 10*time.Second)
if err != nil {
util.Log().Error(err.Error())
}
err = captcha.Verify(service.CaptchaCode)
if err != nil {
util.Log().Error(err.Error())
return serializer.ParamErr("验证失败,请刷新网页后再次验证", nil)
}
}
// 一系列校验 // 一系列校验
if err != nil { if err != nil {
return serializer.Err(serializer.CodeCredentialInvalid, "用户邮箱或密码错误", err) return serializer.Err(serializer.CodeCredentialInvalid, "用户邮箱或密码错误", err)

@ -1,54 +1,27 @@
package user package user
import ( import (
"net/url"
"strings"
"time"
model "github.com/cloudreve/Cloudreve/v3/models" model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/auth" "github.com/cloudreve/Cloudreve/v3/pkg/auth"
"github.com/cloudreve/Cloudreve/v3/pkg/email" "github.com/cloudreve/Cloudreve/v3/pkg/email"
"github.com/cloudreve/Cloudreve/v3/pkg/hashid" "github.com/cloudreve/Cloudreve/v3/pkg/hashid"
"github.com/cloudreve/Cloudreve/v3/pkg/recaptcha"
"github.com/cloudreve/Cloudreve/v3/pkg/serializer" "github.com/cloudreve/Cloudreve/v3/pkg/serializer"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/mojocn/base64Captcha" "net/url"
"strings"
) )
// UserRegisterService 管理用户注册的服务 // UserRegisterService 管理用户注册的服务
type UserRegisterService struct { type UserRegisterService struct {
//TODO 细致调整验证规则 //TODO 细致调整验证规则
UserName string `form:"userName" json:"userName" binding:"required,email"` UserName string `form:"userName" json:"userName" binding:"required,email"`
Password string `form:"Password" json:"Password" binding:"required,min=4,max=64"` Password string `form:"Password" json:"Password" binding:"required,min=4,max=64"`
CaptchaCode string `form:"captchaCode" json:"captchaCode"`
} }
// Register 新用户注册 // Register 新用户注册
func (service *UserRegisterService) Register(c *gin.Context) serializer.Response { func (service *UserRegisterService) Register(c *gin.Context) serializer.Response {
// 相关设定 // 相关设定
options := model.GetSettingByNames("email_active", "reg_captcha") options := model.GetSettingByNames("email_active")
// 检查验证码
isCaptchaRequired := model.IsTrueVal(options["reg_captcha"])
useRecaptcha := model.IsTrueVal(model.GetSettingByName("captcha_IsUseReCaptcha"))
recaptchaSecret := model.GetSettingByName("captcha_ReCaptchaSecret")
if isCaptchaRequired && !useRecaptcha {
captchaID := util.GetSession(c, "captchaID")
util.DeleteSession(c, "captchaID")
if captchaID == nil || !base64Captcha.VerifyCaptcha(captchaID.(string), service.CaptchaCode) {
return serializer.ParamErr("验证码错误", nil)
}
} else if isCaptchaRequired && useRecaptcha {
captcha, err := recaptcha.NewReCAPTCHA(recaptchaSecret, recaptcha.V2, 10*time.Second)
if err != nil {
util.Log().Error(err.Error())
}
err = captcha.Verify(service.CaptchaCode)
if err != nil {
util.Log().Error(err.Error())
return serializer.ParamErr("验证失败,请刷新网页后再次验证", nil)
}
}
// 相关设定 // 相关设定
isEmailRequired := model.IsTrueVal(options["email_active"]) isEmailRequired := model.IsTrueVal(options["email_active"])

Loading…
Cancel
Save