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.
133 lines
3.6 KiB
Go
133 lines
3.6 KiB
Go
package user
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"strings"
|
|
|
|
"github.com/kazeyukiro/3m-ui/backend/internal/database/models"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// ListFilter supports search across proxy users.
|
|
type ListFilter struct {
|
|
Query string // username / remark substring
|
|
Enabled *bool
|
|
Online *bool
|
|
Blocked *bool // computed from IsCredentialActive
|
|
}
|
|
|
|
func (s *Service) ListFiltered(f ListFilter) ([]models.ProxyUser, error) {
|
|
q := s.db.Model(&models.ProxyUser{}).Order("id desc")
|
|
if term := strings.TrimSpace(f.Query); term != "" {
|
|
like := "%" + term + "%"
|
|
q = q.Where("username LIKE ? OR remark LIKE ?", like, like)
|
|
}
|
|
if f.Enabled != nil {
|
|
q = q.Where("enabled = ?", *f.Enabled)
|
|
}
|
|
if f.Online != nil {
|
|
q = q.Where("online = ?", *f.Online)
|
|
}
|
|
var users []models.ProxyUser
|
|
if err := q.Find(&users).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
if f.Blocked == nil {
|
|
return users, nil
|
|
}
|
|
out := make([]models.ProxyUser, 0, len(users))
|
|
for _, u := range users {
|
|
blocked := !IsCredentialActive(u)
|
|
if blocked == *f.Blocked {
|
|
out = append(out, u)
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// BatchAction is a bulk operation over proxy user IDs.
|
|
type BatchAction string
|
|
|
|
const (
|
|
BatchEnable BatchAction = "enable"
|
|
BatchDisable BatchAction = "disable"
|
|
BatchResetTraffic BatchAction = "reset-traffic"
|
|
BatchDelete BatchAction = "delete"
|
|
)
|
|
|
|
func (s *Service) Batch(action BatchAction, ids []uint) (int, error) {
|
|
if len(ids) == 0 {
|
|
return 0, errors.New("ids is required")
|
|
}
|
|
// de-dupe
|
|
seen := map[uint]struct{}{}
|
|
clean := make([]uint, 0, len(ids))
|
|
for _, id := range ids {
|
|
if id == 0 {
|
|
continue
|
|
}
|
|
if _, ok := seen[id]; ok {
|
|
continue
|
|
}
|
|
seen[id] = struct{}{}
|
|
clean = append(clean, id)
|
|
}
|
|
if len(clean) == 0 {
|
|
return 0, errors.New("ids is required")
|
|
}
|
|
|
|
switch action {
|
|
case BatchEnable:
|
|
res := s.db.Model(&models.ProxyUser{}).Where("id IN ?", clean).Update("enabled", true)
|
|
if res.Error != nil {
|
|
return 0, res.Error
|
|
}
|
|
if err := s.notifyCredentialsChanged(); err != nil {
|
|
log.Printf("warning: credentials changed notification failed: %v", err)
|
|
}
|
|
return int(res.RowsAffected), nil
|
|
case BatchDisable:
|
|
res := s.db.Model(&models.ProxyUser{}).Where("id IN ?", clean).Update("enabled", false)
|
|
if res.Error != nil {
|
|
return 0, res.Error
|
|
}
|
|
if err := s.notifyCredentialsChanged(); err != nil {
|
|
log.Printf("warning: credentials changed notification failed: %v", err)
|
|
}
|
|
return int(res.RowsAffected), nil
|
|
case BatchResetTraffic:
|
|
res := s.db.Model(&models.ProxyUser{}).Where("id IN ?", clean).Updates(map[string]interface{}{
|
|
"traffic_used": 0,
|
|
"upload_bytes": 0,
|
|
"download_bytes": 0,
|
|
})
|
|
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 {
|
|
if err := tx.Unscoped().Where("proxy_user_id IN ?", clean).Delete(&models.ListenerUser{}).Error; err != nil {
|
|
return err
|
|
}
|
|
return tx.Unscoped().Where("id IN ?", clean).Delete(&models.ProxyUser{}).Error
|
|
}); err != nil {
|
|
return 0, err
|
|
}
|
|
if err := s.notifyCredentialsChanged(); err != nil {
|
|
log.Printf("warning: credentials changed notification failed: %v", err)
|
|
}
|
|
return len(clean), nil
|
|
default:
|
|
return 0, fmt.Errorf("unknown batch action %q (enable|disable|reset-traffic|delete)", action)
|
|
}
|
|
}
|