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

knotserver: rework knot ownership process

This is now the same as what we do in spindle.

Signed-off-by: Anirudh Oppiliappan <anirudh@tangled.sh>

authored by anirudh.fi and committed by oppi.li 55096ee0 a3c6611d

Changed files
+818 -76
knotserver
+1 -1
knotserver/config/config.go
··· 17 17 type Server struct { 18 18 ListenAddr string `env:"LISTEN_ADDR, default=0.0.0.0:5555"` 19 19 InternalListenAddr string `env:"INTERNAL_LISTEN_ADDR, default=127.0.0.1:5444"` 20 - Secret string `env:"SECRET, required"` 21 20 DBPath string `env:"DB_PATH, default=knotserver.db"` 22 21 Hostname string `env:"HOSTNAME, required"` 23 22 JetstreamEndpoint string `env:"JETSTREAM_ENDPOINT, default=wss://jetstream1.us-west.bsky.network/subscribe"` 23 + Owner string `env:"OWNER, required"` 24 24 LogDids bool `env:"LOG_DIDS, default=true"` 25 25 26 26 // This disables signature verification so use with caution.
+817 -21
knotserver/handler.go
··· 27 27 l *slog.Logger 28 28 n *notifier.Notifier 29 29 resolver *idresolver.Resolver 30 - 31 - // init is a channel that is closed when the knot has been initailized 32 - // i.e. when the first user (knot owner) has been added. 33 - init chan struct{} 34 - knotInitialized bool 35 30 } 36 31 37 32 func Setup(ctx context.Context, c *config.Config, db *db.DB, e *rbac.Enforcer, jc *jetstream.JetstreamClient, l *slog.Logger, n *notifier.Notifier) (http.Handler, error) { ··· 45 40 jc: jc, 46 41 n: n, 47 42 resolver: idresolver.DefaultResolver(), 48 - init: make(chan struct{}), 49 43 } 50 44 51 45 err := e.AddKnot(rbac.ThisServer) ··· 53 47 return nil, fmt.Errorf("failed to setup enforcer: %w", err) 54 48 } 55 49 56 - // Check if the knot knows about any Dids; 57 - // if it does, it is already initialized and we can repopulate the 58 - // Jetstream subscriptions. 59 - dids, err := db.GetAllDids() 50 + // configure owner 51 + if err = h.configureOwner(); err != nil { 52 + return nil, err 53 + } 54 + h.l.Info("owner set", "did", h.c.Server.Owner) 55 + h.jc.AddDid(h.c.Server.Owner) 56 + 57 + // configure known-dids in jetstream consumer 58 + dids, err := h.db.GetAllDids() 60 59 if err != nil { 61 - return nil, fmt.Errorf("failed to get all Dids: %w", err) 60 + return nil, fmt.Errorf("failed to get all dids: %w", err) 62 61 } 63 - 64 - if len(dids) > 0 { 65 - h.knotInitialized = true 66 - close(h.init) 67 - for _, d := range dids { 68 - h.jc.AddDid(d) 69 - } 62 + for _, d := range dids { 63 + jc.AddDid(d) 70 64 } 71 65 72 66 err = h.jc.StartJetstream(ctx, h.processMessages) ··· 77 71 r.Get("/", h.Index) 78 72 r.Get("/capabilities", h.Capabilities) 79 73 r.Get("/version", h.Version) 74 + r.Get("/owner", func(w http.ResponseWriter, r *http.Request) { 75 + w.Write([]byte(h.c.Server.Owner)) 76 + }) 80 77 r.Route("/{did}", func(r chi.Router) { 81 78 // Repo routes 82 79 r.Route("/{name}", func(r chi.Router) { ··· 155 152 // Socket that streams git oplogs 156 153 r.Get("/events", h.Events) 157 154 158 - // Initialize the knot with an owner and public key. 159 - r.With(h.VerifySignature).Post("/init", h.Init) 160 - 161 155 // Health check. Used for two-way verification with appview. 162 156 r.With(h.VerifySignature).Get("/health", h.Health) 163 157 ··· 212 206 w.Header().Set("Content-Type", "text/plain; charset=utf-8") 213 207 fmt.Fprintf(w, "knotserver/%s", version) 214 208 } 209 + 210 + func (h *Handle) configureOwner() error { 211 + cfgOwner := h.c.Server.Owner 212 + 213 + rbacDomain := "thisserver" 214 + 215 + existing, err := h.e.GetKnotUsersByRole("server:owner", rbacDomain) 216 + if err != nil { 217 + return err 218 + } 219 + 220 + switch len(existing) { 221 + case 0: 222 + // no owner configured, continue 223 + case 1: 224 + // find existing owner 225 + existingOwner := existing[0] 226 + 227 + // no ownership change, this is okay 228 + if existingOwner == h.c.Server.Owner { 229 + break 230 + } 231 + 232 + // remove existing owner 233 + err = h.e.RemoveKnotOwner(rbacDomain, existingOwner) 234 + if err != nil { 235 + return nil 236 + } 237 + default: 238 + l.Error("attempted to serve disallowed file type", "mimetype", mimeType) 239 + writeError(w, "only image, video, and text files can be accessed directly", http.StatusForbidden) 240 + return 241 + } 242 + 243 + w.Header().Set("Content-Type", mimeType) 244 + w.Write(contents) 245 + } 246 + 247 + func (h *Handle) Blob(w http.ResponseWriter, r *http.Request) { 248 + treePath := chi.URLParam(r, "*") 249 + ref := chi.URLParam(r, "ref") 250 + ref, _ = url.PathUnescape(ref) 251 + 252 + l := h.l.With("handler", "Blob", "ref", ref, "treePath", treePath) 253 + 254 + path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 255 + gr, err := git.Open(path, ref) 256 + if err != nil { 257 + notFound(w) 258 + return 259 + } 260 + 261 + var isBinaryFile bool = false 262 + contents, err := gr.FileContent(treePath) 263 + if errors.Is(err, git.ErrBinaryFile) { 264 + isBinaryFile = true 265 + } else if errors.Is(err, object.ErrFileNotFound) { 266 + notFound(w) 267 + return 268 + } else if err != nil { 269 + writeError(w, err.Error(), http.StatusInternalServerError) 270 + return 271 + } 272 + 273 + bytes := []byte(contents) 274 + // safe := string(sanitize(bytes)) 275 + sizeHint := len(bytes) 276 + 277 + resp := types.RepoBlobResponse{ 278 + Ref: ref, 279 + Contents: string(bytes), 280 + Path: treePath, 281 + IsBinary: isBinaryFile, 282 + SizeHint: uint64(sizeHint), 283 + } 284 + 285 + h.showFile(resp, w, l) 286 + } 287 + 288 + func (h *Handle) Archive(w http.ResponseWriter, r *http.Request) { 289 + name := chi.URLParam(r, "name") 290 + file := chi.URLParam(r, "file") 291 + 292 + l := h.l.With("handler", "Archive", "name", name, "file", file) 293 + 294 + // TODO: extend this to add more files compression (e.g.: xz) 295 + if !strings.HasSuffix(file, ".tar.gz") { 296 + notFound(w) 297 + return 298 + } 299 + 300 + ref := strings.TrimSuffix(file, ".tar.gz") 301 + 302 + unescapedRef, err := url.PathUnescape(ref) 303 + if err != nil { 304 + notFound(w) 305 + return 306 + } 307 + 308 + safeRefFilename := strings.ReplaceAll(plumbing.ReferenceName(unescapedRef).Short(), "/", "-") 309 + 310 + // This allows the browser to use a proper name for the file when 311 + // downloading 312 + filename := fmt.Sprintf("%s-%s.tar.gz", name, safeRefFilename) 313 + setContentDisposition(w, filename) 314 + setGZipMIME(w) 315 + 316 + path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 317 + gr, err := git.Open(path, unescapedRef) 318 + if err != nil { 319 + notFound(w) 320 + return 321 + } 322 + 323 + gw := gzip.NewWriter(w) 324 + defer gw.Close() 325 + 326 + prefix := fmt.Sprintf("%s-%s", name, safeRefFilename) 327 + err = gr.WriteTar(gw, prefix) 328 + if err != nil { 329 + // once we start writing to the body we can't report error anymore 330 + // so we are only left with printing the error. 331 + l.Error("writing tar file", "error", err.Error()) 332 + return 333 + } 334 + 335 + err = gw.Flush() 336 + if err != nil { 337 + // once we start writing to the body we can't report error anymore 338 + // so we are only left with printing the error. 339 + l.Error("flushing?", "error", err.Error()) 340 + return 341 + } 342 + } 343 + 344 + func (h *Handle) Log(w http.ResponseWriter, r *http.Request) { 345 + ref := chi.URLParam(r, "ref") 346 + ref, _ = url.PathUnescape(ref) 347 + 348 + path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 349 + 350 + l := h.l.With("handler", "Log", "ref", ref, "path", path) 351 + 352 + gr, err := git.Open(path, ref) 353 + if err != nil { 354 + notFound(w) 355 + return 356 + } 357 + 358 + // Get page parameters 359 + page := 1 360 + pageSize := 30 361 + 362 + if pageParam := r.URL.Query().Get("page"); pageParam != "" { 363 + if p, err := strconv.Atoi(pageParam); err == nil && p > 0 { 364 + page = p 365 + } 366 + } 367 + 368 + if pageSizeParam := r.URL.Query().Get("per_page"); pageSizeParam != "" { 369 + if ps, err := strconv.Atoi(pageSizeParam); err == nil && ps > 0 { 370 + pageSize = ps 371 + } 372 + } 373 + 374 + // convert to offset/limit 375 + offset := (page - 1) * pageSize 376 + limit := pageSize 377 + 378 + commits, err := gr.Commits(offset, limit) 379 + if err != nil { 380 + writeError(w, err.Error(), http.StatusInternalServerError) 381 + l.Error("fetching commits", "error", err.Error()) 382 + return 383 + } 384 + 385 + total := len(commits) 386 + 387 + resp := types.RepoLogResponse{ 388 + Commits: commits, 389 + Ref: ref, 390 + Description: getDescription(path), 391 + Log: true, 392 + Total: total, 393 + Page: page, 394 + PerPage: pageSize, 395 + } 396 + 397 + writeJSON(w, resp) 398 + } 399 + 400 + func (h *Handle) Diff(w http.ResponseWriter, r *http.Request) { 401 + ref := chi.URLParam(r, "ref") 402 + ref, _ = url.PathUnescape(ref) 403 + 404 + l := h.l.With("handler", "Diff", "ref", ref) 405 + 406 + path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 407 + gr, err := git.Open(path, ref) 408 + if err != nil { 409 + notFound(w) 410 + return 411 + } 412 + 413 + diff, err := gr.Diff() 414 + if err != nil { 415 + writeError(w, err.Error(), http.StatusInternalServerError) 416 + l.Error("getting diff", "error", err.Error()) 417 + return 418 + } 419 + 420 + resp := types.RepoCommitResponse{ 421 + Ref: ref, 422 + Diff: diff, 423 + } 424 + 425 + writeJSON(w, resp) 426 + } 427 + 428 + func (h *Handle) Tags(w http.ResponseWriter, r *http.Request) { 429 + path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 430 + l := h.l.With("handler", "Refs") 431 + 432 + gr, err := git.Open(path, "") 433 + if err != nil { 434 + notFound(w) 435 + return 436 + } 437 + 438 + tags, err := gr.Tags() 439 + if err != nil { 440 + // Non-fatal, we *should* have at least one branch to show. 441 + l.Warn("getting tags", "error", err.Error()) 442 + } 443 + 444 + rtags := []*types.TagReference{} 445 + for _, tag := range tags { 446 + var target *object.Tag 447 + if tag.Target != plumbing.ZeroHash { 448 + target = &tag 449 + } 450 + tr := types.TagReference{ 451 + Tag: target, 452 + } 453 + 454 + tr.Reference = types.Reference{ 455 + Name: tag.Name, 456 + Hash: tag.Hash.String(), 457 + } 458 + 459 + if tag.Message != "" { 460 + tr.Message = tag.Message 461 + } 462 + 463 + rtags = append(rtags, &tr) 464 + } 465 + 466 + resp := types.RepoTagsResponse{ 467 + Tags: rtags, 468 + } 469 + 470 + writeJSON(w, resp) 471 + } 472 + 473 + func (h *Handle) Branches(w http.ResponseWriter, r *http.Request) { 474 + path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 475 + 476 + gr, err := git.PlainOpen(path) 477 + if err != nil { 478 + notFound(w) 479 + return 480 + } 481 + 482 + branches, _ := gr.Branches() 483 + 484 + resp := types.RepoBranchesResponse{ 485 + Branches: branches, 486 + } 487 + 488 + writeJSON(w, resp) 489 + } 490 + 491 + func (h *Handle) Branch(w http.ResponseWriter, r *http.Request) { 492 + path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 493 + branchName := chi.URLParam(r, "branch") 494 + branchName, _ = url.PathUnescape(branchName) 495 + 496 + l := h.l.With("handler", "Branch") 497 + 498 + gr, err := git.PlainOpen(path) 499 + if err != nil { 500 + notFound(w) 501 + return 502 + } 503 + 504 + ref, err := gr.Branch(branchName) 505 + if err != nil { 506 + l.Error("getting branch", "error", err.Error()) 507 + writeError(w, err.Error(), http.StatusInternalServerError) 508 + return 509 + } 510 + 511 + commit, err := gr.Commit(ref.Hash()) 512 + if err != nil { 513 + l.Error("getting commit object", "error", err.Error()) 514 + writeError(w, err.Error(), http.StatusInternalServerError) 515 + return 516 + } 517 + 518 + defaultBranch, err := gr.FindMainBranch() 519 + isDefault := false 520 + if err != nil { 521 + l.Error("getting default branch", "error", err.Error()) 522 + // do not quit though 523 + } else if defaultBranch == branchName { 524 + isDefault = true 525 + } 526 + 527 + resp := types.RepoBranchResponse{ 528 + Branch: types.Branch{ 529 + Reference: types.Reference{ 530 + Name: ref.Name().Short(), 531 + Hash: ref.Hash().String(), 532 + }, 533 + Commit: commit, 534 + IsDefault: isDefault, 535 + }, 536 + } 537 + 538 + writeJSON(w, resp) 539 + } 540 + 541 + func (h *Handle) Keys(w http.ResponseWriter, r *http.Request) { 542 + l := h.l.With("handler", "Keys") 543 + 544 + switch r.Method { 545 + case http.MethodGet: 546 + keys, err := h.db.GetAllPublicKeys() 547 + if err != nil { 548 + writeError(w, err.Error(), http.StatusInternalServerError) 549 + l.Error("getting public keys", "error", err.Error()) 550 + return 551 + } 552 + 553 + data := make([]map[string]any, 0) 554 + for _, key := range keys { 555 + j := key.JSON() 556 + data = append(data, j) 557 + } 558 + writeJSON(w, data) 559 + return 560 + 561 + case http.MethodPut: 562 + pk := db.PublicKey{} 563 + if err := json.NewDecoder(r.Body).Decode(&pk); err != nil { 564 + writeError(w, "invalid request body", http.StatusBadRequest) 565 + return 566 + } 567 + 568 + _, _, _, _, err := ssh.ParseAuthorizedKey([]byte(pk.Key)) 569 + if err != nil { 570 + writeError(w, "invalid pubkey", http.StatusBadRequest) 571 + } 572 + 573 + if err := h.db.AddPublicKey(pk); err != nil { 574 + writeError(w, err.Error(), http.StatusInternalServerError) 575 + l.Error("adding public key", "error", err.Error()) 576 + return 577 + } 578 + 579 + w.WriteHeader(http.StatusNoContent) 580 + return 581 + } 582 + } 583 + 584 + // func (h *Handle) RepoForkAheadBehind(w http.ResponseWriter, r *http.Request) { 585 + // l := h.l.With("handler", "RepoForkSync") 586 + // 587 + // data := struct { 588 + // Did string `json:"did"` 589 + // Source string `json:"source"` 590 + // Name string `json:"name,omitempty"` 591 + // HiddenRef string `json:"hiddenref"` 592 + // }{} 593 + // 594 + // if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 595 + // writeError(w, "invalid request body", http.StatusBadRequest) 596 + // return 597 + // } 598 + // 599 + // did := data.Did 600 + // source := data.Source 601 + // 602 + // if did == "" || source == "" { 603 + // l.Error("invalid request body, empty did or name") 604 + // w.WriteHeader(http.StatusBadRequest) 605 + // return 606 + // } 607 + // 608 + // var name string 609 + // if data.Name != "" { 610 + // name = data.Name 611 + // } else { 612 + // name = filepath.Base(source) 613 + // } 614 + // 615 + // branch := chi.URLParam(r, "branch") 616 + // branch, _ = url.PathUnescape(branch) 617 + // 618 + // relativeRepoPath := filepath.Join(did, name) 619 + // repoPath, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, relativeRepoPath) 620 + // 621 + // gr, err := git.PlainOpen(repoPath) 622 + // if err != nil { 623 + // log.Println(err) 624 + // notFound(w) 625 + // return 626 + // } 627 + // 628 + // forkCommit, err := gr.ResolveRevision(branch) 629 + // if err != nil { 630 + // l.Error("error resolving ref revision", "msg", err.Error()) 631 + // writeError(w, fmt.Sprintf("error resolving revision %s", branch), http.StatusBadRequest) 632 + // return 633 + // } 634 + // 635 + // sourceCommit, err := gr.ResolveRevision(data.HiddenRef) 636 + // if err != nil { 637 + // l.Error("error resolving hidden ref revision", "msg", err.Error()) 638 + // writeError(w, fmt.Sprintf("error resolving revision %s", data.HiddenRef), http.StatusBadRequest) 639 + // return 640 + // } 641 + // 642 + // status := types.UpToDate 643 + // if forkCommit.Hash.String() != sourceCommit.Hash.String() { 644 + // isAncestor, err := forkCommit.IsAncestor(sourceCommit) 645 + // if err != nil { 646 + // log.Printf("error resolving whether %s is ancestor of %s: %s", branch, data.HiddenRef, err) 647 + // return 648 + // } 649 + // 650 + // if isAncestor { 651 + // status = types.FastForwardable 652 + // } else { 653 + // status = types.Conflict 654 + // } 655 + // } 656 + // 657 + // w.Header().Set("Content-Type", "application/json") 658 + // json.NewEncoder(w).Encode(types.AncestorCheckResponse{Status: status}) 659 + // } 660 + 661 + func (h *Handle) RepoLanguages(w http.ResponseWriter, r *http.Request) { 662 + repoPath, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 663 + ref := chi.URLParam(r, "ref") 664 + ref, _ = url.PathUnescape(ref) 665 + 666 + l := h.l.With("handler", "RepoLanguages") 667 + 668 + gr, err := git.Open(repoPath, ref) 669 + if err != nil { 670 + l.Error("opening repo", "error", err.Error()) 671 + notFound(w) 672 + return 673 + } 674 + 675 + ctx, cancel := context.WithTimeout(r.Context(), 1*time.Second) 676 + defer cancel() 677 + 678 + sizes, err := gr.AnalyzeLanguages(ctx) 679 + if err != nil { 680 + l.Error("failed to analyze languages", "error", err.Error()) 681 + writeError(w, err.Error(), http.StatusNoContent) 682 + return 683 + } 684 + 685 + resp := types.RepoLanguageResponse{Languages: sizes} 686 + 687 + writeJSON(w, resp) 688 + } 689 + 690 + // func (h *Handle) RepoForkSync(w http.ResponseWriter, r *http.Request) { 691 + // l := h.l.With("handler", "RepoForkSync") 692 + // 693 + // data := struct { 694 + // Did string `json:"did"` 695 + // Source string `json:"source"` 696 + // Name string `json:"name,omitempty"` 697 + // }{} 698 + // 699 + // if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 700 + // writeError(w, "invalid request body", http.StatusBadRequest) 701 + // return 702 + // } 703 + // 704 + // did := data.Did 705 + // source := data.Source 706 + // 707 + // if did == "" || source == "" { 708 + // l.Error("invalid request body, empty did or name") 709 + // w.WriteHeader(http.StatusBadRequest) 710 + // return 711 + // } 712 + // 713 + // var name string 714 + // if data.Name != "" { 715 + // name = data.Name 716 + // } else { 717 + // name = filepath.Base(source) 718 + // } 719 + // 720 + // branch := chi.URLParam(r, "branch") 721 + // branch, _ = url.PathUnescape(branch) 722 + // 723 + // relativeRepoPath := filepath.Join(did, name) 724 + // repoPath, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, relativeRepoPath) 725 + // 726 + // gr, err := git.Open(repoPath, branch) 727 + // if err != nil { 728 + // log.Println(err) 729 + // notFound(w) 730 + // return 731 + // } 732 + // 733 + // err = gr.Sync() 734 + // if err != nil { 735 + // l.Error("error syncing repo fork", "error", err.Error()) 736 + // writeError(w, err.Error(), http.StatusInternalServerError) 737 + // return 738 + // } 739 + // 740 + // w.WriteHeader(http.StatusNoContent) 741 + // } 742 + 743 + // func (h *Handle) RepoFork(w http.ResponseWriter, r *http.Request) { 744 + // l := h.l.With("handler", "RepoFork") 745 + // 746 + // data := struct { 747 + // Did string `json:"did"` 748 + // Source string `json:"source"` 749 + // Name string `json:"name,omitempty"` 750 + // }{} 751 + // 752 + // if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 753 + // writeError(w, "invalid request body", http.StatusBadRequest) 754 + // return 755 + // } 756 + // 757 + // did := data.Did 758 + // source := data.Source 759 + // 760 + // if did == "" || source == "" { 761 + // l.Error("invalid request body, empty did or name") 762 + // w.WriteHeader(http.StatusBadRequest) 763 + // return 764 + // } 765 + // 766 + // var name string 767 + // if data.Name != "" { 768 + // name = data.Name 769 + // } else { 770 + // name = filepath.Base(source) 771 + // } 772 + // 773 + // relativeRepoPath := filepath.Join(did, name) 774 + // repoPath, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, relativeRepoPath) 775 + // 776 + // err := git.Fork(repoPath, source) 777 + // if err != nil { 778 + // l.Error("forking repo", "error", err.Error()) 779 + // writeError(w, err.Error(), http.StatusInternalServerError) 780 + // return 781 + // } 782 + // 783 + // // add perms for this user to access the repo 784 + // err = h.e.AddRepo(did, rbac.ThisServer, relativeRepoPath) 785 + // if err != nil { 786 + // l.Error("adding repo permissions", "error", err.Error()) 787 + // writeError(w, err.Error(), http.StatusInternalServerError) 788 + // return 789 + // } 790 + // 791 + // hook.SetupRepo( 792 + // hook.Config( 793 + // hook.WithScanPath(h.c.Repo.ScanPath), 794 + // hook.WithInternalApi(h.c.Server.InternalListenAddr), 795 + // ), 796 + // repoPath, 797 + // ) 798 + // 799 + // w.WriteHeader(http.StatusNoContent) 800 + // } 801 + 802 + // func (h *Handle) RemoveRepo(w http.ResponseWriter, r *http.Request) { 803 + // l := h.l.With("handler", "RemoveRepo") 804 + // 805 + // data := struct { 806 + // Did string `json:"did"` 807 + // Name string `json:"name"` 808 + // }{} 809 + // 810 + // if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 811 + // writeError(w, "invalid request body", http.StatusBadRequest) 812 + // return 813 + // } 814 + // 815 + // did := data.Did 816 + // name := data.Name 817 + // 818 + // if did == "" || name == "" { 819 + // l.Error("invalid request body, empty did or name") 820 + // w.WriteHeader(http.StatusBadRequest) 821 + // return 822 + // } 823 + // 824 + // relativeRepoPath := filepath.Join(did, name) 825 + // repoPath, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, relativeRepoPath) 826 + // err := os.RemoveAll(repoPath) 827 + // if err != nil { 828 + // l.Error("removing repo", "error", err.Error()) 829 + // writeError(w, err.Error(), http.StatusInternalServerError) 830 + // return 831 + // } 832 + // 833 + // w.WriteHeader(http.StatusNoContent) 834 + // 835 + // } 836 + 837 + // func (h *Handle) Merge(w http.ResponseWriter, r *http.Request) { 838 + // path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 839 + // 840 + // data := types.MergeRequest{} 841 + // 842 + // if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 843 + // writeError(w, err.Error(), http.StatusBadRequest) 844 + // h.l.Error("git: failed to unmarshal json patch", "handler", "Merge", "error", err) 845 + // return 846 + // } 847 + // 848 + // mo := &git.MergeOptions{ 849 + // AuthorName: data.AuthorName, 850 + // AuthorEmail: data.AuthorEmail, 851 + // CommitBody: data.CommitBody, 852 + // CommitMessage: data.CommitMessage, 853 + // } 854 + // 855 + // patch := data.Patch 856 + // branch := data.Branch 857 + // gr, err := git.Open(path, branch) 858 + // if err != nil { 859 + // notFound(w) 860 + // return 861 + // } 862 + // 863 + // mo.FormatPatch = patchutil.IsFormatPatch(patch) 864 + // 865 + // if err := gr.MergeWithOptions([]byte(patch), branch, mo); err != nil { 866 + // var mergeErr *git.ErrMerge 867 + // if errors.As(err, &mergeErr) { 868 + // conflicts := make([]types.ConflictInfo, len(mergeErr.Conflicts)) 869 + // for i, conflict := range mergeErr.Conflicts { 870 + // conflicts[i] = types.ConflictInfo{ 871 + // Filename: conflict.Filename, 872 + // Reason: conflict.Reason, 873 + // } 874 + // } 875 + // response := types.MergeCheckResponse{ 876 + // IsConflicted: true, 877 + // Conflicts: conflicts, 878 + // Message: mergeErr.Message, 879 + // } 880 + // writeConflict(w, response) 881 + // h.l.Error("git: merge conflict", "handler", "Merge", "error", mergeErr) 882 + // } else { 883 + // writeError(w, err.Error(), http.StatusBadRequest) 884 + // h.l.Error("git: failed to merge", "handler", "Merge", "error", err.Error()) 885 + // } 886 + // return 887 + // } 888 + // 889 + // w.WriteHeader(http.StatusOK) 890 + // } 891 + 892 + // func (h *Handle) MergeCheck(w http.ResponseWriter, r *http.Request) { 893 + // path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 894 + // 895 + // var data struct { 896 + // Patch string `json:"patch"` 897 + // Branch string `json:"branch"` 898 + // } 899 + // 900 + // if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 901 + // writeError(w, err.Error(), http.StatusBadRequest) 902 + // h.l.Error("git: failed to unmarshal json patch", "handler", "MergeCheck", "error", err) 903 + // return 904 + // } 905 + // 906 + // patch := data.Patch 907 + // branch := data.Branch 908 + // gr, err := git.Open(path, branch) 909 + // if err != nil { 910 + // notFound(w) 911 + // return 912 + // } 913 + // 914 + // err = gr.MergeCheck([]byte(patch), branch) 915 + // if err == nil { 916 + // response := types.MergeCheckResponse{ 917 + // IsConflicted: false, 918 + // } 919 + // writeJSON(w, response) 920 + // return 921 + // } 922 + // 923 + // var mergeErr *git.ErrMerge 924 + // if errors.As(err, &mergeErr) { 925 + // conflicts := make([]types.ConflictInfo, len(mergeErr.Conflicts)) 926 + // for i, conflict := range mergeErr.Conflicts { 927 + // conflicts[i] = types.ConflictInfo{ 928 + // Filename: conflict.Filename, 929 + // Reason: conflict.Reason, 930 + // } 931 + // } 932 + // response := types.MergeCheckResponse{ 933 + // IsConflicted: true, 934 + // Conflicts: conflicts, 935 + // Message: mergeErr.Message, 936 + // } 937 + // writeConflict(w, response) 938 + // h.l.Error("git: merge conflict", "handler", "MergeCheck", "error", mergeErr.Error()) 939 + // return 940 + // } 941 + // writeError(w, err.Error(), http.StatusInternalServerError) 942 + // h.l.Error("git: failed to check merge", "handler", "MergeCheck", "error", err.Error()) 943 + // } 944 + 945 + func (h *Handle) Compare(w http.ResponseWriter, r *http.Request) { 946 + rev1 := chi.URLParam(r, "rev1") 947 + rev1, _ = url.PathUnescape(rev1) 948 + 949 + rev2 := chi.URLParam(r, "rev2") 950 + rev2, _ = url.PathUnescape(rev2) 951 + 952 + l := h.l.With("handler", "Compare", "r1", rev1, "r2", rev2) 953 + 954 + path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 955 + gr, err := git.PlainOpen(path) 956 + if err != nil { 957 + notFound(w) 958 + return 959 + } 960 + 961 + commit1, err := gr.ResolveRevision(rev1) 962 + if err != nil { 963 + l.Error("error resolving revision 1", "msg", err.Error()) 964 + writeError(w, fmt.Sprintf("error resolving revision %s", rev1), http.StatusBadRequest) 965 + return 966 + } 967 + 968 + commit2, err := gr.ResolveRevision(rev2) 969 + if err != nil { 970 + l.Error("error resolving revision 2", "msg", err.Error()) 971 + writeError(w, fmt.Sprintf("error resolving revision %s", rev2), http.StatusBadRequest) 972 + return 973 + } 974 + 975 + rawPatch, formatPatch, err := gr.FormatPatch(commit1, commit2) 976 + if err != nil { 977 + l.Error("error comparing revisions", "msg", err.Error()) 978 + writeError(w, "error comparing revisions", http.StatusBadRequest) 979 + return 980 + } 981 + 982 + writeJSON(w, types.RepoFormatPatchResponse{ 983 + Rev1: commit1.Hash.String(), 984 + Rev2: commit2.Hash.String(), 985 + FormatPatch: formatPatch, 986 + Patch: rawPatch, 987 + }) 988 + } 989 + 990 + func (h *Handle) DefaultBranch(w http.ResponseWriter, r *http.Request) { 991 + l := h.l.With("handler", "DefaultBranch") 992 + path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 993 + 994 + gr, err := git.Open(path, "") 995 + if err != nil { 996 + notFound(w) 997 + return 998 + } 999 + 1000 + branch, err := gr.FindMainBranch() 1001 + if err != nil { 1002 + writeError(w, err.Error(), http.StatusInternalServerError) 1003 + l.Error("getting default branch", "error", err.Error()) 1004 + return 1005 + } 1006 + 1007 + writeJSON(w, types.RepoDefaultBranchResponse{ 1008 + Branch: branch, 1009 + }) 1010 + }
-54
knotserver/routes.go
··· 3 3 import ( 4 4 "compress/gzip" 5 5 "context" 6 - "crypto/hmac" 7 6 "crypto/sha256" 8 - "encoding/hex" 9 7 "encoding/json" 10 8 "errors" 11 9 "fmt" ··· 1206 1204 l.Error("setting default branch", "error", err.Error()) 1207 1205 return 1208 1206 } 1209 - 1210 - w.WriteHeader(http.StatusNoContent) 1211 - } 1212 - 1213 - func (h *Handle) Init(w http.ResponseWriter, r *http.Request) { 1214 - l := h.l.With("handler", "Init") 1215 - 1216 - if h.knotInitialized { 1217 - writeError(w, "knot already initialized", http.StatusConflict) 1218 - return 1219 - } 1220 - 1221 - data := struct { 1222 - Did string `json:"did"` 1223 - }{} 1224 - 1225 - if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 1226 - l.Error("failed to decode request body", "error", err.Error()) 1227 - writeError(w, "invalid request body", http.StatusBadRequest) 1228 - return 1229 - } 1230 - 1231 - if data.Did == "" { 1232 - l.Error("empty DID in request", "did", data.Did) 1233 - writeError(w, "did is empty", http.StatusBadRequest) 1234 - return 1235 - } 1236 - 1237 - if err := h.db.AddDid(data.Did); err != nil { 1238 - l.Error("failed to add DID", "error", err.Error()) 1239 - writeError(w, err.Error(), http.StatusInternalServerError) 1240 - return 1241 - } 1242 - h.jc.AddDid(data.Did) 1243 - 1244 - if err := h.e.AddKnotOwner(rbac.ThisServer, data.Did); err != nil { 1245 - l.Error("adding owner", "error", err.Error()) 1246 - writeError(w, err.Error(), http.StatusInternalServerError) 1247 - return 1248 - } 1249 - 1250 - if err := h.fetchAndAddKeys(r.Context(), data.Did); err != nil { 1251 - l.Error("fetching and adding keys", "error", err.Error()) 1252 - writeError(w, err.Error(), http.StatusInternalServerError) 1253 - return 1254 - } 1255 - 1256 - close(h.init) 1257 - 1258 - mac := hmac.New(sha256.New, []byte(h.c.Server.Secret)) 1259 - mac.Write([]byte("ok")) 1260 - w.Header().Add("X-Signature", hex.EncodeToString(mac.Sum(nil))) 1261 1207 1262 1208 w.WriteHeader(http.StatusNoContent) 1263 1209 }