forked from kevin/meshtastic_mqtt_server
安全加固: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:
+25
-3
@@ -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"})
|
||||
|
||||
Reference in New Issue
Block a user