package verify import ( "bytes" "context" "crypto/rand" "encoding/json" "fmt" "io" "log" "math/big" "net/http" ) // Client handles verification with the hub type Client struct { verifyURL string apiKey string client *http.Client verbose bool } // NewClient creates a new verify client func NewClient(verifyURL, apiKey string, verbose bool) *Client { return &Client{ verifyURL: verifyURL, apiKey: apiKey, client: &http.Client{}, verbose: verbose, } } // VerifyRequest represents the request to the verify endpoint type VerifyRequest struct { APIKey string `json:"apikey"` Task string `json:"task"` Answer VerifyAnswer `json:"answer"` } // VerifyAnswer represents the answer part of verify request type VerifyAnswer struct { URL string `json:"url"` SessionID string `json:"sessionID"` } // VerifyResponse represents the response from verify endpoint type VerifyResponse struct { Code int `json:"code"` Message string `json:"message"` Error string `json:"error,omitempty"` } // Register registers the proxy with the hub func (c *Client) Register(ctx context.Context, tunnelURL, sessionID string) error { reqBody := VerifyRequest{ APIKey: c.apiKey, Task: "proxy", Answer: VerifyAnswer{ URL: tunnelURL, SessionID: sessionID, }, } jsonData, err := json.Marshal(reqBody) if err != nil { return fmt.Errorf("marshaling request: %w", err) } if c.verbose { var prettyJSON bytes.Buffer if err := json.Indent(&prettyJSON, jsonData, "", " "); err == nil { log.Printf("\n========== VERIFY REQUEST ==========\nURL: %s\nBody:\n%s\n====================================\n", c.verifyURL, prettyJSON.String()) } } req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.verifyURL, bytes.NewBuffer(jsonData)) if err != nil { return fmt.Errorf("creating request: %w", err) } req.Header.Set("Content-Type", "application/json") resp, err := c.client.Do(req) if err != nil { return fmt.Errorf("sending request: %w", err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return fmt.Errorf("reading response: %w", err) } if c.verbose { var prettyJSON bytes.Buffer if err := json.Indent(&prettyJSON, body, "", " "); err == nil { log.Printf("\n========== VERIFY RESPONSE ==========\nStatus: %d\nBody:\n%s\n=====================================\n", resp.StatusCode, prettyJSON.String()) } } var apiResp VerifyResponse if err := json.Unmarshal(body, &apiResp); err != nil { return fmt.Errorf("unmarshaling response: %w (body: %s)", err, string(body)) } if apiResp.Error != "" { return fmt.Errorf("API error: %s", apiResp.Error) } return nil } // GenerateSessionID generates a random alphanumeric session ID func GenerateSessionID(length int) (string, error) { const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" result := make([]byte, length) for i := range result { num, err := rand.Int(rand.Reader, big.NewInt(int64(len(charset)))) if err != nil { return "", fmt.Errorf("generating random number: %w", err) } result[i] = charset[num.Int64()] } return string(result), nil }