mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-06 13:14:11 +00:00
fix: auto-fill flow for registration-created eligible clients
This commit is contained in:
parent
b47bac3dc6
commit
7ff73313a9
3 changed files with 144 additions and 0 deletions
|
|
@ -0,0 +1,54 @@
|
|||
Task Record
|
||||
|
||||
Date: 2026-04-26
|
||||
Related Module: web/service user registration client auto-provisioning
|
||||
Change Type: Fix
|
||||
|
||||
Background
|
||||
|
||||
New user registration auto-creates clients across inbounds via `addUserClientsToAllInbounds`.
|
||||
This path bypassed the previously added AddInboundClient flow auto-fill logic, so newly registered users could still get empty `flow` in eligible VLESS contexts.
|
||||
|
||||
Changes
|
||||
|
||||
Updated registration auto-provisioning path in `web/service/user.go`:
|
||||
- When target inbound requires flow (`VLESS + TCP + TLS/Reality`), set client `Flow` to `xtls-rprx-vision`.
|
||||
- Persist `flow` field in generated client entry when populated.
|
||||
|
||||
Added test in `web/service/user_test.go`:
|
||||
- `TestRegisterUser_AutoFillFlowForEligibleVlessInbound`
|
||||
- Verifies registered user gets `xtls-rprx-vision` flow in eligible VLESS inbound.
|
||||
- Verifies non-VLESS inbound does not get forced flow.
|
||||
|
||||
Impact
|
||||
|
||||
Affected modules or files.
|
||||
- `web/service/user.go`
|
||||
- `web/service/user_test.go`
|
||||
|
||||
Whether APIs, database, config, build, or compatibility are affected.
|
||||
- API unchanged.
|
||||
- DB schema unchanged.
|
||||
- Runtime behavior fixed for registration-created clients only.
|
||||
|
||||
Whether upstream or downstream callers are affected.
|
||||
- Newly registered users now receive expected default flow in eligible VLESS inbounds.
|
||||
|
||||
Verification
|
||||
|
||||
List validation commands or checks performed.
|
||||
- `go test ./web/service/...`
|
||||
|
||||
State the result.
|
||||
- Passed.
|
||||
|
||||
If not verified, explain why.
|
||||
- No remote runtime verification in deployed environment was performed locally.
|
||||
|
||||
Risks And Follow-Up
|
||||
|
||||
Remaining risks.
|
||||
- Existing already-created clients are unaffected (no migration applied).
|
||||
|
||||
Recommended follow-up work.
|
||||
- If needed, add a one-time migration tool to backfill empty flow for existing eligible clients.
|
||||
|
|
@ -102,6 +102,9 @@ func (s *UserService) addUserClientsToAllInbounds(tx *gorm.DB, username string,
|
|||
SubID: uuid.New().String()[:8],
|
||||
Comment: "auto-added on registration",
|
||||
}
|
||||
if shouldAutoFillVisionFlow(inbound.Protocol, inbound.StreamSettings) {
|
||||
client.Flow = "xtls-rprx-vision"
|
||||
}
|
||||
|
||||
clientEntry := map[string]any{
|
||||
"email": client.Email,
|
||||
|
|
@ -122,6 +125,9 @@ func (s *UserService) addUserClientsToAllInbounds(tx *gorm.DB, username string,
|
|||
default:
|
||||
clientEntry["id"] = clientID
|
||||
}
|
||||
if client.Flow != "" {
|
||||
clientEntry["flow"] = client.Flow
|
||||
}
|
||||
|
||||
var settings map[string]any
|
||||
if err := json.Unmarshal([]byte(inbound.Settings), &settings); err != nil {
|
||||
|
|
|
|||
|
|
@ -285,3 +285,87 @@ func TestDeleteUser_RemovesClientsFromAllInbounds(t *testing.T) {
|
|||
t.Fatalf("expected managed_user inbound_client_ips to be deleted, remaining=%d", ipsCount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegisterUser_AutoFillFlowForEligibleVlessInbound(t *testing.T) {
|
||||
setupTestDB(t)
|
||||
|
||||
db := database.GetDB()
|
||||
userSvc := &UserService{}
|
||||
inboundSvc := &InboundService{}
|
||||
|
||||
vlessSettingsBytes, err := json.Marshal(map[string]any{
|
||||
"clients": []map[string]any{},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("marshal vless settings failed: %v", err)
|
||||
}
|
||||
|
||||
vlessInbound := &model.Inbound{
|
||||
UserId: 1,
|
||||
Port: 21011,
|
||||
Protocol: model.VLESS,
|
||||
Tag: "register-flow-vless",
|
||||
Settings: string(vlessSettingsBytes),
|
||||
StreamSettings: `{"network":"tcp","security":"tls"}`,
|
||||
}
|
||||
if err := db.Create(vlessInbound).Error; err != nil {
|
||||
t.Fatalf("create vless inbound failed: %v", err)
|
||||
}
|
||||
|
||||
vmessSettingsBytes, err := json.Marshal(map[string]any{
|
||||
"clients": []map[string]any{},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("marshal vmess settings failed: %v", err)
|
||||
}
|
||||
vmessInbound := &model.Inbound{
|
||||
UserId: 1,
|
||||
Port: 21012,
|
||||
Protocol: model.VMESS,
|
||||
Tag: "register-flow-vmess",
|
||||
Settings: string(vmessSettingsBytes),
|
||||
StreamSettings: `{"network":"tcp","security":"tls"}`,
|
||||
}
|
||||
if err := db.Create(vmessInbound).Error; err != nil {
|
||||
t.Fatalf("create vmess inbound failed: %v", err)
|
||||
}
|
||||
|
||||
if err := userSvc.RegisterUser("flow_user", "password123", inboundSvc); err != nil {
|
||||
t.Fatalf("RegisterUser failed: %v", err)
|
||||
}
|
||||
|
||||
assertClientFlow := func(inboundID int, expectedFlow string) {
|
||||
t.Helper()
|
||||
var inbound model.Inbound
|
||||
if err := db.First(&inbound, inboundID).Error; err != nil {
|
||||
t.Fatalf("load inbound %d failed: %v", inboundID, err)
|
||||
}
|
||||
var settings map[string]any
|
||||
if err := json.Unmarshal([]byte(inbound.Settings), &settings); err != nil {
|
||||
t.Fatalf("unmarshal inbound settings failed: %v", err)
|
||||
}
|
||||
clients, ok := settings["clients"].([]any)
|
||||
if !ok {
|
||||
t.Fatalf("invalid clients format in inbound %d", inboundID)
|
||||
}
|
||||
for _, clientRaw := range clients {
|
||||
clientMap, ok := clientRaw.(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
email, _ := clientMap["email"].(string)
|
||||
if email != "flow_user" {
|
||||
continue
|
||||
}
|
||||
flow, _ := clientMap["flow"].(string)
|
||||
if flow != expectedFlow {
|
||||
t.Fatalf("unexpected flow for inbound %d: expected %q, got %q", inboundID, expectedFlow, flow)
|
||||
}
|
||||
return
|
||||
}
|
||||
t.Fatalf("flow_user not found in inbound %d", inboundID)
|
||||
}
|
||||
|
||||
assertClientFlow(vlessInbound.Id, "xtls-rprx-vision")
|
||||
assertClientFlow(vmessInbound.Id, "")
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue