diff --git a/backend/internal/converter/client.go b/backend/internal/converter/client.go index b857f06..d7bf55c 100644 --- a/backend/internal/converter/client.go +++ b/backend/internal/converter/client.go @@ -136,10 +136,10 @@ func listenerToProxies(l models.Listener, server string, credentials []user.Cred credentials = cfgCreds } case "tuic-v5": - // Config users map wins; otherwise keep panel-bound credentials. - if tuicConfigHasUsers(opts) && len(cfgCreds) > 0 { - credentials = cfgCreds - } else if len(credentials) == 0 { + // Panel-bound UUID/password is what the subscription user authenticates + // with. Config autofill UUID is only a fallback when unbound. + // (Inbound compile merges both via asUsersMapUUID.) + if len(credentials) == 0 { credentials = cfgCreds } case "tuic": @@ -387,6 +387,15 @@ func listenerToProxies(l models.Listener, server string, credentials []user.Cred p["name-cert-verify"] = v } ensureTUICClientDefaults(p, opts) + if p["skip-cert-verify"] == nil { + if certutil.ShouldSkipCertVerify(opts) { + p["skip-cert-verify"] = true + } else if c, _, ok := certstore.Load(l.ID); ok && certutil.IsPanelSelfSignedPEM(c) { + p["skip-cert-verify"] = true + } else if cert, _ := opts["certificate"].(string); certutil.IsPanelSelfSignedPEM(cert) { + p["skip-cert-verify"] = true + } + } if p["sni"] == nil && p["servername"] == nil && server != "" { p["sni"] = server } diff --git a/backend/internal/protocol/compile.go b/backend/internal/protocol/compile.go index 9874b7a..a583787 100644 --- a/backend/internal/protocol/compile.go +++ b/backend/internal/protocol/compile.go @@ -265,33 +265,44 @@ func asUsersMap(cfg map[string]interface{}, fromCreds []UserCred, hasCredState b // Mihomo listener schema expects that shape (TUIC v5: users is a map keyed by UUID). // Falls back to Username when UUID is empty (legacy panel users without UUID). func asUsersMapUUID(cfg map[string]interface{}, fromCreds []UserCred, hasCredState bool) map[string]interface{} { - if len(fromCreds) > 0 { - out := make(map[string]interface{}, len(fromCreds)) - for _, c := range fromCreds { - key := c.UUID - if key == "" { - key = c.Username - } - if key == "" { - key = "default" - } - out[key] = c.Password - } - return out - } + // Union: Config autofill UUID(s) + every bound panel user. + // Subscription exports panel UUID/password; inbound must accept the same + // keys or TUIC v5 auth fails (config-only random UUID ≠ panel UUID). + out := map[string]interface{}{} if raw, ok := cfg["users"]; ok { switch users := raw.(type) { case map[string]interface{}: - return users - case map[interface{}]interface{}: - out := make(map[string]interface{}, len(users)) for k, v := range users { - out[fmt.Sprint(k)] = fmt.Sprint(v) + if s := strings.TrimSpace(fmt.Sprint(v)); s != "" { + out[k] = s + } + } + case map[interface{}]interface{}: + for k, v := range users { + if s := strings.TrimSpace(fmt.Sprint(v)); s != "" { + out[fmt.Sprint(k)] = s + } } - return out } } - return nil + for _, c := range fromCreds { + key := strings.TrimSpace(c.UUID) + if key == "" { + key = strings.TrimSpace(c.Username) + } + if key == "" { + continue + } + pass := strings.TrimSpace(c.Password) + if pass == "" { + continue + } + out[key] = pass + } + if len(out) == 0 { + return nil + } + return out } func normalizeUsersValue(value interface{}) []map[string]interface{} {