| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145 |
- package service
- import (
- "encoding/json"
- "fmt"
- "io"
- "net/http"
- "time"
- "github.com/mhsanaei/3x-ui/v2/util/common"
- )
- type NordService struct {
- SettingService
- }
- var nordHTTPClient = &http.Client{Timeout: 15 * time.Second}
- // maxResponseSize limits the maximum size of NordVPN API responses (10 MB).
- const maxResponseSize = 10 << 20
- func (s *NordService) GetCountries() (string, error) {
- resp, err := nordHTTPClient.Get("https://api.nordvpn.com/v1/countries")
- if err != nil {
- return "", err
- }
- defer resp.Body.Close()
- body, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseSize))
- if err != nil {
- return "", err
- }
- return string(body), nil
- }
- func (s *NordService) GetServers(countryId string) (string, error) {
- // Validate countryId is numeric to prevent URL injection
- for _, c := range countryId {
- if c < '0' || c > '9' {
- return "", common.NewError("invalid country ID")
- }
- }
- url := fmt.Sprintf("https://api.nordvpn.com/v2/servers?limit=0&filters[servers_technologies][id]=35&filters[country_id]=%s", countryId)
- resp, err := nordHTTPClient.Get(url)
- if err != nil {
- return "", err
- }
- defer resp.Body.Close()
- body, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseSize))
- if err != nil {
- return "", err
- }
- var data map[string]any
- if err := json.Unmarshal(body, &data); err != nil {
- return string(body), nil
- }
- servers, ok := data["servers"].([]any)
- if !ok {
- return string(body), nil
- }
- var filtered []any
- for _, s := range servers {
- if server, ok := s.(map[string]any); ok {
- if load, ok := server["load"].(float64); ok && load > 7 {
- filtered = append(filtered, s)
- }
- }
- }
- data["servers"] = filtered
- result, _ := json.Marshal(data)
- return string(result), nil
- }
- func (s *NordService) SetKey(privateKey string) (string, error) {
- if privateKey == "" {
- return "", common.NewError("private key cannot be empty")
- }
- nordData := map[string]string{
- "private_key": privateKey,
- "token": "",
- }
- data, _ := json.Marshal(nordData)
- err := s.SettingService.SetNord(string(data))
- if err != nil {
- return "", err
- }
- return string(data), nil
- }
- func (s *NordService) GetCredentials(token string) (string, error) {
- url := "https://api.nordvpn.com/v1/users/services/credentials"
- req, err := http.NewRequest("GET", url, nil)
- if err != nil {
- return "", err
- }
- req.SetBasicAuth("token", token)
- client := &http.Client{Timeout: 10 * time.Second}
- resp, err := client.Do(req)
- if err != nil {
- return "", err
- }
- defer resp.Body.Close()
- if resp.StatusCode != http.StatusOK {
- return "", common.NewErrorf("NordVPN API error: %s", resp.Status)
- }
- body, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseSize))
- if err != nil {
- return "", err
- }
- var creds map[string]any
- if err := json.Unmarshal(body, &creds); err != nil {
- return "", err
- }
- privateKey, ok := creds["nordlynx_private_key"].(string)
- if !ok || privateKey == "" {
- return "", common.NewError("failed to retrieve NordLynx private key")
- }
- nordData := map[string]string{
- "private_key": privateKey,
- "token": token,
- }
- data, _ := json.Marshal(nordData)
- err = s.SettingService.SetNord(string(data))
- if err != nil {
- return "", err
- }
- return string(data), nil
- }
- func (s *NordService) GetNordData() (string, error) {
- return s.SettingService.GetNord()
- }
- func (s *NordService) DelNordData() error {
- return s.SettingService.SetNord("")
- }
|