You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
208 lines
6.7 KiB
208 lines
6.7 KiB
package api
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"time"
|
|
|
|
fhttp "github.com/bogdanfinn/fhttp"
|
|
"github.com/cacggghp/vk-turn-proxy/client/warp/internal"
|
|
"github.com/cacggghp/vk-turn-proxy/client/warp/models"
|
|
)
|
|
|
|
// Register creates a new user account by registering a WireGuard public key and generating a random Android-like device identifier.
|
|
// The WireGuard private key isn't stored anywhere, therefore it won't be usable. It's sole purpose is to mimic the Android app's registration process.
|
|
//
|
|
// This function sends a POST request to the API to register a new user and returns the created account data.
|
|
//
|
|
// Parameters:
|
|
// - model: string - The device model string to register. (e.g., "PC")
|
|
// - locale: string - The user's locale. (e.g., "en-US")
|
|
// - jwt: string - Team token to register.
|
|
// - acceptTos: bool - Whether the user accepts the Terms of Service (TOS). If false, the user will be prompted to accept.
|
|
//
|
|
// Returns:
|
|
// - models.AccountData: The account data returned from the registration process.
|
|
// - error: An error if registration fails at any step.
|
|
//
|
|
// Example:
|
|
//
|
|
// account, err := Register("PC", "en-US", "", false)
|
|
// if err != nil {
|
|
// log.Fatalf("Registration failed: %v", err)
|
|
// }
|
|
func Register(model, locale, jwt string, acceptTos bool) (models.AccountData, error) {
|
|
wgKey, err := internal.GenerateRandomWgPubkey()
|
|
if err != nil {
|
|
return models.AccountData{}, fmt.Errorf("failed to generate wg key: %v", err)
|
|
}
|
|
serial, err := internal.GenerateRandomAndroidSerial()
|
|
if err != nil {
|
|
return models.AccountData{}, fmt.Errorf("failed to generate serial: %v", err)
|
|
}
|
|
|
|
if !acceptTos {
|
|
fmt.Print("You must accept the Terms of Service (https://www.cloudflare.com/application/terms/) to register. Do you agree? (y/n): ")
|
|
var response string
|
|
if _, err := fmt.Scanln(&response); err != nil {
|
|
return models.AccountData{}, fmt.Errorf("failed to read user input: %v", err)
|
|
}
|
|
if response != "y" {
|
|
return models.AccountData{}, fmt.Errorf("user did not accept TOS")
|
|
}
|
|
}
|
|
|
|
data := models.Registration{
|
|
Key: wgKey,
|
|
InstallID: "",
|
|
FcmToken: "",
|
|
Tos: internal.TimeAsCfString(time.Now()),
|
|
Model: model,
|
|
Serial: serial,
|
|
OsVersion: "",
|
|
KeyType: internal.KeyTypeWg,
|
|
TunType: internal.TunTypeWg,
|
|
Locale: locale,
|
|
}
|
|
|
|
jsonData, err := json.Marshal(data)
|
|
if err != nil {
|
|
return models.AccountData{}, fmt.Errorf("failed to marshal json: %v", err)
|
|
}
|
|
|
|
tr := &fhttp.Transport{
|
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
ips, err := net.DefaultResolver.LookupIP(ctx, "ip", "api.cloudflareclient.com")
|
|
if err != nil || len(ips) == 0 {
|
|
return nil, fmt.Errorf("DNS resolution failed for api.cloudflareclient.com")
|
|
}
|
|
return net.DialTimeout("tcp", net.JoinHostPort(ips[0].String(), "443"), 10*time.Second)
|
|
},
|
|
}
|
|
httpClient := &fhttp.Client{
|
|
Transport: tr,
|
|
Timeout: 60 * time.Second,
|
|
}
|
|
|
|
req, err := fhttp.NewRequest("POST", "https://consumer-masque.cloudflareclient.com/"+internal.ApiVersion+"/reg", bytes.NewBuffer(jsonData))
|
|
if err != nil {
|
|
return models.AccountData{}, fmt.Errorf("failed to create request: %v", err)
|
|
}
|
|
req.Host = "api.cloudflareclient.com"
|
|
|
|
for k, v := range internal.Headers {
|
|
req.Header.Set(k, v)
|
|
}
|
|
|
|
if jwt != "" {
|
|
req.Header.Set("CF-Access-Jwt-Assertion", jwt)
|
|
}
|
|
|
|
resp, err := httpClient.Do(req)
|
|
if err != nil {
|
|
return models.AccountData{}, fmt.Errorf("failed to send request: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != fhttp.StatusOK {
|
|
return models.AccountData{}, fmt.Errorf("failed to register: %v", resp.Status)
|
|
}
|
|
|
|
var accountData models.AccountData
|
|
if err := json.NewDecoder(resp.Body).Decode(&accountData); err != nil {
|
|
return models.AccountData{}, fmt.Errorf("failed to decode response: %v", err)
|
|
}
|
|
|
|
return accountData, nil
|
|
}
|
|
|
|
// EnrollKey updates an existing user account with a new MASQUE public key.
|
|
//
|
|
// This function sends a PATCH request to update the user's account with a new key.
|
|
//
|
|
// Parameters:
|
|
// - accountData: models.AccountData - The account data of the user being updated.
|
|
// - pubKey: []byte - The new MASQUE public key in binary format.
|
|
// - deviceName: string - The name of the device to enroll. (optional)
|
|
//
|
|
// Returns:
|
|
// - models.AccountData: The updated account data.
|
|
// - error: An error if the update process fails.
|
|
//
|
|
// Example:
|
|
//
|
|
// updatedAccount, apiErr, err := EnrollKey(account, pubKey, "PC")
|
|
// if err != nil {
|
|
// log.Fatalf("Key enrollment failed: %v", err)
|
|
// }
|
|
func EnrollKey(accountData models.AccountData, pubKey []byte, deviceName string) (models.AccountData, *models.APIError, error) {
|
|
deviceUpdate := models.DeviceUpdate{
|
|
Key: base64.StdEncoding.EncodeToString(pubKey),
|
|
KeyType: internal.KeyTypeMasque,
|
|
TunType: internal.TunTypeMasque,
|
|
}
|
|
|
|
if deviceName != "" {
|
|
deviceUpdate.Name = deviceName
|
|
}
|
|
|
|
jsonData, err := json.Marshal(deviceUpdate)
|
|
if err != nil {
|
|
return models.AccountData{}, nil, fmt.Errorf("failed to marshal json: %v", err)
|
|
}
|
|
|
|
tr := &fhttp.Transport{
|
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
ips, err := net.DefaultResolver.LookupIP(ctx, "ip", "api.cloudflareclient.com")
|
|
if err != nil || len(ips) == 0 {
|
|
return nil, fmt.Errorf("DNS resolution failed for api.cloudflareclient.com")
|
|
}
|
|
return net.DialTimeout("tcp", net.JoinHostPort(ips[0].String(), "443"), 10*time.Second)
|
|
},
|
|
}
|
|
httpClient := &fhttp.Client{
|
|
Transport: tr,
|
|
Timeout: 60 * time.Second,
|
|
}
|
|
|
|
req, err := fhttp.NewRequest("PATCH", "https://consumer-masque.cloudflareclient.com/"+internal.ApiVersion+"/reg/"+accountData.ID, bytes.NewBuffer(jsonData))
|
|
if err != nil {
|
|
return models.AccountData{}, nil, fmt.Errorf("failed to create request: %v", err)
|
|
}
|
|
req.Host = "api.cloudflareclient.com"
|
|
|
|
for k, v := range internal.Headers {
|
|
req.Header.Set(k, v)
|
|
}
|
|
req.Header.Set("Authorization", "Bearer "+accountData.Token)
|
|
|
|
resp, err := httpClient.Do(req)
|
|
if err != nil {
|
|
return models.AccountData{}, nil, fmt.Errorf("failed to send request: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return models.AccountData{}, nil, fmt.Errorf("failed to read response body: %v", err)
|
|
}
|
|
|
|
if resp.StatusCode != fhttp.StatusOK {
|
|
var apiErr models.APIError
|
|
if err := json.Unmarshal(body, &apiErr); err != nil {
|
|
return models.AccountData{}, nil, fmt.Errorf("failed to parse error response: %v", err)
|
|
}
|
|
return models.AccountData{}, &apiErr, fmt.Errorf("failed to update: %s", resp.Status)
|
|
}
|
|
|
|
if err := json.Unmarshal(body, &accountData); err != nil {
|
|
return models.AccountData{}, nil, fmt.Errorf("failed to decode response: %v", err)
|
|
}
|
|
|
|
return accountData, nil, nil
|
|
}
|
|
|