Skip to content

feat: Add NordVPN NordLynx (WireGuard) integration#3827

Open
PedroLiu1999 wants to merge 5 commits intoMHSanaei:mainfrom
PedroLiu1999:feature/nord-vpn
Open

feat: Add NordVPN NordLynx (WireGuard) integration#3827
PedroLiu1999 wants to merge 5 commits intoMHSanaei:mainfrom
PedroLiu1999:feature/nord-vpn

Conversation

@PedroLiu1999
Copy link
Copy Markdown

Description

This PR introduces comprehensive support for NordVPN outbounds using the WireGuard (NordLynx) protocol. It enables users to easily route traffic through NordVPN servers directly from the 3x-ui panel.

Key Features

  • Integrated NordVPN Service: A new backend service (NordService) for interacting with NordVPN APIs.
  • API v2 Integration: Uses the latest NordVPN API for real-time server discovery, load information, and intelligent sorting (lowest load first).
  • Authentication Modes:
    • Access Token: Simplified login using a NordVPN access token to automatically retrieve credentials.
    • Private Key: Support for manual private key entry for advanced users.
  • Frontend Refinements:
    • Dedicated NordVPN modal with server load indicators.
    • Automated server selection and configuration generation.
    • Proactive "Logout/Cleanup" that removes orphaned outbounds and routing rules.
  • Advanced Routing:
    • Direct integration into the "Basics" settings for easy routing rule creation.
    • Individual tags for each NordVPN server (nord-<hostname>) for precise traffic control.
  • Multi-language Support: Full localization in English, Farsi, and Chinese.

Changes

Backend

  • [NEW] web/service/nord.go: Core logic for NordVPN API interaction.
  • web/controller/xray_setting.go: Endpoints for server fetching and credential management.
  • web/service/setting.go: Storage and retrieval of NordVPN configuration.

Frontend

  • [NEW] web/html/modals/nord_modal.html: The main user interface for NordVPN setup.
  • web/html/settings/xray/outbounds.html: Added "NordVPN" quick-access button.
  • web/html/settings/xray/basics.html: Added NordVPN routing rule configuration.
  • web/html/xray.html: Enhanced template processing for dynamic outbound tags.

Localization

  • web/translation/translate.en_US.toml
  • web/translation/translate.fa_IR.toml
  • web/translation/translate.zh_CN.toml

Type of Changes

  • New feature

Screenshots

image image image

@PedroLiu1999 PedroLiu1999 marked this pull request as ready for review February 19, 2026 16:28
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new NordVPN (NordLynx/WireGuard) outbound integration to 3x-ui, including backend endpoints/services, UI modal + routing integration, and localization updates.

Changes:

  • Introduces NordService with NordVPN API calls and setting persistence.
  • Adds /panel/xray/nord/:action endpoints and UI flows to configure/add/reset NordVPN wireguard outbounds.
  • Adds NordVPN-related UI elements and translations (EN/FA/ZH).

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 13 comments.

Show a summary per file
File Description
web/service/nord.go New backend service for NordVPN country/server discovery + credential storage
web/controller/xray_setting.go Adds NordVPN action router/handler under Xray settings controller
web/service/setting.go Adds nord setting storage getters/setters + default value
web/html/modals/nord_modal.html New NordVPN configuration modal and outbound creation/reset logic
web/html/xray.html Wires Nord modal into the Xray page and adds routing-related computed props
web/html/settings/xray/outbounds.html Adds “NordVPN” quick-access button
web/html/settings/xray/basics.html Adds NordVPN routing rule configuration UI
web/translation/translate.en_US.toml Adds NordVPN + save/logout keys
web/translation/translate.fa_IR.toml Adds NordVPN + save/logout keys (missing some keys used by UI)
web/translation/translate.zh_CN.toml Adds NordVPN + save/logout keys (regresses outbound test toast keys)

Comment on lines +113 to +114
data, _ := json.Marshal(nordData)
s.SettingService.SetNord(string(data))
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetCredentials ignores the error returned by SetNord(...). If the settings write fails, the endpoint will still return credentials that weren’t actually saved. Please handle/return the SetNord error.

Suggested change
data, _ := json.Marshal(nordData)
s.SettingService.SetNord(string(data))
data, err := json.Marshal(nordData)
if err != nil {
return "", err
}
if err := s.SettingService.SetNord(string(data)); err != nil {
return "", err
}

Copilot uses AI. Check for mistakes.
Comment on lines +133 to +137
switch action {
case "countries":
resp, err = a.NordService.GetCountries()
case "servers":
countryId := c.PostForm("countryId")
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The nord action switch has no default case. For unknown/typo actions, this will fall through with resp == "" and err == nil, which will be treated as a successful response. Please return an explicit error (e.g., 400) for unsupported actions.

Copilot uses AI. Check for mistakes.
Comment on lines +189 to +191
if (this.servers.length === 0) {
app.$message.warning('No servers found for the selected country');
}
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hard-coded English warning message (No servers found for the selected country) is not localized via i18n. Since the PR claims full localization, consider moving this into translation files and using an i18n key here.

Copilot uses AI. Check for mistakes.
Comment on lines +30 to +33
func (s *NordService) GetServers(countryId string) (string, error) {
url := fmt.Sprintf("https://api.nordvpn.com/v2/servers?limit=0&filters[servers_technologies][id]=35&filters[country_id]=%s", countryId)
resp, err := http.Get(url)
if err != nil {
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetServers interpolates countryId directly into the query string. Since this value comes from PostForm, it should be validated/encoded (e.g., ensure it’s numeric or use url.Values) to avoid query-parameter injection and unexpected API requests.

Copilot uses AI. Check for mistakes.
}
var data map[string]any
if err := json.Unmarshal(body, &data); err != nil {
return string(body), nil
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In GetServers, JSON unmarshal failures currently return the raw body with a nil error. The controller will treat this as success and the frontend will likely JSON.parse it and crash. This should return the unmarshal error (and/or a structured API error) instead of pretending the response is valid.

Suggested change
return string(body), nil
return "", err

Copilot uses AI. Check for mistakes.
Comment on lines +254 to +256
this.close();
app.$message.success('NordVPN outbound updated');
},
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hard-coded English success message (NordVPN outbound updated) is not localized via i18n. Please replace with an i18n key to match the multi-language support described in the PR.

Copilot uses AI. Check for mistakes.
Comment on lines +54 to +56
if load, ok := server["load"].(float64); ok && load > 7 {
filtered = append(filtered, s)
}
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The server filtering logic keeps only servers where load > 7. This seems inconsistent with the PR description (“lowest load first”) and also discards the lowest-load servers. Please confirm the intended threshold/condition (e.g., cap high-load servers) and adjust the comparison/value accordingly.

Copilot uses AI. Check for mistakes.
Comment on lines +18 to +22
resp, err := http.Get("https://api.nordvpn.com/v1/countries")
if err != nil {
return "", err
}
defer resp.Body.Close()
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetCountries uses http.Get without a timeout and doesn’t check resp.StatusCode. A stalled connection can hang the request, and non-200 responses will be treated as success and sent to the UI. Consider using an http.Client{Timeout: ...} and returning an error when the status is not 200 OK.

Copilot uses AI. Check for mistakes.
@MHSanaei
Copy link
Copy Markdown
Owner

MHSanaei commented Mar 4, 2026

Thanks for your PR
please give me access to edit your PR

@PedroLiu1999
Copy link
Copy Markdown
Author

Thanks for your PR please give me access to edit your PR

Permission granted

@MHSanaei MHSanaei force-pushed the main branch 2 times, most recently from 0dc4df2 to f0f98c7 Compare March 17, 2026 22:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants