new features: Based on IP user registration restrictions

This commit is contained in:
EUForest 2025-11-13 14:52:02 +08:00
parent 0fb92f380f
commit ff2d3f85f3
7 changed files with 60 additions and 1 deletions

View File

@ -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:"

View File

@ -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 {

View File

@ -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
}

View File

@ -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{

View File

@ -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{

View File

@ -27,6 +27,7 @@ const (
TelegramNotBound uint32 = 20007
UserNotBindOauth uint32 = 20008
InviteCodeError uint32 = 20009
RegisterIPLimit uint32 = 20010
)
// Node error

View File

@ -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",