nord.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  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. // maxResponseSize limits the maximum size of NordVPN API responses (10 MB).
  17. const maxResponseSize = 10 << 20
  18. func (s *NordService) GetCountries() (string, error) {
  19. req, reqErr := http.NewRequestWithContext(context.Background(), http.MethodGet, "https://api.nordvpn.com/v1/countries", nil)
  20. if reqErr != nil {
  21. return "", reqErr
  22. }
  23. resp, err := nordHTTPClient.Do(req)
  24. if err != nil {
  25. return "", err
  26. }
  27. defer resp.Body.Close()
  28. if resp.StatusCode != http.StatusOK {
  29. return "", common.NewErrorf("NordVPN API error: %s", resp.Status)
  30. }
  31. body, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseSize))
  32. if err != nil {
  33. return "", err
  34. }
  35. return string(body), nil
  36. }
  37. func (s *NordService) GetServers(countryId string) (string, error) {
  38. // Validate countryId is numeric to prevent URL injection
  39. for _, c := range countryId {
  40. if c < '0' || c > '9' {
  41. return "", common.NewError("invalid country ID")
  42. }
  43. }
  44. url := fmt.Sprintf("https://api.nordvpn.com/v2/servers?limit=0&filters[servers_technologies][id]=35&filters[country_id]=%s", countryId)
  45. req, reqErr := http.NewRequestWithContext(context.Background(), http.MethodGet, url, nil)
  46. if reqErr != nil {
  47. return "", reqErr
  48. }
  49. resp, err := nordHTTPClient.Do(req)
  50. if err != nil {
  51. return "", err
  52. }
  53. defer resp.Body.Close()
  54. if resp.StatusCode != http.StatusOK {
  55. return "", common.NewErrorf("NordVPN API error: %s", resp.Status)
  56. }
  57. body, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseSize))
  58. if err != nil {
  59. return "", err
  60. }
  61. var data map[string]any
  62. if err := json.Unmarshal(body, &data); err != nil {
  63. return string(body), nil
  64. }
  65. servers, ok := data["servers"].([]any)
  66. if !ok {
  67. return string(body), nil
  68. }
  69. var filtered []any
  70. for _, s := range servers {
  71. if server, ok := s.(map[string]any); ok {
  72. if load, ok := server["load"].(float64); ok && load > 7 {
  73. filtered = append(filtered, s)
  74. }
  75. }
  76. }
  77. data["servers"] = filtered
  78. result, _ := json.Marshal(data)
  79. return string(result), nil
  80. }
  81. func (s *NordService) SetKey(privateKey string) (string, error) {
  82. if privateKey == "" {
  83. return "", common.NewError("private key cannot be empty")
  84. }
  85. nordData := map[string]string{
  86. "private_key": privateKey,
  87. "token": "",
  88. }
  89. data, _ := json.Marshal(nordData)
  90. err := s.SetNord(string(data))
  91. if err != nil {
  92. return "", err
  93. }
  94. return string(data), nil
  95. }
  96. func (s *NordService) GetCredentials(token string) (string, error) {
  97. url := "https://api.nordvpn.com/v1/users/services/credentials"
  98. req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, url, nil)
  99. if err != nil {
  100. return "", err
  101. }
  102. req.SetBasicAuth("token", token)
  103. resp, err := nordHTTPClient.Do(req)
  104. if err != nil {
  105. return "", err
  106. }
  107. defer resp.Body.Close()
  108. if resp.StatusCode != http.StatusOK {
  109. return "", common.NewErrorf("NordVPN API error: %s", resp.Status)
  110. }
  111. body, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseSize))
  112. if err != nil {
  113. return "", err
  114. }
  115. var creds map[string]any
  116. if err := json.Unmarshal(body, &creds); err != nil {
  117. return "", err
  118. }
  119. privateKey, ok := creds["nordlynx_private_key"].(string)
  120. if !ok || privateKey == "" {
  121. return "", common.NewError("failed to retrieve NordLynx private key")
  122. }
  123. nordData := map[string]string{
  124. "private_key": privateKey,
  125. "token": token,
  126. }
  127. data, _ := json.Marshal(nordData)
  128. err = s.SetNord(string(data))
  129. if err != nil {
  130. return "", err
  131. }
  132. return string(data), nil
  133. }
  134. func (s *NordService) GetNordData() (string, error) {
  135. return s.GetNord()
  136. }
  137. func (s *NordService) DelNordData() error {
  138. return s.SetNord("")
  139. }