From 9e34bdce9e60cfbf6c6fa8da8ada0feec6acbba0 Mon Sep 17 00:00:00 2001 From: admin Date: Wed, 16 Sep 2026 13:33:27 +0800 Subject: [PATCH] feat: self-connection blocking API support (token-device internal endpoint, jti) --- conf/config.yaml | 2 +- config/config.go | 2 ++ http/controller/api/internal.go | 50 +++++++++++++++++++++++++++++++++ http/router/api.go | 6 ++++ lib/jwt/jwt.go | 4 +++ service/user.go | 10 +++++++ 6 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 http/controller/api/internal.go diff --git a/conf/config.yaml b/conf/config.yaml index 542b802..84e21d2 100644 --- a/conf/config.yaml +++ b/conf/config.yaml @@ -1,6 +1,6 @@ lang: "zh-CN" app: - web-client: 1 # 1:启用 0:禁用 + web-client: 0 # 1:启用 0:禁用(DMCA 后端永久禁用,代码强制为 0,此配置已失效) register: false #是否开启注册 register-status: 1 # 注册用户默认状态 1:启用 2:禁用 captcha-threshold: 3 # <0:disabled, 0 always, >0:enabled diff --git a/config/config.go b/config/config.go index cd40d10..9669a31 100644 --- a/config/config.go +++ b/config/config.go @@ -91,6 +91,8 @@ func Init(rowVal *Config, path string) *viper.Viper { if err := v.Unmarshal(rowVal); err != nil { panic(fmt.Errorf("Fatal error config: %s \n", err)) } + // webclient2 因 DMCA 已移除前端资源,永久禁用,不再受配置文件或环境变量控制 + rowVal.App.WebClient = 0 rowVal.Rustdesk.LoadKeyFile() rowVal.Admin.Init() return v diff --git a/http/controller/api/internal.go b/http/controller/api/internal.go new file mode 100644 index 0000000..29ad00f --- /dev/null +++ b/http/controller/api/internal.go @@ -0,0 +1,50 @@ +package api + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/lejianwen/rustdesk-api/v2/global" + "github.com/lejianwen/rustdesk-api/v2/service" +) + +type Internal struct { +} + +// localOnly 仅允许本机/容器内回环访问 +func (in *Internal) localOnly(c *gin.Context) bool { + ip := c.RemoteIP() + return ip == "127.0.0.1" || ip == "::1" +} + +// TokenDevice 根据token查询登录设备ID,供hbbs自连拦截内部调用 +// @Tags 内部接口 +// @Summary 根据token查询设备ID +// @Description 仅供hbbs在容器内调用,需回环地址与共享密钥 +// @Param token query string true "访问令牌" +// @Success 200 {object} map[string]string +// @Failure 403 {object} map[string]string +// @Failure 404 {object} map[string]string +// @Router /api/internal/token-device [get] +func (in *Internal) TokenDevice(c *gin.Context) { + if !in.localOnly(c) { + c.JSON(http.StatusForbidden, gin.H{"error": "forbidden"}) + return + } + // 共享密钥:hbbs 通过 RUSTDESK_API_JWT_KEY 持有同一密钥 + if global.Config.Jwt.Key == "" || c.GetHeader("X-Secret") != global.Config.Jwt.Key { + c.JSON(http.StatusForbidden, gin.H{"error": "forbidden"}) + return + } + token := c.Query("token") + if token == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "token required"}) + return + } + deviceId := service.AllService.UserService.GetDeviceIdByToken(token) + if deviceId == "" { + c.JSON(http.StatusNotFound, gin.H{"error": "not found"}) + return + } + c.JSON(http.StatusOK, gin.H{"device_id": deviceId}) +} diff --git a/http/router/api.go b/http/router/api.go index 52c7773..b2456ab 100644 --- a/http/router/api.go +++ b/http/router/api.go @@ -73,6 +73,12 @@ func ApiInit(g *gin.Engine) { frg.POST("/audit/file", au.AuditFile) } + { + in := &api.Internal{} + // 仅供hbbs容器内调用:token -> device_id(自连拦截用) + frg.GET("/internal/token-device", in.TokenDevice) + } + frg.Use(middleware.RustAuth()) { u := &api.User{} diff --git a/lib/jwt/jwt.go b/lib/jwt/jwt.go index e522be2..4a6352c 100644 --- a/lib/jwt/jwt.go +++ b/lib/jwt/jwt.go @@ -3,6 +3,7 @@ package jwt import ( "fmt" "github.com/golang-jwt/jwt/v5" + "github.com/google/uuid" "time" ) @@ -33,6 +34,9 @@ func (s *Jwt) GenerateToken(userId uint) string { UserId: userId, RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(time.Now().Add(s.TokenExpireDuration)), + // 每次登录生成唯一 jti,避免同一账号同秒多次登录产生相同 token, + // 导致 UserToken 表中 token 无法区分设备 + ID: uuid.NewString(), }, }) token, err := t.SignedString(s.Key) diff --git a/service/user.go b/service/user.go index 9eb7c51..1bddd0d 100644 --- a/service/user.go +++ b/service/user.go @@ -85,6 +85,16 @@ func (us *UserService) InfoByAccessToken(token string) (*model.User, *model.User return u, ut } +// GetDeviceIdByToken 根据token查询登录设备ID,供hbbs自连拦截内部调用 +func (us *UserService) GetDeviceIdByToken(token string) string { + ut := &model.UserToken{} + DB.Where("token = ?", token).First(ut) + if ut.Id == 0 || ut.ExpiredAt < time.Now().Unix() { + return "" + } + return ut.DeviceId +} + // GenerateToken 生成token func (us *UserService) GenerateToken(u *model.User) string { if len(Jwt.Key) > 0 {