this repo has no description
1package state 2 3import ( 4 "encoding/json" 5 "fmt" 6 "io" 7 "log" 8 "net/http" 9 "path" 10 "strings" 11 12 "github.com/bluesky-social/indigo/atproto/identity" 13 securejoin "github.com/cyphar/filepath-securejoin" 14 "github.com/go-chi/chi/v5" 15 "github.com/sotangled/tangled/appview/auth" 16 "github.com/sotangled/tangled/appview/pages" 17 "github.com/sotangled/tangled/types" 18) 19 20func (s *State) RepoIndex(w http.ResponseWriter, r *http.Request) { 21 ref := chi.URLParam(r, "ref") 22 f, err := fullyResolvedRepo(r) 23 if err != nil { 24 log.Println("failed to fully resolve repo", err) 25 return 26 } 27 var reqUrl string 28 if ref != "" { 29 reqUrl = fmt.Sprintf("http://%s/%s/%s/tree/%s", f.Knot, f.OwnerDid(), f.RepoName, ref) 30 } else { 31 reqUrl = fmt.Sprintf("http://%s/%s/%s", f.Knot, f.OwnerDid(), f.RepoName) 32 } 33 34 resp, err := http.Get(reqUrl) 35 if err != nil { 36 s.pages.Error503(w) 37 log.Println("failed to reach knotserver", err) 38 return 39 } 40 defer resp.Body.Close() 41 42 body, err := io.ReadAll(resp.Body) 43 if err != nil { 44 log.Fatalf("Error reading response body: %v", err) 45 return 46 } 47 48 var result types.RepoIndexResponse 49 err = json.Unmarshal(body, &result) 50 if err != nil { 51 log.Fatalf("Error unmarshalling response body: %v", err) 52 return 53 } 54 55 log.Println(resp.Status, result) 56 57 user := s.auth.GetUser(r) 58 s.pages.RepoIndexPage(w, pages.RepoIndexParams{ 59 LoggedInUser: user, 60 RepoInfo: pages.RepoInfo{ 61 OwnerDid: f.OwnerDid(), 62 OwnerHandle: f.OwnerHandle(), 63 Name: f.RepoName, 64 SettingsAllowed: settingsAllowed(s, user, f), 65 }, 66 RepoIndexResponse: result, 67 }) 68 69 return 70} 71 72func (s *State) RepoLog(w http.ResponseWriter, r *http.Request) { 73 f, err := fullyResolvedRepo(r) 74 if err != nil { 75 log.Println("failed to fully resolve repo", err) 76 return 77 } 78 79 ref := chi.URLParam(r, "ref") 80 resp, err := http.Get(fmt.Sprintf("http://%s/%s/%s/log/%s", f.Knot, f.OwnerDid(), f.RepoName, ref)) 81 if err != nil { 82 log.Println("failed to reach knotserver", err) 83 return 84 } 85 86 body, err := io.ReadAll(resp.Body) 87 if err != nil { 88 log.Fatalf("Error reading response body: %v", err) 89 return 90 } 91 92 var result types.RepoLogResponse 93 err = json.Unmarshal(body, &result) 94 if err != nil { 95 log.Println("failed to parse json response", err) 96 return 97 } 98 99 user := s.auth.GetUser(r) 100 s.pages.RepoLog(w, pages.RepoLogParams{ 101 LoggedInUser: user, 102 RepoInfo: pages.RepoInfo{ 103 OwnerDid: f.OwnerDid(), 104 OwnerHandle: f.OwnerHandle(), 105 Name: f.RepoName, 106 SettingsAllowed: settingsAllowed(s, user, f), 107 }, 108 RepoLogResponse: result, 109 }) 110 return 111} 112 113func (s *State) RepoCommit(w http.ResponseWriter, r *http.Request) { 114 f, err := fullyResolvedRepo(r) 115 if err != nil { 116 log.Println("failed to fully resolve repo", err) 117 return 118 } 119 120 ref := chi.URLParam(r, "ref") 121 resp, err := http.Get(fmt.Sprintf("http://%s/%s/%s/commit/%s", f.Knot, f.OwnerDid(), f.RepoName, ref)) 122 if err != nil { 123 log.Println("failed to reach knotserver", err) 124 return 125 } 126 127 body, err := io.ReadAll(resp.Body) 128 if err != nil { 129 log.Fatalf("Error reading response body: %v", err) 130 return 131 } 132 133 var result types.RepoCommitResponse 134 err = json.Unmarshal(body, &result) 135 if err != nil { 136 log.Println("failed to parse response:", err) 137 return 138 } 139 140 user := s.auth.GetUser(r) 141 s.pages.RepoCommit(w, pages.RepoCommitParams{ 142 LoggedInUser: user, 143 RepoInfo: pages.RepoInfo{ 144 OwnerDid: f.OwnerDid(), 145 OwnerHandle: f.OwnerHandle(), 146 Name: f.RepoName, 147 SettingsAllowed: settingsAllowed(s, user, f), 148 }, 149 RepoCommitResponse: result, 150 }) 151 return 152} 153 154func (s *State) RepoTree(w http.ResponseWriter, r *http.Request) { 155 f, err := fullyResolvedRepo(r) 156 if err != nil { 157 log.Println("failed to fully resolve repo", err) 158 return 159 } 160 161 ref := chi.URLParam(r, "ref") 162 treePath := chi.URLParam(r, "*") 163 resp, err := http.Get(fmt.Sprintf("http://%s/%s/%s/tree/%s/%s", f.Knot, f.OwnerDid(), f.RepoName, ref, treePath)) 164 if err != nil { 165 log.Println("failed to reach knotserver", err) 166 return 167 } 168 169 body, err := io.ReadAll(resp.Body) 170 if err != nil { 171 log.Fatalf("Error reading response body: %v", err) 172 return 173 } 174 175 var result types.RepoTreeResponse 176 err = json.Unmarshal(body, &result) 177 if err != nil { 178 log.Println("failed to parse response:", err) 179 return 180 } 181 182 user := s.auth.GetUser(r) 183 184 var breadcrumbs [][]string 185 breadcrumbs = append(breadcrumbs, []string{f.RepoName, fmt.Sprintf("/%s/%s/tree/%s", f.OwnerDid(), f.RepoName, ref)}) 186 if treePath != "" { 187 for idx, elem := range strings.Split(treePath, "/") { 188 breadcrumbs = append(breadcrumbs, []string{elem, fmt.Sprintf("%s/%s", breadcrumbs[idx][1], elem)}) 189 } 190 } 191 192 baseTreeLink := path.Join(f.OwnerDid(), f.RepoName, "tree", ref, treePath) 193 baseBlobLink := path.Join(f.OwnerDid(), f.RepoName, "blob", ref, treePath) 194 195 s.pages.RepoTree(w, pages.RepoTreeParams{ 196 LoggedInUser: user, 197 BreadCrumbs: breadcrumbs, 198 BaseTreeLink: baseTreeLink, 199 BaseBlobLink: baseBlobLink, 200 RepoInfo: pages.RepoInfo{ 201 OwnerDid: f.OwnerDid(), 202 OwnerHandle: f.OwnerHandle(), 203 Name: f.RepoName, 204 SettingsAllowed: settingsAllowed(s, user, f), 205 }, 206 RepoTreeResponse: result, 207 }) 208 return 209} 210 211func (s *State) RepoTags(w http.ResponseWriter, r *http.Request) { 212 f, err := fullyResolvedRepo(r) 213 if err != nil { 214 log.Println("failed to get repo and knot", err) 215 return 216 } 217 218 resp, err := http.Get(fmt.Sprintf("http://%s/%s/%s/tags", f.Knot, f.OwnerDid(), f.RepoName)) 219 if err != nil { 220 log.Println("failed to reach knotserver", err) 221 return 222 } 223 224 body, err := io.ReadAll(resp.Body) 225 if err != nil { 226 log.Fatalf("Error reading response body: %v", err) 227 return 228 } 229 230 var result types.RepoTagsResponse 231 err = json.Unmarshal(body, &result) 232 if err != nil { 233 log.Println("failed to parse response:", err) 234 return 235 } 236 237 user := s.auth.GetUser(r) 238 s.pages.RepoTags(w, pages.RepoTagsParams{ 239 LoggedInUser: user, 240 RepoInfo: pages.RepoInfo{ 241 OwnerDid: f.OwnerDid(), 242 OwnerHandle: f.OwnerHandle(), 243 Name: f.RepoName, 244 SettingsAllowed: settingsAllowed(s, user, f), 245 }, 246 RepoTagsResponse: result, 247 }) 248 return 249} 250 251func (s *State) RepoBranches(w http.ResponseWriter, r *http.Request) { 252 f, err := fullyResolvedRepo(r) 253 if err != nil { 254 log.Println("failed to get repo and knot", err) 255 return 256 } 257 258 resp, err := http.Get(fmt.Sprintf("http://%s/%s/%s/branches", f.Knot, f.OwnerDid(), f.RepoName)) 259 if err != nil { 260 log.Println("failed to reach knotserver", err) 261 return 262 } 263 264 body, err := io.ReadAll(resp.Body) 265 if err != nil { 266 log.Fatalf("Error reading response body: %v", err) 267 return 268 } 269 270 var result types.RepoBranchesResponse 271 err = json.Unmarshal(body, &result) 272 if err != nil { 273 log.Println("failed to parse response:", err) 274 return 275 } 276 277 log.Println(result) 278 279 user := s.auth.GetUser(r) 280 s.pages.RepoBranches(w, pages.RepoBranchesParams{ 281 LoggedInUser: user, 282 RepoInfo: pages.RepoInfo{ 283 OwnerDid: f.OwnerDid(), 284 OwnerHandle: f.OwnerHandle(), 285 Name: f.RepoName, 286 SettingsAllowed: settingsAllowed(s, user, f), 287 }, 288 RepoBranchesResponse: result, 289 }) 290 return 291} 292 293func (s *State) RepoBlob(w http.ResponseWriter, r *http.Request) { 294 f, err := fullyResolvedRepo(r) 295 if err != nil { 296 log.Println("failed to get repo and knot", err) 297 return 298 } 299 300 ref := chi.URLParam(r, "ref") 301 filePath := chi.URLParam(r, "*") 302 resp, err := http.Get(fmt.Sprintf("http://%s/%s/%s/blob/%s/%s", f.Knot, f.OwnerDid(), f.RepoName, ref, filePath)) 303 if err != nil { 304 log.Println("failed to reach knotserver", err) 305 return 306 } 307 308 body, err := io.ReadAll(resp.Body) 309 if err != nil { 310 log.Fatalf("Error reading response body: %v", err) 311 return 312 } 313 314 var result types.RepoBlobResponse 315 err = json.Unmarshal(body, &result) 316 if err != nil { 317 log.Println("failed to parse response:", err) 318 return 319 } 320 321 var breadcrumbs [][]string 322 breadcrumbs = append(breadcrumbs, []string{f.RepoName, fmt.Sprintf("/%s/%s/tree/%s", f.OwnerDid(), f.RepoName, ref)}) 323 if filePath != "" { 324 for idx, elem := range strings.Split(filePath, "/") { 325 breadcrumbs = append(breadcrumbs, []string{elem, fmt.Sprintf("%s/%s", breadcrumbs[idx][1], elem)}) 326 } 327 } 328 329 user := s.auth.GetUser(r) 330 s.pages.RepoBlob(w, pages.RepoBlobParams{ 331 LoggedInUser: user, 332 RepoInfo: pages.RepoInfo{ 333 OwnerDid: f.OwnerDid(), 334 OwnerHandle: f.OwnerHandle(), 335 Name: f.RepoName, 336 SettingsAllowed: settingsAllowed(s, user, f), 337 }, 338 RepoBlobResponse: result, 339 BreadCrumbs: breadcrumbs, 340 }) 341 return 342} 343 344func (s *State) AddCollaborator(w http.ResponseWriter, r *http.Request) { 345 f, err := fullyResolvedRepo(r) 346 if err != nil { 347 log.Println("failed to get repo and knot", err) 348 return 349 } 350 351 collaborator := r.FormValue("collaborator") 352 if collaborator == "" { 353 http.Error(w, "malformed form", http.StatusBadRequest) 354 return 355 } 356 357 collaboratorIdent, err := s.resolver.ResolveIdent(r.Context(), collaborator) 358 if err != nil { 359 w.Write([]byte("failed to resolve collaborator did to a handle")) 360 return 361 } 362 log.Printf("adding %s to %s\n", collaboratorIdent.Handle.String(), f.Knot) 363 364 // TODO: create an atproto record for this 365 366 secret, err := s.db.GetRegistrationKey(f.Knot) 367 if err != nil { 368 log.Printf("no key found for domain %s: %s\n", f.Knot, err) 369 return 370 } 371 372 ksClient, err := NewSignedClient(f.Knot, secret) 373 if err != nil { 374 log.Println("failed to create client to ", f.Knot) 375 return 376 } 377 378 ksResp, err := ksClient.AddCollaborator(f.OwnerDid(), f.RepoName, collaboratorIdent.DID.String()) 379 if err != nil { 380 log.Printf("failed to make request to %s: %s", f.Knot, err) 381 return 382 } 383 384 if ksResp.StatusCode != http.StatusNoContent { 385 w.Write([]byte(fmt.Sprint("knotserver failed to add collaborator: ", err))) 386 return 387 } 388 389 err = s.enforcer.AddCollaborator(collaboratorIdent.DID.String(), f.Knot, f.OwnerSlashRepo()) 390 if err != nil { 391 w.Write([]byte(fmt.Sprint("failed to add collaborator: ", err))) 392 return 393 } 394 395 w.Write([]byte(fmt.Sprint("added collaborator: ", collaboratorIdent.Handle.String()))) 396 397} 398 399func (s *State) RepoSettings(w http.ResponseWriter, r *http.Request) { 400 f, err := fullyResolvedRepo(r) 401 if err != nil { 402 log.Println("failed to get repo and knot", err) 403 return 404 } 405 406 switch r.Method { 407 case http.MethodGet: 408 // for now, this is just pubkeys 409 user := s.auth.GetUser(r) 410 repoCollaborators, err := s.enforcer.E.GetImplicitUsersForResourceByDomain(f.OwnerSlashRepo(), f.Knot) 411 if err != nil { 412 log.Println("failed to get collaborators", err) 413 } 414 log.Println(repoCollaborators) 415 416 isCollaboratorInviteAllowed := false 417 if user != nil { 418 ok, err := s.enforcer.IsCollaboratorInviteAllowed(user.Did, f.Knot, f.OwnerSlashRepo()) 419 if err == nil && ok { 420 isCollaboratorInviteAllowed = true 421 } 422 } 423 424 s.pages.RepoSettings(w, pages.RepoSettingsParams{ 425 LoggedInUser: user, 426 RepoInfo: pages.RepoInfo{ 427 OwnerDid: f.OwnerDid(), 428 OwnerHandle: f.OwnerHandle(), 429 Name: f.RepoName, 430 SettingsAllowed: settingsAllowed(s, user, f), 431 }, 432 Collaborators: repoCollaborators, 433 IsCollaboratorInviteAllowed: isCollaboratorInviteAllowed, 434 }) 435 } 436} 437 438type FullyResolvedRepo struct { 439 Knot string 440 OwnerId identity.Identity 441 RepoName string 442} 443 444func (f *FullyResolvedRepo) OwnerDid() string { 445 return f.OwnerId.DID.String() 446} 447 448func (f *FullyResolvedRepo) OwnerHandle() string { 449 return f.OwnerId.Handle.String() 450} 451 452func (f *FullyResolvedRepo) OwnerSlashRepo() string { 453 p, _ := securejoin.SecureJoin(f.OwnerDid(), f.RepoName) 454 return p 455} 456 457func fullyResolvedRepo(r *http.Request) (*FullyResolvedRepo, error) { 458 repoName := chi.URLParam(r, "repo") 459 knot, ok := r.Context().Value("knot").(string) 460 if !ok { 461 log.Println("malformed middleware") 462 return nil, fmt.Errorf("malformed middleware") 463 } 464 id, ok := r.Context().Value("resolvedId").(identity.Identity) 465 if !ok { 466 log.Println("malformed middleware") 467 return nil, fmt.Errorf("malformed middleware") 468 } 469 470 return &FullyResolvedRepo{ 471 Knot: knot, 472 OwnerId: id, 473 RepoName: repoName, 474 }, nil 475} 476 477func settingsAllowed(s *State, u *auth.User, f *FullyResolvedRepo) bool { 478 settingsAllowed := false 479 if u != nil { 480 ok, err := s.enforcer.IsSettingsAllowed(u.Did, f.Knot, f.OwnerSlashRepo()) 481 if err == nil && ok { 482 settingsAllowed = true 483 } else { 484 log.Println(err, ok) 485 } 486 } 487 488 return settingsAllowed 489}