Merge pull request #9 from Snawoot/fix

Use login
This commit is contained in:
Snawoot
2022-07-27 02:33:18 +03:00
committed by GitHub
5 changed files with 101 additions and 56 deletions

2
.gitignore vendored
View File

@@ -16,4 +16,4 @@
bin/
*.snap
windscribe-proxy
wndstate.json
wndstate.json*

View File

@@ -70,10 +70,12 @@ windscribe-proxy -list-proxies
| list-locations | - | list available locations and exit |
| list-proxies | - | output proxy list and exit |
| location | String | desired proxy location. Default: best location |
| password | String | password for login |
| proxy | String | sets base proxy to use for all dial-outs. Format: `<http\|https\|socks5\|socks5h>://[login:password@]host[:port]` Examples: `http://user:password@192.168.1.1:3128`, `socks5://10.0.0.1:1080` |
| resolver | String | Use DNS/DoH/DoT/DoQ resolver for all dial-outs. See https://github.com/ameshkov/dnslookup/ for upstream DNS URL format. Examples: `https://1.1.1.1/dns-query`, `quic://dns.adguard.com` |
| state-file | String | file name used to persist Windscribe API client state. Default: `wndstate.json` |
| timeout | Duration | timeout for network operations. Default: `10s` |
| username | String | username for login |
| verbosity | Number | logging verbosity (10 - debug, 20 - info, 30 - warning, 40 - error, 50 - critical). Default: `20` |
| version | - | show program version and exit |

21
main.go
View File

@@ -58,6 +58,8 @@ type CLIArgs struct {
caFile string
clientAuthSecret string
stateFile string
username string
password string
}
func parse_args() CLIArgs {
@@ -82,6 +84,8 @@ func parse_args() CLIArgs {
flag.StringVar(&args.clientAuthSecret, "auth-secret", DEFAULT_CLIENT_AUTH_SECRET, "client auth secret")
flag.StringVar(&args.stateFile, "state-file", "wndstate.json", "file name used to persist "+
"Windscribe API client state")
flag.StringVar(&args.username, "username", "", "username for login")
flag.StringVar(&args.password, "password", "", "password for login")
flag.Parse()
if args.listLocations && args.listProxies {
arg_fail("list-locations and list-proxies flags are mutually exclusive")
@@ -187,7 +191,7 @@ func run() int {
state, err := loadState(args.stateFile)
if err != nil {
mainLogger.Warning("Failed to load client state: %v. Performing cold init...", err)
err = coldInit(wndc, args.timeout)
err = coldInit(wndc, args.username, args.password, args.timeout)
if err != nil {
mainLogger.Critical("Cold init failed: %v", err)
return 9
@@ -367,26 +371,19 @@ func saveState(filename string, state *wndclient.WndClientState) error {
return err
}
func coldInit(wndc *wndclient.WndClient, timeout time.Duration) error {
func coldInit(wndc *wndclient.WndClient, username, password string, timeout time.Duration) error {
ctx, cl := context.WithTimeout(context.Background(), timeout)
err := wndc.RegisterToken(ctx)
err := wndc.Session(ctx, username, password)
cl()
if err != nil {
return err
}
ctx, cl = context.WithTimeout(context.Background(), timeout)
err = wndc.Users(ctx)
cl()
if err != nil {
return err
return fmt.Errorf("Session call failed: %w", err)
}
ctx, cl = context.WithTimeout(context.Background(), timeout)
err = wndc.ServerCredentials(ctx)
cl()
if err != nil {
return err
return fmt.Errorf("ServerCredentials call failed: %w", err)
}
return nil

View File

@@ -14,6 +14,24 @@ type RegisterTokenResponse struct {
} `json:"data"`
}
type SessionResponse struct {
Data *struct {
SessionAuthHash string `json:"session_auth_hash"`
Username string `json:"username"`
UserID string `json:"user_id"`
TrafficUsed float64 `json:"traffic_used"`
TrafficMax float64 `json:"traffic_max"`
Status int `json:"status"`
Email *string `json:"email"`
EmailStatus int `json:"email_status"`
BillingPlanID int64 `json:"billing_plan_id"`
IsPremium int `json:"is_premium"`
RegDate float64 `json:"reg_date"`
LocationRevision int `json:"loc_rev"`
LocationHash string `json:"loc_hash"`
} `json:"data"`
}
type UsersRequest struct {
ClientAuthHash string `json:"client_auth_hash"`
SessionType int `json:"session_type_id"`

View File

@@ -12,6 +12,7 @@ import (
"net/url"
"path"
"strconv"
"strings"
"sync"
)
@@ -29,16 +30,14 @@ const (
var ErrNoDataInResponse = errors.New("no \"data\" key in response")
type WndEndpoints struct {
RegisterToken string `json:"RegisterToken"`
Users string `json:"Users"`
Session string `json:"Session"`
ServerList string `json:"serverlist"`
ServerCredentials string `json:"ServerCredentials"`
BestLocation string `json:"BestLocation"`
}
var DefaultWndEndpoints = WndEndpoints{
RegisterToken: "https://api.windscribe.com/RegToken",
Users: "https://api.windscribe.com/Users",
Session: "https://api.windscribe.com/Session",
ServerList: "https://assets.windscribe.com/serverlist",
ServerCredentials: "https://api.windscribe.com/ServerCredentials",
BestLocation: "https://api.windscribe.com/BestLocation",
@@ -58,7 +57,7 @@ var DefaultWndSettings = WndSettings{
ClientAuthSecret: "952b4412f002315aa50751032fcaab03",
Platform: "chrome",
Type: "chrome",
UserAgent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.101 Safari/537.36",
UserAgent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.53 Safari/537.36",
Origin: "chrome-extension://hnmpcagpplmpfojmgmnngilcnanddlhb",
SessionType: SESSION_TYPE_EXT,
Endpoints: DefaultWndEndpoints,
@@ -103,49 +102,22 @@ func NewWndClient(transport http.RoundTripper) (*WndClient, error) {
}, nil
}
func (c *WndClient) RegisterToken(ctx context.Context) error {
func (c *WndClient) Session(ctx context.Context, username, password string) error {
c.Mux.Lock()
defer c.Mux.Unlock()
clientAuthHash, authTime := MakeAuthHash(c.State.Settings.ClientAuthSecret)
input := RegisterTokenRequest{
ClientAuthHash: clientAuthHash,
Time: authTime,
input := url.Values{
"client_auth_hash": []string{clientAuthHash},
"time": []string{strconv.FormatInt(authTime, 10)},
"session_type_id": []string{strconv.FormatInt(SESSION_TYPE_EXT, 10)},
"username": []string{username},
"password": []string{password},
}
var output RegisterTokenResponse
var output SessionResponse
err := c.postJSON(ctx, c.State.Settings.Endpoints.RegisterToken, input, &output)
if err != nil {
return err
}
if output.Data == nil {
return ErrNoDataInResponse
}
c.State.TokenID = output.Data.TokenID
c.State.Token = output.Data.Token
c.State.TokenSignature = output.Data.TokenSignature
c.State.TokenSignatureTime = output.Data.TokenTime
return nil
}
func (c *WndClient) Users(ctx context.Context) error {
c.Mux.Lock()
defer c.Mux.Unlock()
clientAuthHash, authTime := MakeAuthHash(c.State.Settings.ClientAuthSecret)
input := UsersRequest{
ClientAuthHash: clientAuthHash,
Time: authTime,
SessionType: SESSION_TYPE_EXT,
Token: c.State.Token,
}
var output UsersResponse
err := c.postJSON(ctx, c.State.Settings.Endpoints.Users, input, &output)
err := c.postForm(ctx, c.State.Settings.Endpoints.Session, input, &output)
if err != nil {
return err
}
@@ -279,7 +251,56 @@ func (c *WndClient) postJSON(ctx context.Context, endpoint string, input, output
}
if resp.StatusCode < 200 || resp.StatusCode > 299 {
return fmt.Errorf("bad http status: %s, headers: %#v", resp.Status, resp.Header)
errBodyBytes, _ := ioutil.ReadAll(
&io.LimitedReader{
R: resp.Body,
N: 1024,
})
defer resp.Body.Close()
return fmt.Errorf("bad http status: %s, headers: %#v, body: %q",
resp.Status, resp.Header, string(errBodyBytes))
}
decoder := json.NewDecoder(resp.Body)
err = decoder.Decode(output)
cleanupBody(resp.Body)
if err != nil {
return err
}
return nil
}
func (c *WndClient) postForm(ctx context.Context, endpoint string, input url.Values, output interface{}) error {
req, err := http.NewRequestWithContext(
ctx,
"POST",
endpoint,
strings.NewReader(input.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")
resp, err := c.httpClient.Do(req)
if err != nil {
return err
}
if resp.StatusCode < 200 || resp.StatusCode > 299 {
errBodyBytes, _ := ioutil.ReadAll(
&io.LimitedReader{
R: resp.Body,
N: 1024,
})
defer resp.Body.Close()
return fmt.Errorf("bad http status: %s, headers: %#v, body: %q",
resp.Status, resp.Header, string(errBodyBytes))
}
decoder := json.NewDecoder(resp.Body)
@@ -319,7 +340,14 @@ func (c *WndClient) getJSON(ctx context.Context, requestUrl string, output inter
}
if resp.StatusCode < 200 || resp.StatusCode > 299 {
return fmt.Errorf("bad http status: %s, headers: %#v", resp.Status, resp.Header)
errBodyBytes, _ := ioutil.ReadAll(
&io.LimitedReader{
R: resp.Body,
N: 1024,
})
defer resp.Body.Close()
return fmt.Errorf("bad http status: %s, headers: %#v, body: %q",
resp.Status, resp.Header, string(errBodyBytes))
}
decoder := json.NewDecoder(resp.Body)