From ff2d3f85f37b42aa2c2499693f8c5e18f7890d19 Mon Sep 17 00:00:00 2001 From: EUForest Date: Thu, 13 Nov 2025 14:52:02 +0800 Subject: [PATCH] new features: Based on IP user registration restrictions --- internal/config/cacheKey.go | 2 + internal/logic/auth/deviceLoginLogic.go | 3 ++ internal/logic/auth/registerLimitLogic.go | 45 +++++++++++++++++++ .../logic/auth/telephoneUserRegisterLogic.go | 4 +- internal/logic/auth/userRegisterLogic.go | 5 +++ pkg/xerr/errCode.go | 1 + pkg/xerr/errMsg.go | 1 + 7 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 internal/logic/auth/registerLimitLogic.go diff --git a/internal/config/cacheKey.go b/internal/config/cacheKey.go index b2b290b..3bce8bd 100644 --- a/internal/config/cacheKey.go +++ b/internal/config/cacheKey.go @@ -62,3 +62,5 @@ const SendIntervalKeyPrefix = "send:interval:" // SendCountLimitKeyPrefix Send Count Limit Key Prefix eg. send:limit:register:email:xxx@ppanel.dev const SendCountLimitKeyPrefix = "send:limit:" + +const RegisterIpKeyPrefix = "register:ip:" diff --git a/internal/logic/auth/deviceLoginLogic.go b/internal/logic/auth/deviceLoginLogic.go index 69250d1..985813e 100644 --- a/internal/logic/auth/deviceLoginLogic.go +++ b/internal/logic/auth/deviceLoginLogic.go @@ -71,6 +71,9 @@ func (l *DeviceLoginLogic) DeviceLogin(req *types.DeviceLoginRequest) (resp *typ deviceInfo, err := l.svcCtx.UserModel.FindOneDeviceByIdentifier(l.ctx, req.Identifier) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { + if !registerIpLimit(l.svcCtx, l.ctx, req.IP, "device", req.Identifier) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.RegisterIPLimit), "register ip limit: %v", req.IP) + } // Device not found, create new user and device userInfo, err = l.registerUserAndDevice(req) if err != nil { diff --git a/internal/logic/auth/registerLimitLogic.go b/internal/logic/auth/registerLimitLogic.go new file mode 100644 index 0000000..40ce3ac --- /dev/null +++ b/internal/logic/auth/registerLimitLogic.go @@ -0,0 +1,45 @@ +package auth + +import ( + "context" + "fmt" + "time" + + "github.com/perfect-panel/server/internal/config" + "github.com/perfect-panel/server/internal/svc" + "go.uber.org/zap" +) + +func registerIpLimit(svcCtx *svc.ServiceContext, ctx context.Context, registerIp, authType, account string) (isOk bool) { + if !svcCtx.Config.Register.EnableIpRegisterLimit { + return true + } + cacheKey := fmt.Sprintf("%s%s:*", config.RegisterIpKeyPrefix, registerIp) + var cacheKeys []string + var cursor uint64 + for { + keys, newCursor, err := svcCtx.Redis.Scan(ctx, 0, cacheKey, 100).Result() + if err != nil { + zap.S().Errorf("[registerIpLimit] Err: %v", err) + return true + } + if len(keys) > 0 { + cacheKeys = append(cacheKeys, keys...) + } + cursor = newCursor + if cursor == 0 { + break + } + } + + defer func() { + key := fmt.Sprintf("%s%s:%s:%s", config.RegisterIpKeyPrefix, registerIp, authType, account) + if err := svcCtx.Redis.Set(ctx, key, account, time.Minute*time.Duration(svcCtx.Config.Register.IpRegisterLimitDuration)).Err(); err != nil { + zap.S().Errorf("[registerIpLimit] Set Err: %v", err) + } + }() + if len(cacheKeys) < int(svcCtx.Config.Register.IpRegisterLimit) { + return true + } + return false +} diff --git a/internal/logic/auth/telephoneUserRegisterLogic.go b/internal/logic/auth/telephoneUserRegisterLogic.go index 64c1dec..ac54796 100644 --- a/internal/logic/auth/telephoneUserRegisterLogic.go +++ b/internal/logic/auth/telephoneUserRegisterLogic.go @@ -102,7 +102,9 @@ func (l *TelephoneUserRegisterLogic) TelephoneUserRegister(req *types.TelephoneR return nil, errors.Wrapf(xerr.NewErrCode(xerr.InviteCodeError), "invite code is invalid") } } - + if !registerIpLimit(l.svcCtx, l.ctx, req.IP, "mobile", phoneNumber) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.RegisterIPLimit), "register ip limit: %v", req.IP) + } // Generate password pwd := tool.EncodePassWord(req.Password) userInfo := &user.User{ diff --git a/internal/logic/auth/userRegisterLogic.go b/internal/logic/auth/userRegisterLogic.go index 56773a5..0b5a4b0 100644 --- a/internal/logic/auth/userRegisterLogic.go +++ b/internal/logic/auth/userRegisterLogic.go @@ -86,6 +86,11 @@ func (l *UserRegisterLogic) UserRegister(req *types.UserRegisterRequest) (resp * } else if err == nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.UserExist), "user email exist: %v", req.Email) } + + if !registerIpLimit(l.svcCtx, l.ctx, req.IP, "email", req.Email) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.RegisterIPLimit), "register ip limit: %v", req.IP) + } + // Generate password pwd := tool.EncodePassWord(req.Password) userInfo := &user.User{ diff --git a/pkg/xerr/errCode.go b/pkg/xerr/errCode.go index c9377c4..3b1c601 100644 --- a/pkg/xerr/errCode.go +++ b/pkg/xerr/errCode.go @@ -27,6 +27,7 @@ const ( TelegramNotBound uint32 = 20007 UserNotBindOauth uint32 = 20008 InviteCodeError uint32 = 20009 + RegisterIPLimit uint32 = 20010 ) // Node error diff --git a/pkg/xerr/errMsg.go b/pkg/xerr/errMsg.go index f688854..7e46e76 100644 --- a/pkg/xerr/errMsg.go +++ b/pkg/xerr/errMsg.go @@ -33,6 +33,7 @@ func init() { TelegramNotBound: "Telegram not bound ", UserNotBindOauth: "User not bind oauth method", InviteCodeError: "Invite code error", + RegisterIPLimit: "Too many registrations", // Node error NodeExist: "Node already exists",