fix(functional): batch autofill, soft-delete name reclaim, hasCredState test, sub_token error, batch reset traffic
Deep functional audit of node/user/subscription/traffic features found 5 bugs. All fixed. All tests pass (including the previously failing hasCredState test). == CRITICAL == F1: BatchCreate skips AutofillListenerDefaults Batch-created listeners had no auto-generated UUIDs, passwords, REALITY keys, SS ciphers, snell PSKs, or sudoku keys → mihomo rejected the config → entire batch failed. Fix: call AutofillListenerDefaults before ValidateModel in loop. F2: BatchCreate doesn't reclaim soft-deleted names Inline Count check used GORM default scope (excludes soft-deleted) but UNIQUE index covers them → DB constraint error on name clash. Fix: use ensureUniqueName which renames soft-deleted rows. F3: Failing test contradicts hasCredState fallback fix Test asserted no fallback when hasCredState=true + empty creds. But we intentionally removed the guard to fix 'unset fields: users'. Fix: updated test to assert config users ARE included as fallback. == HIGH == F4: sub_token generation ignores error subTok, _ := randomHex(16) — empty sub_token on rand.Read failure. Fix: check error, return on failure. F5: BatchResetTraffic doesn't trigger config regeneration Over-quota users stayed blocked for ~10s after traffic reset. Fix: added notifyCredentialsChanged() after reset. Verification: go build/vet/test all PASS. gofmt clean. fix(functional): batch autofill, soft-delete name reclaim, hasCredState test, sub_token error, batch reset traffic Deep functional audit of node/user/subscription/traffic features found 5 bugs. All fixed. All tests pass (including the previously failing hasCredState test). == CRITICAL == F1: BatchCreate skips AutofillListenerDefaults Batch-created listeners had no auto-generated UUIDs, passwords, REALITY keys, SS ciphers, snell PSKs, or sudoku keys → mihomo rejected the config → entire batch failed. Fix: call AutofillListenerDefaults before ValidateModel in loop. F2: BatchCreate doesn't reclaim soft-deleted names Inline Count check used GORM default scope (excludes soft-deleted) but UNIQUE index covers them → DB constraint error on name clash. Fix: use ensureUniqueName which renames soft-deleted rows. F3: Failing test contradicts hasCredState fallback fix Test asserted no fallback when hasCredState=true + empty creds. But we intentionally removed the guard to fix 'unset fields: users'. Fix: updated test to assert config users ARE included as fallback. == HIGH == F4: sub_token generation ignores error subTok, _ := randomHex(16) — empty sub_token on rand.Read failure. Fix: check error, return on failure. F5: BatchResetTraffic doesn't trigger config regeneration Over-quota users stayed blocked for ~10s after traffic reset. Fix: added notifyCredentialsChanged() after reset. Verification: go build/vet/test all PASS. gofmt clean. fix(functional): batch autofill, soft-delete name reclaim, hasCredState test, sub_token error, batch reset traffic Deep functional audit of node/user/subscription/traffic features found 5 bugs. All fixed. All tests pass (including the previously failing hasCredState test). == CRITICAL == F1: BatchCreate skips AutofillListenerDefaults Batch-created listeners had no auto-generated UUIDs, passwords, REALITY keys, SS ciphers, snell PSKs, or sudoku keys → mihomo rejected the config → entire batch failed. Fix: call AutofillListenerDefaults before ValidateModel in loop. F2: BatchCreate doesn't reclaim soft-deleted names Inline Count check used GORM default scope (excludes soft-deleted) but UNIQUE index covers them → DB constraint error on name clash. Fix: use ensureUniqueName which renames soft-deleted rows. F3: Failing test contradicts hasCredState fallback fix Test asserted no fallback when hasCredState=true + empty creds. But we intentionally removed the guard to fix 'unset fields: users'. Fix: updated test to assert config users ARE included as fallback. == HIGH == F4: sub_token generation ignores error subTok, _ := randomHex(16) — empty sub_token on rand.Read failure. Fix: check error, return on failure. F5: BatchResetTraffic doesn't trigger config regeneration Over-quota users stayed blocked for ~10s after traffic reset. Fix: added notifyCredentialsChanged() after reset. Verification: go build/vet/test all PASS. gofmt clean. fix(functional): batch autofill, soft-delete name reclaim, hasCredState test, sub_token error, batch reset traffic Deep functional audit of node/user/subscription/traffic features found 5 bugs. All fixed. All tests pass (including the previously failing hasCredState test). == CRITICAL == F1: BatchCreate skips AutofillListenerDefaults Batch-created listeners had no auto-generated UUIDs, passwords, REALITY keys, SS ciphers, snell PSKs, or sudoku keys → mihomo rejected the config → entire batch failed. Fix: call AutofillListenerDefaults before ValidateModel in loop. F2: BatchCreate doesn't reclaim soft-deleted names Inline Count check used GORM default scope (excludes soft-deleted) but UNIQUE index covers them → DB constraint error on name clash. Fix: use ensureUniqueName which renames soft-deleted rows. F3: Failing test contradicts hasCredState fallback fix Test asserted no fallback when hasCredState=true + empty creds. But we intentionally removed the guard to fix 'unset fields: users'. Fix: updated test to assert config users ARE included as fallback. == HIGH == F4: sub_token generation ignores error subTok, _ := randomHex(16) — empty sub_token on rand.Read failure. Fix: check error, return on failure. F5: BatchResetTraffic doesn't trigger config regeneration Over-quota users stayed blocked for ~10s after traffic reset. Fix: added notifyCredentialsChanged() after reset. Verification: go build/vet/test all PASS. gofmt clean. fix(functional): batch autofill, soft-delete name reclaim, hasCredState test, sub_token error, batch reset traffic Deep functional audit of node/user/subscription/traffic features found 5 bugs. All fixed. All tests pass (including the previously failing hasCredState test). == CRITICAL == F1: BatchCreate skips AutofillListenerDefaults Batch-created listeners had no auto-generated UUIDs, passwords, REALITY keys, SS ciphers, snell PSKs, or sudoku keys → mihomo rejected the config → entire batch failed. Fix: call AutofillListenerDefaults before ValidateModel in loop. F2: BatchCreate doesn't reclaim soft-deleted names Inline Count check used GORM default scope (excludes soft-deleted) but UNIQUE index covers them → DB constraint error on name clash. Fix: use ensureUniqueName which renames soft-deleted rows. F3: Failing test contradicts hasCredState fallback fix Test asserted no fallback when hasCredState=true + empty creds. But we intentionally removed the guard to fix 'unset fields: users'. Fix: updated test to assert config users ARE included as fallback. == HIGH == F4: sub_token generation ignores error subTok, _ := randomHex(16) — empty sub_token on rand.Read failure. Fix: check error, return on failure. F5: BatchResetTraffic doesn't trigger config regeneration Over-quota users stayed blocked for ~10s after traffic reset. Fix: added notifyCredentialsChanged() after reset. Verification: go build/vet/test all PASS. gofmt clean. fix(functional): batch autofill, soft-delete name reclaim, hasCredState test, sub_token error, batch reset traffic Deep functional audit of node/user/subscription/traffic features found 5 bugs. All fixed. All tests pass (including the previously failing hasCredState test). == CRITICAL == F1: BatchCreate skips AutofillListenerDefaults Batch-created listeners had no auto-generated UUIDs, passwords, REALITY keys, SS ciphers, snell PSKs, or sudoku keys → mihomo rejected the config → entire batch failed. Fix: call AutofillListenerDefaults before ValidateModel in loop. F2: BatchCreate doesn't reclaim soft-deleted names Inline Count check used GORM default scope (excludes soft-deleted) but UNIQUE index covers them → DB constraint error on name clash. Fix: use ensureUniqueName which renames soft-deleted rows. F3: Failing test contradicts hasCredState fallback fix Test asserted no fallback when hasCredState=true + empty creds. But we intentionally removed the guard to fix 'unset fields: users'. Fix: updated test to assert config users ARE included as fallback. == HIGH == F4: sub_token generation ignores error subTok, _ := randomHex(16) — empty sub_token on rand.Read failure. Fix: check error, return on failure. F5: BatchResetTraffic doesn't trigger config regeneration Over-quota users stayed blocked for ~10s after traffic reset. Fix: added notifyCredentialsChanged() after reset. Verification: go build/vet/test all PASS. gofmt clean. fix(functional): batch autofill, soft-delete name reclaim, hasCredState test, sub_token error, batch reset traffic Deep functional audit of node/user/subscription/traffic features found 5 bugs. All fixed. All tests pass (including the previously failing hasCredState test). == CRITICAL == F1: BatchCreate skips AutofillListenerDefaults Batch-created listeners had no auto-generated UUIDs, passwords, REALITY keys, SS ciphers, snell PSKs, or sudoku keys → mihomo rejected the config → entire batch failed. Fix: call AutofillListenerDefaults before ValidateModel in loop. F2: BatchCreate doesn't reclaim soft-deleted names Inline Count check used GORM default scope (excludes soft-deleted) but UNIQUE index covers them → DB constraint error on name clash. Fix: use ensureUniqueName which renames soft-deleted rows. F3: Failing test contradicts hasCredState fallback fix Test asserted no fallback when hasCredState=true + empty creds. But we intentionally removed the guard to fix 'unset fields: users'. Fix: updated test to assert config users ARE included as fallback. == HIGH == F4: sub_token generation ignores error subTok, _ := randomHex(16) — empty sub_token on rand.Read failure. Fix: check error, return on failure. F5: BatchResetTraffic doesn't trigger config regeneration Over-quota users stayed blocked for ~10s after traffic reset. Fix: added notifyCredentialsChanged() after reset. Verification: go build/vet/test all PASS. gofmt clean. fix(functional): batch autofill, soft-delete name reclaim, hasCredState test, sub_token error, batch reset traffic Deep functional audit of node/user/subscription/traffic features found 5 bugs. All fixed. All tests pass (including the previously failing hasCredState test). == CRITICAL == F1: BatchCreate skips AutofillListenerDefaults Batch-created listeners had no auto-generated UUIDs, passwords, REALITY keys, SS ciphers, snell PSKs, or sudoku keys → mihomo rejected the config → entire batch failed. Fix: call AutofillListenerDefaults before ValidateModel in loop. F2: BatchCreate doesn't reclaim soft-deleted names Inline Count check used GORM default scope (excludes soft-deleted) but UNIQUE index covers them → DB constraint error on name clash. Fix: use ensureUniqueName which renames soft-deleted rows. F3: Failing test contradicts hasCredState fallback fix Test asserted no fallback when hasCredState=true + empty creds. But we intentionally removed the guard to fix 'unset fields: users'. Fix: updated test to assert config users ARE included as fallback. == HIGH == F4: sub_token generation ignores error subTok, _ := randomHex(16) — empty sub_token on rand.Read failure. Fix: check error, return on failure. F5: BatchResetTraffic doesn't trigger config regeneration Over-quota users stayed blocked for ~10s after traffic reset. Fix: added notifyCredentialsChanged() after reset. Verification: go build/vet/test all PASS. gofmt clean. fix(functional): batch autofill, soft-delete name reclaim, hasCredState test, sub_token error, batch reset traffic Deep functional audit of node/user/subscription/traffic features found 5 bugs. All fixed. All tests pass (including the previously failing hasCredState test). == CRITICAL == F1: BatchCreate skips AutofillListenerDefaults Batch-created listeners had no auto-generated UUIDs, passwords, REALITY keys, SS ciphers, snell PSKs, or sudoku keys → mihomo rejected the config → entire batch failed. Fix: call AutofillListenerDefaults before ValidateModel in loop. F2: BatchCreate doesn't reclaim soft-deleted names Inline Count check used GORM default scope (excludes soft-deleted) but UNIQUE index covers them → DB constraint error on name clash. Fix: use ensureUniqueName which renames soft-deleted rows. F3: Failing test contradicts hasCredState fallback fix Test asserted no fallback when hasCredState=true + empty creds. But we intentionally removed the guard to fix 'unset fields: users'. Fix: updated test to assert config users ARE included as fallback. == HIGH == F4: sub_token generation ignores error subTok, _ := randomHex(16) — empty sub_token on rand.Read failure. Fix: check error, return on failure. F5: BatchResetTraffic doesn't trigger config regeneration Over-quota users stayed blocked for ~10s after traffic reset. Fix: added notifyCredentialsChanged() after reset. Verification: go build/vet/test all PASS. gofmt clean. fix(functional): batch autofill, soft-delete name reclaim, hasCredState test, sub_token error, batch reset traffic Deep functional audit of node/user/subscription/traffic features found 5 bugs. All fixed. All tests pass (including the previously failing hasCredState test). == CRITICAL == F1: BatchCreate skips AutofillListenerDefaults Batch-created listeners had no auto-generated UUIDs, passwords, REALITY keys, SS ciphers, snell PSKs, or sudoku keys → mihomo rejected the config → entire batch failed. Fix: call AutofillListenerDefaults before ValidateModel in loop. F2: BatchCreate doesn't reclaim soft-deleted names Inline Count check used GORM default scope (excludes soft-deleted) but UNIQUE index covers them → DB constraint error on name clash. Fix: use ensureUniqueName which renames soft-deleted rows. F3: Failing test contradicts hasCredState fallback fix Test asserted no fallback when hasCredState=true + empty creds. But we intentionally removed the guard to fix 'unset fields: users'. Fix: updated test to assert config users ARE included as fallback. == HIGH == F4: sub_token generation ignores error subTok, _ := randomHex(16) — empty sub_token on rand.Read failure. Fix: check error, return on failure. F5: BatchResetTraffic doesn't trigger config regeneration Over-quota users stayed blocked for ~10s after traffic reset. Fix: added notifyCredentialsChanged() after reset. Verification: go build/vet/test all PASS. gofmt clean. fix(functional): batch autofill, soft-delete name reclaim, hasCredState test, sub_token error, batch reset traffic Deep functional audit of node/user/subscription/traffic features found 5 bugs. All fixed. All tests pass (including the previously failing hasCredState test). == CRITICAL == F1: BatchCreate skips AutofillListenerDefaults Batch-created listeners had no auto-generated UUIDs, passwords, REALITY keys, SS ciphers, snell PSKs, or sudoku keys → mihomo rejected the config → entire batch failed. Fix: call AutofillListenerDefaults before ValidateModel in loop. F2: BatchCreate doesn't reclaim soft-deleted names Inline Count check used GORM default scope (excludes soft-deleted) but UNIQUE index covers them → DB constraint error on name clash. Fix: use ensureUniqueName which renames soft-deleted rows. F3: Failing test contradicts hasCredState fallback fix Test asserted no fallback when hasCredState=true + empty creds. But we intentionally removed the guard to fix 'unset fields: users'. Fix: updated test to assert config users ARE included as fallback. == HIGH == F4: sub_token generation ignores error subTok, _ := randomHex(16) — empty sub_token on rand.Read failure. Fix: check error, return on failure. F5: BatchResetTraffic doesn't trigger config regeneration Over-quota users stayed blocked for ~10s after traffic reset. Fix: added notifyCredentialsChanged() after reset. Verification: go build/vet/test all PASS. gofmt clean. fix(functional): batch autofill, soft-delete name reclaim, hasCredState test, sub_token error, batch reset traffic Deep functional audit of node/user/subscription/traffic features found 5 bugs. All fixed. All tests pass (including the previously failing hasCredState test). == CRITICAL == F1: BatchCreate skips AutofillListenerDefaults Batch-created listeners had no auto-generated UUIDs, passwords, REALITY keys, SS ciphers, snell PSKs, or sudoku keys → mihomo rejected the config → entire batch failed. Fix: call AutofillListenerDefaults before ValidateModel in loop. F2: BatchCreate doesn't reclaim soft-deleted names Inline Count check used GORM default scope (excludes soft-deleted) but UNIQUE index covers them → DB constraint error on name clash. Fix: use ensureUniqueName which renames soft-deleted rows. F3: Failing test contradicts hasCredState fallback fix Test asserted no fallback when hasCredState=true + empty creds. But we intentionally removed the guard to fix 'unset fields: users'. Fix: updated test to assert config users ARE included as fallback. == HIGH == F4: sub_token generation ignores error subTok, _ := randomHex(16) — empty sub_token on rand.Read failure. Fix: check error, return on failure. F5: BatchResetTraffic doesn't trigger config regeneration Over-quota users stayed blocked for ~10s after traffic reset. Fix: added notifyCredentialsChanged() after reset. Verification: go build/vet/test all PASS. gofmt clean.
This commit is contained in:
1 parent
5b9674987f
commit
4b00a1268a
4 files changed
+43
-10
No files matched your search
@@ -126,19 +126,25 @@ func (s *Service) BatchCreate(list []models.Listener) ([]models.Listener, error)
|
||||
}
|
||||
created := make([]models.Listener, 0, len(list))
|
||||
for i := range list {
|
||||
// Batch-created listeners must go through the same autofill path as
|
||||
// single Create so UUIDs / passwords / REALITY keys are generated and
|
||||
// the config passes mihomo validation. Without this, mihomo rejects the
|
||||
// entire config and the whole batch fails.
|
||||
if err := AutofillListenerDefaults(&list[i]); err != nil {
|
||||
tx.Rollback()
|
||||
return nil, fmt.Errorf("autofill listener defaults: %w", err)
|
||||
}
|
||||
if err := ValidateModel(&list[i]); err != nil {
|
||||
tx.Rollback()
|
||||
return nil, err
|
||||
}
|
||||
var n int64
|
||||
if err := tx.Model(&models.Listener{}).Where("name = ?", list[i].Name).Count(&n).Error; err != nil {
|
||||
// GORM's default scope excludes soft-deleted rows, but the UNIQUE(name)
|
||||
// index covers them. Use ensureUniqueName so soft-deleted rows with the
|
||||
// same name are renamed out of the way before Create hits the index.
|
||||
if err := s.ensureUniqueName(&list[i]); err != nil {
|
||||
tx.Rollback()
|
||||
return nil, err
|
||||
}
|
||||
if n > 0 {
|
||||
tx.Rollback()
|
||||
return nil, fmt.Errorf("listener name %q already exists", list[i].Name)
|
||||
}
|
||||
if err := tx.Create(&list[i]).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return nil, err
|
||||
|
||||
@@ -17,13 +17,31 @@ func TestGenerateListenersUsesModelTLS(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateListenersDoesNotFallbackWhenCredentialStateIsExplicitlyEmpty(t *testing.T) {
|
||||
func TestGenerateListenersFallsBackToConfigUsersWhenCredentialStateIsExplicitlyEmpty(t *testing.T) {
|
||||
// When the credential provider reports hasCredState=true but the panel has
|
||||
// no active credentials for a listener (e.g. all bound users blocked /
|
||||
// expired), the compiled listener must fall back to the users already
|
||||
// embedded in the listener Config. Returning nil users here would make
|
||||
// mihomo reject the entire config ("unset fields: users"), taking down
|
||||
// every other listener. Falling back to config users keeps the listener
|
||||
// valid; the enforcer is responsible for disabling the listener when all
|
||||
// panel users are inactive.
|
||||
listeners := []models.Listener{{BaseModel: models.BaseModel{ID: 1}, Name: "vless", Protocol: "vless", Port: "443", Enabled: true, Config: `{"users":[{"uuid":"legacy"}]}`}}
|
||||
got, err := generateListeners(listeners, map[uint][]Credential{1: {}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, ok := got[0]["users"]; ok {
|
||||
t.Fatalf("legacy users leaked into explicitly empty credential state: %#v", got[0]["users"])
|
||||
if len(got) != 1 {
|
||||
t.Fatalf("expected one compiled listener, got %d", len(got))
|
||||
}
|
||||
users, ok := got[0]["users"].([]map[string]interface{})
|
||||
if !ok {
|
||||
t.Fatalf("expected config users to be present as fallback, got %#v", got[0]["users"])
|
||||
}
|
||||
if len(users) != 1 {
|
||||
t.Fatalf("expected one fallback user, got %d (%#v)", len(users), users)
|
||||
}
|
||||
if uid, _ := users[0]["uuid"].(string); uid != "legacy" {
|
||||
t.Fatalf("expected legacy config user uuid %q, got %#v", "legacy", users[0])
|
||||
}
|
||||
}
|
||||
@@ -106,6 +106,12 @@ func (s *Service) Batch(action BatchAction, ids []uint) (int, error) {
|
||||
if res.Error != nil {
|
||||
return 0, res.Error
|
||||
}
|
||||
// Resetting traffic may unblock previously over-quota users. Trigger a
|
||||
// config regeneration so blocked users regain access immediately
|
||||
// instead of waiting for the next enforcer tick (up to 10s latency).
|
||||
if err := s.notifyCredentialsChanged(); err != nil {
|
||||
log.Printf("warning: credentials changed notification failed: %v", err)
|
||||
}
|
||||
return int(res.RowsAffected), nil
|
||||
case BatchDelete:
|
||||
if err := s.db.Transaction(func(tx *gorm.DB) error {
|
||||
|
||||
@@ -43,7 +43,10 @@ func (s *Service) Create(in CreateInput) (*models.ProxyUser, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
subTok, _ := randomHex(16)
|
||||
subTok, err := randomHex(16)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("generate proxy user sub token: %w", err)
|
||||
}
|
||||
u := &models.ProxyUser{
|
||||
Username: username,
|
||||
PasswordEncrypted: encrypted,
|
||||
|
||||
Reference in new issue
Block a user