forked from tangled.org/core
this repo has no description

add server policies through the firehose

Changed files
+205 -159
api
appview
cmd
knotserver
knotserver
lexicons
rbac
+65 -75
api/tangled/cbor_gen.go
··· 216 216 217 217 return nil 218 218 } 219 - func (t *KnotPolicy) MarshalCBOR(w io.Writer) error { 219 + func (t *KnotMember) MarshalCBOR(w io.Writer) error { 220 220 if t == nil { 221 221 _, err := w.Write(cbg.CborNull) 222 222 return err 223 223 } 224 224 225 225 cw := cbg.NewCborWriter(w) 226 + fieldCount := 4 226 227 227 - if _, err := cw.Write([]byte{165}); err != nil { 228 + if t.AddedAt == nil { 229 + fieldCount-- 230 + } 231 + 232 + if _, err := cw.Write(cbg.CborEncodeMajorType(cbg.MajMap, uint64(fieldCount))); err != nil { 228 233 return err 229 234 } 230 235 ··· 240 245 return err 241 246 } 242 247 243 - if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("sh.tangled.knot.policy"))); err != nil { 244 - return err 245 - } 246 - if _, err := cw.WriteString(string("sh.tangled.knot.policy")); err != nil { 248 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("sh.tangled.knot.member"))); err != nil { 247 249 return err 248 250 } 249 - 250 - // t.Action (string) (string) 251 - if len("action") > 1000000 { 252 - return xerrors.Errorf("Value in field \"action\" was too long") 253 - } 254 - 255 - if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("action"))); err != nil { 256 - return err 257 - } 258 - if _, err := cw.WriteString(string("action")); err != nil { 259 - return err 260 - } 261 - 262 - if len(t.Action) > 1000000 { 263 - return xerrors.Errorf("Value in field t.Action was too long") 264 - } 265 - 266 - if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Action))); err != nil { 267 - return err 268 - } 269 - if _, err := cw.WriteString(string(t.Action)); err != nil { 251 + if _, err := cw.WriteString(string("sh.tangled.knot.member")); err != nil { 270 252 return err 271 253 } 272 254 ··· 293 275 return err 294 276 } 295 277 296 - // t.Object (string) (string) 297 - if len("object") > 1000000 { 298 - return xerrors.Errorf("Value in field \"object\" was too long") 278 + // t.Member (string) (string) 279 + if len("member") > 1000000 { 280 + return xerrors.Errorf("Value in field \"member\" was too long") 299 281 } 300 282 301 - if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("object"))); err != nil { 283 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("member"))); err != nil { 302 284 return err 303 285 } 304 - if _, err := cw.WriteString(string("object")); err != nil { 286 + if _, err := cw.WriteString(string("member")); err != nil { 305 287 return err 306 288 } 307 289 308 - if len(t.Object) > 1000000 { 309 - return xerrors.Errorf("Value in field t.Object was too long") 290 + if len(t.Member) > 1000000 { 291 + return xerrors.Errorf("Value in field t.Member was too long") 310 292 } 311 293 312 - if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Object))); err != nil { 294 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Member))); err != nil { 313 295 return err 314 296 } 315 - if _, err := cw.WriteString(string(t.Object)); err != nil { 297 + if _, err := cw.WriteString(string(t.Member)); err != nil { 316 298 return err 317 299 } 318 300 319 - // t.Subject (string) (string) 320 - if len("subject") > 1000000 { 321 - return xerrors.Errorf("Value in field \"subject\" was too long") 322 - } 301 + // t.AddedAt (string) (string) 302 + if t.AddedAt != nil { 323 303 324 - if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("subject"))); err != nil { 325 - return err 326 - } 327 - if _, err := cw.WriteString(string("subject")); err != nil { 328 - return err 329 - } 304 + if len("addedAt") > 1000000 { 305 + return xerrors.Errorf("Value in field \"addedAt\" was too long") 306 + } 330 307 331 - if len(t.Subject) > 1000000 { 332 - return xerrors.Errorf("Value in field t.Subject was too long") 333 - } 308 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("addedAt"))); err != nil { 309 + return err 310 + } 311 + if _, err := cw.WriteString(string("addedAt")); err != nil { 312 + return err 313 + } 334 314 335 - if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Subject))); err != nil { 336 - return err 337 - } 338 - if _, err := cw.WriteString(string(t.Subject)); err != nil { 339 - return err 315 + if t.AddedAt == nil { 316 + if _, err := cw.Write(cbg.CborNull); err != nil { 317 + return err 318 + } 319 + } else { 320 + if len(*t.AddedAt) > 1000000 { 321 + return xerrors.Errorf("Value in field t.AddedAt was too long") 322 + } 323 + 324 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(*t.AddedAt))); err != nil { 325 + return err 326 + } 327 + if _, err := cw.WriteString(string(*t.AddedAt)); err != nil { 328 + return err 329 + } 330 + } 340 331 } 341 332 return nil 342 333 } 343 334 344 - func (t *KnotPolicy) UnmarshalCBOR(r io.Reader) (err error) { 345 - *t = KnotPolicy{} 335 + func (t *KnotMember) UnmarshalCBOR(r io.Reader) (err error) { 336 + *t = KnotMember{} 346 337 347 338 cr := cbg.NewCborReader(r) 348 339 ··· 361 352 } 362 353 363 354 if extra > cbg.MaxLength { 364 - return fmt.Errorf("KnotPolicy: map struct too large (%d)", extra) 355 + return fmt.Errorf("KnotMember: map struct too large (%d)", extra) 365 356 } 366 357 367 358 n := extra ··· 393 384 394 385 t.LexiconTypeID = string(sval) 395 386 } 396 - // t.Action (string) (string) 397 - case "action": 398 - 399 - { 400 - sval, err := cbg.ReadStringWithMax(cr, 1000000) 401 - if err != nil { 402 - return err 403 - } 404 - 405 - t.Action = string(sval) 406 - } 407 387 // t.Domain (string) (string) 408 388 case "domain": 409 389 ··· 415 395 416 396 t.Domain = string(sval) 417 397 } 418 - // t.Object (string) (string) 419 - case "object": 398 + // t.Member (string) (string) 399 + case "member": 420 400 421 401 { 422 402 sval, err := cbg.ReadStringWithMax(cr, 1000000) ··· 424 404 return err 425 405 } 426 406 427 - t.Object = string(sval) 407 + t.Member = string(sval) 428 408 } 429 - // t.Subject (string) (string) 430 - case "subject": 409 + // t.AddedAt (string) (string) 410 + case "addedAt": 431 411 432 412 { 433 - sval, err := cbg.ReadStringWithMax(cr, 1000000) 413 + b, err := cr.ReadByte() 434 414 if err != nil { 435 415 return err 436 416 } 417 + if b != cbg.CborNull[0] { 418 + if err := cr.UnreadByte(); err != nil { 419 + return err 420 + } 437 421 438 - t.Subject = string(sval) 422 + sval, err := cbg.ReadStringWithMax(cr, 1000000) 423 + if err != nil { 424 + return err 425 + } 426 + 427 + t.AddedAt = (*string)(&sval) 428 + } 439 429 } 440 430 441 431 default:
+25
api/tangled/knotmember.go
··· 1 + // Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT. 2 + 3 + package tangled 4 + 5 + // schema: sh.tangled.knot.member 6 + 7 + import ( 8 + "github.com/bluesky-social/indigo/lex/util" 9 + ) 10 + 11 + const ( 12 + KnotMemberNSID = "sh.tangled.knot.member" 13 + ) 14 + 15 + func init() { 16 + util.RegisterType("sh.tangled.knot.member", &KnotMember{}) 17 + } // 18 + // RECORDTYPE: KnotMember 19 + type KnotMember struct { 20 + LexiconTypeID string `json:"$type,const=sh.tangled.knot.member" cborgen:"$type,const=sh.tangled.knot.member"` 21 + AddedAt *string `json:"addedAt,omitempty" cborgen:"addedAt,omitempty"` 22 + // domain: domain that this member now belongs to 23 + Domain string `json:"domain" cborgen:"domain"` 24 + Member string `json:"member" cborgen:"member"` 25 + }
-29
api/tangled/knotpolicy.go
··· 1 - // Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT. 2 - 3 - package tangled 4 - 5 - // schema: sh.tangled.knot.policy 6 - 7 - import ( 8 - "github.com/bluesky-social/indigo/lex/util" 9 - ) 10 - 11 - const ( 12 - KnotPolicyNSID = "sh.tangled.knot.policy" 13 - ) 14 - 15 - func init() { 16 - util.RegisterType("sh.tangled.knot.policy", &KnotPolicy{}) 17 - } // 18 - // RECORDTYPE: KnotPolicy 19 - type KnotPolicy struct { 20 - LexiconTypeID string `json:"$type,const=sh.tangled.knot.policy" cborgen:"$type,const=sh.tangled.knot.policy"` 21 - // action: action associated with the key 22 - Action string `json:"action" cborgen:"action"` 23 - // domain: domain of the key 24 - Domain string `json:"domain" cborgen:"domain"` 25 - // object: object associated with the key 26 - Object string `json:"object" cborgen:"object"` 27 - // subject: subject of the key 28 - Subject string `json:"subject" cborgen:"subject"` 29 - }
+1
appview/consts.go
··· 11 11 SessionExpiry = "expiry" 12 12 SessionAuthenticated = "authenticated" 13 13 TimeLayout = "2006-01-02 15:04:05.999999999 -0700 MST" 14 + SqliteDbPath = "appview.db" 14 15 )
+3 -4
appview/state/rbac.go rbac/rbac.go
··· 1 - package state 1 + package rbac 2 2 3 3 import ( 4 4 "database/sql" ··· 37 37 return matched 38 38 } 39 39 40 - func NewEnforcer() (*Enforcer, error) { 40 + func NewEnforcer(path string) (*Enforcer, error) { 41 41 m, err := model.NewModelFromString(Model) 42 42 if err != nil { 43 43 return nil, err 44 44 } 45 45 46 - // TODO: conf this 47 - db, err := sql.Open("sqlite3", "appview.db") 46 + db, err := sql.Open("sqlite3", path) 48 47 if err != nil { 49 48 return nil, err 50 49 }
+35 -11
appview/state/state.go
··· 22 22 "github.com/sotangled/tangled/appview/auth" 23 23 "github.com/sotangled/tangled/appview/db" 24 24 "github.com/sotangled/tangled/appview/pages" 25 + "github.com/sotangled/tangled/rbac" 25 26 ) 26 27 27 28 type State struct { 28 29 db *db.DB 29 30 auth *auth.Auth 30 - enforcer *Enforcer 31 + enforcer *rbac.Enforcer 31 32 } 32 33 33 34 func Make() (*State, error) { 34 - db, err := db.Make("appview.db") 35 + 36 + db, err := db.Make(appview.SqliteDbPath) 35 37 if err != nil { 36 38 return nil, err 37 39 } ··· 41 43 return nil, err 42 44 } 43 45 44 - enforcer, err := NewEnforcer() 46 + enforcer, err := rbac.NewEnforcer(appview.SqliteDbPath) 45 47 if err != nil { 46 48 return nil, err 47 49 } ··· 175 177 Collection: tangled.PublicKeyNSID, 176 178 Repo: did, 177 179 Rkey: uuid.New().String(), 178 - Record: &lexutil.LexiconTypeDecoder{Val: &tangled.PublicKey{ 179 - Created: time.Now().String(), 180 - Key: key, 181 - Name: name, 182 - }}, 180 + Record: &lexutil.LexiconTypeDecoder{ 181 + Val: &tangled.PublicKey{ 182 + Created: time.Now().Format(time.RFC3339), 183 + Key: key, 184 + Name: name, 185 + }}, 183 186 }) 184 - 185 187 // invalid record 186 188 if err != nil { 187 189 log.Printf("failed to create record: %s", err) ··· 382 384 w.Write([]byte("failed to resolve member did to a handle")) 383 385 return 384 386 } 385 - 386 387 log.Printf("adding %s to %s\n", memberIdent.Handle.String(), domain) 387 388 389 + // announce this relation into the firehose, store into owners' pds 390 + client, _ := s.auth.AuthorizedClient(r) 391 + currentUser := s.auth.GetUser(r) 392 + addedAt := time.Now().Format(time.RFC3339) 393 + resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 394 + Collection: tangled.KnotMemberNSID, 395 + Repo: currentUser.Did, 396 + Rkey: uuid.New().String(), 397 + Record: &lexutil.LexiconTypeDecoder{ 398 + Val: &tangled.KnotMember{ 399 + Member: memberIdent.DID.String(), 400 + Domain: domain, 401 + AddedAt: &addedAt, 402 + }}, 403 + }) 404 + // invalid record 405 + if err != nil { 406 + log.Printf("failed to create record: %s", err) 407 + return 408 + } 409 + 410 + log.Println("created atproto record: ", resp.Uri) 411 + 388 412 err = s.enforcer.AddMember(domain, memberIdent.DID.String()) 389 413 if err != nil { 390 414 w.Write([]byte(fmt.Sprint("failed to add member: ", err))) ··· 481 505 r.Post("/key", s.RegistrationKey) 482 506 483 507 r.Route("/{domain}", func(r chi.Router) { 484 - r.Get("/", s.KnotServerInfo) 485 508 r.Post("/init", s.InitKnotServer) 509 + r.Get("/", s.KnotServerInfo) 486 510 r.Route("/member", func(r chi.Router) { 487 511 r.Use(RoleMiddleware(s, "server:owner")) 488 512 r.Get("/", s.ListMembers)
+1 -1
cmd/gen.go
··· 15 15 "api/tangled/cbor_gen.go", 16 16 "tangled", 17 17 shtangled.PublicKey{}, 18 - shtangled.KnotPolicy{}, 18 + shtangled.KnotMember{}, 19 19 ); err != nil { 20 20 panic(err) 21 21 }
+7 -1
cmd/knotserver/main.go
··· 11 11 "github.com/sotangled/tangled/knotserver" 12 12 "github.com/sotangled/tangled/knotserver/config" 13 13 "github.com/sotangled/tangled/knotserver/db" 14 + "github.com/sotangled/tangled/rbac" 14 15 ) 15 16 16 17 func main() { ··· 34 35 log.Fatalf("failed to setup db: %s", err) 35 36 } 36 37 37 - mux, err := knotserver.Setup(ctx, c, db) 38 + e, err := rbac.NewEnforcer(c.Server.DBPath) 39 + if err != nil { 40 + log.Fatalf("failed to setup rbac enforcer: %s", err) 41 + } 42 + 43 + mux, err := knotserver.Setup(ctx, c, db, e) 38 44 if err != nil { 39 45 log.Fatal(err) 40 46 }
+26 -4
knotserver/handler.go
··· 12 12 "github.com/sotangled/tangled/knotserver/config" 13 13 "github.com/sotangled/tangled/knotserver/db" 14 14 "github.com/sotangled/tangled/knotserver/jsclient" 15 + "github.com/sotangled/tangled/rbac" 16 + ) 17 + 18 + const ( 19 + ThisServer = "thisserver" // resource identifier for rbac enforcement 15 20 ) 16 21 17 22 type Handle struct { 18 23 c *config.Config 19 24 db *db.DB 20 25 js *jsclient.JetstreamClient 26 + e *rbac.Enforcer 21 27 22 28 // init is a channel that is closed when the knot has been initailized 23 29 // i.e. when the first user (knot owner) has been added. ··· 25 31 knotInitialized bool 26 32 } 27 33 28 - func Setup(ctx context.Context, c *config.Config, db *db.DB) (http.Handler, error) { 34 + func Setup(ctx context.Context, c *config.Config, db *db.DB, e *rbac.Enforcer) (http.Handler, error) { 29 35 r := chi.NewRouter() 30 36 31 37 h := Handle{ 32 38 c: c, 33 39 db: db, 40 + e: e, 34 41 init: make(chan struct{}), 35 42 } 36 43 37 - err := h.StartJetstream(ctx) 44 + err := e.AddDomain(ThisServer) 45 + if err != nil { 46 + return nil, fmt.Errorf("failed to setup enforcer: %w", err) 47 + } 48 + 49 + err = h.StartJetstream(ctx) 38 50 if err != nil { 39 51 return nil, fmt.Errorf("failed to start jetstream: %w", err) 40 52 } ··· 94 106 } 95 107 96 108 func (h *Handle) StartJetstream(ctx context.Context) error { 97 - colections := []string{tangled.PublicKeyNSID} 109 + collections := []string{tangled.PublicKeyNSID, tangled.KnotMemberNSID} 98 110 dids := []string{} 99 111 100 - h.js = jsclient.NewJetstreamClient(colections, dids) 112 + h.js = jsclient.NewJetstreamClient(collections, dids) 101 113 messages, err := h.js.ReadJetstream(ctx) 102 114 if err != nil { 103 115 return fmt.Errorf("failed to read from jetstream: %w", err) ··· 126 138 log.Printf("failed to add public key: %v", err) 127 139 } else { 128 140 log.Printf("added public key from firehose: %s", data["did"]) 141 + } 142 + case tangled.KnotMemberNSID: 143 + did := data["did"].(string) 144 + record := commit["record"].(map[string]interface{}) 145 + ok, err := h.e.E.Enforce(did, ThisServer, ThisServer, "server:invite") 146 + if err != nil || !ok { 147 + log.Printf("failed to add member from did %s", did) 148 + } else { 149 + log.Printf("adding member") 150 + h.e.AddMember(ThisServer, record["member"].(string)) 129 151 } 130 152 default: 131 153 }
+8
knotserver/jsclient/jetstream.go
··· 55 55 j.triggerReconnect() 56 56 } 57 57 58 + // Adds one did to the did list 59 + func (j *JetstreamClient) AddDid(did string) { 60 + j.mu.Lock() 61 + j.dids = append(j.dids, did) 62 + j.mu.Unlock() 63 + j.triggerReconnect() 64 + } 65 + 58 66 func (j *JetstreamClient) triggerReconnect() { 59 67 select { 60 68 case j.reconnectCh <- struct{}{}:
+1
knotserver/routes.go
··· 432 432 } 433 433 434 434 h.js.UpdateDids([]string{data.Did}) 435 + h.e.AddOwner(ThisServer, data.Did) 435 436 // Signal that the knot is ready 436 437 close(h.init) 437 438
+33
lexicons/member.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.tangled.knot.member", 4 + "needsCbor": true, 5 + "needsType": true, 6 + "defs": { 7 + "main": { 8 + "type": "record", 9 + "key": "tid", 10 + "record": { 11 + "type": "object", 12 + "required": [ 13 + "member", 14 + "domain" 15 + ], 16 + "properties": { 17 + "member": { 18 + "type": "string", 19 + "format": "did" 20 + }, 21 + "domain": { 22 + "type": "string", 23 + "description": "domain that this member now belongs to" 24 + }, 25 + "addedAt": { 26 + "type": "string", 27 + "format": "datetime" 28 + } 29 + } 30 + } 31 + } 32 + } 33 + }
-34
lexicons/policy.json
··· 1 - { 2 - "lexicon": 1, 3 - "id": "sh.tangled.knot.policy", 4 - "needsCbor": true, 5 - "needsType": true, 6 - "defs": { 7 - "main": { 8 - "type": "record", 9 - "key": "tid", 10 - "record": { 11 - "type": "object", 12 - "required": ["subject", "domain", "object", "action"], 13 - "properties": { 14 - "subject": { 15 - "type": "string", 16 - "description": "subject of the key" 17 - }, 18 - "domain": { 19 - "type": "string", 20 - "description": "domain of the key" 21 - }, 22 - "object": { 23 - "type": "string", 24 - "description": "object associated with the key" 25 - }, 26 - "action": { 27 - "type": "string", 28 - "description": "action associated with the key" 29 - } 30 - } 31 - } 32 - } 33 - } 34 - }