nord.go 3.6 KB

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