Mirror of @tangled.org/core. Running on a Raspberry Pi Zero 2 (Please be gentle).
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

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 Oppiliappan and committed by
oppiliappan
55096ee0 a3c6611d

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