feat(auth): split inventory scope out of server scope

Carve a new nezha:inventory:{read,delete,*} resource family out of
nezha:server:*. Listing and deleting servers/server-groups (GET /server,
/server-group, /ws/server, batch-delete/server[-group], MCP server.list)
now require nezha:inventory:*, while nezha:server:* covers per-server
runtime operations (get, exec, fs, config, metrics, batch-move,
force-update).

Also declare MCP tool OutputSchema for exec/fs/meta/server/transfer and
wrap server.list output in {servers,count} so strict MCP clients accept
the structured result. Correct the /file all-of scope entry and the stale
server:read documentation.
This commit is contained in:
naiba
2026-05-31 15:14:41 +00:00
parent f7f8264ec0
commit 083bc985c5
17 changed files with 257 additions and 76 deletions
@@ -20,14 +20,14 @@ func setupRESTScopeServer(t *testing.T) (*httptest.Server, string, func()) {
t.Helper()
cleanupBase, uid := setupMCPTest(t)
_, plain := mkToken(t, uid, []string{model.ScopeServerRead}, nil)
_, plain := mkToken(t, uid, []string{model.ScopeInventoryRead}, nil)
gin.SetMode(gin.TestMode)
r := gin.New()
pat := apiTokenAuthMiddleware()
r.GET("/api/v1/server",
pat,
restScopeMiddleware(model.ScopeServerRead),
restScopeMiddleware(model.ScopeInventoryRead),
func(c *gin.Context) { c.JSON(200, gin.H{"ok": true}) },
)
r.POST("/api/v1/server/config",
@@ -122,7 +122,7 @@ func TestRESTScope_JWTUserSkipsScope(t *testing.T) {
c.Set(model.CtxKeyAuthorizedUser, &model.User{Common: model.Common{ID: uid}, Role: model.RoleMember})
c.Next()
},
restScopeMiddleware(model.ScopeServerRead),
restScopeMiddleware(model.ScopeInventoryRead),
func(c *gin.Context) { c.JSON(200, gin.H{"ok": true}) },
)
ts := httptest.NewServer(r)
@@ -148,7 +148,7 @@ func TestRESTScope_NezhaAllUnlocksEverything(t *testing.T) {
)
r.POST("/api/v1/batch-delete/server",
pat,
restScopeMiddleware(model.ScopeServerDelete),
restScopeMiddleware(model.ScopeInventoryDelete),
func(c *gin.Context) { c.JSON(200, gin.H{"ok": true}) },
)
ts := httptest.NewServer(r)
@@ -193,7 +193,7 @@ func TestRESTScope_GoodPATClearsWAFCounter(t *testing.T) {
cleanupBase, uid := setupMCPTest(t)
defer cleanupBase()
_, plain := mkToken(t, uid, []string{model.ScopeServerRead}, nil)
_, plain := mkToken(t, uid, []string{model.ScopeInventoryRead}, nil)
gin.SetMode(gin.TestMode)
r := gin.New()
@@ -203,7 +203,7 @@ func TestRESTScope_GoodPATClearsWAFCounter(t *testing.T) {
})
r.GET("/api/v1/server",
apiTokenAuthMiddleware(),
restScopeMiddleware(model.ScopeServerRead),
restScopeMiddleware(model.ScopeInventoryRead),
func(c *gin.Context) { c.JSON(200, gin.H{"ok": true}) },
)
ts := httptest.NewServer(r)