telebit/tunnel/discover.go

104 lines
2.4 KiB
Go

package tunnel
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"os"
"strings"
)
// Endpoints represents the endpoints listed in the API service directory.
// Ideally the Relay URL is discoverable and will provide the authn endpoint,
// which will provide the tunnel endpoint. However, for the sake of testing,
// these things may happen out-of-order.
type Endpoints struct {
ToS string `json:"terms_of_service"`
APIHost string `json:"api_host"`
Tunnel Endpoint `json:"tunnel"`
Authenticate Endpoint `json:"authn"`
/*
{
"terms_of_service": ":hostname/tos/",
"api_host": ":hostname/api",
"pair_request": {
"method": "POST",
"pathname": "api/telebit.app/pair_request"
}
}
*/
}
// Endpoint represents a URL Request
type Endpoint struct {
URL string `json:"-"`
Method string `json:"method"`
Scheme string `json:"scheme"`
Host string `json:"host"`
Pathname string `json:"pathname"`
}
// Discover checks the .well-known directory for service endpoints
func Discover(relay string) (*Endpoints, error) {
directives := &Endpoints{}
relayURL, err := url.Parse(relay)
if nil != err {
fmt.Fprintf(os.Stderr, "Error: invalid Tunnel Relay URL %q: %s\n", relay, err)
os.Exit(1)
}
if "ws" != relayURL.Scheme[:2] {
if '/' != relayURL.Path[len(relayURL.Path)-1] {
relayURL.Path += "/"
}
resp, err := http.Get(relayURL.String() + ".well-known/telebit.app/index.json")
if nil != err {
fmt.Fprintf(os.Stderr, "Error: invalid Tunnel Relay URL %q: %s\n", relay, err)
os.Exit(1)
}
b, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if nil != err {
return nil, err
}
body := strings.Replace(string(b), ":hostname", relayURL.Host, -1)
err = json.Unmarshal([]byte(body), directives)
if nil != err {
return nil, err
}
directives.Tunnel.URL = endpointToURLString(directives.APIHost, directives.Tunnel)
} else {
directives.Tunnel.URL = relayURL.String()
directives.APIHost = relayURL.Host
}
directives.Authenticate.URL = endpointToURLString(directives.APIHost, directives.Authenticate)
return directives, nil
}
func endpointToURLString(apiHost string, endpoint Endpoint) string {
pathname := endpoint.Pathname
if "" == pathname {
return ""
}
host := endpoint.Host
if "" == host {
host = apiHost
}
scheme := endpoint.Scheme
if "" == scheme {
scheme = "https:"
}
return fmt.Sprintf("%s//%s/%s", scheme, host, pathname)
}