mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-10-18 20:05:03 +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:
@@ -60,8 +60,10 @@ func NormalizeScope(scope string) (string, error) {
|
||||
return model.ApiScopeMonitor, nil
|
||||
case model.ApiScopeNodeSync:
|
||||
return model.ApiScopeNodeSync, nil
|
||||
case model.ApiScopeNodeAdmin:
|
||||
return model.ApiScopeNodeAdmin, nil
|
||||
default:
|
||||
return "", common.NewError("scope must be 'admin', 'monitor', or 'node-sync'")
|
||||
return "", common.NewError("scope must be 'admin', 'monitor', 'node-sync', or 'node-admin'")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -83,6 +83,23 @@ func TestAPITokenScopeExpiryAndExpectedRevoke(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPITokenNodeAdminScopeRoundTrips(t *testing.T) {
|
||||
setupAPITokenTestDB(t)
|
||||
svc := &ApiTokenService{}
|
||||
|
||||
created, err := svc.Create("node-updater", model.ApiScopeNodeAdmin, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("create node-admin token: %v", err)
|
||||
}
|
||||
row, ok := svc.MatchToken(created.Token)
|
||||
if !ok {
|
||||
t.Fatal("fresh node-admin token did not authenticate")
|
||||
}
|
||||
if created.Scope != model.ApiScopeNodeAdmin || row.Scope != model.ApiScopeNodeAdmin {
|
||||
t.Fatalf("created/matched scopes = %q/%q, want node-admin/node-admin", created.Scope, row.Scope)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPITokenDeleteAndEnableRequireExpectedScope(t *testing.T) {
|
||||
setupAPITokenTestDB(t)
|
||||
svc := &ApiTokenService{}
|
||||
|
||||
@@ -126,8 +126,9 @@ func TestRecreateByNameAppliesGivenScope(t *testing.T) {
|
||||
wantErr string
|
||||
}{
|
||||
{name: "replaces a monitor token as node-sync", seedScope: model.ApiScopeMonitor, scope: model.ApiScopeNodeSync, want: model.ApiScopeNodeSync},
|
||||
{name: "replaces a monitor token as node-admin", seedScope: model.ApiScopeMonitor, scope: model.ApiScopeNodeAdmin, want: model.ApiScopeNodeAdmin},
|
||||
{name: "creates a new token as monitor", scope: model.ApiScopeMonitor, want: model.ApiScopeMonitor},
|
||||
{name: "refuses an unknown scope", seedScope: model.ApiScopeMonitor, scope: "root", want: model.ApiScopeMonitor, wantErr: "scope must be 'admin', 'monitor', or 'node-sync'"},
|
||||
{name: "refuses an unknown scope", seedScope: model.ApiScopeMonitor, scope: "root", want: model.ApiScopeMonitor, wantErr: "scope must be 'admin', 'monitor', 'node-sync', or 'node-admin'"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
@@ -172,12 +173,12 @@ func TestRecreateByNameRefusesUnknownStoredScope(t *testing.T) {
|
||||
dbtest.InitDB(t, config.GetDBPath())
|
||||
|
||||
db := database.GetDB()
|
||||
stored := model.ApiToken{Name: "remote", Token: "stored-hash", Enabled: true, Scope: "node-admin"}
|
||||
stored := model.ApiToken{Name: "remote", Token: "stored-hash", Enabled: true, Scope: "future-node-scope"}
|
||||
if err := db.Create(&stored).Error; err != nil {
|
||||
t.Fatalf("seed remote: %v", err)
|
||||
}
|
||||
|
||||
const wantErr = `token "remote" has unknown scope "node-admin"`
|
||||
const wantErr = `token "remote" has unknown scope "future-node-scope"`
|
||||
_, err := (&ApiTokenService{}).RecreateByName("remote", "")
|
||||
if err == nil || strings.TrimSpace(err.Error()) != wantErr {
|
||||
t.Fatalf("error = %v, want %q", err, wantErr)
|
||||
|
||||
Reference in New Issue
Block a user