1
0

tgbot_client_links_authz_test.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. package tgbot
  2. import (
  3. "path/filepath"
  4. "testing"
  5. "time"
  6. "github.com/mhsanaei/3x-ui/v3/internal/database"
  7. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  8. "github.com/mhsanaei/3x-ui/v3/internal/web/global"
  9. "github.com/mhsanaei/3x-ui/v3/internal/xray"
  10. "github.com/mymmrac/telego"
  11. )
  12. const (
  13. ownerTgID = int64(4242)
  14. ownerMail = "owner@x"
  15. )
  16. // newLinksCallbackTgbot seeds one inbound whose settings bind email to
  17. // ownerTgID, the traffic row the ownership lookup joins on, and a mocked API.
  18. func newLinksCallbackTgbot(t *testing.T, email string) (*Tgbot, func(string) int) {
  19. t.Helper()
  20. mock, calls := staleButtonServer(t, map[string]any{
  21. "answerCallbackQuery": map[string]any{"ok": true, "result": true},
  22. "sendMessage": map[string]any{"ok": true, "result": map[string]any{
  23. "message_id": 1,
  24. "date": 0,
  25. "chat": map[string]any{"id": ownerTgID, "type": "private"},
  26. }},
  27. })
  28. swapTestBot(t, mock.URL)
  29. t.Cleanup(mock.Close)
  30. if err := database.InitDB(filepath.Join(t.TempDir(), "x-ui.db")); err != nil {
  31. t.Fatalf("InitDB: %v", err)
  32. }
  33. t.Cleanup(func() { _ = database.CloseDB() })
  34. inbound := &model.Inbound{
  35. UserId: 1,
  36. Remark: "in",
  37. Port: 443,
  38. Protocol: model.VLESS,
  39. Enable: true,
  40. Settings: `{"clients":[{"email":"` + email + `","tgId":4242,"subId":"sub-owned"}]}`,
  41. }
  42. if err := database.GetDB().Create(inbound).Error; err != nil {
  43. t.Fatalf("seed inbound: %v", err)
  44. }
  45. if err := database.GetDB().Create(&xray.ClientTraffic{
  46. InboundId: inbound.Id,
  47. Email: email,
  48. Enable: true,
  49. }).Error; err != nil {
  50. t.Fatalf("seed traffic: %v", err)
  51. }
  52. origRunning := isRunning
  53. t.Cleanup(func() { isRunning = origRunning })
  54. isRunning = true
  55. return &Tgbot{}, calls
  56. }
  57. func tapClientLinks(t *testing.T, tb *Tgbot, tgUserID int64, data string) {
  58. t.Helper()
  59. tb.answerCallback(&telego.CallbackQuery{
  60. ID: "q1",
  61. From: telego.User{ID: tgUserID},
  62. Data: data,
  63. Message: &telego.Message{Chat: telego.Chat{ID: tgUserID}},
  64. }, false)
  65. }
  66. // Regression test: a non-admin tapping a link callback carrying another
  67. // client's email must be refused; without the ownership check it is served.
  68. func TestClientLinkCallbackRefusesForeignClient(t *testing.T) {
  69. tb, calls := newLinksCallbackTgbot(t, ownerMail)
  70. tapClientLinks(t, tb, ownerTgID, "client_sub_links someone-else@x")
  71. if n := calls("sendMessage"); n != 0 {
  72. t.Errorf("sendMessage calls = %d, want 0: a non-admin received a foreign client's links", n)
  73. }
  74. if n := calls("answerCallbackQuery"); n != 1 {
  75. t.Errorf("answerCallbackQuery calls = %d, want 1: the refused tap must be answered", n)
  76. }
  77. }
  78. // The same guard must not lock the owner out of their own links.
  79. func TestClientLinkCallbackServesOwnClient(t *testing.T) {
  80. tb, calls := newLinksCallbackTgbot(t, ownerMail)
  81. tapClientLinks(t, tb, ownerTgID, "client_sub_links "+ownerMail)
  82. if n := calls("sendMessage"); n != 1 {
  83. t.Errorf("sendMessage calls = %d, want 1: the owner must still get its links", n)
  84. }
  85. if n := calls("answerCallbackQuery"); n != 0 {
  86. t.Errorf("answerCallbackQuery calls = %d, want 0: an allowed tap is not refused", n)
  87. }
  88. }
  89. // Regression test: a payload past 64 chars arrives as its hash, so an email long
  90. // enough to be hashed must still be decoded and served to its owner.
  91. func TestHashedLinkCallbackServesOwnClient(t *testing.T) {
  92. const longMail = "[email protected]"
  93. tb, calls := newLinksCallbackTgbot(t, longMail)
  94. origStorage := hashStorage
  95. hashStorage = global.NewHashStorage(20 * time.Minute)
  96. t.Cleanup(func() { hashStorage = origStorage })
  97. data := tb.encodeQuery("client_sub_links " + longMail)
  98. if data == "client_sub_links "+longMail {
  99. t.Fatalf("encodeQuery left %q unhashed; the test needs a hashed payload", data)
  100. }
  101. tapClientLinks(t, tb, ownerTgID, data)
  102. if n := calls("sendMessage"); n != 1 {
  103. t.Errorf("sendMessage calls = %d, want 1: the owner's hashed button must still be served", n)
  104. }
  105. }