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

refactor signed client

Changed files
+176 -43
appview
knotserver
rbac
+93 -9
appview/state/signer.go
··· 1 package state 2 3 import ( 4 "crypto/hmac" 5 "crypto/sha256" 6 "encoding/hex" 7 "net/http" 8 "time" 9 ) 10 11 type SignerTransport struct { 12 Secret string 13 - } 14 - 15 - func SignedClient(secret string) *http.Client { 16 - return &http.Client{ 17 - Timeout: 5 * time.Second, 18 - Transport: SignerTransport{ 19 - Secret: secret, 20 - }, 21 - } 22 } 23 24 func (s SignerTransport) RoundTrip(req *http.Request) (*http.Response, error) { ··· 31 req.Header.Set("X-Timestamp", timestamp) 32 return http.DefaultTransport.RoundTrip(req) 33 }
··· 1 package state 2 3 import ( 4 + "bytes" 5 "crypto/hmac" 6 "crypto/sha256" 7 "encoding/hex" 8 + "encoding/json" 9 + "fmt" 10 "net/http" 11 + "net/url" 12 "time" 13 ) 14 15 type SignerTransport struct { 16 Secret string 17 } 18 19 func (s SignerTransport) RoundTrip(req *http.Request) (*http.Response, error) { ··· 26 req.Header.Set("X-Timestamp", timestamp) 27 return http.DefaultTransport.RoundTrip(req) 28 } 29 + 30 + type SignedClient struct { 31 + Secret string 32 + Url *url.URL 33 + client *http.Client 34 + } 35 + 36 + func NewSignedClient(domain, secret string) (*SignedClient, error) { 37 + client := &http.Client{ 38 + Timeout: 5 * time.Second, 39 + Transport: SignerTransport{ 40 + Secret: secret, 41 + }, 42 + } 43 + 44 + url, err := url.Parse(fmt.Sprintf("http://%s", domain)) 45 + if err != nil { 46 + return nil, err 47 + } 48 + 49 + signedClient := &SignedClient{ 50 + Secret: secret, 51 + client: client, 52 + Url: url, 53 + } 54 + 55 + return signedClient, nil 56 + } 57 + 58 + func (s *SignedClient) newRequest(method, endpoint string, body []byte) (*http.Request, error) { 59 + return http.NewRequest(method, s.Url.JoinPath(endpoint).String(), bytes.NewReader(body)) 60 + } 61 + 62 + func (s *SignedClient) Init(did string, keys []string) (*http.Response, error) { 63 + const ( 64 + Method = "POST" 65 + Endpoint = "/init" 66 + ) 67 + 68 + body, _ := json.Marshal(map[string]interface{}{ 69 + "did": did, 70 + "keys": keys, 71 + }) 72 + 73 + req, err := s.newRequest(Method, Endpoint, body) 74 + if err != nil { 75 + return nil, err 76 + } 77 + 78 + return s.client.Do(req) 79 + } 80 + 81 + func (s *SignedClient) NewRepo(did, repoName string) (*http.Response, error) { 82 + const ( 83 + Method = "PUT" 84 + Endpoint = "/repo/new" 85 + ) 86 + 87 + body, _ := json.Marshal(map[string]interface{}{ 88 + "did": did, 89 + "name": repoName, 90 + }) 91 + 92 + req, err := s.newRequest(Method, Endpoint, body) 93 + if err != nil { 94 + return nil, err 95 + } 96 + 97 + return s.client.Do(req) 98 + } 99 + 100 + func (s *SignedClient) AddMember(did string, keys []string) (*http.Response, error) { 101 + const ( 102 + Method = "PUT" 103 + Endpoint = "/member/add" 104 + ) 105 + 106 + body, _ := json.Marshal(map[string]interface{}{ 107 + "did": did, 108 + "keys": keys, 109 + }) 110 + 111 + req, err := s.newRequest(Method, Endpoint, body) 112 + if err != nil { 113 + return nil, err 114 + } 115 + 116 + return s.client.Do(req) 117 + }
+43 -30
appview/state/state.go
··· 1 package state 2 3 import ( 4 - "bytes" 5 "crypto/hmac" 6 "crypto/sha256" 7 "encoding/hex" 8 - "encoding/json" 9 "fmt" 10 "log" 11 "net/http" 12 "strings" 13 "time" 14 ··· 234 } 235 log.Println("checking ", domain) 236 237 - url := fmt.Sprintf("http://%s/init", domain) 238 - 239 - body, _ := json.Marshal(map[string]interface{}{ 240 - "did": user.Did, 241 - "keys": []string{}, 242 - }) 243 - pingRequest, err := http.NewRequest("POST", url, bytes.NewBuffer(body)) 244 if err != nil { 245 - log.Println("failed to build ping request", err) 246 return 247 } 248 249 - secret, err := s.db.GetRegistrationKey(domain) 250 if err != nil { 251 - log.Printf("no key found for domain %s: %s\n", domain, err) 252 - return 253 } 254 - client := SignedClient(secret) 255 256 - resp, err := client.Do(pingRequest) 257 if err != nil { 258 w.Write([]byte("no dice")) 259 log.Println("domain was unreachable after 5 seconds") ··· 338 339 var members []string 340 if reg.Registered != nil { 341 - members, err = s.enforcer.E.GetUsersForRole("server:member", domain) 342 if err != nil { 343 w.Write([]byte("failed to fetch member list")) 344 return 345 } 346 } 347 348 - ok, err := s.enforcer.E.HasGroupingPolicy(user.Did, "server:owner", domain) 349 isOwner := err == nil && ok 350 351 p := pages.KnotParams{ ··· 382 } 383 384 // list all members for this domain 385 - memberDids, err := s.enforcer.E.GetUsersForRole("server:member", domain) 386 if err != nil { 387 w.Write([]byte("failed to fetch member list")) 388 return ··· 433 log.Printf("failed to create record: %s", err) 434 return 435 } 436 437 - log.Println("created atproto record: ", resp.Uri) 438 439 err = s.enforcer.AddMember(domain, memberIdent.DID.String()) 440 if err != nil { ··· 481 return 482 } 483 484 - client := SignedClient(secret) 485 - url := fmt.Sprintf("http://%s/repo/new", domain) 486 - body, _ := json.Marshal(map[string]interface{}{ 487 - "did": user.Did, 488 - "name": repoName, 489 - }) 490 - createRepoRequest, err := http.NewRequest("PUT", url, bytes.NewReader(body)) 491 492 - resp, err := client.Do(createRepoRequest) 493 - 494 if err != nil { 495 log.Println("failed to send create repo request", err) 496 return 497 } 498 - 499 if resp.StatusCode != http.StatusNoContent { 500 log.Println("server returned ", resp.StatusCode) 501 return ··· 507 Name: repoName, 508 Knot: domain, 509 } 510 - 511 err = s.db.AddRepo(repo) 512 if err != nil { 513 log.Println("failed to add repo to db", err) 514 return 515 } 516
··· 1 package state 2 3 import ( 4 "crypto/hmac" 5 "crypto/sha256" 6 "encoding/hex" 7 "fmt" 8 "log" 9 "net/http" 10 + "path/filepath" 11 "strings" 12 "time" 13 ··· 233 } 234 log.Println("checking ", domain) 235 236 + secret, err := s.db.GetRegistrationKey(domain) 237 if err != nil { 238 + log.Printf("no key found for domain %s: %s\n", domain, err) 239 return 240 } 241 242 + client, err := NewSignedClient(domain, secret) 243 if err != nil { 244 + log.Println("failed to create client to ", domain) 245 } 246 247 + resp, err := client.Init(user.Did, []string{}) 248 if err != nil { 249 w.Write([]byte("no dice")) 250 log.Println("domain was unreachable after 5 seconds") ··· 329 330 var members []string 331 if reg.Registered != nil { 332 + members, err = s.enforcer.GetUserByRole("server:member", domain) 333 if err != nil { 334 w.Write([]byte("failed to fetch member list")) 335 return 336 } 337 } 338 339 + ok, err := s.enforcer.IsServerOwner(user.Did, domain) 340 isOwner := err == nil && ok 341 342 p := pages.KnotParams{ ··· 373 } 374 375 // list all members for this domain 376 + memberDids, err := s.enforcer.GetUserByRole("server:member", domain) 377 if err != nil { 378 w.Write([]byte("failed to fetch member list")) 379 return ··· 424 log.Printf("failed to create record: %s", err) 425 return 426 } 427 + log.Println("created atproto record: ", resp.Uri) 428 429 + secret, err := s.db.GetRegistrationKey(domain) 430 + if err != nil { 431 + log.Printf("no key found for domain %s: %s\n", domain, err) 432 + return 433 + } 434 + 435 + ksClient, err := NewSignedClient(domain, secret) 436 + if err != nil { 437 + log.Println("failed to create client to ", domain) 438 + return 439 + } 440 + 441 + ksResp, err := ksClient.AddMember(memberIdent.DID.String(), []string{}) 442 + if err != nil { 443 + log.Printf("failet to make request to %s: %s", domain, err) 444 + } 445 + 446 + if ksResp.StatusCode != http.StatusNoContent { 447 + w.Write([]byte(fmt.Sprint("knotserver failed to add member: ", err))) 448 + return 449 + } 450 451 err = s.enforcer.AddMember(domain, memberIdent.DID.String()) 452 if err != nil { ··· 493 return 494 } 495 496 + client, err := NewSignedClient(domain, secret) 497 + if err != nil { 498 + log.Println("failed to create client to ", domain) 499 + } 500 501 + resp, err := client.NewRepo(user.Did, repoName) 502 if err != nil { 503 log.Println("failed to send create repo request", err) 504 return 505 } 506 if resp.StatusCode != http.StatusNoContent { 507 log.Println("server returned ", resp.StatusCode) 508 return ··· 514 Name: repoName, 515 Knot: domain, 516 } 517 err = s.db.AddRepo(repo) 518 if err != nil { 519 log.Println("failed to add repo to db", err) 520 + return 521 + } 522 + 523 + // acls 524 + err = s.enforcer.AddRepo(user.Did, domain, filepath.Join(user.Did, repoName)) 525 + if err != nil { 526 + log.Println("failed to set up acls", err) 527 return 528 } 529
+1 -1
knotserver/handler.go
··· 92 93 r.Route("/member", func(r chi.Router) { 94 r.Use(h.VerifySignature) 95 - r.Put("/add", h.NewRepo) 96 }) 97 98 // Initialize the knot with an owner and public key.
··· 92 93 r.Route("/member", func(r chi.Router) { 94 r.Use(h.VerifySignature) 95 + r.Put("/add", h.AddMember) 96 }) 97 98 // Initialize the knot with an owner and public key.
+8 -3
knotserver/jetstream.go
··· 7 "io" 8 "log" 9 "net/http" 10 - "path" 11 "strings" 12 "time" 13 ··· 66 } 67 68 func (h *Handle) fetchAndAddKeys(did string) { 69 - resp, err := http.Get(path.Join(h.c.AppViewEndpoint, did)) 70 if err != nil { 71 log.Printf("error getting keys for %s: %v", did, err) 72 return ··· 112 } 113 114 func (h *Handle) processMessages(messages <-chan []byte) { 115 - log.Println("waiting for knot to be initialized") 116 <-h.init 117 log.Println("initalized jetstream watcher") 118
··· 7 "io" 8 "log" 9 "net/http" 10 + "net/url" 11 "strings" 12 "time" 13 ··· 66 } 67 68 func (h *Handle) fetchAndAddKeys(did string) { 69 + keysEndpoint, err := url.JoinPath(h.c.AppViewEndpoint, "keys", did) 70 + if err != nil { 71 + log.Printf("error building endpoint url: %s: %v", did, err) 72 + return 73 + } 74 + 75 + resp, err := http.Get(keysEndpoint) 76 if err != nil { 77 log.Printf("error getting keys for %s: %v", did, err) 78 return ··· 118 } 119 120 func (h *Handle) processMessages(messages <-chan []byte) { 121 <-h.init 122 log.Println("initalized jetstream watcher") 123
+31
rbac/rbac.go
··· 3 import ( 4 "database/sql" 5 "path" 6 7 sqladapter "github.com/Blank-Xu/sql-adapter" 8 "github.com/casbin/casbin/v2" ··· 98 {"server:owner", domain, repo, "repo:delete"}, // server owner can delete any repo 99 }) 100 return err 101 } 102 103 // keyMatch2Func is a wrapper for keyMatch2 to make it compatible with Casbin
··· 3 import ( 4 "database/sql" 5 "path" 6 + "strings" 7 8 sqladapter "github.com/Blank-Xu/sql-adapter" 9 "github.com/casbin/casbin/v2" ··· 99 {"server:owner", domain, repo, "repo:delete"}, // server owner can delete any repo 100 }) 101 return err 102 + } 103 + 104 + func (e *Enforcer) GetUserByRole(role, domain string) ([]string, error) { 105 + var membersWithoutRoles []string 106 + 107 + // this includes roles too, casbin does not differentiate. 108 + // the filtering criteria is to remove strings not starting with `did:` 109 + members, err := e.E.Enforcer.GetImplicitUsersForRole(role, domain) 110 + for _, m := range members { 111 + if strings.HasPrefix(m, "did:") { 112 + membersWithoutRoles = append(membersWithoutRoles, m) 113 + } 114 + } 115 + if err != nil { 116 + return nil, err 117 + } 118 + 119 + return membersWithoutRoles, nil 120 + } 121 + 122 + func (e *Enforcer) isRole(user, role, domain string) (bool, error) { 123 + return e.E.HasGroupingPolicy(user, role, domain) 124 + } 125 + 126 + func (e *Enforcer) IsServerOwner(user, domain string) (bool, error) { 127 + return e.isRole(user, "server:owner", domain) 128 + } 129 + 130 + func (e *Enforcer) IsServerMember(user, domain string) (bool, error) { 131 + return e.isRole(user, "server:member", domain) 132 } 133 134 // keyMatch2Func is a wrapper for keyMatch2 to make it compatible with Casbin