Files
opera-proxy/seclient/seclient.go
T

177 lines
4.3 KiB
Go
Raw Normal View History

2021-03-25 16:25:36 +02:00
package seclient
import (
2021-03-25 19:58:30 +02:00
"context"
2021-03-25 20:29:37 +02:00
"encoding/json"
2021-03-25 19:58:30 +02:00
"fmt"
2021-03-25 20:29:37 +02:00
"io"
"io/ioutil"
2021-03-25 18:50:53 +02:00
"math/rand"
2021-03-25 16:25:36 +02:00
"net/http"
"net/http/cookiejar"
2021-03-25 19:58:30 +02:00
"net/url"
"strings"
2021-03-25 16:25:36 +02:00
dac "github.com/Snawoot/go-http-digest-auth-client"
"golang.org/x/net/publicsuffix"
)
2021-03-25 18:50:53 +02:00
const (
2021-03-25 20:29:37 +02:00
ANON_EMAIL_LOCALPART_BYTES = 32
ANON_PASSWORD_BYTES = 20
DEVICE_ID_BYTES = 20
READ_LIMIT int64 = 128 * 1024
2021-03-25 18:50:53 +02:00
)
2021-03-25 16:25:36 +02:00
type SEEndpoints struct {
RegisterSubscriber string
SubscriberLogin string
RegisterDevice string
GeoList string
Discover string
}
var DefaultSEEndpoints = SEEndpoints{
RegisterSubscriber: "https://api.sec-tunnel.com/v4/register_subscriber",
SubscriberLogin: "https://api.sec-tunnel.com/v4/subscriber_login",
RegisterDevice: "https://api.sec-tunnel.com/v4/register_device",
GeoList: "https://api.sec-tunnel.com/v4/geo_list",
Discover: "https://api.sec-tunnel.com/v4/discover",
}
type SESettings struct {
ClientVersion string
2021-03-25 18:50:53 +02:00
ClientType string
DeviceHash string
DeviceName string
2021-03-25 16:25:36 +02:00
OperatingSystem string
2021-03-25 19:58:30 +02:00
UserAgent string
2021-03-25 16:25:36 +02:00
Endpoints SEEndpoints
}
var DefaultSESettings = SESettings{
ClientVersion: "Stable 74.0.3911.232",
2021-03-25 18:50:53 +02:00
ClientType: "se0316",
2021-03-25 19:58:30 +02:00
UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.192 Safari/537.36 OPR/74.0.3911.232",
2021-03-25 18:50:53 +02:00
DeviceName: "Opera-Browser-Client",
DeviceHash: "",
2021-03-25 16:25:36 +02:00
OperatingSystem: "Windows",
Endpoints: DefaultSEEndpoints,
}
type SEClient struct {
2021-03-25 18:50:53 +02:00
HttpClient *http.Client
Settings SESettings
SubscriberEmail string
SubscriberPassword string
DeviceID string
AssignedDeviceID string
AssignedDevideIDHash string
DevicePassword string
rng *rand.Rand
2021-03-25 16:25:36 +02:00
}
// Instantiates SurfEasy client with default settings and given API keys.
// Optional `transport` parameter allows to override HTTP transport used
// for HTTP calls
func NewSEClient(apiUsername, apiSecret string, transport http.RoundTripper) (*SEClient, error) {
if transport == nil {
transport = http.DefaultTransport
}
jar, err := cookiejar.New(&cookiejar.Options{
PublicSuffixList: publicsuffix.List,
})
if err != nil {
return nil, err
}
2021-03-25 19:58:30 +02:00
rng := rand.New(RandomSource)
2021-03-25 18:50:53 +02:00
device_id, err := randomCapitalHexString(rng, DEVICE_ID_BYTES)
if err != nil {
return nil, err
}
2021-03-25 16:25:36 +02:00
return &SEClient{
HttpClient: &http.Client{
Transport: dac.NewDigestTransport(apiUsername, apiSecret, transport),
2021-03-25 18:50:53 +02:00
Jar: jar,
2021-03-25 16:25:36 +02:00
},
Settings: DefaultSESettings,
2021-03-25 18:50:53 +02:00
rng: rng,
DeviceID: device_id,
2021-03-25 16:25:36 +02:00
}, nil
}
2021-03-25 18:50:53 +02:00
2021-03-25 19:58:30 +02:00
func (c *SEClient) AnonRegister(ctx context.Context) error {
localPart, err := randomEmailLocalPart(c.rng)
2021-03-25 18:50:53 +02:00
if err != nil {
2021-03-25 19:58:30 +02:00
return err
2021-03-25 18:50:53 +02:00
}
2021-03-25 19:58:30 +02:00
c.SubscriberEmail = fmt.Sprintf("%s@%s.best.vpn", localPart, c.Settings.ClientType)
password, err := randomCapitalHexString(c.rng, ANON_PASSWORD_BYTES)
if err != nil {
return err
}
c.SubscriberPassword = password
return c.Register(ctx)
}
func (c *SEClient) Register(ctx context.Context) error {
registerInput := url.Values{
"email": {c.SubscriberEmail},
"password": {c.SubscriberPassword},
}
req, err := http.NewRequestWithContext(
ctx,
"POST",
c.Settings.Endpoints.RegisterSubscriber,
strings.NewReader(registerInput.Encode()),
)
if err != nil {
return err
}
c.populateRequest(req)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Accept", "application/json")
2021-03-25 20:29:37 +02:00
resp, err := c.HttpClient.Do(req)
if err != nil {
return err
}
decoder := json.NewDecoder(resp.Body)
var regRes SERegisterSubscriberResponse
err = decoder.Decode(&regRes)
cleanupBody(resp.Body)
if err != nil {
return err
}
if regRes.Status.Code != SE_STATUS_OK {
return fmt.Errorf("API responded with error message: code=%d, msg=\"%s\"",
regRes.Status.Code, regRes.Status.Message)
}
2021-03-25 19:58:30 +02:00
return nil
}
func (c *SEClient) populateRequest(req *http.Request) {
req.Header.Set("SE-Client-Version", c.Settings.ClientVersion)
req.Header.Set("SE-Operating-System", c.Settings.OperatingSystem)
req.Header.Set("User-Agent", c.Settings.UserAgent)
2021-03-25 18:50:53 +02:00
}
2021-03-25 20:29:37 +02:00
// Does cleanup of HTTP response in order to make it reusable by keep-alive
// logic of HTTP client
func cleanupBody(body io.ReadCloser) {
2021-03-25 20:45:48 +02:00
io.Copy(ioutil.Discard, &io.LimitedReader{
R: body,
2021-03-25 20:29:37 +02:00
N: READ_LIMIT,
})
body.Close()
}