安全加固:LLM 会话按 (bot,peer) 隔离+历史上限50条,LLM 入队按 (bot,from_node) 限流(60s内5条),瓦片磁盘缓存单源配额(3000文件/300MB 按mtime淘汰),签到墙读接口限速+全站每日1000条封顶,敏感数据落盘 AES-256-GCM 加密(MESH_SECRET_KEY, 兼容明文迁移),admin 改密需验证当前密码+pwd_version 会话撤销,后端 v1.5.0

This commit is contained in:
2026-08-20 17:13:13 +08:00
parent f21e8337af
commit ba9be5b68b
24 changed files with 619 additions and 51 deletions
+25 -3
View File
@@ -85,7 +85,7 @@ func NewRouter(cfg configpkg.WebConfig, consoleLog bool, store *storepkg.Store,
return r
}
const BackendVersion = "1.4.0"
const BackendVersion = "1.5.0"
var CommitVersion = "dev"
@@ -111,7 +111,13 @@ func registerAPIRoutes(r gin.IRouter, store *storepkg.Store, mapTileCacheDir str
mappkg.RegisterPublicRoutes(r, store)
registerMapTileProxyRoutes(r, store, mapTileCacheDir)
helppkg.RegisterPublicRoutes(r, store)
// 公开签到墙限速:60 次/分钟/IP,防遍历拉取。
signsLimiter := ratelimit.New(ratelimit.Options{MaxFailures: 60, Window: time.Minute})
r.GET("/signs", func(c *gin.Context) {
if signsLimiter.Exceeded(c.ClientIP()) {
c.JSON(http.StatusTooManyRequests, gin.H{"error": "too many requests"})
return
}
opts, ok := parseListOptions(c)
if !ok {
return
@@ -125,6 +131,10 @@ func registerAPIRoutes(r gin.IRouter, store *storepkg.Store, mapTileCacheDir str
writeListResponseWithTotal(c, rows, opts, total, err, signpkg.SignDTO)
})
r.GET("/signs/daily", func(c *gin.Context) {
if signsLimiter.Exceeded(c.ClientIP()) {
c.JSON(http.StatusTooManyRequests, gin.H{"error": "too many requests"})
return
}
opts, ok := parseListOptions(c)
if !ok {
return
@@ -207,7 +217,8 @@ func registerAdminRoutes(r gin.IRouter, store *storepkg.Store, sessions *auth.Ma
Password string `json:"password"`
}
type updatePasswordRequest struct {
Password string `json:"password"`
Password string `json:"password"`
CurrentPassword string `json:"current_password"`
}
userDTO := func(user storepkg.UserRecord) gin.H {
return gin.H{"id": user.ID, "username": user.Username, "role": user.Role, "created_at": user.CreatedAt, "updated_at": user.UpdatedAt}
@@ -280,7 +291,7 @@ func registerAdminRoutes(r gin.IRouter, store *storepkg.Store, sessions *auth.Ma
})
protected := r.Group("")
protected.Use(auth.RequireAdmin(sessions))
protected.Use(auth.RequireAdmin(sessions, store))
blockingpkg.RegisterRoutes(protected, store, blocking)
signpkg.RegisterAdminRoutes(protected, store)
mqttforwardpkg.RegisterRoutes(protected, store, forwarder)
@@ -408,6 +419,17 @@ func registerAdminRoutes(r gin.IRouter, store *storepkg.Store, sessions *auth.Ma
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid password request"})
return
}
// 修改任何用户的密码都必须验证请求方自己的当前密码,防止已登录会话被冒用改密。
claims := c.MustGet(auth.AdminClaimsKey).(*auth.SessionClaims)
requester, err := store.GetUserByUsername(claims.Username)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "admin login required"})
return
}
if !auth.VerifyPassword(requester.PasswordHash, req.CurrentPassword) {
c.JSON(http.StatusForbidden, gin.H{"error": "current password is incorrect"})
return
}
user, err := store.UpdateUserPassword(id, req.Password)
if errors.Is(err, gorm.ErrRecordNotFound) {
c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})