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