feat: self-connection blocking API support (token-device internal endpoint, jti)

This commit is contained in:
admin committed 2026-09-16 13:33:27 +08:00
1 parent 37b9991d0e
commit 9e34bdce9e
6 files changed
+73 -1

No files matched your search

+1 -1
View File
@@ -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
+2
View File
@@ -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
+50
View File
@@ -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})
}
+6
View File
@@ -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{}
+4
View File
@@ -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)
+10
View File
@@ -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 {