Monorepo for Tangled
0

Configure Feed

Select the types of activity you want to include in your feed.

core / appview / serververify / verify.go
4.5 kB 203 lines
1package serververify 2 3import ( 4 "context" 5 "errors" 6 "fmt" 7 "net" 8 "net/http" 9 "syscall" 10 "time" 11 12 indigoxrpc "github.com/bluesky-social/indigo/xrpc" 13 "tangled.org/core/api/tangled" 14 "tangled.org/core/appview/db" 15 "tangled.org/core/orm" 16 "tangled.org/core/rbac" 17 "tangled.org/core/xrpc/xrpcclient" 18) 19 20var ( 21 FetchError = errors.New("failed to fetch owner") 22) 23 24const verifyTimeout = 10 * time.Second 25 26// fetchOwner fetches the owner DID from a server's /owner endpoint 27func fetchOwner(ctx context.Context, domain string, dev bool) (string, error) { 28 scheme := "https" 29 if dev { 30 scheme = "http" 31 } 32 33 host := fmt.Sprintf("%s://%s", scheme, domain) 34 transport := &http.Transport{ 35 DialContext: safeDialer(dev).DialContext, 36 } 37 xrpcc := &indigoxrpc.Client{ 38 Host: host, 39 Client: &http.Client{ 40 Timeout: verifyTimeout, 41 Transport: transport, 42 }, 43 } 44 45 res, err := tangled.Owner(ctx, xrpcc) 46 if err != nil { 47 var xrpcerr *indigoxrpc.Error 48 if !errors.As(err, &xrpcerr) { 49 return "", err 50 } 51 if handled := xrpcclient.HandleXrpcErr(err); handled != nil { 52 return "", handled 53 } 54 } 55 56 return res.Owner, nil 57} 58 59type OwnerMismatch struct { 60 expected string 61 observed string 62} 63 64func (e *OwnerMismatch) Error() string { 65 return fmt.Sprintf("owner mismatch: %q != %q", e.expected, e.observed) 66} 67 68// RunVerification verifies that the server at the given domain has the expected owner 69func RunVerification(ctx context.Context, domain, expectedOwner string, dev bool) error { 70 observedOwner, err := fetchOwner(ctx, domain, dev) 71 if err != nil { 72 return err 73 } 74 75 if observedOwner != expectedOwner { 76 return &OwnerMismatch{ 77 expected: expectedOwner, 78 observed: observedOwner, 79 } 80 } 81 82 return nil 83} 84 85// MarkSpindleVerified marks a spindle as verified in the DB and adds the user as its owner 86func MarkSpindleVerified(d *db.DB, e *rbac.Enforcer, instance, owner string) (int64, error) { 87 tx, err := d.Begin() 88 if err != nil { 89 return 0, fmt.Errorf("failed to create txn: %w", err) 90 } 91 committed := false 92 defer func() { 93 if committed { 94 return 95 } 96 tx.Rollback() 97 e.E.LoadPolicy() 98 }() 99 100 // mark this spindle as verified in the db 101 rowId, err := db.VerifySpindle( 102 tx, 103 orm.FilterEq("owner", owner), 104 orm.FilterEq("instance", instance), 105 ) 106 if err != nil { 107 return 0, fmt.Errorf("failed to write to DB: %w", err) 108 } 109 110 err = e.AddSpindleOwner(instance, owner) 111 if err != nil { 112 return 0, fmt.Errorf("failed to update ACL: %w", err) 113 } 114 115 err = tx.Commit() 116 if err != nil { 117 return 0, fmt.Errorf("failed to commit txn: %w", err) 118 } 119 120 err = e.E.SavePolicy() 121 if err != nil { 122 return 0, fmt.Errorf("failed to update ACL: %w", err) 123 } 124 committed = true 125 126 return rowId, nil 127} 128 129// MarkKnotVerified marks a knot as verified and sets up ownership/permissions 130func MarkKnotVerified(d *db.DB, e *rbac.Enforcer, domain, owner string) error { 131 tx, err := d.BeginTx(context.Background(), nil) 132 if err != nil { 133 return fmt.Errorf("failed to start tx: %w", err) 134 } 135 committed := false 136 defer func() { 137 if committed { 138 return 139 } 140 tx.Rollback() 141 e.E.LoadPolicy() 142 }() 143 144 // mark as registered 145 err = db.MarkRegistered( 146 tx, 147 orm.FilterEq("did", owner), 148 orm.FilterEq("domain", domain), 149 ) 150 if err != nil { 151 return fmt.Errorf("failed to register domain: %w", err) 152 } 153 154 // add basic acls for this domain 155 err = e.AddKnot(domain) 156 if err != nil { 157 return fmt.Errorf("failed to add knot to enforcer: %w", err) 158 } 159 160 // add this did as owner of this domain 161 err = e.AddKnotOwner(domain, owner) 162 if err != nil { 163 return fmt.Errorf("failed to add knot owner to enforcer: %w", err) 164 } 165 166 err = tx.Commit() 167 if err != nil { 168 return fmt.Errorf("failed to commit changes: %w", err) 169 } 170 171 err = e.E.SavePolicy() 172 if err != nil { 173 return fmt.Errorf("failed to update ACLs: %w", err) 174 } 175 committed = true 176 177 return nil 178} 179func safeDialer(dev bool) *net.Dialer { 180 d := &net.Dialer{ 181 Timeout: 5 * time.Second, 182 KeepAlive: 30 * time.Second, 183 } 184 if dev { 185 return d 186 } 187 d.Control = func(network, address string, _ syscall.RawConn) error { 188 host, _, err := net.SplitHostPort(address) 189 if err != nil { 190 return fmt.Errorf("invalid dial address %q: %w", address, err) 191 } 192 ip := net.ParseIP(host) 193 if ip == nil { 194 return fmt.Errorf("dial address %q did not resolve to IP", address) 195 } 196 if ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() || 197 ip.IsLinkLocalMulticast() || ip.IsMulticast() || ip.IsUnspecified() { 198 return fmt.Errorf("refusing to dial %s: reserved or private address", ip) 199 } 200 return nil 201 } 202 return d 203}