1
0

mtproto_migration_test.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. package database
  2. import (
  3. "encoding/json"
  4. "path/filepath"
  5. "testing"
  6. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  7. )
  8. func initMtprotoMigrationDB(t *testing.T) {
  9. t.Helper()
  10. dbDir := t.TempDir()
  11. t.Setenv("XUI_DB_FOLDER", dbDir)
  12. if err := InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
  13. t.Fatalf("InitDB failed: %v", err)
  14. }
  15. t.Cleanup(func() { _ = CloseDB() })
  16. }
  17. func createMtprotoInbound(t *testing.T, remark string, port int, settings map[string]any) *model.Inbound {
  18. t.Helper()
  19. raw, err := json.Marshal(settings)
  20. if err != nil {
  21. t.Fatalf("marshal settings: %v", err)
  22. }
  23. in := &model.Inbound{
  24. UserId: 1,
  25. Remark: remark,
  26. Port: port,
  27. Protocol: model.MTProto,
  28. Settings: string(raw),
  29. Tag: remark,
  30. }
  31. if err := db.Create(in).Error; err != nil {
  32. t.Fatalf("create mtproto inbound: %v", err)
  33. }
  34. return in
  35. }
  36. func clearSeederHistory(t *testing.T, name string) {
  37. t.Helper()
  38. if err := db.Where("seeder_name = ?", name).Delete(&model.HistoryOfSeeders{}).Error; err != nil {
  39. t.Fatalf("clear %s history: %v", name, err)
  40. }
  41. }
  42. func TestStripMtprotoInboundSecretsRemovesDeadSecret(t *testing.T) {
  43. initMtprotoMigrationDB(t)
  44. in := createMtprotoInbound(t, "mt-mc", 8443, map[string]any{
  45. "fakeTlsDomain": "www.cloudflare.com",
  46. "secret": "eedeadbeef7777772e636c6f7564666c6172652e636f6d",
  47. "clients": []any{
  48. map[string]any{"email": "alice", "secret": "eeaaaa7777772e636c6f7564666c6172652e636f6d", "enable": true, "subId": "s1"},
  49. },
  50. })
  51. clearSeederHistory(t, "StripMtprotoInboundSecrets")
  52. if err := stripMtprotoInboundSecrets(); err != nil {
  53. t.Fatalf("stripMtprotoInboundSecrets: %v", err)
  54. }
  55. settings := reloadInboundSettings(t, in.Id)
  56. if _, ok := settings["secret"]; ok {
  57. t.Fatalf("inbound-level secret must be removed, got %v", settings)
  58. }
  59. clients, ok := settings["clients"].([]any)
  60. if !ok || len(clients) != 1 {
  61. t.Fatalf("clients must be preserved, got %v", settings["clients"])
  62. }
  63. if clients[0].(map[string]any)["secret"] != "eeaaaa7777772e636c6f7564666c6172652e636f6d" {
  64. t.Fatalf("client secret must be preserved, got %v", clients[0])
  65. }
  66. if settings["fakeTlsDomain"] != "www.cloudflare.com" {
  67. t.Fatalf("fakeTlsDomain must be preserved, got %v", settings)
  68. }
  69. }
  70. func TestStripMtprotoInboundSecretsIsGated(t *testing.T) {
  71. initMtprotoMigrationDB(t)
  72. in := createMtprotoInbound(t, "mt-gate", 8444, map[string]any{
  73. "fakeTlsDomain": "a.com",
  74. "secret": "eebeef61",
  75. "clients": []any{map[string]any{"email": "x", "secret": "eeaa61", "enable": true}},
  76. })
  77. if err := stripMtprotoInboundSecrets(); err != nil {
  78. t.Fatalf("first run: %v", err)
  79. }
  80. if _, ok := reloadInboundSettings(t, in.Id)["secret"]; ok {
  81. t.Fatal("secret should be stripped on the first run")
  82. }
  83. var raw model.Inbound
  84. if err := db.First(&raw, in.Id).Error; err != nil {
  85. t.Fatalf("reload: %v", err)
  86. }
  87. raw.Settings = `{"fakeTlsDomain":"a.com","secret":"eereintroduced","clients":[]}`
  88. if err := db.Save(&raw).Error; err != nil {
  89. t.Fatalf("reintroduce secret: %v", err)
  90. }
  91. if err := stripMtprotoInboundSecrets(); err != nil {
  92. t.Fatalf("second run (history gate): %v", err)
  93. }
  94. if _, ok := reloadInboundSettings(t, in.Id)["secret"]; !ok {
  95. t.Fatal("second run must be a no-op: the seeder is one-time, so a re-added secret is left alone")
  96. }
  97. }
  98. func TestSeedThenStripPreservesLegacySecretOnClient(t *testing.T) {
  99. initMtprotoMigrationDB(t)
  100. const legacy = "eedeadbeefdeadbeefdeadbeefdeadbe7777772e636c6f7564666c6172652e636f6d"
  101. in := createMtprotoInbound(t, "mt-legacy", 8445, map[string]any{
  102. "fakeTlsDomain": "www.cloudflare.com",
  103. "secret": legacy,
  104. })
  105. clearSeederHistory(t, "MtprotoSecretsToClients")
  106. clearSeederHistory(t, "StripMtprotoInboundSecrets")
  107. if err := seedMtprotoSecretsToClients(); err != nil {
  108. t.Fatalf("seedMtprotoSecretsToClients: %v", err)
  109. }
  110. if err := stripMtprotoInboundSecrets(); err != nil {
  111. t.Fatalf("stripMtprotoInboundSecrets: %v", err)
  112. }
  113. settings := reloadInboundSettings(t, in.Id)
  114. if _, ok := settings["secret"]; ok {
  115. t.Fatalf("inbound-level secret must be gone after seed+strip, got %v", settings)
  116. }
  117. clients, ok := settings["clients"].([]any)
  118. if !ok || len(clients) != 1 {
  119. t.Fatalf("seed should have created exactly one client, got %v", settings["clients"])
  120. }
  121. if got := clients[0].(map[string]any)["secret"]; got != legacy {
  122. t.Fatalf("the legacy secret must survive on the seeded client, got %v want %s", got, legacy)
  123. }
  124. }