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