mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-10-19 12:25:00 +03:00
fix(auth): add node-admin token scope for panel updates (#6666)
* feat(auth): add node-admin API token scope Keep ordinary node-sync credentials unable to self-update a remote panel while allowing explicitly elevated node administrators to inherit synchronization access and invoke updatePanel. * style(docs): format API scope table * fix(auth): preserve mTLS node-sync semantics Allow only an explicit node-admin bearer to elevate a verified mTLS request. Existing admin, monitor, and node-sync bearer values continue to use the historical node-sync identity on mTLS connections. * chore(ci): rerun flaky checks --------- Co-authored-by: libmur-dev <333915961+libmur-dev@users.noreply.github.com>
This commit is contained in:
@@ -35,30 +35,34 @@ func NewAPIController(g *gin.RouterGroup) *APIController {
|
||||
}
|
||||
|
||||
func (a *APIController) checkAPIAuth(c *gin.Context) {
|
||||
// A verified client certificate (a completed mTLS handshake) authenticates
|
||||
// the caller, equivalent to a valid bearer token. api_authed must be set so
|
||||
// the CSRF middleware lets cert-authed mutations through.
|
||||
if c.Request.TLS != nil && len(c.Request.TLS.VerifiedChains) > 0 {
|
||||
setAuthenticated := func(scope string) {
|
||||
if u, err := a.userService.GetFirstUser(); err == nil {
|
||||
session.SetAPIAuthUser(c, u)
|
||||
}
|
||||
c.Set("api_authed", true)
|
||||
c.Set("api_token_scope", model.ApiScopeNodeSync)
|
||||
c.Set("api_token_scope", scope)
|
||||
}
|
||||
matchedScope := ""
|
||||
if after, ok := strings.CutPrefix(c.GetHeader("Authorization"), "Bearer "); ok {
|
||||
if row, matched := a.apiTokenService.MatchToken(after); matched {
|
||||
matchedScope = row.Scope
|
||||
}
|
||||
}
|
||||
// Verified mTLS keeps its historical node-sync identity. Only an explicit
|
||||
// node-admin bearer elevates it to software-update authority.
|
||||
if c.Request.TLS != nil && len(c.Request.TLS.VerifiedChains) > 0 {
|
||||
scope := model.ApiScopeNodeSync
|
||||
if matchedScope == model.ApiScopeNodeAdmin {
|
||||
scope = model.ApiScopeNodeAdmin
|
||||
}
|
||||
setAuthenticated(scope)
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
auth := c.GetHeader("Authorization")
|
||||
if after, ok := strings.CutPrefix(auth, "Bearer "); ok {
|
||||
tok := after
|
||||
if row, ok := a.apiTokenService.MatchToken(tok); ok {
|
||||
if u, err := a.userService.GetFirstUser(); err == nil {
|
||||
session.SetAPIAuthUser(c, u)
|
||||
}
|
||||
c.Set("api_authed", true)
|
||||
c.Set("api_token_scope", row.Scope)
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
if matchedScope != "" {
|
||||
setAuthenticated(matchedScope)
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
if !session.IsLogin(c) {
|
||||
// A presented Bearer token is not an anonymous scan: return 401 so
|
||||
@@ -120,7 +124,20 @@ var nodeSyncScopeAllow = map[string]map[string]struct{}{
|
||||
"/hosts/list": {http.MethodGet: {}},
|
||||
}
|
||||
|
||||
// enforceTokenScope applies explicit allowlists to monitor and node-sync tokens.
|
||||
var nodeAdminScopeAllow = map[string]map[string]struct{}{
|
||||
"/server/updatePanel": {http.MethodPost: {}},
|
||||
}
|
||||
|
||||
func scopeAllows(allow map[string]map[string]struct{}, path, method string) bool {
|
||||
methods, exists := allow[path]
|
||||
if !exists {
|
||||
return false
|
||||
}
|
||||
_, exists = methods[method]
|
||||
return exists
|
||||
}
|
||||
|
||||
// enforceTokenScope applies explicit allowlists to restricted API tokens.
|
||||
// Admin tokens and session-login users retain their existing behavior.
|
||||
func (a *APIController) enforceTokenScope(c *gin.Context) {
|
||||
scopeVal, ok := c.Get("api_token_scope")
|
||||
@@ -147,11 +164,14 @@ func (a *APIController) enforceTokenScope(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
case model.ApiScopeNodeSync:
|
||||
if methods, allowed := nodeSyncScopeAllow[rel]; allowed {
|
||||
if _, allowedMethod := methods[c.Request.Method]; allowedMethod {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
if scopeAllows(nodeSyncScopeAllow, rel, c.Request.Method) {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
case model.ApiScopeNodeAdmin:
|
||||
if scopeAllows(nodeSyncScopeAllow, rel, c.Request.Method) || scopeAllows(nodeAdminScopeAllow, rel, c.Request.Method) {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
default:
|
||||
deny()
|
||||
|
||||
Reference in New Issue
Block a user