+13
appview/db/registration.go
+13
appview/db/registration.go
···
161
161
162
162
return err
163
163
}
164
+
165
+
func RegisterV2(e Execer, domain, did string) error {
166
+
// TODO: this secret is useless because it is never used
167
+
// all comms happen through authenticated records on the firehose
168
+
secret := genSecret()
169
+
_, err := e.Exec(`
170
+
insert into registrations (domain, did, secret)
171
+
values (?, ?, ?)
172
+
on conflict(domain) do update set did = excluded.did, created = excluded.created, registered = excluded.created
173
+
`, domain, did, secret)
174
+
175
+
return err
176
+
}
+54
-1
appview/ingester.go
+54
-1
appview/ingester.go
···
5
5
"encoding/json"
6
6
"fmt"
7
7
"log"
8
+
"strings"
8
9
"time"
9
10
10
11
"github.com/bluesky-social/indigo/atproto/syntax"
···
13
14
"github.com/ipfs/go-cid"
14
15
"tangled.sh/tangled.sh/core/api/tangled"
15
16
"tangled.sh/tangled.sh/core/appview/db"
17
+
"tangled.sh/tangled.sh/core/knotclient"
16
18
"tangled.sh/tangled.sh/core/rbac"
17
19
)
18
20
19
21
type Ingester func(ctx context.Context, e *models.Event) error
20
22
21
-
func Ingest(d db.DbWrapper, enforcer *rbac.Enforcer) Ingester {
23
+
func Ingest(d db.DbWrapper, enforcer *rbac.Enforcer, dev bool) Ingester {
22
24
return func(ctx context.Context, e *models.Event) error {
23
25
var err error
24
26
defer func() {
···
44
46
ingestArtifact(&d, e, enforcer)
45
47
case tangled.ActorProfileNSID:
46
48
ingestProfile(&d, e)
49
+
case tangled.KnotNSID:
50
+
ingestKnot(&d, e, dev)
47
51
}
48
52
49
53
return err
···
285
289
286
290
return nil
287
291
}
292
+
293
+
func ingestKnot(d *db.DbWrapper, e *models.Event, dev bool) error {
294
+
did := e.Did
295
+
var err error
296
+
297
+
switch e.Commit.Operation {
298
+
case models.CommitOperationCreate:
299
+
log.Println("processing knot creation")
300
+
raw := json.RawMessage(e.Commit.Record)
301
+
record := tangled.Knot{}
302
+
err = json.Unmarshal(raw, &record)
303
+
if err != nil {
304
+
log.Printf("invalid record: %s", err)
305
+
return err
306
+
}
307
+
308
+
host := record.Host
309
+
310
+
if strings.HasPrefix(host, "localhost") && !dev {
311
+
// localhost knots are not ingested except in dev mode
312
+
return fmt.Errorf("localhost knots not registered this appview: %s", host)
313
+
}
314
+
315
+
// two-way confirmation that this knot is owned by this did
316
+
us, err := knotclient.NewUnsignedClient(host, dev)
317
+
if err != nil {
318
+
return err
319
+
}
320
+
321
+
resp, err := us.Owner()
322
+
if err != nil {
323
+
return err
324
+
}
325
+
326
+
if resp.OwnerDid != did {
327
+
return fmt.Errorf("incorrect owner reported from knot %s: wanted: %s, got: %s", host, resp.OwnerDid, did)
328
+
}
329
+
330
+
err = db.RegisterV2(d, host, resp.OwnerDid)
331
+
default:
332
+
log.Println("this operation is not yet handled", e.Commit.Operation)
333
+
}
334
+
335
+
if err != nil {
336
+
return fmt.Errorf("failed to %s knot record: %w", e.Commit.Operation, err)
337
+
}
338
+
339
+
return nil
340
+
}
+2
-2
appview/state/state.go
+2
-2
appview/state/state.go
···
83
83
tangled.PublicKeyNSID,
84
84
tangled.RepoArtifactNSID,
85
85
tangled.ActorProfileNSID,
86
+
tangled.KnotNSID,
86
87
},
87
88
nil,
88
89
slog.Default(),
···
92
93
if err != nil {
93
94
return nil, fmt.Errorf("failed to create jetstream client: %w", err)
94
95
}
95
-
err = jc.StartJetstream(context.Background(), appview.Ingest(wrapper, enforcer))
96
+
err = jc.StartJetstream(context.Background(), appview.Ingest(wrapper, enforcer, config.Core.Dev))
96
97
if err != nil {
97
98
return nil, fmt.Errorf("failed to start jetstream watcher: %w", err)
98
99
}
···
408
409
409
410
// get knots registered by this user
410
411
func (s *State) Knots(w http.ResponseWriter, r *http.Request) {
411
-
// for now, this is just pubkeys
412
412
user := s.oauth.GetUser(r)
413
413
registrations, err := db.RegistrationsByDid(s.db, user.Did)
414
414
if err != nil {
+14
knotclient/unsigned.go
+14
knotclient/unsigned.go
···
248
248
249
249
return &formatPatchResponse, nil
250
250
}
251
+
252
+
func (us *UnsignedClient) Owner() (*types.KnotOwnerResponse, error) {
253
+
const (
254
+
Method = "GET"
255
+
Endpoint = "/owner"
256
+
)
257
+
258
+
req, err := us.newRequest(Method, Endpoint, nil, nil)
259
+
if err != nil {
260
+
return nil, err
261
+
}
262
+
263
+
return do[types.KnotOwnerResponse](us, req)
264
+
}
+1
knotserver/db/init.go
+1
knotserver/db/init.go
+3
-3
knotserver/db/owner.go
+3
-3
knotserver/db/owner.go
···
1
1
package db
2
2
3
-
func (d *DB) SetOwner(did string) error {
4
-
query := `insert into owner (id, did) values (?, ?)`
5
-
_, err := d.db.Exec(query, 0, did)
3
+
func (d *DB) SetOwner(did, rkey string) error {
4
+
query := `insert into owner (id, did, rkey) values (?, ?, ?)`
5
+
_, err := d.db.Exec(query, 0, did, rkey)
6
6
return err
7
7
}
8
8
+4
-1
knotserver/handler.go
+4
-1
knotserver/handler.go
···
157
157
// Health check. Used for two-way verification with appview.
158
158
r.With(h.VerifySignature).Get("/health", h.Health)
159
159
160
+
// Return did of the owner of this knot
161
+
r.Get("/owner", h.Owner)
162
+
160
163
// All public keys on the knot.
161
164
r.Get("/keys", h.Keys)
162
165
···
241
244
return err
242
245
}
243
246
244
-
err = h.db.SetOwner(ownerDid)
247
+
err = h.db.SetOwner(ownerDid, rkey)
245
248
if err != nil {
246
249
return err
247
250
}
+12
knotserver/routes.go
+12
knotserver/routes.go
···
1287
1287
w.Write([]byte("ok"))
1288
1288
}
1289
1289
1290
+
func (h *Handle) Owner(w http.ResponseWriter, r *http.Request) {
1291
+
owner, err := h.db.Owner()
1292
+
if err != nil {
1293
+
writeError(w, "no owner", http.StatusNotFound)
1294
+
return
1295
+
}
1296
+
1297
+
writeJSON(w, types.KnotOwnerResponse{
1298
+
OwnerDid: owner,
1299
+
})
1300
+
}
1301
+
1290
1302
func validateRepoName(name string) error {
1291
1303
// check for path traversal attempts
1292
1304
if name == "." || name == ".." ||