| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293 |
- package controller
- import (
- "net/http"
- "net/http/httptest"
- "net/url"
- "path/filepath"
- "strconv"
- "strings"
- "testing"
- "github.com/gin-gonic/gin"
- "github.com/mhsanaei/3x-ui/v3/internal/database"
- "github.com/mhsanaei/3x-ui/v3/internal/database/model"
- "github.com/mhsanaei/3x-ui/v3/internal/util/crypto"
- "github.com/mhsanaei/3x-ui/v3/internal/web/runtime"
- )
- // A sub-node stores whatever the master pushes. A master row whose certificate
- // predates the TLS guard must still land, or the node silently falls out of sync.
- func TestNodeSyncPushSkipsOperatorTLSGuard(t *testing.T) {
- gin.SetMode(gin.TestMode)
- dbDir := t.TempDir()
- t.Setenv("XUI_DB_FOLDER", dbDir)
- if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
- t.Fatalf("InitDB: %v", err)
- }
- t.Cleanup(func() { _ = database.CloseDB() })
- prev := runtime.GetManager()
- runtime.SetManager(runtime.NewManager(runtime.LocalDeps{APIPort: func() int { return 0 }, SetNeedRestart: func() {}}))
- t.Cleanup(func() { runtime.SetManager(prev) })
- for name, scope := range map[string]string{"node-sync": model.ApiScopeNodeSync, "admin": model.ApiScopeAdmin} {
- row := &model.ApiToken{Name: name, Token: crypto.HashTokenSHA256(name + "-token"), Enabled: true, Scope: scope}
- if err := database.GetDB().Create(row).Error; err != nil {
- t.Fatalf("seed %s token: %v", name, err)
- }
- }
- engine := gin.New()
- a := &APIController{}
- api := engine.Group("/panel/api")
- api.Use(a.checkAPIAuth, a.enforceTokenScope)
- NewInboundController(api.Group("/inbounds"))
- const legacyStream = `{"network":"tcp","security":"tls","tlsSettings":{"certificates":[{"certificateFile":"","keyFile":"","certificate":[],"key":[]}]}}`
- add := func(t *testing.T, token string, port int) string {
- t.Helper()
- form := url.Values{
- "protocol": {"vless"},
- "port": {strconv.Itoa(port)},
- "tag": {"tls-legacy-" + strconv.Itoa(port)},
- "enable": {"true"},
- "settings": {`{"clients":[]}`},
- "streamSettings": {legacyStream},
- }
- req := httptest.NewRequest(http.MethodPost, "/panel/api/inbounds/add", strings.NewReader(form.Encode()))
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- req.Header.Set("Authorization", "Bearer "+token)
- w := httptest.NewRecorder()
- engine.ServeHTTP(w, req)
- return w.Body.String()
- }
- rows := func(t *testing.T, tag string) int64 {
- t.Helper()
- var n int64
- if err := database.GetDB().Model(&model.Inbound{}).Where("tag = ?", tag).Count(&n).Error; err != nil {
- t.Fatalf("count %s: %v", tag, err)
- }
- return n
- }
- t.Run("a master push lands on the node", func(t *testing.T) {
- body := add(t, "node-sync-token", 45001)
- if !strings.Contains(body, `"success":true`) {
- t.Fatalf("node-sync add rejected: %s", body)
- }
- if got := rows(t, "tls-legacy-45001"); got != 1 {
- t.Fatalf("stored rows = %d, want 1", got)
- }
- })
- t.Run("an operator token is still held to the guard", func(t *testing.T) {
- body := add(t, "admin-token", 45002)
- if !strings.Contains(body, `"success":false`) || !strings.Contains(body, "TLS") {
- t.Fatalf("admin add should fail on TLS, got: %s", body)
- }
- if got := rows(t, "tls-legacy-45002"); got != 0 {
- t.Fatalf("stored rows = %d, want 0", got)
- }
- })
- }
|