1
0

nord.go 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. package integration
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "io"
  7. "net/http"
  8. "time"
  9. "github.com/mhsanaei/3x-ui/v3/internal/util/common"
  10. "github.com/mhsanaei/3x-ui/v3/internal/web/service"
  11. )
  12. type NordService struct {
  13. service.SettingService
  14. }
  15. var nordHTTPClient = &http.Client{Timeout: 15 * time.Second}
  16. // nordAPIBase is a var so integration tests can use a local HTTP server.
  17. var nordAPIBase = "https://api.nordvpn.com"
  18. // maxResponseSize limits the maximum size of NordVPN API responses (10 MB).
  19. const maxResponseSize = 10 << 20
  20. func (s *NordService) GetCountries() (string, error) {
  21. req, reqErr := http.NewRequestWithContext(context.Background(), http.MethodGet, nordAPIBase+"/v1/servers/countries?filters[servers_technologies][identifier]=wireguard_udp", nil)
  22. if reqErr != nil {
  23. return "", reqErr
  24. }
  25. resp, err := nordHTTPClient.Do(req)
  26. if err != nil {
  27. return "", err
  28. }
  29. defer resp.Body.Close()
  30. if resp.StatusCode != http.StatusOK {
  31. return "", common.NewErrorf("NordVPN API error: %s", resp.Status)
  32. }
  33. body, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseSize))
  34. if err != nil {
  35. return "", err
  36. }
  37. return string(body), nil
  38. }
  39. func (s *NordService) GetServers(countryId string) (string, error) {
  40. // Validate countryId is numeric to prevent URL injection
  41. for _, c := range countryId {
  42. if c < '0' || c > '9' {
  43. return "", common.NewError("invalid country ID")
  44. }
  45. }
  46. url := fmt.Sprintf("%s/v2/servers?limit=0&filters[servers_technologies][identifier]=wireguard_udp&filters[country_id]=%s", nordAPIBase, countryId)
  47. req, reqErr := http.NewRequestWithContext(context.Background(), http.MethodGet, url, nil)
  48. if reqErr != nil {
  49. return "", reqErr
  50. }
  51. resp, err := nordHTTPClient.Do(req)
  52. if err != nil {
  53. return "", err
  54. }
  55. defer resp.Body.Close()
  56. if resp.StatusCode != http.StatusOK {
  57. return "", common.NewErrorf("NordVPN API error: %s", resp.Status)
  58. }
  59. body, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseSize))
  60. if err != nil {
  61. return "", err
  62. }
  63. return string(body), nil
  64. }
  65. func (s *NordService) SetKey(privateKey string) (string, error) {
  66. if privateKey == "" {
  67. return "", common.NewError("private key cannot be empty")
  68. }
  69. nordData := map[string]string{
  70. "private_key": privateKey,
  71. "token": "",
  72. }
  73. data, _ := json.Marshal(nordData)
  74. err := s.SetNord(string(data))
  75. if err != nil {
  76. return "", err
  77. }
  78. return string(data), nil
  79. }
  80. func (s *NordService) GetCredentials(token string) (string, error) {
  81. url := nordAPIBase + "/v1/users/services/credentials"
  82. req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, url, nil)
  83. if err != nil {
  84. return "", err
  85. }
  86. req.SetBasicAuth("token", token)
  87. resp, err := nordHTTPClient.Do(req)
  88. if err != nil {
  89. return "", err
  90. }
  91. defer resp.Body.Close()
  92. if resp.StatusCode != http.StatusOK {
  93. return "", common.NewErrorf("NordVPN API error: %s", resp.Status)
  94. }
  95. body, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseSize))
  96. if err != nil {
  97. return "", err
  98. }
  99. var creds map[string]any
  100. if err := json.Unmarshal(body, &creds); err != nil {
  101. return "", err
  102. }
  103. privateKey, ok := creds["nordlynx_private_key"].(string)
  104. if !ok || privateKey == "" {
  105. return "", common.NewError("failed to retrieve NordLynx private key")
  106. }
  107. nordData := map[string]string{
  108. "private_key": privateKey,
  109. "token": token,
  110. }
  111. data, _ := json.Marshal(nordData)
  112. err = s.SetNord(string(data))
  113. if err != nil {
  114. return "", err
  115. }
  116. return string(data), nil
  117. }
  118. func (s *NordService) GetNordData() (string, error) {
  119. return s.GetNord()
  120. }
  121. func (s *NordService) DelNordData() error {
  122. return s.SetNord("")
  123. }