this repo has no description
1package knotserver
2
3import (
4 "context"
5 "encoding/json"
6 "log/slog"
7 "net/http"
8 "path/filepath"
9
10 "github.com/go-chi/chi/v5"
11 "github.com/go-chi/chi/v5/middleware"
12 "tangled.sh/tangled.sh/core/api/tangled"
13 "tangled.sh/tangled.sh/core/knotserver/config"
14 "tangled.sh/tangled.sh/core/knotserver/db"
15 "tangled.sh/tangled.sh/core/knotserver/git"
16 "tangled.sh/tangled.sh/core/knotserver/notifier"
17 "tangled.sh/tangled.sh/core/rbac"
18)
19
20type InternalHandle struct {
21 db *db.DB
22 c *config.Config
23 e *rbac.Enforcer
24 l *slog.Logger
25 n *notifier.Notifier
26}
27
28func (h *InternalHandle) PushAllowed(w http.ResponseWriter, r *http.Request) {
29 user := r.URL.Query().Get("user")
30 repo := r.URL.Query().Get("repo")
31
32 if user == "" || repo == "" {
33 w.WriteHeader(http.StatusBadRequest)
34 return
35 }
36
37 ok, err := h.e.IsPushAllowed(user, ThisServer, repo)
38 if err != nil || !ok {
39 w.WriteHeader(http.StatusForbidden)
40 return
41 }
42
43 w.WriteHeader(http.StatusNoContent)
44 return
45}
46
47func (h *InternalHandle) InternalKeys(w http.ResponseWriter, r *http.Request) {
48 keys, err := h.db.GetAllPublicKeys()
49 if err != nil {
50 writeError(w, err.Error(), http.StatusInternalServerError)
51 return
52 }
53
54 data := make([]map[string]interface{}, 0)
55 for _, key := range keys {
56 j := key.JSON()
57 data = append(data, j)
58 }
59 writeJSON(w, data)
60 return
61}
62
63func (h *InternalHandle) PostReceiveHook(w http.ResponseWriter, r *http.Request) {
64 l := h.l.With("handler", "PostReceiveHook")
65
66 gitAbsoluteDir := r.Header.Get("X-Git-Dir")
67 gitRelativeDir, err := filepath.Rel(h.c.Repo.ScanPath, gitAbsoluteDir)
68 if err != nil {
69 l.Error("failed to calculate relative git dir", "scanPath", h.c.Repo.ScanPath, "gitAbsoluteDir", gitAbsoluteDir)
70 return
71 }
72
73 parts := strings.SplitN(gitRelativeDir, "/", 2)
74 if len(parts) != 2 {
75 l.Error("invalid git dir", "gitRelativeDir", gitRelativeDir)
76 return
77 }
78 repoDid := parts[0]
79 repoName := parts[1]
80
81 gitUserDid := r.Header.Get("X-Git-User-Did")
82
83 lines, err := git.ParsePostReceive(r.Body)
84 if err != nil {
85 l.Error("failed to parse post-receive payload", "err", err)
86 // non-fatal
87 }
88
89 for _, line := range lines {
90 err := h.insertRefUpdate(line, gitUserDid, repoDid, repoName)
91 if err != nil {
92 l.Error("failed to insert op", "err", err, "line", line, "did", gitUserDid, "repo", gitRelativeDir)
93 // non-fatal
94 }
95 }
96}
97
98func (h *InternalHandle) insertRefUpdate(line git.PostReceiveLine, gitUserDid, repoDid, repoName string) error {
99 refUpdate := tangled.GitRefUpdate{
100 OldSha: line.OldSha,
101 NewSha: line.NewSha,
102 Ref: line.Ref,
103 CommitterDid: gitUserDid,
104 RepoDid: repoDid,
105 RepoName: repoName,
106 }
107 eventJson, err := json.Marshal(refUpdate)
108 if err != nil {
109 return err
110 }
111
112 event := db.Event{
113 Rkey: TID(),
114 Nsid: tangled.GitRefUpdateNSID,
115 EventJson: string(eventJson),
116 }
117
118 return h.db.InsertEvent(event, h.n)
119}
120
121func Internal(ctx context.Context, c *config.Config, db *db.DB, e *rbac.Enforcer, l *slog.Logger, n *notifier.Notifier) http.Handler {
122 r := chi.NewRouter()
123
124 h := InternalHandle{
125 db,
126 c,
127 e,
128 l,
129 n,
130 }
131
132 r.Get("/push-allowed", h.PushAllowed)
133 r.Get("/keys", h.InternalKeys)
134 r.Post("/hooks/post-receive", h.PostReceiveHook)
135 r.Mount("/debug", middleware.Profiler())
136
137 return r
138}