forked from tangled.org/core
Monorepo for Tangled

knotserver: implement new registration flow

this flow writes a record to the owner's pds to announce knot creation

Signed-off-by: oppiliappan <me@oppi.li>

Changed files
+108 -7
appview
knotclient
knotserver
types
+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
··· 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
··· 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
··· 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
··· 29 29 create table if not exists owner ( 30 30 id integer primary key check (id = 0), 31 31 did text not null, 32 + rkey text not null, 32 33 createdAt text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')) 33 34 ); 34 35
+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
··· 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
··· 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 == ".." ||
+5
types/knot.go
··· 1 + package types 2 + 3 + type KnotOwnerResponse struct { 4 + OwnerDid string `json:"did"` 5 + }