forked from tangled.org/core
Monorepo for Tangled — https://tangled.org
1package state 2 3import ( 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 15type SignerTransport struct { 16 Secret string 17} 18 19func (s SignerTransport) RoundTrip(req *http.Request) (*http.Response, error) { 20 timestamp := time.Now().Format(time.RFC3339) 21 mac := hmac.New(sha256.New, []byte(s.Secret)) 22 message := req.Method + req.URL.Path + timestamp 23 mac.Write([]byte(message)) 24 signature := hex.EncodeToString(mac.Sum(nil)) 25 req.Header.Set("X-Signature", signature) 26 req.Header.Set("X-Timestamp", timestamp) 27 return http.DefaultTransport.RoundTrip(req) 28} 29 30type SignedClient struct { 31 Secret string 32 Url *url.URL 33 client *http.Client 34} 35 36func NewSignedClient(domain, secret string, dev bool) (*SignedClient, error) { 37 client := &http.Client{ 38 Timeout: 5 * time.Second, 39 Transport: SignerTransport{ 40 Secret: secret, 41 }, 42 } 43 44 scheme := "https" 45 if dev { 46 scheme = "http" 47 } 48 url, err := url.Parse(fmt.Sprintf("%s://%s", scheme, domain)) 49 if err != nil { 50 return nil, err 51 } 52 53 signedClient := &SignedClient{ 54 Secret: secret, 55 client: client, 56 Url: url, 57 } 58 59 return signedClient, nil 60} 61 62func (s *SignedClient) newRequest(method, endpoint string, body []byte) (*http.Request, error) { 63 return http.NewRequest(method, s.Url.JoinPath(endpoint).String(), bytes.NewReader(body)) 64} 65 66func (s *SignedClient) Init(did string) (*http.Response, error) { 67 const ( 68 Method = "POST" 69 Endpoint = "/init" 70 ) 71 72 body, _ := json.Marshal(map[string]any{ 73 "did": did, 74 }) 75 76 req, err := s.newRequest(Method, Endpoint, body) 77 if err != nil { 78 return nil, err 79 } 80 81 return s.client.Do(req) 82} 83 84func (s *SignedClient) NewRepo(did, repoName, defaultBranch string) (*http.Response, error) { 85 const ( 86 Method = "PUT" 87 Endpoint = "/repo/new" 88 ) 89 90 body, _ := json.Marshal(map[string]any{ 91 "did": did, 92 "name": repoName, 93 "default_branch": defaultBranch, 94 }) 95 96 req, err := s.newRequest(Method, Endpoint, body) 97 if err != nil { 98 return nil, err 99 } 100 101 return s.client.Do(req) 102} 103 104func (s *SignedClient) RemoveRepo(did, repoName string) (*http.Response, error) { 105 const ( 106 Method = "DELETE" 107 Endpoint = "/repo" 108 ) 109 110 body, _ := json.Marshal(map[string]any{ 111 "did": did, 112 "name": repoName, 113 }) 114 115 req, err := s.newRequest(Method, Endpoint, body) 116 if err != nil { 117 return nil, err 118 } 119 120 return s.client.Do(req) 121} 122 123func (s *SignedClient) AddMember(did string) (*http.Response, error) { 124 const ( 125 Method = "PUT" 126 Endpoint = "/member/add" 127 ) 128 129 body, _ := json.Marshal(map[string]any{ 130 "did": did, 131 }) 132 133 req, err := s.newRequest(Method, Endpoint, body) 134 if err != nil { 135 return nil, err 136 } 137 138 return s.client.Do(req) 139} 140 141func (s *SignedClient) AddCollaborator(ownerDid, repoName, memberDid string) (*http.Response, error) { 142 const ( 143 Method = "POST" 144 ) 145 endpoint := fmt.Sprintf("/%s/%s/collaborator/add", ownerDid, repoName) 146 147 body, _ := json.Marshal(map[string]any{ 148 "did": memberDid, 149 }) 150 151 req, err := s.newRequest(Method, endpoint, body) 152 if err != nil { 153 return nil, err 154 } 155 156 return s.client.Do(req) 157} 158 159func (s *SignedClient) Merge(patch []byte, ownerDid, targetRepo, branch string) (*http.Response, error) { 160 const ( 161 Method = "POST" 162 ) 163 endpoint := fmt.Sprintf("/%s/%s/merge", ownerDid, targetRepo) 164 165 body, _ := json.Marshal(map[string]interface{}{ 166 "patch": string(patch), 167 "branch": branch, 168 }) 169 170 req, err := s.newRequest(Method, endpoint, body) 171 if err != nil { 172 return nil, err 173 } 174 175 return s.client.Do(req) 176} 177 178func (s *SignedClient) MergeCheck(patch []byte, ownerDid, targetRepo, branch string) (*http.Response, error) { 179 const ( 180 Method = "POST" 181 ) 182 endpoint := fmt.Sprintf("/%s/%s/merge/check", ownerDid, targetRepo) 183 184 body, _ := json.Marshal(map[string]interface{}{ 185 "patch": string(patch), 186 "branch": branch, 187 }) 188 189 req, err := s.newRequest(Method, endpoint, body) 190 if err != nil { 191 return nil, err 192 } 193 194 return s.client.Do(req) 195} 196 197type UnsignedClient struct { 198 Url *url.URL 199 client *http.Client 200} 201 202func NewUnsignedClient(domain string, dev bool) (*UnsignedClient, error) { 203 client := &http.Client{ 204 Timeout: 5 * time.Second, 205 } 206 207 scheme := "https" 208 if dev { 209 scheme = "http" 210 } 211 url, err := url.Parse(fmt.Sprintf("%s://%s", scheme, domain)) 212 if err != nil { 213 return nil, err 214 } 215 216 unsignedClient := &UnsignedClient{ 217 client: client, 218 Url: url, 219 } 220 221 return unsignedClient, nil 222} 223 224func (us *UnsignedClient) newRequest(method, endpoint string, body []byte) (*http.Request, error) { 225 return http.NewRequest(method, us.Url.JoinPath(endpoint).String(), bytes.NewReader(body)) 226} 227 228func (us *UnsignedClient) Index(ownerDid, repoName, ref string) (*http.Response, error) { 229 const ( 230 Method = "GET" 231 ) 232 233 endpoint := fmt.Sprintf("/%s/%s/tree/%s", ownerDid, repoName, ref) 234 if ref == "" { 235 endpoint = fmt.Sprintf("/%s/%s", ownerDid, repoName) 236 } 237 238 req, err := us.newRequest(Method, endpoint, nil) 239 if err != nil { 240 return nil, err 241 } 242 243 return us.client.Do(req) 244} 245 246func (us *UnsignedClient) Branches(ownerDid, repoName string) (*http.Response, error) { 247 const ( 248 Method = "GET" 249 ) 250 251 endpoint := fmt.Sprintf("/%s/%s/branches", ownerDid, repoName) 252 253 req, err := us.newRequest(Method, endpoint, nil) 254 if err != nil { 255 return nil, err 256 } 257 258 return us.client.Do(req) 259}