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 "context"
5 "fmt"
6 "log/slog"
7 "net/http"
8 "strings"
9
10 "github.com/go-chi/chi/v5"
11 "tangled.org/core/idresolver"
12 "tangled.org/core/jetstream"
13 "tangled.org/core/knotserver/config"
14 "tangled.org/core/knotserver/db"
15 "tangled.org/core/knotserver/xrpc"
16 "tangled.org/core/log"
17 "tangled.org/core/notifier"
18 "tangled.org/core/rbac"
19 "tangled.org/core/xrpc/serviceauth"
20)
21
22type Knot struct {
23 c *config.Config
24 db *db.DB
25 jc *jetstream.JetstreamClient
26 e *rbac.Enforcer
27 l *slog.Logger
28 n *notifier.Notifier
29 resolver *idresolver.Resolver
30}
31
32func Setup(ctx context.Context, c *config.Config, db *db.DB, e *rbac.Enforcer, jc *jetstream.JetstreamClient, n *notifier.Notifier) (http.Handler, error) {
33 h := Knot{
34 c: c,
35 db: db,
36 e: e,
37 l: log.FromContext(ctx),
38 jc: jc,
39 n: n,
40 resolver: idresolver.DefaultResolver(c.Server.PlcUrl),
41 }
42
43 err := e.AddKnot(rbac.ThisServer)
44 if err != nil {
45 return nil, fmt.Errorf("failed to setup enforcer: %w", err)
46 }
47
48 // configure owner
49 if err = h.configureOwner(); err != nil {
50 return nil, err
51 }
52 h.l.Info("owner set", "did", h.c.Server.Owner)
53 h.jc.AddDid(h.c.Server.Owner)
54
55 // configure known-dids in jetstream consumer
56 dids, err := h.db.GetAllDids()
57 if err != nil {
58 return nil, fmt.Errorf("failed to get all dids: %w", err)
59 }
60 for _, d := range dids {
61 jc.AddDid(d)
62 }
63
64 err = h.jc.StartJetstream(ctx, h.processMessages)
65 if err != nil {
66 return nil, fmt.Errorf("failed to start jetstream: %w", err)
67 }
68
69 return h.Router(), nil
70}
71
72func (h *Knot) Router() http.Handler {
73 r := chi.NewRouter()
74
75 r.Use(h.CORS)
76 r.Use(h.RequestLogger)
77
78 r.Get("/", func(w http.ResponseWriter, r *http.Request) {
79 w.Write([]byte("This is a knot server. More info at https://tangled.sh"))
80 })
81
82 r.Route("/{did}", func(r chi.Router) {
83 r.Use(h.resolveDidRedirect)
84 r.Route("/{name}", func(r chi.Router) {
85 // routes for git operations
86 r.Get("/info/refs", h.InfoRefs)
87 r.Post("/git-upload-archive", h.UploadArchive)
88 r.Post("/git-upload-pack", h.UploadPack)
89 r.Post("/git-receive-pack", h.ReceivePack)
90 })
91 })
92
93 // xrpc apis
94 r.Mount("/xrpc", h.XrpcRouter())
95
96 // Socket that streams git oplogs
97 r.Get("/events", h.Events)
98
99 return r
100}
101
102func (h *Knot) XrpcRouter() http.Handler {
103 serviceAuth := serviceauth.NewServiceAuth(h.l, h.resolver, h.c.Server.Did().String())
104
105 l := log.SubLogger(h.l, "xrpc")
106
107 xrpc := &xrpc.Xrpc{
108 Config: h.c,
109 Db: h.db,
110 Ingester: h.jc,
111 Enforcer: h.e,
112 Logger: l,
113 Notifier: h.n,
114 Resolver: h.resolver,
115 ServiceAuth: serviceAuth,
116 }
117
118 return xrpc.Router()
119}
120
121func (h *Knot) resolveDidRedirect(next http.Handler) http.Handler {
122 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
123 didOrHandle := chi.URLParam(r, "did")
124 if strings.HasPrefix(didOrHandle, "did:") {
125 next.ServeHTTP(w, r)
126 return
127 }
128
129 trimmed := strings.TrimPrefix(didOrHandle, "@")
130 id, err := h.resolver.ResolveIdent(r.Context(), trimmed)
131 if err != nil {
132 // invalid did or handle
133 h.l.Error("failed to resolve did/handle", "handle", trimmed, "err", err)
134 http.Error(w, fmt.Sprintf("failed to resolve did/handle: %s", trimmed), http.StatusInternalServerError)
135 return
136 }
137
138 suffix := strings.TrimPrefix(r.URL.Path, "/"+didOrHandle)
139 newPath := fmt.Sprintf("/%s/%s?%s", id.DID.String(), suffix, r.URL.RawQuery)
140 http.Redirect(w, r, newPath, http.StatusTemporaryRedirect)
141 })
142}
143
144func (h *Knot) configureOwner() error {
145 cfgOwner := h.c.Server.Owner
146
147 rbacDomain := "thisserver"
148
149 existing, err := h.e.GetKnotUsersByRole("server:owner", rbacDomain)
150 if err != nil {
151 return err
152 }
153
154 switch len(existing) {
155 case 0:
156 // no owner configured, continue
157 case 1:
158 // find existing owner
159 existingOwner := existing[0]
160
161 // no ownership change, this is okay
162 if existingOwner == h.c.Server.Owner {
163 break
164 }
165
166 // remove existing owner
167 if err = h.db.RemoveDid(existingOwner); err != nil {
168 return err
169 }
170 if err = h.e.RemoveKnotOwner(rbacDomain, existingOwner); err != nil {
171 return err
172 }
173
174 default:
175 return fmt.Errorf("more than one owner in DB, try deleting %q and starting over", h.c.Server.DBPath)
176 }
177
178 if err = h.db.AddDid(cfgOwner); err != nil {
179 return fmt.Errorf("failed to add owner to DB: %w", err)
180 }
181 if err := h.e.AddKnotOwner(rbacDomain, cfgOwner); err != nil {
182 return fmt.Errorf("failed to add owner to RBAC: %w", err)
183 }
184
185 return nil
186}