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