mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-29 14:37:13 +00:00
feat(pia): add PIA login-and-add WireGuard outbounds (#6272)
* feat(pia): add login-and-add WireGuard outbounds (#2) * fix(pia): keep PIA outbounds identifiable after the editor strips hostname The outbound editor drops piaHostname, so last-segment matching failed for hyphenated servers. Identify rows by the computed tag, re-encrypt stored tokens onto the active key, skip unusable catalog rows, and always release the catalog refresh latch.
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
package pia
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ServerListSource interface {
|
||||
Fetch(ctx context.Context) (ServerListSnapshot, error)
|
||||
}
|
||||
|
||||
type ServerListSnapshot struct {
|
||||
Payload []byte
|
||||
SchemaHint string
|
||||
SignatureVerified bool
|
||||
}
|
||||
|
||||
type CatalogClient struct {
|
||||
Endpoint string
|
||||
PublicKeyPEM []byte
|
||||
HTTPClient *http.Client
|
||||
MaxBody int64
|
||||
UserAgent string
|
||||
}
|
||||
|
||||
func NewCatalogClient(endpoint string, publicKey []byte) *CatalogClient {
|
||||
return &CatalogClient{
|
||||
Endpoint: endpoint,
|
||||
PublicKeyPEM: publicKey,
|
||||
MaxBody: DefaultMaxServerListBody,
|
||||
UserAgent: DefaultUserAgent,
|
||||
HTTPClient: &http.Client{Timeout: DefaultRequestTimeout, CheckRedirect: noRedirect},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CatalogClient) Fetch(ctx context.Context) (ServerListSnapshot, error) {
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, c.Endpoint, nil)
|
||||
if err != nil {
|
||||
return ServerListSnapshot{}, WrapError(CodeCatalogUnavailable, "The PIA region-list endpoint is invalid.", err)
|
||||
}
|
||||
request.Header.Set("Accept", "application/json, text/plain;q=0.9")
|
||||
request.Header.Set("User-Agent", c.UserAgent)
|
||||
response, err := c.HTTPClient.Do(request)
|
||||
if err != nil {
|
||||
return ServerListSnapshot{}, classifyNetworkError(ctx, CodeCatalogUnavailable, "The PIA region list could not be downloaded.", err)
|
||||
}
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return ServerListSnapshot{}, NewError(CodeCatalogUnavailable, fmt.Sprintf("PIA returned HTTP %d for the region list.", response.StatusCode))
|
||||
}
|
||||
if !expectedContentType(response.Header.Get("Content-Type"), "application/json", "text/plain", "application/octet-stream") {
|
||||
return ServerListSnapshot{}, NewError(CodeCatalogSchemaUnsupported, "PIA returned an unexpected region-list content type.")
|
||||
}
|
||||
raw, err := readLimitedBody(response.Body, c.MaxBody)
|
||||
if err != nil {
|
||||
return ServerListSnapshot{}, WrapError(CodeCatalogUnavailable, "The PIA region-list response is too large or incomplete.", err)
|
||||
}
|
||||
verified, err := VerifySignedServerList(raw, c.PublicKeyPEM)
|
||||
if err != nil {
|
||||
return ServerListSnapshot{}, err
|
||||
}
|
||||
return ServerListSnapshot{Payload: verified, SchemaHint: schemaHint(c.Endpoint), SignatureVerified: true}, nil
|
||||
}
|
||||
|
||||
func schemaHint(endpoint string) string {
|
||||
parsed, err := url.Parse(endpoint)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
base := strings.ToLower(path.Base(parsed.Path))
|
||||
if base == "v6" || base == "v7" {
|
||||
return strings.TrimPrefix(base, "v")
|
||||
}
|
||||
return ""
|
||||
}
|
||||
Reference in New Issue
Block a user