mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-11 22:00:59 +00:00
feat(mtproto): per-client ad-tags, management-API auth, and record secret sync
Catch the panel up to the mtg-multi README (v1.14.0): - Each client can now carry its own 32-hex advertising tag overriding the inbound-level one. The tag lives on the client (settings JSON is the source of truth, clients.ad_tag is the UI projection), is rendered into the fork's [secret-ad-tags] section for active secrets only (mtg rejects a config whose override names an unknown secret), is pushed per entry through PUT /secrets, and is part of the reload fingerprint so a tag edit hot-applies without dropping connections. - The loopback management API can replace the whole secret set, so every mtg process now gets a random per-process api-token; the manager sends it as a bearer token on PUT /secrets and GET /stats and reuses it across config rewrites, because mtg reads the token only at startup. - Malformed tags are rejected at every save path and additionally dropped in InstanceFromInbound: one bad tag would otherwise fail the whole generated config and take every client of the inbound down with it. - SyncInbound never copied a re-keyed mtproto secret into the canonical clients table, so the clients page and subscription links kept serving the old secret, which mtg then rejects. It is now guarded-copied like the other credentials.
This commit is contained in:
@@ -116,26 +116,34 @@ func TestApplySecrets(t *testing.T) {
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var gotMethod, gotPath string
|
||||
var gotMethod, gotPath, gotAuth string
|
||||
var gotBody secretsPutBody
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
gotMethod, gotPath = r.Method, r.URL.Path
|
||||
gotMethod, gotPath, gotAuth = r.Method, r.URL.Path, r.Header.Get("Authorization")
|
||||
_ = json.NewDecoder(r.Body).Decode(&gotBody)
|
||||
w.WriteHeader(tc.status)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
inst := mtgInst(1, SecretEntry{Name: "alice", Secret: "ee01"})
|
||||
inst := mtgInst(1,
|
||||
SecretEntry{Name: "alice", Secret: "ee01"},
|
||||
SecretEntry{Name: "bob", Secret: "ee02", AdTag: "fedcba9876543210fedcba9876543210"})
|
||||
inst.AdTag = "0123456789abcdef0123456789abcdef"
|
||||
if got := applySecrets(serverPort(t, srv), inst); got != tc.want {
|
||||
if got := applySecrets(serverPort(t, srv), "sesame", inst); got != tc.want {
|
||||
t.Fatalf("applySecrets = %v, want %v", got, tc.want)
|
||||
}
|
||||
if gotMethod != http.MethodPut || gotPath != "/secrets" {
|
||||
t.Fatalf("expected PUT /secrets, got %s %s", gotMethod, gotPath)
|
||||
}
|
||||
if gotAuth != "Bearer sesame" {
|
||||
t.Fatalf("expected the bearer token on the request, got %q", gotAuth)
|
||||
}
|
||||
if gotBody.Secrets["alice"].Secret != "ee01" || gotBody.AdTag != "0123456789abcdef0123456789abcdef" {
|
||||
t.Fatalf("payload must carry the secret and ad-tag: %+v", gotBody)
|
||||
}
|
||||
if gotBody.Secrets["alice"].AdTag != "" || gotBody.Secrets["bob"].AdTag != "fedcba9876543210fedcba9876543210" {
|
||||
t.Fatalf("payload must carry per-client ad-tag overrides only where set: %+v", gotBody)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -143,7 +151,7 @@ func TestApplySecrets(t *testing.T) {
|
||||
srv := httptest.NewServer(http.NotFoundHandler())
|
||||
port := serverPort(t, srv)
|
||||
srv.Close()
|
||||
if applySecrets(port, mtgInst(1, SecretEntry{Name: "a", Secret: "ee"})) {
|
||||
if applySecrets(port, "", mtgInst(1, SecretEntry{Name: "a", Secret: "ee"})) {
|
||||
t.Fatal("a refused connection must yield false")
|
||||
}
|
||||
})
|
||||
@@ -159,6 +167,10 @@ func TestEnsureHotReloadKeepsProcess(t *testing.T) {
|
||||
}
|
||||
waitSpawnCount(t, pidFile, 1)
|
||||
orig := mgr.procs[1].proc
|
||||
origToken := mgr.procs[1].apiToken
|
||||
if origToken == "" {
|
||||
t.Fatal("a started process must get an api token")
|
||||
}
|
||||
|
||||
reloaded := make(chan struct{}, 1)
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -201,6 +213,9 @@ func TestEnsureHotReloadKeepsProcess(t *testing.T) {
|
||||
if !strings.Contains(string(cfg), fmt.Sprintf("api-bind-to = \"127.0.0.1:%d\"", serverPort(t, srv))) {
|
||||
t.Fatalf("reload must reuse the same api port:\n%s", cfg)
|
||||
}
|
||||
if !strings.Contains(string(cfg), fmt.Sprintf("api-token = %q", origToken)) {
|
||||
t.Fatalf("reload must reuse the token the running process was started with:\n%s", cfg)
|
||||
}
|
||||
mgr.StopAll()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user