forked from
tangled.org/core
fork
Configure Feed
Select the types of activity you want to include in your feed.
this repo has no description
fork
Configure Feed
Select the types of activity you want to include in your feed.
1package knotserver
2
3import (
4 "compress/gzip"
5 "crypto/hmac"
6 "crypto/sha256"
7 "encoding/hex"
8 "encoding/json"
9 "errors"
10 "fmt"
11 "log"
12 "net/http"
13 "net/url"
14 "os"
15 "path/filepath"
16 "strconv"
17 "strings"
18
19 securejoin "github.com/cyphar/filepath-securejoin"
20 "github.com/gliderlabs/ssh"
21 "github.com/go-chi/chi/v5"
22 gogit "github.com/go-git/go-git/v5"
23 "github.com/go-git/go-git/v5/plumbing"
24 "github.com/go-git/go-git/v5/plumbing/object"
25 "tangled.sh/tangled.sh/core/knotserver/db"
26 "tangled.sh/tangled.sh/core/knotserver/git"
27 "tangled.sh/tangled.sh/core/types"
28)
29
30func (h *Handle) Index(w http.ResponseWriter, r *http.Request) {
31 w.Write([]byte("This is a knot server. More info at https://tangled.sh"))
32}
33
34func (h *Handle) Capabilities(w http.ResponseWriter, r *http.Request) {
35 w.Header().Set("Content-Type", "application/json")
36
37 capabilities := map[string]any{
38 "pull_requests": map[string]any{
39 "patch_submissions": true,
40 "branch_submissions": true,
41 "fork_submissions": false,
42 },
43 }
44
45 jsonData, err := json.Marshal(capabilities)
46 if err != nil {
47 http.Error(w, "Failed to serialize JSON", http.StatusInternalServerError)
48 return
49 }
50
51 w.Write(jsonData)
52}
53
54func (h *Handle) RepoIndex(w http.ResponseWriter, r *http.Request) {
55 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
56 l := h.l.With("path", path, "handler", "RepoIndex")
57 ref := chi.URLParam(r, "ref")
58 ref, _ = url.PathUnescape(ref)
59
60 gr, err := git.Open(path, ref)
61 if err != nil {
62 log.Println(err)
63 if errors.Is(err, plumbing.ErrReferenceNotFound) {
64 resp := types.RepoIndexResponse{
65 IsEmpty: true,
66 }
67 writeJSON(w, resp)
68 return
69 } else {
70 l.Error("opening repo", "error", err.Error())
71 notFound(w)
72 return
73 }
74 }
75
76 commits, err := gr.Commits()
77 total := len(commits)
78 if err != nil {
79 writeError(w, err.Error(), http.StatusInternalServerError)
80 l.Error("fetching commits", "error", err.Error())
81 return
82 }
83 if len(commits) > 10 {
84 commits = commits[:10]
85 }
86
87 branches, err := gr.Branches()
88 if err != nil {
89 l.Error("getting branches", "error", err.Error())
90 writeError(w, err.Error(), http.StatusInternalServerError)
91 return
92 }
93
94 bs := []types.Branch{}
95 for _, branch := range branches {
96 b := types.Branch{}
97 b.Hash = branch.Hash().String()
98 b.Name = branch.Name().Short()
99 bs = append(bs, b)
100 }
101
102 tags, err := gr.Tags()
103 if err != nil {
104 // Non-fatal, we *should* have at least one branch to show.
105 l.Warn("getting tags", "error", err.Error())
106 }
107
108 rtags := []*types.TagReference{}
109 for _, tag := range tags {
110 tr := types.TagReference{
111 Tag: tag.TagObject(),
112 }
113
114 tr.Reference = types.Reference{
115 Name: tag.Name(),
116 Hash: tag.Hash().String(),
117 }
118
119 if tag.Message() != "" {
120 tr.Message = tag.Message()
121 }
122
123 rtags = append(rtags, &tr)
124 }
125
126 var readmeContent string
127 var readmeFile string
128 for _, readme := range h.c.Repo.Readme {
129 content, _ := gr.FileContent(readme)
130 if len(content) > 0 {
131 readmeContent = string(content)
132 readmeFile = readme
133 }
134 }
135
136 files, err := gr.FileTree("")
137 if err != nil {
138 writeError(w, err.Error(), http.StatusInternalServerError)
139 l.Error("file tree", "error", err.Error())
140 return
141 }
142
143 if ref == "" {
144 mainBranch, err := gr.FindMainBranch()
145 if err != nil {
146 writeError(w, err.Error(), http.StatusInternalServerError)
147 l.Error("finding main branch", "error", err.Error())
148 return
149 }
150 ref = mainBranch
151 }
152
153 resp := types.RepoIndexResponse{
154 IsEmpty: false,
155 Ref: ref,
156 Commits: commits,
157 Description: getDescription(path),
158 Readme: readmeContent,
159 ReadmeFileName: readmeFile,
160 Files: files,
161 Branches: bs,
162 Tags: rtags,
163 TotalCommits: total,
164 }
165
166 writeJSON(w, resp)
167 return
168}
169
170func (h *Handle) RepoTree(w http.ResponseWriter, r *http.Request) {
171 treePath := chi.URLParam(r, "*")
172 ref := chi.URLParam(r, "ref")
173 ref, _ = url.PathUnescape(ref)
174
175 l := h.l.With("handler", "RepoTree", "ref", ref, "treePath", treePath)
176
177 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
178 gr, err := git.Open(path, ref)
179 if err != nil {
180 notFound(w)
181 return
182 }
183
184 files, err := gr.FileTree(treePath)
185 if err != nil {
186 writeError(w, err.Error(), http.StatusInternalServerError)
187 l.Error("file tree", "error", err.Error())
188 return
189 }
190
191 resp := types.RepoTreeResponse{
192 Ref: ref,
193 Parent: treePath,
194 Description: getDescription(path),
195 DotDot: filepath.Dir(treePath),
196 Files: files,
197 }
198
199 writeJSON(w, resp)
200 return
201}
202
203func (h *Handle) Blob(w http.ResponseWriter, r *http.Request) {
204 treePath := chi.URLParam(r, "*")
205 ref := chi.URLParam(r, "ref")
206 ref, _ = url.PathUnescape(ref)
207
208 l := h.l.With("handler", "FileContent", "ref", ref, "treePath", treePath)
209
210 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
211 gr, err := git.Open(path, ref)
212 if err != nil {
213 notFound(w)
214 return
215 }
216
217 var isBinaryFile bool = false
218 contents, err := gr.FileContent(treePath)
219 if errors.Is(err, git.ErrBinaryFile) {
220 isBinaryFile = true
221 } else if errors.Is(err, object.ErrFileNotFound) {
222 notFound(w)
223 return
224 } else if err != nil {
225 writeError(w, err.Error(), http.StatusInternalServerError)
226 return
227 }
228
229 bytes := []byte(contents)
230 // safe := string(sanitize(bytes))
231 sizeHint := len(bytes)
232
233 resp := types.RepoBlobResponse{
234 Ref: ref,
235 Contents: string(bytes),
236 Path: treePath,
237 IsBinary: isBinaryFile,
238 SizeHint: uint64(sizeHint),
239 }
240
241 h.showFile(resp, w, l)
242}
243
244func (h *Handle) Archive(w http.ResponseWriter, r *http.Request) {
245 name := chi.URLParam(r, "name")
246 file := chi.URLParam(r, "file")
247
248 l := h.l.With("handler", "Archive", "name", name, "file", file)
249
250 // TODO: extend this to add more files compression (e.g.: xz)
251 if !strings.HasSuffix(file, ".tar.gz") {
252 notFound(w)
253 return
254 }
255
256 ref := strings.TrimSuffix(file, ".tar.gz")
257
258 // This allows the browser to use a proper name for the file when
259 // downloading
260 filename := fmt.Sprintf("%s-%s.tar.gz", name, ref)
261 setContentDisposition(w, filename)
262 setGZipMIME(w)
263
264 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
265 gr, err := git.Open(path, ref)
266 if err != nil {
267 notFound(w)
268 return
269 }
270
271 gw := gzip.NewWriter(w)
272 defer gw.Close()
273
274 prefix := fmt.Sprintf("%s-%s", name, ref)
275 err = gr.WriteTar(gw, prefix)
276 if err != nil {
277 // once we start writing to the body we can't report error anymore
278 // so we are only left with printing the error.
279 l.Error("writing tar file", "error", err.Error())
280 return
281 }
282
283 err = gw.Flush()
284 if err != nil {
285 // once we start writing to the body we can't report error anymore
286 // so we are only left with printing the error.
287 l.Error("flushing?", "error", err.Error())
288 return
289 }
290}
291
292func (h *Handle) Log(w http.ResponseWriter, r *http.Request) {
293 ref := chi.URLParam(r, "ref")
294 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
295
296 l := h.l.With("handler", "Log", "ref", ref, "path", path)
297
298 gr, err := git.Open(path, ref)
299 if err != nil {
300 notFound(w)
301 return
302 }
303
304 commits, err := gr.Commits()
305 if err != nil {
306 writeError(w, err.Error(), http.StatusInternalServerError)
307 l.Error("fetching commits", "error", err.Error())
308 return
309 }
310
311 // Get page parameters
312 page := 1
313 pageSize := 30
314
315 if pageParam := r.URL.Query().Get("page"); pageParam != "" {
316 if p, err := strconv.Atoi(pageParam); err == nil && p > 0 {
317 page = p
318 }
319 }
320
321 if pageSizeParam := r.URL.Query().Get("per_page"); pageSizeParam != "" {
322 if ps, err := strconv.Atoi(pageSizeParam); err == nil && ps > 0 {
323 pageSize = ps
324 }
325 }
326
327 // Calculate pagination
328 start := (page - 1) * pageSize
329 end := start + pageSize
330 total := len(commits)
331
332 if start >= total {
333 commits = []*object.Commit{}
334 } else {
335 if end > total {
336 end = total
337 }
338 commits = commits[start:end]
339 }
340
341 resp := types.RepoLogResponse{
342 Commits: commits,
343 Ref: ref,
344 Description: getDescription(path),
345 Log: true,
346 Total: total,
347 Page: page,
348 PerPage: pageSize,
349 }
350
351 writeJSON(w, resp)
352 return
353}
354
355func (h *Handle) Diff(w http.ResponseWriter, r *http.Request) {
356 ref := chi.URLParam(r, "ref")
357 ref, _ = url.PathUnescape(ref)
358
359 l := h.l.With("handler", "Diff", "ref", ref)
360
361 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
362 gr, err := git.Open(path, ref)
363 if err != nil {
364 notFound(w)
365 return
366 }
367
368 diff, err := gr.Diff()
369 if err != nil {
370 writeError(w, err.Error(), http.StatusInternalServerError)
371 l.Error("getting diff", "error", err.Error())
372 return
373 }
374
375 resp := types.RepoCommitResponse{
376 Ref: ref,
377 Diff: diff,
378 }
379
380 writeJSON(w, resp)
381 return
382}
383
384func (h *Handle) Tags(w http.ResponseWriter, r *http.Request) {
385 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
386 l := h.l.With("handler", "Refs")
387
388 gr, err := git.Open(path, "")
389 if err != nil {
390 notFound(w)
391 return
392 }
393
394 tags, err := gr.Tags()
395 if err != nil {
396 // Non-fatal, we *should* have at least one branch to show.
397 l.Warn("getting tags", "error", err.Error())
398 }
399
400 rtags := []*types.TagReference{}
401 for _, tag := range tags {
402 tr := types.TagReference{
403 Tag: tag.TagObject(),
404 }
405
406 tr.Reference = types.Reference{
407 Name: tag.Name(),
408 Hash: tag.Hash().String(),
409 }
410
411 if tag.Message() != "" {
412 tr.Message = tag.Message()
413 }
414
415 rtags = append(rtags, &tr)
416 }
417
418 resp := types.RepoTagsResponse{
419 Tags: rtags,
420 }
421
422 writeJSON(w, resp)
423 return
424}
425
426func (h *Handle) Branches(w http.ResponseWriter, r *http.Request) {
427 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
428 l := h.l.With("handler", "Branches")
429
430 gr, err := git.Open(path, "")
431 if err != nil {
432 notFound(w)
433 return
434 }
435
436 branches, err := gr.Branches()
437 if err != nil {
438 l.Error("getting branches", "error", err.Error())
439 writeError(w, err.Error(), http.StatusInternalServerError)
440 return
441 }
442
443 bs := []types.Branch{}
444 for _, branch := range branches {
445 b := types.Branch{}
446 b.Hash = branch.Hash().String()
447 b.Name = branch.Name().Short()
448 bs = append(bs, b)
449 }
450
451 resp := types.RepoBranchesResponse{
452 Branches: bs,
453 }
454
455 writeJSON(w, resp)
456 return
457}
458
459func (h *Handle) Branch(w http.ResponseWriter, r *http.Request) {
460 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
461 branchName := chi.URLParam(r, "branch")
462 l := h.l.With("handler", "Branch")
463
464 gr, err := git.PlainOpen(path)
465 if err != nil {
466 notFound(w)
467 return
468 }
469
470 ref, err := gr.Branch(branchName)
471 if err != nil {
472 l.Error("getting branches", "error", err.Error())
473 writeError(w, err.Error(), http.StatusInternalServerError)
474 return
475 }
476
477 resp := types.RepoBranchResponse{
478 Branch: types.Branch{
479 Reference: types.Reference{
480 Name: ref.Name().Short(),
481 Hash: ref.Hash().String(),
482 },
483 },
484 }
485
486 writeJSON(w, resp)
487 return
488}
489
490func (h *Handle) Keys(w http.ResponseWriter, r *http.Request) {
491 l := h.l.With("handler", "Keys")
492
493 switch r.Method {
494 case http.MethodGet:
495 keys, err := h.db.GetAllPublicKeys()
496 if err != nil {
497 writeError(w, err.Error(), http.StatusInternalServerError)
498 l.Error("getting public keys", "error", err.Error())
499 return
500 }
501
502 data := make([]map[string]any, 0)
503 for _, key := range keys {
504 j := key.JSON()
505 data = append(data, j)
506 }
507 writeJSON(w, data)
508 return
509
510 case http.MethodPut:
511 pk := db.PublicKey{}
512 if err := json.NewDecoder(r.Body).Decode(&pk); err != nil {
513 writeError(w, "invalid request body", http.StatusBadRequest)
514 return
515 }
516
517 _, _, _, _, err := ssh.ParseAuthorizedKey([]byte(pk.Key))
518 if err != nil {
519 writeError(w, "invalid pubkey", http.StatusBadRequest)
520 }
521
522 if err := h.db.AddPublicKey(pk); err != nil {
523 writeError(w, err.Error(), http.StatusInternalServerError)
524 l.Error("adding public key", "error", err.Error())
525 return
526 }
527
528 w.WriteHeader(http.StatusNoContent)
529 return
530 }
531}
532
533func (h *Handle) NewRepo(w http.ResponseWriter, r *http.Request) {
534 l := h.l.With("handler", "NewRepo")
535
536 data := struct {
537 Did string `json:"did"`
538 Name string `json:"name"`
539 DefaultBranch string `json:"default_branch,omitempty"`
540 }{}
541
542 if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
543 writeError(w, "invalid request body", http.StatusBadRequest)
544 return
545 }
546
547 if data.DefaultBranch == "" {
548 data.DefaultBranch = h.c.Repo.MainBranch
549 }
550
551 did := data.Did
552 name := data.Name
553 defaultBranch := data.DefaultBranch
554
555 relativeRepoPath := filepath.Join(did, name)
556 repoPath, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, relativeRepoPath)
557 err := git.InitBare(repoPath, defaultBranch)
558 if err != nil {
559 l.Error("initializing bare repo", "error", err.Error())
560 if errors.Is(err, gogit.ErrRepositoryAlreadyExists) {
561 writeError(w, "That repo already exists!", http.StatusConflict)
562 return
563 } else {
564 writeError(w, err.Error(), http.StatusInternalServerError)
565 return
566 }
567 }
568
569 // add perms for this user to access the repo
570 err = h.e.AddRepo(did, ThisServer, relativeRepoPath)
571 if err != nil {
572 l.Error("adding repo permissions", "error", err.Error())
573 writeError(w, err.Error(), http.StatusInternalServerError)
574 return
575 }
576
577 w.WriteHeader(http.StatusNoContent)
578}
579
580func (h *Handle) RepoFork(w http.ResponseWriter, r *http.Request) {
581 l := h.l.With("handler", "RepoFork")
582
583 data := struct {
584 Did string `json:"did"`
585 Source string `json:"source"`
586 Name string `json:"name,omitempty"`
587 }{}
588
589 if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
590 writeError(w, "invalid request body", http.StatusBadRequest)
591 return
592 }
593
594 did := data.Did
595 source := data.Source
596
597 if did == "" || source == "" {
598 l.Error("invalid request body, empty did or name")
599 w.WriteHeader(http.StatusBadRequest)
600 return
601 }
602
603 var name string
604 if data.Name != "" {
605 name = data.Name
606 } else {
607 name = filepath.Base(source)
608 }
609
610 relativeRepoPath := filepath.Join(did, name)
611 repoPath, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, relativeRepoPath)
612
613 err := git.Fork(repoPath, source)
614 if err != nil {
615 l.Error("forking repo", "error", err.Error())
616 writeError(w, err.Error(), http.StatusInternalServerError)
617 return
618 }
619
620 w.WriteHeader(http.StatusNoContent)
621}
622
623func (h *Handle) RemoveRepo(w http.ResponseWriter, r *http.Request) {
624 l := h.l.With("handler", "RemoveRepo")
625
626 data := struct {
627 Did string `json:"did"`
628 Name string `json:"name"`
629 }{}
630
631 if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
632 writeError(w, "invalid request body", http.StatusBadRequest)
633 return
634 }
635
636 did := data.Did
637 name := data.Name
638
639 if did == "" || name == "" {
640 l.Error("invalid request body, empty did or name")
641 w.WriteHeader(http.StatusBadRequest)
642 return
643 }
644
645 relativeRepoPath := filepath.Join(did, name)
646 repoPath, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, relativeRepoPath)
647 err := os.RemoveAll(repoPath)
648 if err != nil {
649 l.Error("removing repo", "error", err.Error())
650 writeError(w, err.Error(), http.StatusInternalServerError)
651 return
652 }
653
654 w.WriteHeader(http.StatusNoContent)
655
656}
657func (h *Handle) Merge(w http.ResponseWriter, r *http.Request) {
658 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
659
660 data := types.MergeRequest{}
661
662 if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
663 writeError(w, err.Error(), http.StatusBadRequest)
664 h.l.Error("git: failed to unmarshal json patch", "handler", "Merge", "error", err)
665 return
666 }
667
668 mo := &git.MergeOptions{
669 AuthorName: data.AuthorName,
670 AuthorEmail: data.AuthorEmail,
671 CommitBody: data.CommitBody,
672 CommitMessage: data.CommitMessage,
673 }
674
675 patch := data.Patch
676 branch := data.Branch
677 gr, err := git.Open(path, branch)
678 if err != nil {
679 notFound(w)
680 return
681 }
682 if err := gr.MergeWithOptions([]byte(patch), branch, mo); err != nil {
683 var mergeErr *git.ErrMerge
684 if errors.As(err, &mergeErr) {
685 conflicts := make([]types.ConflictInfo, len(mergeErr.Conflicts))
686 for i, conflict := range mergeErr.Conflicts {
687 conflicts[i] = types.ConflictInfo{
688 Filename: conflict.Filename,
689 Reason: conflict.Reason,
690 }
691 }
692 response := types.MergeCheckResponse{
693 IsConflicted: true,
694 Conflicts: conflicts,
695 Message: mergeErr.Message,
696 }
697 writeConflict(w, response)
698 h.l.Error("git: merge conflict", "handler", "Merge", "error", mergeErr)
699 } else {
700 writeError(w, err.Error(), http.StatusBadRequest)
701 h.l.Error("git: failed to merge", "handler", "Merge", "error", err.Error())
702 }
703 return
704 }
705
706 w.WriteHeader(http.StatusOK)
707}
708
709func (h *Handle) MergeCheck(w http.ResponseWriter, r *http.Request) {
710 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
711
712 var data struct {
713 Patch string `json:"patch"`
714 Branch string `json:"branch"`
715 }
716
717 if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
718 writeError(w, err.Error(), http.StatusBadRequest)
719 h.l.Error("git: failed to unmarshal json patch", "handler", "MergeCheck", "error", err)
720 return
721 }
722
723 patch := data.Patch
724 branch := data.Branch
725 gr, err := git.Open(path, branch)
726 if err != nil {
727 notFound(w)
728 return
729 }
730
731 err = gr.MergeCheck([]byte(patch), branch)
732 if err == nil {
733 response := types.MergeCheckResponse{
734 IsConflicted: false,
735 }
736 writeJSON(w, response)
737 return
738 }
739
740 var mergeErr *git.ErrMerge
741 if errors.As(err, &mergeErr) {
742 conflicts := make([]types.ConflictInfo, len(mergeErr.Conflicts))
743 for i, conflict := range mergeErr.Conflicts {
744 conflicts[i] = types.ConflictInfo{
745 Filename: conflict.Filename,
746 Reason: conflict.Reason,
747 }
748 }
749 response := types.MergeCheckResponse{
750 IsConflicted: true,
751 Conflicts: conflicts,
752 Message: mergeErr.Message,
753 }
754 writeConflict(w, response)
755 h.l.Error("git: merge conflict", "handler", "MergeCheck", "error", mergeErr.Error())
756 return
757 }
758 writeError(w, err.Error(), http.StatusInternalServerError)
759 h.l.Error("git: failed to check merge", "handler", "MergeCheck", "error", err.Error())
760}
761
762func (h *Handle) Compare(w http.ResponseWriter, r *http.Request) {
763 rev1 := chi.URLParam(r, "rev1")
764 rev1, _ = url.PathUnescape(rev1)
765
766 rev2 := chi.URLParam(r, "rev2")
767 rev2, _ = url.PathUnescape(rev2)
768
769 l := h.l.With("handler", "Compare", "r1", rev1, "r2", rev2)
770
771 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
772 gr, err := git.PlainOpen(path)
773 if err != nil {
774 notFound(w)
775 return
776 }
777
778 difftree, err := gr.DiffTree(rev1, rev2)
779 if err != nil {
780 l.Error("error comparing revisions", "msg", err.Error())
781 writeError(w, "error comparing revisions", http.StatusBadRequest)
782 return
783 }
784
785 writeJSON(w, types.RepoDiffTreeResponse{difftree})
786 return
787}
788
789func (h *Handle) AddMember(w http.ResponseWriter, r *http.Request) {
790 l := h.l.With("handler", "AddMember")
791
792 data := struct {
793 Did string `json:"did"`
794 }{}
795
796 if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
797 writeError(w, "invalid request body", http.StatusBadRequest)
798 return
799 }
800
801 did := data.Did
802
803 if err := h.db.AddDid(did); err != nil {
804 l.Error("adding did", "error", err.Error())
805 writeError(w, err.Error(), http.StatusInternalServerError)
806 return
807 }
808 h.jc.AddDid(did)
809
810 if err := h.e.AddMember(ThisServer, did); err != nil {
811 l.Error("adding member", "error", err.Error())
812 writeError(w, err.Error(), http.StatusInternalServerError)
813 return
814 }
815
816 if err := h.fetchAndAddKeys(r.Context(), did); err != nil {
817 l.Error("fetching and adding keys", "error", err.Error())
818 writeError(w, err.Error(), http.StatusInternalServerError)
819 return
820 }
821
822 w.WriteHeader(http.StatusNoContent)
823}
824
825func (h *Handle) AddRepoCollaborator(w http.ResponseWriter, r *http.Request) {
826 l := h.l.With("handler", "AddRepoCollaborator")
827
828 data := struct {
829 Did string `json:"did"`
830 }{}
831
832 ownerDid := chi.URLParam(r, "did")
833 repo := chi.URLParam(r, "name")
834
835 if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
836 writeError(w, "invalid request body", http.StatusBadRequest)
837 return
838 }
839
840 if err := h.db.AddDid(data.Did); err != nil {
841 l.Error("adding did", "error", err.Error())
842 writeError(w, err.Error(), http.StatusInternalServerError)
843 return
844 }
845 h.jc.AddDid(data.Did)
846
847 repoName, _ := securejoin.SecureJoin(ownerDid, repo)
848 if err := h.e.AddCollaborator(data.Did, ThisServer, repoName); err != nil {
849 l.Error("adding repo collaborator", "error", err.Error())
850 writeError(w, err.Error(), http.StatusInternalServerError)
851 return
852 }
853
854 if err := h.fetchAndAddKeys(r.Context(), data.Did); err != nil {
855 l.Error("fetching and adding keys", "error", err.Error())
856 writeError(w, err.Error(), http.StatusInternalServerError)
857 return
858 }
859
860 w.WriteHeader(http.StatusNoContent)
861}
862
863func (h *Handle) DefaultBranch(w http.ResponseWriter, r *http.Request) {
864 l := h.l.With("handler", "DefaultBranch")
865 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
866
867 gr, err := git.Open(path, "")
868 if err != nil {
869 notFound(w)
870 return
871 }
872
873 branch, err := gr.FindMainBranch()
874 if err != nil {
875 writeError(w, err.Error(), http.StatusInternalServerError)
876 l.Error("getting default branch", "error", err.Error())
877 return
878 }
879
880 writeJSON(w, types.RepoDefaultBranchResponse{
881 Branch: branch,
882 })
883}
884
885func (h *Handle) SetDefaultBranch(w http.ResponseWriter, r *http.Request) {
886 l := h.l.With("handler", "SetDefaultBranch")
887 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
888
889 data := struct {
890 Branch string `json:"branch"`
891 }{}
892
893 if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
894 writeError(w, err.Error(), http.StatusBadRequest)
895 return
896 }
897
898 gr, err := git.Open(path, "")
899 if err != nil {
900 notFound(w)
901 return
902 }
903
904 err = gr.SetDefaultBranch(data.Branch)
905 if err != nil {
906 writeError(w, err.Error(), http.StatusInternalServerError)
907 l.Error("setting default branch", "error", err.Error())
908 return
909 }
910
911 w.WriteHeader(http.StatusNoContent)
912}
913
914func (h *Handle) Init(w http.ResponseWriter, r *http.Request) {
915 l := h.l.With("handler", "Init")
916
917 if h.knotInitialized {
918 writeError(w, "knot already initialized", http.StatusConflict)
919 return
920 }
921
922 data := struct {
923 Did string `json:"did"`
924 }{}
925
926 if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
927 l.Error("failed to decode request body", "error", err.Error())
928 writeError(w, "invalid request body", http.StatusBadRequest)
929 return
930 }
931
932 if data.Did == "" {
933 l.Error("empty DID in request", "did", data.Did)
934 writeError(w, "did is empty", http.StatusBadRequest)
935 return
936 }
937
938 if err := h.db.AddDid(data.Did); err != nil {
939 l.Error("failed to add DID", "error", err.Error())
940 writeError(w, err.Error(), http.StatusInternalServerError)
941 return
942 }
943 h.jc.AddDid(data.Did)
944
945 if err := h.e.AddOwner(ThisServer, data.Did); err != nil {
946 l.Error("adding owner", "error", err.Error())
947 writeError(w, err.Error(), http.StatusInternalServerError)
948 return
949 }
950
951 if err := h.fetchAndAddKeys(r.Context(), data.Did); err != nil {
952 l.Error("fetching and adding keys", "error", err.Error())
953 writeError(w, err.Error(), http.StatusInternalServerError)
954 return
955 }
956
957 close(h.init)
958
959 mac := hmac.New(sha256.New, []byte(h.c.Server.Secret))
960 mac.Write([]byte("ok"))
961 w.Header().Add("X-Signature", hex.EncodeToString(mac.Sum(nil)))
962
963 w.WriteHeader(http.StatusNoContent)
964}
965
966func (h *Handle) Health(w http.ResponseWriter, r *http.Request) {
967 w.Write([]byte("ok"))
968}