tinyrelayfile
tinyrelay/internal/daemon/private_peer.go/

internal/daemon/private_peer.go raw
1package daemon
2
3import (
4 "context"
5 "encoding/base64"
6 "encoding/json"
7 "fmt"
8 "io"
9 "net"
10 "net/http"
11 "net/url"
12 "strings"
13
14 "github.com/FelineStateMachine/tinyrelay/internal/policy"
15 "github.com/FelineStateMachine/tinyrelay/protocol/auth"
16)
17
18func (t *Tenant) privateHTTPAuth(ctx context.Context, method, rawURL, payloadHash string) (string, error) {
19 _ = method
20 _ = payloadHash
21 if t == nil || t.records == nil || !t.PrivateServiceEnabled() {
22 return "", fmt.Errorf("private peer: relay identity unavailable")
23 }
24 if policy.PrivatePeerBase(rawURL, t.Policy().PrivatePeers) == "" {
25 return "", fmt.Errorf("private peer: target is no longer configured")
26 }
27 root, ok := auth.GRASP08RepositoryRoot(rawURL)
28 if !ok {
29 return "", fmt.Errorf("private peer: target is not a Git repository URL")
30 }
31 proof, err := t.records.SignNIP98(ctx, http.MethodGet, root, "", 0)
32 if err != nil {
33 return "", err
34 }
35 raw, err := json.Marshal(proof)
36 if err != nil {
37 return "", err
38 }
39 return "Nostr " + base64.StdEncoding.EncodeToString(raw), nil
40}
41
42func privatePeerMatch(source string, peers []string) bool {
43 return policy.PrivatePeerBase(source, peers) != ""
44}
45
46func (t *Tenant) privatePeerReady(ctx context.Context, target string) bool {
47 if !t.PrivateServiceEnabled() || policy.PrivatePeerBase(target, t.Policy().PrivatePeers) == "" {
48 return false
49 }
50 u, err := url.Parse(target)
51 if err != nil {
52 return false
53 }
54 if u.Scheme == "ws" {
55 u.Scheme = "http"
56 } else if u.Scheme == "wss" {
57 u.Scheme = "https"
58 }
59 req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
60 if err != nil {
61 return false
62 }
63 req.Header.Set("Accept", "application/nostr+json")
64 res, err := privatePeerHTTPClient(t.app.cfg.AllowPrivateRelays).Do(req)
65 if err != nil {
66 return false
67 }
68 defer res.Body.Close()
69 if res.StatusCode < 200 || res.StatusCode >= 300 {
70 return false
71 }
72 var info struct {
73 Supported []string `json:"supported_grasps"`
74 Private bool `json:"private_service"`
75 }
76 if err := json.NewDecoder(io.LimitReader(res.Body, 1<<20)).Decode(&info); err != nil {
77 return false
78 }
79 for _, profile := range info.Supported {
80 if strings.EqualFold(profile, "GRASP-08") {
81 return true
82 }
83 }
84 return false
85}
86
87func privatePeerHTTPClient(allowPrivate bool) *http.Client {
88 transport := http.DefaultTransport.(*http.Transport).Clone()
89 transport.Proxy = nil
90 transport.DialContext = func(ctx context.Context, network, address string) (net.Conn, error) {
91 host, port, err := net.SplitHostPort(address)
92 if err != nil {
93 return nil, err
94 }
95 ips, err := net.DefaultResolver.LookupIP(ctx, "ip", host)
96 if err != nil {
97 return nil, err
98 }
99 for _, ip := range ips {
100 if !allowPrivate && (ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() || ip.IsUnspecified()) {
101 continue
102 }
103 conn, dialErr := (&net.Dialer{}).DialContext(ctx, network, net.JoinHostPort(ip.String(), port))
104 if dialErr == nil {
105 return conn, nil
106 }
107 }
108 return nil, fmt.Errorf("private peer: no usable address for %s", host)
109 }
110 return &http.Client{Transport: transport, CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}
111}

About

ownerc01503e8035f
visibilitypublic
headd730679639bf

Maintainers

  • c01503e8035f owner
  • ba1b5beed5b5 agent hermes

Clone

  • https://012.run/npub1cq2s86qrtadf36eqq3epm38wmhdynrrcwxf84eldk3f94dkv7ttsrulrz5/tinyrelay.git

Refs