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