diff --git a/internal/logic/auth/resetPasswordLogic.go b/internal/logic/auth/resetPasswordLogic.go index 22db2c9..7ccc6d0 100644 --- a/internal/logic/auth/resetPasswordLogic.go +++ b/internal/logic/auth/resetPasswordLogic.go @@ -8,6 +8,7 @@ import ( "github.com/perfect-panel/server/internal/model/log" "github.com/perfect-panel/server/internal/model/user" + "github.com/perfect-panel/server/pkg/captcha" "github.com/perfect-panel/server/pkg/jwt" "github.com/perfect-panel/server/pkg/uuidx" @@ -43,7 +44,7 @@ func (l *ResetPasswordLogic) ResetPassword(req *types.ResetPasswordRequest) (res loginStatus := false defer func() { - if userInfo.Id != 0 && loginStatus { + if userInfo != nil && userInfo.Id != 0 && loginStatus { loginLog := log.Login{ Method: "email", LoginIP: req.IP, @@ -85,6 +86,11 @@ func (l *ResetPasswordLogic) ResetPassword(req *types.ResetPasswordRequest) (res } } + // Verify captcha + if err := l.verifyCaptcha(req); err != nil { + return nil, err + } + // Check user authMethod, err := l.svcCtx.UserModel.FindUserAuthMethodByOpenID(l.ctx, "email", req.Email) if err != nil { @@ -149,3 +155,68 @@ func (l *ResetPasswordLogic) ResetPassword(req *types.ResetPasswordRequest) (res Token: token, }, nil } + +func (l *ResetPasswordLogic) verifyCaptcha(req *types.ResetPasswordRequest) error { + // Get verify config from database + verifyCfg, err := l.svcCtx.SystemModel.GetVerifyConfig(l.ctx) + if err != nil { + l.Logger.Error("[ResetPasswordLogic] GetVerifyConfig error: ", logger.Field("error", err.Error())) + return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "GetVerifyConfig error: %v", err.Error()) + } + + var config struct { + CaptchaType string `json:"captcha_type"` + EnableUserResetPasswordCaptcha bool `json:"enable_user_reset_password_captcha"` + TurnstileSecret string `json:"turnstile_secret"` + } + tool.SystemConfigSliceReflectToStruct(verifyCfg, &config) + + // Check if user reset password captcha is enabled + if !config.EnableUserResetPasswordCaptcha { + return nil + } + + // Verify based on captcha type + if config.CaptchaType == "local" { + if req.CaptchaId == "" || req.CaptchaCode == "" { + return errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "captcha required") + } + + captchaService := captcha.NewService(captcha.Config{ + Type: captcha.CaptchaTypeLocal, + RedisClient: l.svcCtx.Redis, + }) + + valid, err := captchaService.Verify(l.ctx, req.CaptchaId, req.CaptchaCode, req.IP) + if err != nil { + l.Logger.Error("[ResetPasswordLogic] Verify captcha error: ", logger.Field("error", err.Error())) + return errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "verify captcha error") + } + + if !valid { + return errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "invalid captcha") + } + } else if config.CaptchaType == "turnstile" { + if req.CfToken == "" { + return errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "captcha required") + } + + captchaService := captcha.NewService(captcha.Config{ + Type: captcha.CaptchaTypeTurnstile, + TurnstileSecret: config.TurnstileSecret, + }) + + valid, err := captchaService.Verify(l.ctx, req.CfToken, "", req.IP) + if err != nil { + l.Logger.Error("[ResetPasswordLogic] Verify turnstile error: ", logger.Field("error", err.Error())) + return errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "verify captcha error") + } + + if !valid { + return errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "invalid captcha") + } + } + + return nil +} + diff --git a/internal/logic/auth/userLoginLogic.go b/internal/logic/auth/userLoginLogic.go index 4e6fac2..9bd5d59 100644 --- a/internal/logic/auth/userLoginLogic.go +++ b/internal/logic/auth/userLoginLogic.go @@ -6,6 +6,7 @@ import ( "time" "github.com/perfect-panel/server/internal/model/log" + "github.com/perfect-panel/server/pkg/captcha" "github.com/perfect-panel/server/pkg/constant" "github.com/perfect-panel/server/pkg/logger" @@ -42,7 +43,7 @@ func (l *UserLoginLogic) UserLogin(req *types.UserLoginRequest) (resp *types.Log var userInfo *user.User // Record login status defer func(svcCtx *svc.ServiceContext) { - if userInfo.Id != 0 { + if userInfo != nil && userInfo.Id != 0 { loginLog := log.Login{ Method: "email", LoginIP: req.IP, @@ -66,6 +67,11 @@ func (l *UserLoginLogic) UserLogin(req *types.UserLoginRequest) (resp *types.Log } }(l.svcCtx) + // Verify captcha + if err := l.verifyCaptcha(req); err != nil { + return nil, err + } + userInfo, err = l.svcCtx.UserModel.FindOneByEmail(l.ctx, req.Email) if userInfo.DeletedAt.Valid { @@ -125,3 +131,67 @@ func (l *UserLoginLogic) UserLogin(req *types.UserLoginRequest) (resp *types.Log Token: token, }, nil } + +func (l *UserLoginLogic) verifyCaptcha(req *types.UserLoginRequest) error { + // Get verify config from database + verifyCfg, err := l.svcCtx.SystemModel.GetVerifyConfig(l.ctx) + if err != nil { + l.Logger.Error("[UserLoginLogic] GetVerifyConfig error: ", logger.Field("error", err.Error())) + return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "GetVerifyConfig error: %v", err.Error()) + } + + var config struct { + CaptchaType string `json:"captcha_type"` + EnableUserLoginCaptcha bool `json:"enable_user_login_captcha"` + TurnstileSecret string `json:"turnstile_secret"` + } + tool.SystemConfigSliceReflectToStruct(verifyCfg, &config) + + // Check if captcha is enabled for user login + if !config.EnableUserLoginCaptcha { + return nil + } + + // Verify based on captcha type + if config.CaptchaType == "local" { + if req.CaptchaId == "" || req.CaptchaCode == "" { + return errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "captcha required") + } + + captchaService := captcha.NewService(captcha.Config{ + Type: captcha.CaptchaTypeLocal, + RedisClient: l.svcCtx.Redis, + }) + + valid, err := captchaService.Verify(l.ctx, req.CaptchaId, req.CaptchaCode, req.IP) + if err != nil { + l.Logger.Error("[UserLoginLogic] Verify captcha error: ", logger.Field("error", err.Error())) + return errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "verify captcha error") + } + + if !valid { + return errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "invalid captcha") + } + } else if config.CaptchaType == "turnstile" { + if req.CfToken == "" { + return errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "captcha required") + } + + captchaService := captcha.NewService(captcha.Config{ + Type: captcha.CaptchaTypeTurnstile, + TurnstileSecret: config.TurnstileSecret, + }) + + valid, err := captchaService.Verify(l.ctx, req.CfToken, "", req.IP) + if err != nil { + l.Logger.Error("[UserLoginLogic] Verify turnstile error: ", logger.Field("error", err.Error())) + return errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "verify captcha error") + } + + if !valid { + return errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "invalid captcha") + } + } + + return nil +} diff --git a/internal/logic/auth/userRegisterLogic.go b/internal/logic/auth/userRegisterLogic.go index 287a39e..423a19c 100644 --- a/internal/logic/auth/userRegisterLogic.go +++ b/internal/logic/auth/userRegisterLogic.go @@ -13,6 +13,7 @@ import ( "github.com/perfect-panel/server/internal/model/user" "github.com/perfect-panel/server/internal/svc" "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/captcha" "github.com/perfect-panel/server/pkg/constant" "github.com/perfect-panel/server/pkg/jwt" "github.com/perfect-panel/server/pkg/logger" @@ -80,6 +81,12 @@ func (l *UserRegisterLogic) UserRegister(req *types.UserRegisterRequest) (resp * return nil, errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "code error") } } + + // Verify captcha + if err := l.verifyCaptcha(req); err != nil { + return nil, err + } + // Check if the user exists u, err := l.svcCtx.UserModel.FindOneByEmail(l.ctx, req.Email) if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { @@ -250,7 +257,7 @@ func (l *UserRegisterLogic) UserRegister(req *types.UserRegisterRequest) (resp * } loginStatus := true defer func() { - if token != "" && userInfo.Id != 0 { + if token != "" && userInfo != nil && userInfo.Id != 0 { loginLog := log.Login{ Method: "email", LoginIP: req.IP, @@ -323,3 +330,67 @@ func (l *UserRegisterLogic) activeTrial(uid int64) (*user.Subscribe, error) { } return userSub, nil } + +func (l *UserRegisterLogic) verifyCaptcha(req *types.UserRegisterRequest) error { + // Get verify config from database + verifyCfg, err := l.svcCtx.SystemModel.GetVerifyConfig(l.ctx) + if err != nil { + l.Logger.Error("[UserRegisterLogic] GetVerifyConfig error: ", logger.Field("error", err.Error())) + return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "GetVerifyConfig error: %v", err.Error()) + } + + var config struct { + CaptchaType string `json:"captcha_type"` + EnableUserRegisterCaptcha bool `json:"enable_user_register_captcha"` + TurnstileSecret string `json:"turnstile_secret"` + } + tool.SystemConfigSliceReflectToStruct(verifyCfg, &config) + + // Check if user register captcha is enabled + if !config.EnableUserRegisterCaptcha { + return nil + } + + // Verify based on captcha type + if config.CaptchaType == "local" { + if req.CaptchaId == "" || req.CaptchaCode == "" { + return errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "captcha required") + } + + captchaService := captcha.NewService(captcha.Config{ + Type: captcha.CaptchaTypeLocal, + RedisClient: l.svcCtx.Redis, + }) + + valid, err := captchaService.Verify(l.ctx, req.CaptchaId, req.CaptchaCode, req.IP) + if err != nil { + l.Logger.Error("[UserRegisterLogic] Verify captcha error: ", logger.Field("error", err.Error())) + return errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "verify captcha error") + } + + if !valid { + return errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "invalid captcha") + } + } else if config.CaptchaType == "turnstile" { + if req.CfToken == "" { + return errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "captcha required") + } + + captchaService := captcha.NewService(captcha.Config{ + Type: captcha.CaptchaTypeTurnstile, + TurnstileSecret: config.TurnstileSecret, + }) + + valid, err := captchaService.Verify(l.ctx, req.CfToken, "", req.IP) + if err != nil { + l.Logger.Error("[UserRegisterLogic] Verify turnstile error: ", logger.Field("error", err.Error())) + return errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "verify captcha error") + } + + if !valid { + return errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "invalid captcha") + } + } + + return nil +}