+3
-3
appview/ingester.go
+3
-3
appview/ingester.go
···
14
"tangled.sh/tangled.sh/core/api/tangled"
15
"tangled.sh/tangled.sh/core/appview/config"
16
"tangled.sh/tangled.sh/core/appview/db"
17
-
"tangled.sh/tangled.sh/core/appview/spindleverify"
18
"tangled.sh/tangled.sh/core/idresolver"
19
"tangled.sh/tangled.sh/core/rbac"
20
)
···
475
return err
476
}
477
478
-
err = spindleverify.RunVerification(context.Background(), instance, did, i.Config.Core.Dev)
479
if err != nil {
480
l.Error("failed to add spindle to db", "err", err, "instance", instance)
481
return err
482
}
483
484
-
_, err = spindleverify.MarkVerified(ddb, i.Enforcer, instance, did)
485
if err != nil {
486
return fmt.Errorf("failed to mark verified: %w", err)
487
}
···
14
"tangled.sh/tangled.sh/core/api/tangled"
15
"tangled.sh/tangled.sh/core/appview/config"
16
"tangled.sh/tangled.sh/core/appview/db"
17
+
"tangled.sh/tangled.sh/core/appview/serververify"
18
"tangled.sh/tangled.sh/core/idresolver"
19
"tangled.sh/tangled.sh/core/rbac"
20
)
···
475
return err
476
}
477
478
+
err = serververify.RunVerification(context.Background(), instance, did, i.Config.Core.Dev)
479
if err != nil {
480
l.Error("failed to add spindle to db", "err", err, "instance", instance)
481
return err
482
}
483
484
+
_, err = serververify.MarkSpindleVerified(ddb, i.Enforcer, instance, did)
485
if err != nil {
486
return fmt.Errorf("failed to mark verified: %w", err)
487
}
+420
-217
appview/knots/knots.go
+420
-217
appview/knots/knots.go
···
1
package knots
2
3
import (
4
-
"context"
5
-
"crypto/hmac"
6
-
"crypto/sha256"
7
-
"encoding/hex"
8
"fmt"
9
"log/slog"
10
"net/http"
11
-
"strings"
12
"time"
13
14
"github.com/go-chi/chi/v5"
···
18
"tangled.sh/tangled.sh/core/appview/middleware"
19
"tangled.sh/tangled.sh/core/appview/oauth"
20
"tangled.sh/tangled.sh/core/appview/pages"
21
"tangled.sh/tangled.sh/core/eventconsumer"
22
"tangled.sh/tangled.sh/core/idresolver"
23
-
"tangled.sh/tangled.sh/core/knotclient"
24
"tangled.sh/tangled.sh/core/rbac"
25
"tangled.sh/tangled.sh/core/tid"
26
···
39
Knotstream *eventconsumer.Consumer
40
}
41
42
-
func (k *Knots) Router(mw *middleware.Middleware) http.Handler {
43
r := chi.NewRouter()
44
45
-
r.Use(middleware.AuthMiddleware(k.OAuth))
46
47
-
r.Get("/", k.index)
48
-
r.Post("/key", k.generateKey)
49
50
-
r.Route("/{domain}", func(r chi.Router) {
51
-
r.Post("/init", k.init)
52
-
r.Get("/", k.dashboard)
53
-
r.Route("/member", func(r chi.Router) {
54
-
r.Use(mw.KnotOwner())
55
-
r.Get("/", k.members)
56
-
r.Put("/", k.addMember)
57
-
r.Delete("/", k.removeMember)
58
-
})
59
-
})
60
61
return r
62
}
63
64
-
// get knots registered by this user
65
-
func (k *Knots) index(w http.ResponseWriter, r *http.Request) {
66
-
l := k.Logger.With("handler", "index")
67
-
68
user := k.OAuth.GetUser(r)
69
registrations, err := db.RegistrationsByDid(k.Db, user.Did)
70
if err != nil {
71
-
l.Error("failed to get registrations by did", "err", err)
72
}
73
74
k.Pages.Knots(w, pages.KnotsParams{
···
77
})
78
}
79
80
-
// requires auth
81
-
func (k *Knots) generateKey(w http.ResponseWriter, r *http.Request) {
82
-
l := k.Logger.With("handler", "generateKey")
83
84
user := k.OAuth.GetUser(r)
85
-
did := user.Did
86
-
l = l.With("did", did)
87
88
-
// check if domain is valid url, and strip extra bits down to just host
89
-
domain := r.FormValue("domain")
90
if domain == "" {
91
-
l.Error("empty domain")
92
-
http.Error(w, "Invalid form", http.StatusBadRequest)
93
return
94
}
95
l = l.With("domain", domain)
96
97
-
noticeId := "registration-error"
98
-
fail := func() {
99
-
k.Pages.Notice(w, noticeId, "Failed to generate registration key.")
100
}
101
102
-
key, err := db.GenerateRegistrationKey(k.Db, domain, did)
103
if err != nil {
104
-
l.Error("failed to generate registration key", "err", err)
105
-
fail()
106
return
107
}
108
109
-
allRegs, err := db.RegistrationsByDid(k.Db, did)
110
if err != nil {
111
-
l.Error("failed to generate registration key", "err", err)
112
-
fail()
113
return
114
}
115
116
-
k.Pages.KnotListingFull(w, pages.KnotListingFullParams{
117
-
Registrations: allRegs,
118
-
})
119
-
k.Pages.KnotSecret(w, pages.KnotSecretParams{
120
-
Secret: key,
121
})
122
}
123
124
-
// create a signed request and check if a node responds to that
125
-
func (k *Knots) init(w http.ResponseWriter, r *http.Request) {
126
-
l := k.Logger.With("handler", "init")
127
user := k.OAuth.GetUser(r)
128
129
-
noticeId := "operation-error"
130
-
defaultErr := "Failed to initialize knot. Try again later."
131
fail := func() {
132
k.Pages.Notice(w, noticeId, defaultErr)
133
}
134
135
-
domain := chi.URLParam(r, "domain")
136
if domain == "" {
137
-
http.Error(w, "malformed url", http.StatusBadRequest)
138
return
139
}
140
l = l.With("domain", domain)
141
142
-
l.Info("checking domain")
143
144
-
registration, err := db.RegistrationByDomain(k.Db, domain)
145
if err != nil {
146
-
l.Error("failed to get registration for domain", "err", err)
147
fail()
148
return
149
}
150
-
if registration.ByDid != user.Did {
151
-
l.Error("unauthorized", "wantedDid", registration.ByDid, "gotDid", user.Did)
152
-
w.WriteHeader(http.StatusUnauthorized)
153
return
154
}
155
156
-
secret, err := db.GetRegistrationKey(k.Db, domain)
157
if err != nil {
158
-
l.Error("failed to get registration key for domain", "err", err)
159
fail()
160
return
161
}
162
163
-
client, err := knotclient.NewSignedClient(domain, secret, k.Config.Core.Dev)
164
if err != nil {
165
-
l.Error("failed to create knotclient", "err", err)
166
fail()
167
return
168
}
169
170
-
resp, err := client.Init(user.Did)
171
if err != nil {
172
-
k.Pages.Notice(w, noticeId, fmt.Sprintf("Failed to make request: %s", err.Error()))
173
-
l.Error("failed to make init request", "err", err)
174
return
175
}
176
177
-
if resp.StatusCode == http.StatusConflict {
178
-
k.Pages.Notice(w, noticeId, "This knot is already registered")
179
-
l.Error("knot already registered", "statuscode", resp.StatusCode)
180
return
181
}
182
183
-
if resp.StatusCode != http.StatusNoContent {
184
-
k.Pages.Notice(w, noticeId, fmt.Sprintf("Received status %d from knot, expected %d", resp.StatusCode, http.StatusNoContent))
185
-
l.Error("incorrect statuscode returned", "statuscode", resp.StatusCode, "expected", http.StatusNoContent)
186
return
187
}
188
189
-
// verify response mac
190
-
signature := resp.Header.Get("X-Signature")
191
-
signatureBytes, err := hex.DecodeString(signature)
192
if err != nil {
193
return
194
}
195
196
-
expectedMac := hmac.New(sha256.New, []byte(secret))
197
-
expectedMac.Write([]byte("ok"))
198
199
-
if !hmac.Equal(expectedMac.Sum(nil), signatureBytes) {
200
-
k.Pages.Notice(w, noticeId, "Response signature mismatch, consider regenerating the secret and retrying.")
201
-
l.Error("signature mismatch", "bytes", signatureBytes)
202
return
203
}
204
205
-
tx, err := k.Db.BeginTx(r.Context(), nil)
206
if err != nil {
207
-
l.Error("failed to start tx", "err", err)
208
fail()
209
return
210
}
211
defer func() {
212
tx.Rollback()
213
-
err = k.Enforcer.E.LoadPolicy()
214
-
if err != nil {
215
-
l.Error("rollback failed", "err", err)
216
-
}
217
}()
218
219
-
// mark as registered
220
-
err = db.Register(tx, domain)
221
if err != nil {
222
-
l.Error("failed to register domain", "err", err)
223
fail()
224
return
225
}
226
227
-
// set permissions for this did as owner
228
-
reg, err := db.RegistrationByDomain(tx, domain)
229
-
if err != nil {
230
-
l.Error("failed get registration by domain", "err", err)
231
-
fail()
232
-
return
233
}
234
235
-
// add basic acls for this domain
236
-
err = k.Enforcer.AddKnot(domain)
237
if err != nil {
238
-
l.Error("failed to add knot to enforcer", "err", err)
239
fail()
240
return
241
}
242
243
-
// add this did as owner of this domain
244
-
err = k.Enforcer.AddKnotOwner(domain, reg.ByDid)
245
if err != nil {
246
-
l.Error("failed to add knot owner to enforcer", "err", err)
247
-
fail()
248
-
return
249
}
250
251
err = tx.Commit()
252
if err != nil {
253
-
l.Error("failed to commit changes", "err", err)
254
fail()
255
return
256
}
257
258
err = k.Enforcer.E.SavePolicy()
259
if err != nil {
260
-
l.Error("failed to update ACLs", "err", err)
261
-
fail()
262
return
263
}
264
265
-
// add this knot to knotstream
266
-
go k.Knotstream.AddSource(
267
-
context.Background(),
268
-
eventconsumer.NewKnotSource(domain),
269
-
)
270
271
-
k.Pages.KnotListing(w, pages.KnotListingParams{
272
-
Registration: *reg,
273
-
})
274
}
275
276
-
func (k *Knots) dashboard(w http.ResponseWriter, r *http.Request) {
277
-
l := k.Logger.With("handler", "dashboard")
278
fail := func() {
279
-
w.WriteHeader(http.StatusInternalServerError)
280
}
281
282
domain := chi.URLParam(r, "domain")
283
if domain == "" {
284
-
http.Error(w, "malformed url", http.StatusBadRequest)
285
return
286
}
287
l = l.With("domain", domain)
288
289
-
user := k.OAuth.GetUser(r)
290
-
l = l.With("did", user.Did)
291
-
292
-
// dashboard is only available to owners
293
-
ok, err := k.Enforcer.IsKnotOwner(user.Did, domain)
294
if err != nil {
295
-
l.Error("failed to query enforcer", "err", err)
296
fail()
297
}
298
-
if !ok {
299
-
http.Error(w, "only owners can view dashboards", http.StatusUnauthorized)
300
return
301
}
302
303
-
reg, err := db.RegistrationByDomain(k.Db, domain)
304
if err != nil {
305
-
l.Error("failed to get registration by domain", "err", err)
306
fail()
307
return
308
}
309
310
-
var members []string
311
-
if reg.Registered != nil {
312
-
members, err = k.Enforcer.GetUserByRole("server:member", domain)
313
if err != nil {
314
-
l.Error("failed to get members list", "err", err)
315
fail()
316
return
317
}
318
}
319
320
-
repos, err := db.GetRepos(
321
k.Db,
322
-
0,
323
-
db.FilterEq("knot", domain),
324
-
db.FilterIn("did", members),
325
)
326
if err != nil {
327
-
l.Error("failed to get repos list", "err", err)
328
fail()
329
return
330
}
331
-
// convert to map
332
-
repoByMember := make(map[string][]db.Repo)
333
-
for _, r := range repos {
334
-
repoByMember[r.Did] = append(repoByMember[r.Did], r)
335
-
}
336
-
337
-
k.Pages.Knot(w, pages.KnotParams{
338
-
LoggedInUser: user,
339
-
Registration: reg,
340
-
Members: members,
341
-
Repos: repoByMember,
342
-
IsOwner: true,
343
-
})
344
-
}
345
-
346
-
// list members of domain, requires auth and requires owner status
347
-
func (k *Knots) members(w http.ResponseWriter, r *http.Request) {
348
-
l := k.Logger.With("handler", "members")
349
-
350
-
domain := chi.URLParam(r, "domain")
351
-
if domain == "" {
352
-
http.Error(w, "malformed url", http.StatusBadRequest)
353
return
354
}
355
-
l = l.With("domain", domain)
356
357
-
// list all members for this domain
358
-
memberDids, err := k.Enforcer.GetUserByRole("server:member", domain)
359
-
if err != nil {
360
-
w.Write([]byte("failed to fetch member list"))
361
-
return
362
-
}
363
364
-
w.Write([]byte(strings.Join(memberDids, "\n")))
365
}
366
367
-
// add member to domain, requires auth and requires invite access
368
func (k *Knots) addMember(w http.ResponseWriter, r *http.Request) {
369
-
l := k.Logger.With("handler", "members")
370
371
domain := chi.URLParam(r, "domain")
372
if domain == "" {
373
-
http.Error(w, "malformed url", http.StatusBadRequest)
374
return
375
}
376
l = l.With("domain", domain)
377
378
-
reg, err := db.RegistrationByDomain(k.Db, domain)
379
if err != nil {
380
-
l.Error("failed to get registration by domain", "err", err)
381
-
http.Error(w, "malformed url", http.StatusBadRequest)
382
return
383
}
384
385
-
noticeId := fmt.Sprintf("add-member-error-%d", reg.Id)
386
-
l = l.With("notice-id", noticeId)
387
defaultErr := "Failed to add member. Try again later."
388
fail := func() {
389
k.Pages.Notice(w, noticeId, defaultErr)
390
}
391
392
-
subjectIdentifier := r.FormValue("subject")
393
-
if subjectIdentifier == "" {
394
-
http.Error(w, "malformed form", http.StatusBadRequest)
395
return
396
}
397
-
l = l.With("subjectIdentifier", subjectIdentifier)
398
399
-
subjectIdentity, err := k.IdResolver.ResolveIdent(r.Context(), subjectIdentifier)
400
if err != nil {
401
-
l.Error("failed to resolve identity", "err", err)
402
k.Pages.Notice(w, noticeId, "Failed to add member, identity resolution failed.")
403
return
404
}
405
-
l = l.With("subjectDid", subjectIdentity.DID)
406
-
407
-
l.Info("adding member to knot")
408
409
-
// announce this relation into the firehose, store into owners' pds
410
client, err := k.OAuth.AuthorizedClient(r)
411
if err != nil {
412
-
l.Error("failed to create client", "err", err)
413
fail()
414
return
415
}
416
417
-
currentUser := k.OAuth.GetUser(r)
418
-
createdAt := time.Now().Format(time.RFC3339)
419
-
resp, err := client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{
420
Collection: tangled.KnotMemberNSID,
421
-
Repo: currentUser.Did,
422
-
Rkey: tid.TID(),
423
Record: &lexutil.LexiconTypeDecoder{
424
Val: &tangled.KnotMember{
425
-
Subject: subjectIdentity.DID.String(),
426
Domain: domain,
427
-
CreatedAt: createdAt,
428
-
}},
429
})
430
-
// invalid record
431
if err != nil {
432
-
l.Error("failed to write to PDS", "err", err)
433
fail()
434
return
435
}
436
-
l = l.With("at-uri", resp.Uri)
437
-
l.Info("wrote record to PDS")
438
439
-
secret, err := db.GetRegistrationKey(k.Db, domain)
440
if err != nil {
441
-
l.Error("failed to get registration key", "err", err)
442
fail()
443
return
444
}
445
446
-
ksClient, err := knotclient.NewSignedClient(domain, secret, k.Config.Core.Dev)
447
if err != nil {
448
-
l.Error("failed to create client", "err", err)
449
-
fail()
450
return
451
}
452
453
-
ksResp, err := ksClient.AddMember(subjectIdentity.DID.String())
454
if err != nil {
455
-
l.Error("failed to reach knotserver", "err", err)
456
-
k.Pages.Notice(w, noticeId, "Failed to reach to knotserver.")
457
return
458
}
459
460
-
if ksResp.StatusCode != http.StatusNoContent {
461
-
l.Error("status mismatch", "got", ksResp.StatusCode, "expected", http.StatusNoContent)
462
-
k.Pages.Notice(w, noticeId, fmt.Sprintf("Unexpected status code from knotserver %d, expected %d", ksResp.StatusCode, http.StatusNoContent))
463
return
464
}
465
466
-
err = k.Enforcer.AddKnotMember(domain, subjectIdentity.DID.String())
467
if err != nil {
468
-
l.Error("failed to add member to enforcer", "err", err)
469
fail()
470
return
471
}
472
473
-
// success
474
-
k.Pages.HxRedirect(w, fmt.Sprintf("/knots/%s", domain))
475
-
}
476
477
-
func (k *Knots) removeMember(w http.ResponseWriter, r *http.Request) {
478
}
···
1
package knots
2
3
import (
4
+
"errors"
5
"fmt"
6
"log/slog"
7
"net/http"
8
+
"slices"
9
"time"
10
11
"github.com/go-chi/chi/v5"
···
15
"tangled.sh/tangled.sh/core/appview/middleware"
16
"tangled.sh/tangled.sh/core/appview/oauth"
17
"tangled.sh/tangled.sh/core/appview/pages"
18
+
"tangled.sh/tangled.sh/core/appview/serververify"
19
"tangled.sh/tangled.sh/core/eventconsumer"
20
"tangled.sh/tangled.sh/core/idresolver"
21
"tangled.sh/tangled.sh/core/rbac"
22
"tangled.sh/tangled.sh/core/tid"
23
···
36
Knotstream *eventconsumer.Consumer
37
}
38
39
+
func (k *Knots) Router() http.Handler {
40
r := chi.NewRouter()
41
42
+
r.With(middleware.AuthMiddleware(k.OAuth)).Get("/", k.knots)
43
+
r.With(middleware.AuthMiddleware(k.OAuth)).Post("/register", k.register)
44
45
+
r.With(middleware.AuthMiddleware(k.OAuth)).Get("/{domain}", k.dashboard)
46
+
r.With(middleware.AuthMiddleware(k.OAuth)).Delete("/{domain}", k.delete)
47
48
+
r.With(middleware.AuthMiddleware(k.OAuth)).Post("/{domain}/retry", k.retry)
49
+
r.With(middleware.AuthMiddleware(k.OAuth)).Post("/{domain}/add", k.addMember)
50
+
r.With(middleware.AuthMiddleware(k.OAuth)).Post("/{domain}/remove", k.removeMember)
51
52
return r
53
}
54
55
+
func (k *Knots) knots(w http.ResponseWriter, r *http.Request) {
56
user := k.OAuth.GetUser(r)
57
registrations, err := db.RegistrationsByDid(k.Db, user.Did)
58
if err != nil {
59
+
k.Logger.Error("failed to fetch knot registrations", "err", err)
60
+
w.WriteHeader(http.StatusInternalServerError)
61
+
return
62
}
63
64
k.Pages.Knots(w, pages.KnotsParams{
···
67
})
68
}
69
70
+
func (k *Knots) dashboard(w http.ResponseWriter, r *http.Request) {
71
+
l := k.Logger.With("handler", "dashboard")
72
73
user := k.OAuth.GetUser(r)
74
+
l = l.With("user", user.Did)
75
76
+
domain := chi.URLParam(r, "domain")
77
if domain == "" {
78
return
79
}
80
l = l.With("domain", domain)
81
82
+
registrations, err := db.GetRegistrations(
83
+
k.Db,
84
+
db.FilterEq("did", user.Did),
85
+
db.FilterEq("domain", domain),
86
+
)
87
+
if err != nil {
88
+
l.Error("failed to get registrations", "err", err)
89
+
http.Error(w, "Not found", http.StatusNotFound)
90
+
return
91
}
92
93
+
// Find the specific registration for this domain
94
+
var registration *db.Registration
95
+
for _, reg := range registrations {
96
+
if reg.Domain == domain && reg.ByDid == user.Did && reg.Registered != nil {
97
+
registration = ®
98
+
break
99
+
}
100
+
}
101
+
102
+
if registration == nil {
103
+
l.Error("registration not found or not verified")
104
+
http.Error(w, "Not found", http.StatusNotFound)
105
+
return
106
+
}
107
+
registration := registrations[0]
108
+
109
+
members, err := k.Enforcer.GetUserByRole("server:member", domain)
110
if err != nil {
111
+
l.Error("failed to get knot members", "err", err)
112
+
http.Error(w, "Not found", http.StatusInternalServerError)
113
return
114
}
115
+
slices.Sort(members)
116
117
+
repos, err := db.GetRepos(
118
+
k.Db,
119
+
0,
120
+
db.FilterEq("knot", domain),
121
+
)
122
if err != nil {
123
+
l.Error("failed to get knot repos", "err", err)
124
+
http.Error(w, "Not found", http.StatusInternalServerError)
125
return
126
}
127
128
+
// organize repos by did
129
+
repoMap := make(map[string][]db.Repo)
130
+
for _, r := range repos {
131
+
repoMap[r.Did] = append(repoMap[r.Did], r)
132
+
}
133
+
134
+
k.Pages.Knot(w, pages.KnotParams{
135
+
LoggedInUser: user,
136
+
Registration: ®istration,
137
+
Members: members,
138
+
Repos: repoMap,
139
+
IsOwner: true,
140
})
141
}
142
143
+
func (k *Knots) register(w http.ResponseWriter, r *http.Request) {
144
user := k.OAuth.GetUser(r)
145
+
l := k.Logger.With("handler", "register")
146
147
+
noticeId := "register-error"
148
+
defaultErr := "Failed to register knot. Try again later."
149
fail := func() {
150
k.Pages.Notice(w, noticeId, defaultErr)
151
}
152
153
+
domain := r.FormValue("domain")
154
if domain == "" {
155
+
k.Pages.Notice(w, noticeId, "Incomplete form.")
156
return
157
}
158
l = l.With("domain", domain)
159
+
l = l.With("user", user.Did)
160
161
+
tx, err := k.Db.Begin()
162
+
if err != nil {
163
+
l.Error("failed to start transaction", "err", err)
164
+
fail()
165
+
return
166
+
}
167
+
defer func() {
168
+
tx.Rollback()
169
+
k.Enforcer.E.LoadPolicy()
170
+
}()
171
172
+
err = db.AddKnot(tx, domain, user.Did)
173
if err != nil {
174
+
l.Error("failed to insert", "err", err)
175
fail()
176
return
177
}
178
+
179
+
err = k.Enforcer.AddKnot(domain)
180
+
if err != nil {
181
+
l.Error("failed to create knot", "err", err)
182
+
fail()
183
return
184
}
185
186
+
// create record on pds
187
+
client, err := k.OAuth.AuthorizedClient(r)
188
if err != nil {
189
+
l.Error("failed to authorize client", "err", err)
190
fail()
191
return
192
}
193
194
+
ex, _ := client.RepoGetRecord(r.Context(), "", tangled.KnotNSID, user.Did, domain)
195
+
var exCid *string
196
+
if ex != nil {
197
+
exCid = ex.Cid
198
+
}
199
+
200
+
// re-announce by registering under same rkey
201
+
_, err = client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{
202
+
Collection: tangled.KnotNSID,
203
+
Repo: user.Did,
204
+
Rkey: domain,
205
+
Record: &lexutil.LexiconTypeDecoder{
206
+
Val: &tangled.Knot{
207
+
CreatedAt: time.Now().Format(time.RFC3339),
208
+
},
209
+
},
210
+
SwapRecord: exCid,
211
+
})
212
+
213
if err != nil {
214
+
l.Error("failed to put record", "err", err)
215
fail()
216
return
217
}
218
219
+
err = tx.Commit()
220
if err != nil {
221
+
l.Error("failed to commit transaction", "err", err)
222
+
fail()
223
return
224
}
225
226
+
err = k.Enforcer.E.SavePolicy()
227
+
if err != nil {
228
+
l.Error("failed to update ACL", "err", err)
229
+
k.Pages.HxRefresh(w)
230
return
231
}
232
233
+
// begin verification
234
+
err = serververify.RunVerification(r.Context(), domain, user.Did, k.Config.Core.Dev)
235
+
if err != nil {
236
+
l.Error("verification failed", "err", err)
237
+
k.Pages.HxRefresh(w)
238
return
239
}
240
241
+
err = serververify.MarkKnotVerified(k.Db, k.Enforcer, domain, user.Did)
242
if err != nil {
243
+
l.Error("failed to mark verified", "err", err)
244
+
k.Pages.HxRefresh(w)
245
return
246
}
247
248
+
// add this knot to knotstream
249
+
go k.Knotstream.AddSource(
250
+
r.Context(),
251
+
eventconsumer.NewKnotSource(domain),
252
+
)
253
254
+
// ok
255
+
k.Pages.HxRefresh(w)
256
+
}
257
+
258
+
func (k *Knots) delete(w http.ResponseWriter, r *http.Request) {
259
+
user := k.OAuth.GetUser(r)
260
+
l := k.Logger.With("handler", "delete")
261
+
262
+
noticeId := "operation-error"
263
+
defaultErr := "Failed to delete knot. Try again later."
264
+
fail := func() {
265
+
k.Pages.Notice(w, noticeId, defaultErr)
266
+
}
267
+
268
+
domain := chi.URLParam(r, "domain")
269
+
if domain == "" {
270
+
l.Error("empty domain")
271
+
fail()
272
+
return
273
+
}
274
+
275
+
// get record from db first
276
+
registrations, err := db.GetRegistrations(
277
+
k.Db,
278
+
db.FilterEq("did", user.Did),
279
+
db.FilterEq("domain", domain),
280
+
)
281
+
if err != nil {
282
+
l.Error("failed to get registration", "err", err)
283
+
fail()
284
return
285
}
286
+
if len(registrations) != 1 {
287
+
l.Error("got incorret number of registrations", "got", len(registrations), "expected", 1)
288
+
fail()
289
+
return
290
+
}
291
+
registration := registrations[0]
292
293
+
tx, err := k.Db.Begin()
294
if err != nil {
295
+
l.Error("failed to start txn", "err", err)
296
fail()
297
return
298
}
299
defer func() {
300
tx.Rollback()
301
+
k.Enforcer.E.LoadPolicy()
302
}()
303
304
+
err = db.DeleteKnot(
305
+
tx,
306
+
db.FilterEq("did", user.Did),
307
+
db.FilterEq("domain", domain),
308
+
)
309
if err != nil {
310
+
l.Error("failed to delete registration", "err", err)
311
fail()
312
return
313
}
314
315
+
// delete from enforcer if it was registered
316
+
if registration.Registered != nil {
317
+
err = k.Enforcer.RemoveKnot(domain)
318
+
if err != nil {
319
+
l.Error("failed to update ACL", "err", err)
320
+
fail()
321
+
return
322
+
}
323
}
324
325
+
client, err := k.OAuth.AuthorizedClient(r)
326
if err != nil {
327
+
l.Error("failed to authorize client", "err", err)
328
fail()
329
return
330
}
331
332
+
_, err = client.RepoDeleteRecord(r.Context(), &comatproto.RepoDeleteRecord_Input{
333
+
Collection: tangled.KnotNSID,
334
+
Repo: user.Did,
335
+
Rkey: domain,
336
+
})
337
if err != nil {
338
+
// non-fatal
339
+
l.Error("failed to delete record", "err", err)
340
}
341
342
err = tx.Commit()
343
if err != nil {
344
+
l.Error("failed to delete knot", "err", err)
345
fail()
346
return
347
}
348
349
err = k.Enforcer.E.SavePolicy()
350
if err != nil {
351
+
l.Error("failed to update ACL", "err", err)
352
+
k.Pages.HxRefresh(w)
353
return
354
}
355
356
+
shouldRedirect := r.Header.Get("shouldRedirect")
357
+
if shouldRedirect == "true" {
358
+
k.Pages.HxRedirect(w, "/knots")
359
+
return
360
+
}
361
362
+
w.Write([]byte{})
363
}
364
365
+
func (k *Knots) retry(w http.ResponseWriter, r *http.Request) {
366
+
user := k.OAuth.GetUser(r)
367
+
l := k.Logger.With("handler", "retry")
368
+
369
+
noticeId := "operation-error"
370
+
defaultErr := "Failed to verify knot. Try again later."
371
fail := func() {
372
+
k.Pages.Notice(w, noticeId, defaultErr)
373
}
374
375
domain := chi.URLParam(r, "domain")
376
if domain == "" {
377
+
l.Error("empty domain")
378
+
fail()
379
return
380
}
381
l = l.With("domain", domain)
382
+
l = l.With("user", user.Did)
383
384
+
// get record from db first
385
+
registrations, err := db.GetRegistrations(
386
+
k.Db,
387
+
db.FilterEq("did", user.Did),
388
+
db.FilterEq("domain", domain),
389
+
)
390
if err != nil {
391
+
l.Error("failed to get registration", "err", err)
392
fail()
393
+
return
394
}
395
+
if len(registrations) != 1 {
396
+
l.Error("got incorret number of registrations", "got", len(registrations), "expected", 1)
397
+
fail()
398
return
399
}
400
+
registration := registrations[0]
401
402
+
// begin verification
403
+
err = serververify.RunVerification(r.Context(), domain, user.Did, k.Config.Core.Dev)
404
if err != nil {
405
+
l.Error("verification failed", "err", err)
406
+
407
+
if errors.Is(err, serververify.FetchError) {
408
+
k.Pages.Notice(w, noticeId, "Failed to verify knot, unable to fetch owner.")
409
+
return
410
+
}
411
+
412
+
if e, ok := err.(*serververify.OwnerMismatch); ok {
413
+
k.Pages.Notice(w, noticeId, e.Error())
414
+
return
415
+
}
416
+
417
fail()
418
return
419
}
420
421
+
err = serververify.MarkKnotVerified(k.Db, k.Enforcer, domain, user.Did)
422
+
if err != nil {
423
+
l.Error("failed to mark verified", "err", err)
424
+
k.Pages.Notice(w, noticeId, err.Error())
425
+
return
426
+
}
427
+
428
+
// if this knot was previously read-only, then emit a record too
429
+
//
430
+
// this is part of migrating from the old knot system to the new one
431
+
if registration.ReadOnly {
432
+
// re-announce by registering under same rkey
433
+
client, err := k.OAuth.AuthorizedClient(r)
434
if err != nil {
435
+
l.Error("failed to authorize client", "err", err)
436
fail()
437
return
438
}
439
+
440
+
ex, _ := client.RepoGetRecord(r.Context(), "", tangled.KnotNSID, user.Did, domain)
441
+
var exCid *string
442
+
if ex != nil {
443
+
exCid = ex.Cid
444
+
}
445
+
446
+
// ignore the error here
447
+
_, err = client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{
448
+
Collection: tangled.KnotNSID,
449
+
Repo: user.Did,
450
+
Rkey: domain,
451
+
Record: &lexutil.LexiconTypeDecoder{
452
+
Val: &tangled.Knot{
453
+
CreatedAt: time.Now().Format(time.RFC3339),
454
+
},
455
+
},
456
+
SwapRecord: exCid,
457
+
})
458
+
if err != nil {
459
+
l.Error("non-fatal: failed to reannouce knot", "err", err)
460
+
}
461
}
462
463
+
// add this knot to knotstream
464
+
go k.Knotstream.AddSource(
465
+
r.Context(),
466
+
eventconsumer.NewKnotSource(domain),
467
+
)
468
+
469
+
shouldRefresh := r.Header.Get("shouldRefresh")
470
+
if shouldRefresh == "true" {
471
+
k.Pages.HxRefresh(w)
472
+
return
473
+
}
474
+
475
+
// Get updated registration to show
476
+
registrations, err = db.GetRegistrations(
477
k.Db,
478
+
db.FilterEq("did", user.Did),
479
+
db.FilterEq("domain", domain),
480
)
481
if err != nil {
482
+
l.Error("failed to get registration", "err", err)
483
fail()
484
return
485
}
486
+
if len(registrations) != 1 {
487
+
l.Error("got incorret number of registrations", "got", len(registrations), "expected", 1)
488
+
fail()
489
return
490
}
491
+
updatedRegistration := registrations[0]
492
493
+
log.Println(updatedRegistration)
494
495
+
w.Header().Set("HX-Reswap", "outerHTML")
496
+
k.Pages.KnotListing(w, pages.KnotListingParams{
497
+
Registration: &updatedRegistration,
498
+
})
499
}
500
501
func (k *Knots) addMember(w http.ResponseWriter, r *http.Request) {
502
+
user := k.OAuth.GetUser(r)
503
+
l := k.Logger.With("handler", "addMember")
504
505
domain := chi.URLParam(r, "domain")
506
if domain == "" {
507
+
l.Error("empty domain")
508
+
http.Error(w, "Not found", http.StatusNotFound)
509
return
510
}
511
l = l.With("domain", domain)
512
+
l = l.With("user", user.Did)
513
514
+
registrations, err := db.GetRegistrations(
515
+
k.Db,
516
+
db.FilterEq("did", user.Did),
517
+
db.FilterEq("domain", domain),
518
+
db.FilterIsNot("registered", "null"),
519
+
)
520
if err != nil {
521
+
l.Error("failed to retrieve domain registration", "err", err)
522
+
http.Error(w, "Not found", http.StatusNotFound)
523
return
524
}
525
526
+
noticeId := fmt.Sprintf("add-member-error-%d", registration.Id)
527
defaultErr := "Failed to add member. Try again later."
528
fail := func() {
529
k.Pages.Notice(w, noticeId, defaultErr)
530
}
531
532
+
member := r.FormValue("member")
533
+
if member == "" {
534
+
l.Error("empty member")
535
+
k.Pages.Notice(w, noticeId, "Failed to add member, empty form.")
536
return
537
}
538
+
l = l.With("member", member)
539
540
+
memberId, err := k.IdResolver.ResolveIdent(r.Context(), member)
541
if err != nil {
542
+
l.Error("failed to resolve member identity to handle", "err", err)
543
k.Pages.Notice(w, noticeId, "Failed to add member, identity resolution failed.")
544
return
545
}
546
+
if memberId.Handle.IsInvalidHandle() {
547
+
l.Error("failed to resolve member identity to handle")
548
+
k.Pages.Notice(w, noticeId, "Failed to add member, identity resolution failed.")
549
+
return
550
+
}
551
552
+
// write to pds
553
client, err := k.OAuth.AuthorizedClient(r)
554
if err != nil {
555
+
l.Error("failed to authorize client", "err", err)
556
fail()
557
return
558
}
559
560
+
rkey := tid.TID()
561
+
562
+
_, err = client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{
563
Collection: tangled.KnotMemberNSID,
564
+
Repo: user.Did,
565
+
Rkey: rkey,
566
Record: &lexutil.LexiconTypeDecoder{
567
Val: &tangled.KnotMember{
568
+
CreatedAt: time.Now().Format(time.RFC3339),
569
Domain: domain,
570
+
Subject: memberId.DID.String(),
571
+
},
572
+
},
573
})
574
if err != nil {
575
+
l.Error("failed to add record to PDS", "err", err)
576
+
k.Pages.Notice(w, noticeId, "Failed to add record to PDS, try again later.")
577
+
return
578
+
}
579
+
580
+
err = k.Enforcer.AddKnotMember(domain, memberId.DID.String())
581
+
if err != nil {
582
+
l.Error("failed to add member to ACLs", "err", err)
583
fail()
584
return
585
}
586
587
+
err = k.Enforcer.E.SavePolicy()
588
if err != nil {
589
+
l.Error("failed to save ACL policy", "err", err)
590
+
fail()
591
+
return
592
+
}
593
+
594
+
// success
595
+
k.Pages.HxRedirect(w, fmt.Sprintf("/knots/%s", domain))
596
+
}
597
+
598
+
func (k *Knots) removeMember(w http.ResponseWriter, r *http.Request) {
599
+
user := k.OAuth.GetUser(r)
600
+
l := k.Logger.With("handler", "removeMember")
601
+
602
+
noticeId := "operation-error"
603
+
defaultErr := "Failed to remove member. Try again later."
604
+
fail := func() {
605
+
k.Pages.Notice(w, noticeId, defaultErr)
606
+
}
607
+
608
+
domain := chi.URLParam(r, "domain")
609
+
if domain == "" {
610
+
l.Error("empty domain")
611
fail()
612
return
613
}
614
+
l = l.With("domain", domain)
615
+
l = l.With("user", user.Did)
616
617
+
registrations, err := db.GetRegistrations(
618
+
k.Db,
619
+
db.FilterEq("did", user.Did),
620
+
db.FilterEq("domain", domain),
621
+
db.FilterIsNot("registered", "null"),
622
+
)
623
if err != nil {
624
+
l.Error("failed to get registration", "err", err)
625
+
return
626
+
}
627
+
if len(registrations) != 1 {
628
+
l.Error("got incorret number of registrations", "got", len(registrations), "expected", 1)
629
return
630
}
631
632
+
member := r.FormValue("member")
633
+
if member == "" {
634
+
l.Error("empty member")
635
+
k.Pages.Notice(w, noticeId, "Failed to remove member, empty form.")
636
+
return
637
+
}
638
+
l = l.With("member", member)
639
+
640
+
memberId, err := k.IdResolver.ResolveIdent(r.Context(), member)
641
if err != nil {
642
+
l.Error("failed to resolve member identity to handle", "err", err)
643
+
k.Pages.Notice(w, noticeId, "Failed to remove member, identity resolution failed.")
644
+
return
645
+
}
646
+
if memberId.Handle.IsInvalidHandle() {
647
+
l.Error("failed to resolve member identity to handle")
648
+
k.Pages.Notice(w, noticeId, "Failed to remove member, identity resolution failed.")
649
return
650
}
651
652
+
// remove from enforcer
653
+
err = k.Enforcer.RemoveKnotMember(domain, memberId.DID.String())
654
+
if err != nil {
655
+
l.Error("failed to update ACLs", "err", err)
656
+
fail()
657
return
658
}
659
660
+
client, err := k.OAuth.AuthorizedClient(r)
661
if err != nil {
662
+
l.Error("failed to authorize client", "err", err)
663
fail()
664
return
665
}
666
667
+
// TODO: We need to track the rkey for knot members to delete the record
668
+
// For now, just remove from ACLs
669
+
_ = client
670
+
671
+
// commit everything
672
+
err = k.Enforcer.E.SavePolicy()
673
+
if err != nil {
674
+
l.Error("failed to save ACLs", "err", err)
675
+
fail()
676
+
return
677
+
}
678
679
+
// ok
680
+
k.Pages.HxRefresh(w)
681
}
+164
appview/serververify/verify.go
+164
appview/serververify/verify.go
···
···
1
+
package serververify
2
+
3
+
import (
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
+
16
+
var (
17
+
FetchError = errors.New("failed to fetch owner")
18
+
)
19
+
20
+
// fetchOwner fetches the owner DID from a server's /owner endpoint
21
+
func 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
+
55
+
type OwnerMismatch struct {
56
+
expected string
57
+
observed string
58
+
}
59
+
60
+
func (e *OwnerMismatch) Error() string {
61
+
return fmt.Sprintf("owner mismatch: %q != %q", e.expected, e.observed)
62
+
}
63
+
64
+
// RunVerification verifies that the server at the given domain has the expected owner
65
+
func RunVerification(ctx context.Context, domain, expectedOwner string, dev bool) error {
66
+
observedOwner, err := fetchOwner(ctx, domain, 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
+
// MarkSpindleVerified marks a spindle as verified in the DB and adds the user as its owner
82
+
func MarkSpindleVerified(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
+
}
119
+
120
+
// MarkKnotVerified marks a knot as verified and sets up ownership/permissions
121
+
func MarkKnotVerified(d *db.DB, e *rbac.Enforcer, domain, owner string) error {
122
+
tx, err := d.BeginTx(context.Background(), nil)
123
+
if err != nil {
124
+
return fmt.Errorf("failed to start tx: %w", err)
125
+
}
126
+
defer func() {
127
+
tx.Rollback()
128
+
e.E.LoadPolicy()
129
+
}()
130
+
131
+
// mark as registered
132
+
err = db.MarkRegistered(
133
+
tx,
134
+
db.FilterEq("did", owner),
135
+
db.FilterEq("domain", domain),
136
+
)
137
+
if err != nil {
138
+
return fmt.Errorf("failed to register domain: %w", err)
139
+
}
140
+
141
+
// add basic acls for this domain
142
+
err = e.AddKnot(domain)
143
+
if err != nil {
144
+
return fmt.Errorf("failed to add knot to enforcer: %w", err)
145
+
}
146
+
147
+
// add this did as owner of this domain
148
+
err = e.AddKnotOwner(domain, owner)
149
+
if err != nil {
150
+
return fmt.Errorf("failed to add knot owner to enforcer: %w", err)
151
+
}
152
+
153
+
err = tx.Commit()
154
+
if err != nil {
155
+
return fmt.Errorf("failed to commit changes: %w", err)
156
+
}
157
+
158
+
err = e.E.SavePolicy()
159
+
if err != nil {
160
+
return fmt.Errorf("failed to update ACLs: %w", err)
161
+
}
162
+
163
+
return nil
164
+
}
+8
-8
appview/spindles/spindles.go
+8
-8
appview/spindles/spindles.go
···
15
"tangled.sh/tangled.sh/core/appview/middleware"
16
"tangled.sh/tangled.sh/core/appview/oauth"
17
"tangled.sh/tangled.sh/core/appview/pages"
18
-
verify "tangled.sh/tangled.sh/core/appview/spindleverify"
19
"tangled.sh/tangled.sh/core/idresolver"
20
"tangled.sh/tangled.sh/core/rbac"
21
"tangled.sh/tangled.sh/core/tid"
···
227
}
228
229
// begin verification
230
-
err = verify.RunVerification(r.Context(), instance, user.Did, s.Config.Core.Dev)
231
if err != nil {
232
l.Error("verification failed", "err", err)
233
s.Pages.HxRefresh(w)
234
return
235
}
236
237
-
_, err = verify.MarkVerified(s.Db, s.Enforcer, instance, user.Did)
238
if err != nil {
239
l.Error("failed to mark verified", "err", err)
240
s.Pages.HxRefresh(w)
···
400
}
401
402
// begin verification
403
-
err = verify.RunVerification(r.Context(), instance, user.Did, s.Config.Core.Dev)
404
if err != nil {
405
l.Error("verification failed", "err", err)
406
407
-
if errors.Is(err, verify.FetchError) {
408
-
s.Pages.Notice(w, noticeId, err.Error())
409
return
410
}
411
412
-
if e, ok := err.(*verify.OwnerMismatch); ok {
413
s.Pages.Notice(w, noticeId, e.Error())
414
return
415
}
···
418
return
419
}
420
421
-
rowId, err := verify.MarkVerified(s.Db, s.Enforcer, instance, user.Did)
422
if err != nil {
423
l.Error("failed to mark verified", "err", err)
424
s.Pages.Notice(w, noticeId, err.Error())
···
15
"tangled.sh/tangled.sh/core/appview/middleware"
16
"tangled.sh/tangled.sh/core/appview/oauth"
17
"tangled.sh/tangled.sh/core/appview/pages"
18
+
"tangled.sh/tangled.sh/core/appview/serververify"
19
"tangled.sh/tangled.sh/core/idresolver"
20
"tangled.sh/tangled.sh/core/rbac"
21
"tangled.sh/tangled.sh/core/tid"
···
227
}
228
229
// begin verification
230
+
err = serververify.RunVerification(r.Context(), instance, user.Did, s.Config.Core.Dev)
231
if err != nil {
232
l.Error("verification failed", "err", err)
233
s.Pages.HxRefresh(w)
234
return
235
}
236
237
+
_, err = serververify.MarkSpindleVerified(s.Db, s.Enforcer, instance, user.Did)
238
if err != nil {
239
l.Error("failed to mark verified", "err", err)
240
s.Pages.HxRefresh(w)
···
400
}
401
402
// begin verification
403
+
err = serververify.RunVerification(r.Context(), instance, user.Did, s.Config.Core.Dev)
404
if err != nil {
405
l.Error("verification failed", "err", err)
406
407
+
if errors.Is(err, serververify.FetchError) {
408
+
s.Pages.Notice(w, noticeId, "Failed to verify knot, unable to fetch owner.")
409
return
410
}
411
412
+
if e, ok := err.(*serververify.OwnerMismatch); ok {
413
s.Pages.Notice(w, noticeId, e.Error())
414
return
415
}
···
418
return
419
}
420
421
+
rowId, err := serververify.MarkSpindleVerified(s.Db, s.Enforcer, instance, user.Did)
422
if err != nil {
423
l.Error("failed to mark verified", "err", err)
424
s.Pages.Notice(w, noticeId, err.Error())
-118
appview/spindleverify/verify.go
-118
appview/spindleverify/verify.go
···
1
-
package spindleverify
2
-
3
-
import (
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
-
16
-
var (
17
-
FetchError = errors.New("failed to fetch owner")
18
-
)
19
-
20
-
// TODO: move this to "spindleclient" or similar
21
-
func 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
-
55
-
type OwnerMismatch struct {
56
-
expected string
57
-
observed string
58
-
}
59
-
60
-
func (e *OwnerMismatch) Error() string {
61
-
return fmt.Sprintf("owner mismatch: %q != %q", e.expected, e.observed)
62
-
}
63
-
64
-
func 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
82
-
func 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
-
}
···
+3
-3
appview/state/router.go
+3
-3
appview/state/router.go
···
147
148
r.Mount("/settings", s.SettingsRouter())
149
r.Mount("/strings", s.StringsRouter(mw))
150
-
r.Mount("/knots", s.KnotsRouter(mw))
151
r.Mount("/spindles", s.SpindlesRouter())
152
r.Mount("/signup", s.SignupRouter())
153
r.Mount("/", s.OAuthRouter())
···
195
return spindles.Router()
196
}
197
198
-
func (s *State) KnotsRouter(mw *middleware.Middleware) http.Handler {
199
logger := log.New("knots")
200
201
knots := &knots.Knots{
···
209
Logger: logger,
210
}
211
212
-
return knots.Router(mw)
213
}
214
215
func (s *State) StringsRouter(mw *middleware.Middleware) http.Handler {
···
147
148
r.Mount("/settings", s.SettingsRouter())
149
r.Mount("/strings", s.StringsRouter(mw))
150
+
r.Mount("/knots", s.KnotsRouter())
151
r.Mount("/spindles", s.SpindlesRouter())
152
r.Mount("/signup", s.SignupRouter())
153
r.Mount("/", s.OAuthRouter())
···
195
return spindles.Router()
196
}
197
198
+
func (s *State) KnotsRouter() http.Handler {
199
logger := log.New("knots")
200
201
knots := &knots.Knots{
···
209
Logger: logger,
210
}
211
212
+
return knots.Router()
213
}
214
215
func (s *State) StringsRouter(mw *middleware.Middleware) http.Handler {
+5
rbac/rbac.go
+5
rbac/rbac.go