inbound_masque_test.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. package service
  2. import (
  3. "encoding/json"
  4. "testing"
  5. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  6. )
  7. const masqueTestStream = `{"network":"masque","security":"tls","masqueSettings":{"path":"/.well-known/masque/ip/*/*/"},
  8. "tlsSettings":{"alpn":["h3"],"certificates":[{"certificateFile":"/etc/ssl/certs/m.crt","keyFile":"/etc/ssl/private/m.key"}]}}`
  9. // GetXrayConfig rebuilds every inbound's users from the clients table; a MASQUE user
  10. // emitted without its password makes xray-core refuse the whole config at startup.
  11. func TestGetXrayConfig_EmitsMasqueUserPasswords(t *testing.T) {
  12. setupConflictDB(t)
  13. in := &model.Inbound{
  14. Tag: "in-8443-masque", Enable: true, Listen: "127.0.0.1", Port: 8443, Protocol: model.MASQUE,
  15. Settings: `{"clients":[{"email":"[email protected]","password":"masque-pass-ivy","enable":true}],"address":["10.14.0.1/24"]}`,
  16. StreamSettings: masqueTestStream,
  17. }
  18. if _, _, err := (&InboundService{}).AddInbound(in); err != nil {
  19. t.Fatalf("AddInbound: %v", err)
  20. }
  21. cfg, err := (&XrayService{}).GetXrayConfig()
  22. if err != nil {
  23. t.Fatalf("GetXrayConfig: %v", err)
  24. }
  25. for i := range cfg.InboundConfigs {
  26. if cfg.InboundConfigs[i].Tag != "in-8443-masque" {
  27. continue
  28. }
  29. var settings struct {
  30. Clients []map[string]any `json:"clients"`
  31. }
  32. if err := json.Unmarshal(cfg.InboundConfigs[i].Settings, &settings); err != nil {
  33. t.Fatalf("decode emitted settings: %v", err)
  34. }
  35. if len(settings.Clients) != 1 || settings.Clients[0]["pass"] != "masque-pass-ivy" {
  36. t.Fatalf("emitted clients = %v, want one with pass masque-pass-ivy", settings.Clients)
  37. }
  38. raw, err := json.Marshal(cfg.InboundConfigs[i])
  39. if err != nil {
  40. t.Fatalf("marshal emitted inbound: %v", err)
  41. }
  42. var emitted map[string]any
  43. if err := json.Unmarshal(raw, &emitted); err != nil {
  44. t.Fatalf("decode emitted inbound: %v", err)
  45. }
  46. if stream, _ := emitted["streamSettings"].(map[string]any); stream["network"] != "masque" {
  47. t.Fatalf("emitted streamSettings = %v, want the stored masque transport", emitted["streamSettings"])
  48. }
  49. assertXrayAccepts(t, "the emitted MASQUE inbound", buildGoldenInbound(t, emitted))
  50. return
  51. }
  52. t.Fatal("inbound in-8443-masque not found in the generated config")
  53. }
  54. // A client added to a MASQUE inbound without a password could never authenticate,
  55. // and xray-core refuses the whole inbound over one empty pass.
  56. func TestFillProtocolDefaults_MintsMasquePassword(t *testing.T) {
  57. client := model.Client{Email: "[email protected]"}
  58. if err := (&ClientService{}).fillProtocolDefaults(&client, &model.Inbound{Protocol: model.MASQUE}); err != nil {
  59. t.Fatalf("fillProtocolDefaults: %v", err)
  60. }
  61. if len(client.Password) != 32 {
  62. t.Fatalf("Password = %q, want a minted 32-character password", client.Password)
  63. }
  64. kept := model.Client{Email: "[email protected]", Password: "chosen"}
  65. if err := (&ClientService{}).fillProtocolDefaults(&kept, &model.Inbound{Protocol: model.MASQUE}); err != nil {
  66. t.Fatalf("fillProtocolDefaults: %v", err)
  67. }
  68. if kept.Password != "chosen" {
  69. t.Fatalf("Password = %q, want the chosen password kept", kept.Password)
  70. }
  71. }
  72. // Editing a MASQUE client must key on its password like trojan; falling back to the
  73. // empty UUID refused every MASQUE client edit with "empty client ID".
  74. func TestUpdateInboundClient_MasquePasswordChange(t *testing.T) {
  75. setupBulkDB(t)
  76. svc := &ClientService{}
  77. source := []model.Client{{Email: "ivy@x", Password: "pw-old", SubID: "sub-ivy", Enable: true}}
  78. ib := mkInbound(t, 22101, model.MASQUE, clientsSettings(t, source))
  79. if err := svc.SyncInbound(nil, ib.Id, source); err != nil {
  80. t.Fatalf("seed linkage: %v", err)
  81. }
  82. updated := []model.Client{{Email: "ivy@x", Password: "pw-new", SubID: "sub-ivy", Enable: true}}
  83. if _, err := svc.UpdateInboundClient(&InboundService{}, &model.Inbound{
  84. Id: ib.Id,
  85. Settings: clientsSettings(t, updated),
  86. }, "ivy@x"); err != nil {
  87. t.Fatalf("UpdateInboundClient: %v", err)
  88. }
  89. if rec := lookupClientRecord(t, "ivy@x"); rec.Password != "pw-new" {
  90. t.Fatalf("stored password = %q, want pw-new", rec.Password)
  91. }
  92. }