1package spindleverify
2
3import (
4 "context"
5 "errors"
6 "fmt"
7 "io"
8 "net/http"
9 "strings"
10 "time"
11
12 "tangled.sh/tangled.sh/core/appview/db"
13 "tangled.sh/tangled.sh/core/rbac"
14)
15
16var (
17 FetchError = errors.New("failed to fetch owner")
18)
19
20// TODO: move this to "spindleclient" or similar
21func fetchOwner(ctx context.Context, domain string, dev bool) (string, error) {
22 scheme := "https"
23 if dev {
24 scheme = "http"
25 }
26
27 url := fmt.Sprintf("%s://%s/owner", scheme, domain)
28 req, err := http.NewRequest("GET", url, nil)
29 if err != nil {
30 return "", err
31 }
32
33 client := &http.Client{
34 Timeout: 1 * time.Second,
35 }
36
37 resp, err := client.Do(req.WithContext(ctx))
38 if err != nil || resp.StatusCode != 200 {
39 return "", fmt.Errorf("failed to fetch /owner")
40 }
41
42 body, err := io.ReadAll(io.LimitReader(resp.Body, 1024)) // read atmost 1kb of data
43 if err != nil {
44 return "", fmt.Errorf("failed to read /owner response: %w", err)
45 }
46
47 did := strings.TrimSpace(string(body))
48 if did == "" {
49 return "", fmt.Errorf("empty DID in /owner response")
50 }
51
52 return did, nil
53}
54
55type OwnerMismatch struct {
56 expected string
57 observed string
58}
59
60func (e *OwnerMismatch) Error() string {
61 return fmt.Sprintf("owner mismatch: %q != %q", e.expected, e.observed)
62}
63
64func RunVerification(ctx context.Context, instance, expectedOwner string, dev bool) error {
65 // begin verification
66 observedOwner, err := fetchOwner(ctx, instance, dev)
67 if err != nil {
68 return fmt.Errorf("%w: %w", FetchError, err)
69 }
70
71 if observedOwner != expectedOwner {
72 return &OwnerMismatch{
73 expected: expectedOwner,
74 observed: observedOwner,
75 }
76 }
77
78 return nil
79}
80
81// mark this spindle as verified in the DB and add this user as its owner
82func MarkVerified(d *db.DB, e *rbac.Enforcer, instance, owner string) (int64, error) {
83 tx, err := d.Begin()
84 if err != nil {
85 return 0, fmt.Errorf("failed to create txn: %w", err)
86 }
87 defer func() {
88 tx.Rollback()
89 e.E.LoadPolicy()
90 }()
91
92 // mark this spindle as verified in the db
93 rowId, err := db.VerifySpindle(
94 tx,
95 db.FilterEq("owner", owner),
96 db.FilterEq("instance", instance),
97 )
98 if err != nil {
99 return 0, fmt.Errorf("failed to write to DB: %w", err)
100 }
101
102 err = e.AddSpindleOwner(instance, owner)
103 if err != nil {
104 return 0, fmt.Errorf("failed to update ACL: %w", err)
105 }
106
107 err = tx.Commit()
108 if err != nil {
109 return 0, fmt.Errorf("failed to commit txn: %w", err)
110 }
111
112 err = e.E.SavePolicy()
113 if err != nil {
114 return 0, fmt.Errorf("failed to update ACL: %w", err)
115 }
116
117 return rowId, nil
118}