mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-13 18:15:34 +00:00
fix(security): elevate management HTTP routes from Operator to Admin role
Management endpoints (MCP, Agents, Skills, Channels, Builtin Tools) previously used auto-detect auth which allowed Operator-level users to perform CRUD operations. Now consistent with WS policy: Admin+ required for writes, Viewer+ for reads. Also moves WS skills.update to isAdminMethod and improves 403 error messages to include required role info.
This commit is contained in:
1 parent
8b75e095e9
commit
028ea8a7d8
12 files changed
+84
-54
No files matched your search
@@ -71,7 +71,7 @@ func (r *MethodRouter) Handle(ctx context.Context, client *Client, req *protocol
|
||||
client.SendResponse(protocol.NewErrorResponse(
|
||||
req.ID,
|
||||
protocol.ErrUnauthorized,
|
||||
i18n.T(locale, i18n.MsgPermissionDenied, req.Method),
|
||||
i18n.T(locale, i18n.MsgPermissionDenied, req.Method+" requires "+string(permissions.MethodRole(req.Method))+" role"),
|
||||
))
|
||||
return
|
||||
}
|
||||
|
||||
+19
-9
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/nextlevelbuilder/goclaw/internal/bus"
|
||||
"github.com/nextlevelbuilder/goclaw/internal/config"
|
||||
"github.com/nextlevelbuilder/goclaw/internal/i18n"
|
||||
"github.com/nextlevelbuilder/goclaw/internal/permissions"
|
||||
"github.com/nextlevelbuilder/goclaw/internal/providers"
|
||||
"github.com/nextlevelbuilder/goclaw/internal/store"
|
||||
"github.com/nextlevelbuilder/goclaw/pkg/protocol"
|
||||
@@ -66,27 +67,36 @@ func (h *AgentsHandler) emitCacheInvalidate(kind, key string) {
|
||||
|
||||
// RegisterRoutes registers all agent management routes on the given mux.
|
||||
func (h *AgentsHandler) RegisterRoutes(mux *http.ServeMux) {
|
||||
// Agent CRUD (reads: viewer+, writes: admin+)
|
||||
mux.HandleFunc("GET /v1/agents", h.authMiddleware(h.handleList))
|
||||
mux.HandleFunc("POST /v1/agents", h.authMiddleware(h.handleCreate))
|
||||
mux.HandleFunc("POST /v1/agents", h.adminMiddleware(h.handleCreate))
|
||||
mux.HandleFunc("GET /v1/agents/{id}", h.authMiddleware(h.handleGet))
|
||||
mux.HandleFunc("PUT /v1/agents/{id}", h.authMiddleware(h.handleUpdate))
|
||||
mux.HandleFunc("DELETE /v1/agents/{id}", h.authMiddleware(h.handleDelete))
|
||||
mux.HandleFunc("PUT /v1/agents/{id}", h.adminMiddleware(h.handleUpdate))
|
||||
mux.HandleFunc("DELETE /v1/agents/{id}", h.adminMiddleware(h.handleDelete))
|
||||
// Sharing (admin+)
|
||||
mux.HandleFunc("GET /v1/agents/{id}/shares", h.authMiddleware(h.handleListShares))
|
||||
mux.HandleFunc("POST /v1/agents/{id}/shares", h.authMiddleware(h.handleShare))
|
||||
mux.HandleFunc("DELETE /v1/agents/{id}/shares/{userID}", h.authMiddleware(h.handleRevokeShare))
|
||||
mux.HandleFunc("POST /v1/agents/{id}/regenerate", h.authMiddleware(h.handleRegenerate))
|
||||
mux.HandleFunc("POST /v1/agents/{id}/resummon", h.authMiddleware(h.handleResummon))
|
||||
mux.HandleFunc("POST /v1/agents/{id}/shares", h.adminMiddleware(h.handleShare))
|
||||
mux.HandleFunc("DELETE /v1/agents/{id}/shares/{userID}", h.adminMiddleware(h.handleRevokeShare))
|
||||
// Agent operations (admin+)
|
||||
mux.HandleFunc("POST /v1/agents/{id}/regenerate", h.adminMiddleware(h.handleRegenerate))
|
||||
mux.HandleFunc("POST /v1/agents/{id}/resummon", h.adminMiddleware(h.handleResummon))
|
||||
// Read-only (viewer+)
|
||||
mux.HandleFunc("GET /v1/agents/{id}/codex-pool-activity", h.authMiddleware(h.handleCodexPoolActivity))
|
||||
mux.HandleFunc("GET /v1/agents/{id}/instances", h.authMiddleware(h.handleListInstances))
|
||||
mux.HandleFunc("GET /v1/agents/{id}/instances/{userID}/files", h.authMiddleware(h.handleGetInstanceFiles))
|
||||
mux.HandleFunc("PUT /v1/agents/{id}/instances/{userID}/files/{fileName}", h.authMiddleware(h.handleSetInstanceFile))
|
||||
mux.HandleFunc("PATCH /v1/agents/{id}/instances/{userID}/metadata", h.authMiddleware(h.handleUpdateInstanceMetadata))
|
||||
// Instance writes (admin+)
|
||||
mux.HandleFunc("PUT /v1/agents/{id}/instances/{userID}/files/{fileName}", h.adminMiddleware(h.handleSetInstanceFile))
|
||||
mux.HandleFunc("PATCH /v1/agents/{id}/instances/{userID}/metadata", h.adminMiddleware(h.handleUpdateInstanceMetadata))
|
||||
}
|
||||
|
||||
func (h *AgentsHandler) authMiddleware(next http.HandlerFunc) http.HandlerFunc {
|
||||
return requireAuth("", next)
|
||||
}
|
||||
|
||||
func (h *AgentsHandler) adminMiddleware(next http.HandlerFunc) http.HandlerFunc {
|
||||
return requireAuth(permissions.RoleAdmin, next)
|
||||
}
|
||||
|
||||
func (h *AgentsHandler) handleList(w http.ResponseWriter, r *http.Request) {
|
||||
userID := store.UserIDFromContext(r.Context())
|
||||
if userID == "" {
|
||||
|
||||
@@ -367,7 +367,7 @@ func requireAuth(minRole permissions.Role, next http.HandlerFunc) http.HandlerFu
|
||||
|
||||
if !permissions.HasMinRole(auth.Role, required) {
|
||||
writeJSON(w, http.StatusForbidden, map[string]string{
|
||||
"error": i18n.T(locale, i18n.MsgPermissionDenied, r.URL.Path),
|
||||
"error": i18n.T(locale, i18n.MsgPermissionDenied, r.URL.Path+" requires "+string(required)+" role"),
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -398,7 +398,7 @@ func requireAuthBearer(minRole permissions.Role, bearer string, w http.ResponseW
|
||||
|
||||
if !permissions.HasMinRole(auth.Role, required) {
|
||||
writeJSON(w, http.StatusForbidden, map[string]string{
|
||||
"error": i18n.T(locale, i18n.MsgPermissionDenied, r.URL.Path),
|
||||
"error": i18n.T(locale, i18n.MsgPermissionDenied, r.URL.Path+" requires "+string(required)+" role"),
|
||||
})
|
||||
return r, false
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/nextlevelbuilder/goclaw/internal/bus"
|
||||
"github.com/nextlevelbuilder/goclaw/internal/i18n"
|
||||
"github.com/nextlevelbuilder/goclaw/internal/permissions"
|
||||
"github.com/nextlevelbuilder/goclaw/internal/store"
|
||||
"github.com/nextlevelbuilder/goclaw/pkg/protocol"
|
||||
)
|
||||
@@ -29,17 +30,22 @@ func NewBuiltinToolsHandler(s store.BuiltinToolStore, tenantCfgs store.BuiltinTo
|
||||
|
||||
// RegisterRoutes registers all built-in tool routes on the given mux.
|
||||
func (h *BuiltinToolsHandler) RegisterRoutes(mux *http.ServeMux) {
|
||||
// Builtin tools (reads: viewer+, writes: admin+)
|
||||
mux.HandleFunc("GET /v1/tools/builtin", h.auth(h.handleList))
|
||||
mux.HandleFunc("GET /v1/tools/builtin/{name}", h.auth(h.handleGet))
|
||||
mux.HandleFunc("PUT /v1/tools/builtin/{name}", h.auth(h.handleUpdate))
|
||||
mux.HandleFunc("PUT /v1/tools/builtin/{name}/tenant-config", h.auth(h.handleSetTenantConfig))
|
||||
mux.HandleFunc("DELETE /v1/tools/builtin/{name}/tenant-config", h.auth(h.handleDeleteTenantConfig))
|
||||
mux.HandleFunc("PUT /v1/tools/builtin/{name}", h.adminAuth(h.handleUpdate))
|
||||
mux.HandleFunc("PUT /v1/tools/builtin/{name}/tenant-config", h.adminAuth(h.handleSetTenantConfig))
|
||||
mux.HandleFunc("DELETE /v1/tools/builtin/{name}/tenant-config", h.adminAuth(h.handleDeleteTenantConfig))
|
||||
}
|
||||
|
||||
func (h *BuiltinToolsHandler) auth(next http.HandlerFunc) http.HandlerFunc {
|
||||
return requireAuth("", next)
|
||||
}
|
||||
|
||||
func (h *BuiltinToolsHandler) adminAuth(next http.HandlerFunc) http.HandlerFunc {
|
||||
return requireAuth(permissions.RoleAdmin, next)
|
||||
}
|
||||
|
||||
func (h *BuiltinToolsHandler) emitCacheInvalidate(key string) {
|
||||
if h.msgBus == nil {
|
||||
return
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/nextlevelbuilder/goclaw/internal/bus"
|
||||
"github.com/nextlevelbuilder/goclaw/internal/i18n"
|
||||
"github.com/nextlevelbuilder/goclaw/internal/permissions"
|
||||
"github.com/nextlevelbuilder/goclaw/internal/store"
|
||||
"github.com/nextlevelbuilder/goclaw/pkg/protocol"
|
||||
)
|
||||
@@ -32,18 +33,19 @@ func NewChannelInstancesHandler(s store.ChannelInstanceStore, agentStore store.A
|
||||
|
||||
// RegisterRoutes registers all channel instance routes on the given mux.
|
||||
func (h *ChannelInstancesHandler) RegisterRoutes(mux *http.ServeMux) {
|
||||
// Channel instance CRUD (reads: viewer+, writes: admin+)
|
||||
mux.HandleFunc("GET /v1/channels/instances", h.auth(h.handleList))
|
||||
mux.HandleFunc("POST /v1/channels/instances", h.auth(h.handleCreate))
|
||||
mux.HandleFunc("POST /v1/channels/instances", h.adminAuth(h.handleCreate))
|
||||
mux.HandleFunc("GET /v1/channels/instances/{id}", h.auth(h.handleGet))
|
||||
mux.HandleFunc("PUT /v1/channels/instances/{id}", h.auth(h.handleUpdate))
|
||||
mux.HandleFunc("DELETE /v1/channels/instances/{id}", h.auth(h.handleDelete))
|
||||
mux.HandleFunc("PUT /v1/channels/instances/{id}", h.adminAuth(h.handleUpdate))
|
||||
mux.HandleFunc("DELETE /v1/channels/instances/{id}", h.adminAuth(h.handleDelete))
|
||||
|
||||
// Channel contacts (global, not per-agent)
|
||||
if h.contactStore != nil {
|
||||
mux.HandleFunc("GET /v1/contacts", h.auth(h.handleListContacts))
|
||||
mux.HandleFunc("GET /v1/contacts/resolve", h.auth(h.handleResolveContacts))
|
||||
mux.HandleFunc("POST /v1/contacts/merge", h.auth(h.handleMergeContacts))
|
||||
mux.HandleFunc("POST /v1/contacts/unmerge", h.auth(h.handleUnmergeContacts))
|
||||
mux.HandleFunc("POST /v1/contacts/merge", h.adminAuth(h.handleMergeContacts))
|
||||
mux.HandleFunc("POST /v1/contacts/unmerge", h.adminAuth(h.handleUnmergeContacts))
|
||||
mux.HandleFunc("GET /v1/contacts/merged/{tenantUserId}", h.auth(h.handleListMergedContacts))
|
||||
}
|
||||
if h.tenantStore != nil {
|
||||
@@ -54,8 +56,8 @@ func (h *ChannelInstancesHandler) RegisterRoutes(mux *http.ServeMux) {
|
||||
if h.configPermStore != nil {
|
||||
mux.HandleFunc("GET /v1/channels/instances/{id}/writers/groups", h.auth(h.handleWriterGroups))
|
||||
mux.HandleFunc("GET /v1/channels/instances/{id}/writers", h.auth(h.handleListWriters))
|
||||
mux.HandleFunc("POST /v1/channels/instances/{id}/writers", h.auth(h.handleAddWriter))
|
||||
mux.HandleFunc("DELETE /v1/channels/instances/{id}/writers/{userId}", h.auth(h.handleRemoveWriter))
|
||||
mux.HandleFunc("POST /v1/channels/instances/{id}/writers", h.adminAuth(h.handleAddWriter))
|
||||
mux.HandleFunc("DELETE /v1/channels/instances/{id}/writers/{userId}", h.adminAuth(h.handleRemoveWriter))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +65,10 @@ func (h *ChannelInstancesHandler) auth(next http.HandlerFunc) http.HandlerFunc {
|
||||
return requireAuth("", next)
|
||||
}
|
||||
|
||||
func (h *ChannelInstancesHandler) adminAuth(next http.HandlerFunc) http.HandlerFunc {
|
||||
return requireAuth(permissions.RoleAdmin, next)
|
||||
}
|
||||
|
||||
func (h *ChannelInstancesHandler) emitCacheInvalidate() {
|
||||
if h.msgBus == nil {
|
||||
return
|
||||
|
||||
+22
-17
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/nextlevelbuilder/goclaw/internal/bus"
|
||||
"github.com/nextlevelbuilder/goclaw/internal/i18n"
|
||||
"github.com/nextlevelbuilder/goclaw/internal/permissions"
|
||||
"github.com/nextlevelbuilder/goclaw/internal/store"
|
||||
"github.com/nextlevelbuilder/goclaw/pkg/protocol"
|
||||
)
|
||||
@@ -51,42 +52,46 @@ func (h *MCPHandler) emitCacheInvalidate() {
|
||||
|
||||
// RegisterRoutes registers all MCP management routes on the given mux.
|
||||
func (h *MCPHandler) RegisterRoutes(mux *http.ServeMux) {
|
||||
// Server CRUD
|
||||
// Server CRUD (reads: viewer+, writes: admin+)
|
||||
mux.HandleFunc("GET /v1/mcp/servers", h.auth(h.handleListServers))
|
||||
mux.HandleFunc("POST /v1/mcp/servers", h.auth(h.handleCreateServer))
|
||||
mux.HandleFunc("POST /v1/mcp/servers", h.adminAuth(h.handleCreateServer))
|
||||
mux.HandleFunc("GET /v1/mcp/servers/{id}", h.auth(h.handleGetServer))
|
||||
mux.HandleFunc("PUT /v1/mcp/servers/{id}", h.auth(h.handleUpdateServer))
|
||||
mux.HandleFunc("DELETE /v1/mcp/servers/{id}", h.auth(h.handleDeleteServer))
|
||||
mux.HandleFunc("PUT /v1/mcp/servers/{id}", h.adminAuth(h.handleUpdateServer))
|
||||
mux.HandleFunc("DELETE /v1/mcp/servers/{id}", h.adminAuth(h.handleDeleteServer))
|
||||
|
||||
// Test connection (no save)
|
||||
mux.HandleFunc("POST /v1/mcp/servers/test", h.auth(h.handleTestConnection))
|
||||
// Test connection (admin+ — infra operation)
|
||||
mux.HandleFunc("POST /v1/mcp/servers/test", h.adminAuth(h.handleTestConnection))
|
||||
|
||||
// Reconnect (evict pooled connection so next use re-discovers tools)
|
||||
mux.HandleFunc("POST /v1/mcp/servers/{id}/reconnect", h.auth(h.handleReconnectServer))
|
||||
// Reconnect (admin+ — evict pooled connection)
|
||||
mux.HandleFunc("POST /v1/mcp/servers/{id}/reconnect", h.adminAuth(h.handleReconnectServer))
|
||||
|
||||
// Server tools (runtime-discovered)
|
||||
// Server tools (read-only: viewer+)
|
||||
mux.HandleFunc("GET /v1/mcp/servers/{id}/tools", h.auth(h.handleListServerTools))
|
||||
|
||||
// Agent grants
|
||||
// Agent grants (reads: viewer+, writes: admin+)
|
||||
mux.HandleFunc("GET /v1/mcp/servers/{id}/grants", h.auth(h.handleListServerGrants))
|
||||
mux.HandleFunc("POST /v1/mcp/servers/{id}/grants/agent", h.auth(h.handleGrantAgent))
|
||||
mux.HandleFunc("DELETE /v1/mcp/servers/{id}/grants/agent/{agentID}", h.auth(h.handleRevokeAgent))
|
||||
mux.HandleFunc("POST /v1/mcp/servers/{id}/grants/agent", h.adminAuth(h.handleGrantAgent))
|
||||
mux.HandleFunc("DELETE /v1/mcp/servers/{id}/grants/agent/{agentID}", h.adminAuth(h.handleRevokeAgent))
|
||||
mux.HandleFunc("GET /v1/mcp/grants/agent/{agentID}", h.auth(h.handleListAgentGrants))
|
||||
|
||||
// User grants
|
||||
mux.HandleFunc("POST /v1/mcp/servers/{id}/grants/user", h.auth(h.handleGrantUser))
|
||||
mux.HandleFunc("DELETE /v1/mcp/servers/{id}/grants/user/{userID}", h.auth(h.handleRevokeUser))
|
||||
// User grants (admin+)
|
||||
mux.HandleFunc("POST /v1/mcp/servers/{id}/grants/user", h.adminAuth(h.handleGrantUser))
|
||||
mux.HandleFunc("DELETE /v1/mcp/servers/{id}/grants/user/{userID}", h.adminAuth(h.handleRevokeUser))
|
||||
|
||||
// Access requests
|
||||
// Access requests (create: viewer+, list: viewer+, review: admin+)
|
||||
mux.HandleFunc("POST /v1/mcp/requests", h.auth(h.handleCreateRequest))
|
||||
mux.HandleFunc("GET /v1/mcp/requests", h.auth(h.handleListPendingRequests))
|
||||
mux.HandleFunc("POST /v1/mcp/requests/{id}/review", h.auth(h.handleReviewRequest))
|
||||
mux.HandleFunc("POST /v1/mcp/requests/{id}/review", h.adminAuth(h.handleReviewRequest))
|
||||
}
|
||||
|
||||
func (h *MCPHandler) auth(next http.HandlerFunc) http.HandlerFunc {
|
||||
return requireAuth("", next)
|
||||
}
|
||||
|
||||
func (h *MCPHandler) adminAuth(next http.HandlerFunc) http.HandlerFunc {
|
||||
return requireAuth(permissions.RoleAdmin, next)
|
||||
}
|
||||
|
||||
// --- Server CRUD ---
|
||||
|
||||
// mcpServerWithCounts extends MCPServerData with agent grant count for list responses.
|
||||
|
||||
+13
-10
@@ -58,19 +58,22 @@ func (h *SkillsHandler) emitCacheInvalidate(kind, key string) {
|
||||
|
||||
// RegisterRoutes registers all skill management routes on the given mux.
|
||||
func (h *SkillsHandler) RegisterRoutes(mux *http.ServeMux) {
|
||||
// Skill reads (viewer+)
|
||||
mux.HandleFunc("GET /v1/skills", h.authMiddleware(h.handleList))
|
||||
mux.HandleFunc("POST /v1/skills/upload", h.authMiddleware(h.handleUpload))
|
||||
mux.HandleFunc("GET /v1/skills/{id}", h.authMiddleware(h.handleGet))
|
||||
mux.HandleFunc("PUT /v1/skills/{id}", h.authMiddleware(h.handleUpdate))
|
||||
mux.HandleFunc("DELETE /v1/skills/{id}", h.authMiddleware(h.handleDelete))
|
||||
mux.HandleFunc("POST /v1/skills/{id}/grants/agent", h.authMiddleware(h.handleGrantAgent))
|
||||
mux.HandleFunc("DELETE /v1/skills/{id}/grants/agent/{agentID}", h.authMiddleware(h.handleRevokeAgent))
|
||||
mux.HandleFunc("POST /v1/skills/{id}/grants/user", h.authMiddleware(h.handleGrantUser))
|
||||
mux.HandleFunc("DELETE /v1/skills/{id}/grants/user/{userID}", h.authMiddleware(h.handleRevokeUser))
|
||||
mux.HandleFunc("GET /v1/agents/{agentID}/skills", h.authMiddleware(h.handleListAgentSkills))
|
||||
mux.HandleFunc("GET /v1/skills/{id}/versions", h.authMiddleware(h.handleListVersions))
|
||||
mux.HandleFunc("GET /v1/skills/{id}/files/{path...}", h.authMiddleware(h.handleReadFile))
|
||||
mux.HandleFunc("GET /v1/skills/{id}/files", h.authMiddleware(h.handleListFiles))
|
||||
// Skill writes (admin+)
|
||||
mux.HandleFunc("POST /v1/skills/upload", h.adminMiddleware(h.handleUpload))
|
||||
mux.HandleFunc("PUT /v1/skills/{id}", h.adminMiddleware(h.handleUpdate))
|
||||
mux.HandleFunc("DELETE /v1/skills/{id}", h.adminMiddleware(h.handleDelete))
|
||||
// Skill grants (admin+)
|
||||
mux.HandleFunc("POST /v1/skills/{id}/grants/agent", h.adminMiddleware(h.handleGrantAgent))
|
||||
mux.HandleFunc("DELETE /v1/skills/{id}/grants/agent/{agentID}", h.adminMiddleware(h.handleRevokeAgent))
|
||||
mux.HandleFunc("POST /v1/skills/{id}/grants/user", h.adminMiddleware(h.handleGrantUser))
|
||||
mux.HandleFunc("DELETE /v1/skills/{id}/grants/user/{userID}", h.adminMiddleware(h.handleRevokeUser))
|
||||
// System-level operations: admin + master tenant only.
|
||||
// These execute shell commands (pip/npm install) and affect the entire server.
|
||||
mux.HandleFunc("POST /v1/skills/rescan-deps", h.adminMiddleware(h.handleRescanDeps))
|
||||
@@ -78,9 +81,9 @@ func (h *SkillsHandler) RegisterRoutes(mux *http.ServeMux) {
|
||||
mux.HandleFunc("POST /v1/skills/install-dep", h.adminMiddleware(h.handleInstallDep))
|
||||
mux.HandleFunc("GET /v1/skills/runtimes", h.adminMiddleware(h.handleRuntimes))
|
||||
mux.HandleFunc("POST /v1/skills/{id}/toggle", h.adminMiddleware(h.handleToggle))
|
||||
// Per-tenant overrides: tenant-level admin check inside handler (not system admin).
|
||||
mux.HandleFunc("PUT /v1/skills/{id}/tenant-config", h.authMiddleware(h.handleSetTenantConfig))
|
||||
mux.HandleFunc("DELETE /v1/skills/{id}/tenant-config", h.authMiddleware(h.handleDeleteTenantConfig))
|
||||
// Per-tenant overrides (admin+)
|
||||
mux.HandleFunc("PUT /v1/skills/{id}/tenant-config", h.adminMiddleware(h.handleSetTenantConfig))
|
||||
mux.HandleFunc("DELETE /v1/skills/{id}/tenant-config", h.adminMiddleware(h.handleDeleteTenantConfig))
|
||||
}
|
||||
|
||||
func (h *SkillsHandler) authMiddleware(next http.HandlerFunc) http.HandlerFunc {
|
||||
|
||||
@@ -10,7 +10,7 @@ func init() {
|
||||
MsgInvalidRequest: "invalid request: %s",
|
||||
MsgInvalidJSON: "invalid JSON",
|
||||
MsgUnauthorized: "unauthorized",
|
||||
MsgPermissionDenied: "permission denied: insufficient role for %s",
|
||||
MsgPermissionDenied: "permission denied: %s",
|
||||
MsgInternalError: "internal error: %s",
|
||||
MsgInvalidSlug: "%s must be a valid slug (lowercase letters, numbers, hyphens only)",
|
||||
MsgFailedToList: "failed to list %s",
|
||||
|
||||
@@ -10,7 +10,7 @@ func init() {
|
||||
MsgInvalidRequest: "yêu cầu không hợp lệ: %s",
|
||||
MsgInvalidJSON: "JSON không hợp lệ",
|
||||
MsgUnauthorized: "chưa xác thực",
|
||||
MsgPermissionDenied: "từ chối quyền truy cập: không đủ quyền cho %s",
|
||||
MsgPermissionDenied: "từ chối quyền truy cập: %s",
|
||||
MsgInternalError: "lỗi nội bộ: %s",
|
||||
MsgInvalidSlug: "%s phải là slug hợp lệ (chữ thường, số, dấu gạch ngang)",
|
||||
MsgFailedToList: "không thể liệt kê %s",
|
||||
|
||||
@@ -10,7 +10,7 @@ func init() {
|
||||
MsgInvalidRequest: "无效请求:%s",
|
||||
MsgInvalidJSON: "无效的 JSON",
|
||||
MsgUnauthorized: "未授权",
|
||||
MsgPermissionDenied: "权限不足:无法访问 %s",
|
||||
MsgPermissionDenied: "权限不足:%s",
|
||||
MsgInternalError: "内部错误:%s",
|
||||
MsgInvalidSlug: "%s 必须是有效的 slug(小写字母、数字、连字符)",
|
||||
MsgFailedToList: "获取 %s 列表失败",
|
||||
|
||||
@@ -11,7 +11,7 @@ const (
|
||||
MsgInvalidRequest = "error.invalid_request" // "invalid request: %s"
|
||||
MsgInvalidJSON = "error.invalid_json" // "invalid JSON"
|
||||
MsgUnauthorized = "error.unauthorized" // "unauthorized"
|
||||
MsgPermissionDenied = "error.permission_denied" // "permission denied: insufficient role for %s"
|
||||
MsgPermissionDenied = "error.permission_denied" // "permission denied: %s"
|
||||
MsgInternalError = "error.internal" // "internal error: %s"
|
||||
MsgInvalidSlug = "error.invalid_slug" // "%s must be a valid slug (lowercase letters, numbers, hyphens only)"
|
||||
MsgFailedToList = "error.failed_to_list" // "failed to list %s"
|
||||
|
||||
@@ -180,6 +180,7 @@ func isAdminMethod(method string) bool {
|
||||
protocol.MethodAPIKeysList,
|
||||
protocol.MethodAPIKeysCreate,
|
||||
protocol.MethodAPIKeysRevoke,
|
||||
protocol.MethodSkillsUpdate,
|
||||
}
|
||||
return slices.Contains(adminMethods, method)
|
||||
}
|
||||
@@ -195,7 +196,6 @@ func isWriteMethod(method string) bool {
|
||||
protocol.MethodCronUpdate,
|
||||
protocol.MethodCronDelete,
|
||||
protocol.MethodCronToggle,
|
||||
protocol.MethodSkillsUpdate,
|
||||
"pairing.",
|
||||
"device.pair.",
|
||||
"approvals.",
|
||||
|
||||
Reference in new issue
Block a user