forked from
tangled.org/core
fork
Configure Feed
Select the types of activity you want to include in your feed.
Monorepo for Tangled
fork
Configure Feed
Select the types of activity you want to include in your feed.
1package state
2
3import (
4 "context"
5 "log"
6 "net/http"
7 "strconv"
8 "strings"
9 "time"
10
11 "slices"
12
13 "github.com/bluesky-social/indigo/atproto/identity"
14 "github.com/go-chi/chi/v5"
15 "tangled.sh/tangled.sh/core/appview/db"
16 "tangled.sh/tangled.sh/core/appview/middleware"
17)
18
19func knotRoleMiddleware(s *State, group string) middleware.Middleware {
20 return func(next http.Handler) http.Handler {
21 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
22 // requires auth also
23 actor := s.auth.GetUser(r)
24 if actor == nil {
25 // we need a logged in user
26 log.Printf("not logged in, redirecting")
27 http.Error(w, "Forbiden", http.StatusUnauthorized)
28 return
29 }
30 domain := chi.URLParam(r, "domain")
31 if domain == "" {
32 http.Error(w, "malformed url", http.StatusBadRequest)
33 return
34 }
35
36 ok, err := s.enforcer.E.HasGroupingPolicy(actor.Did, group, domain)
37 if err != nil || !ok {
38 // we need a logged in user
39 log.Printf("%s does not have perms of a %s in domain %s", actor.Did, group, domain)
40 http.Error(w, "Forbiden", http.StatusUnauthorized)
41 return
42 }
43
44 next.ServeHTTP(w, r)
45 })
46 }
47}
48
49func KnotOwner(s *State) middleware.Middleware {
50 return knotRoleMiddleware(s, "server:owner")
51}
52
53func RepoPermissionMiddleware(s *State, requiredPerm string) middleware.Middleware {
54 return func(next http.Handler) http.Handler {
55 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
56 // requires auth also
57 actor := s.auth.GetUser(r)
58 if actor == nil {
59 // we need a logged in user
60 log.Printf("not logged in, redirecting")
61 http.Error(w, "Forbiden", http.StatusUnauthorized)
62 return
63 }
64 f, err := s.fullyResolvedRepo(r)
65 if err != nil {
66 http.Error(w, "malformed url", http.StatusBadRequest)
67 return
68 }
69
70 ok, err := s.enforcer.E.Enforce(actor.Did, f.Knot, f.DidSlashRepo(), requiredPerm)
71 if err != nil || !ok {
72 // we need a logged in user
73 log.Printf("%s does not have perms of a %s in repo %s", actor.Did, requiredPerm, f.OwnerSlashRepo())
74 http.Error(w, "Forbiden", http.StatusUnauthorized)
75 return
76 }
77
78 next.ServeHTTP(w, r)
79 })
80 }
81}
82
83func StripLeadingAt(next http.Handler) http.Handler {
84 return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
85 path := req.URL.EscapedPath()
86 if strings.HasPrefix(path, "/@") {
87 req.URL.RawPath = "/" + strings.TrimPrefix(path, "/@")
88 }
89 next.ServeHTTP(w, req)
90 })
91}
92
93func ResolveIdent(s *State) middleware.Middleware {
94 excluded := []string{"favicon.ico"}
95
96 return func(next http.Handler) http.Handler {
97 return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
98 didOrHandle := chi.URLParam(req, "user")
99 if slices.Contains(excluded, didOrHandle) {
100 next.ServeHTTP(w, req)
101 return
102 }
103
104 id, err := s.resolver.ResolveIdent(req.Context(), didOrHandle)
105 if err != nil {
106 // invalid did or handle
107 log.Println("failed to resolve did/handle:", err)
108 w.WriteHeader(http.StatusNotFound)
109 return
110 }
111
112 ctx := context.WithValue(req.Context(), "resolvedId", *id)
113
114 next.ServeHTTP(w, req.WithContext(ctx))
115 })
116 }
117}
118
119func ResolveRepo(s *State) middleware.Middleware {
120 return func(next http.Handler) http.Handler {
121 return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
122 repoName := chi.URLParam(req, "repo")
123 id, ok := req.Context().Value("resolvedId").(identity.Identity)
124 if !ok {
125 log.Println("malformed middleware")
126 w.WriteHeader(http.StatusInternalServerError)
127 return
128 }
129
130 repo, err := db.GetRepo(s.db, id.DID.String(), repoName)
131 if err != nil {
132 // invalid did or handle
133 log.Println("failed to resolve repo")
134 w.WriteHeader(http.StatusNotFound)
135 return
136 }
137
138 ctx := context.WithValue(req.Context(), "knot", repo.Knot)
139 ctx = context.WithValue(ctx, "repoAt", repo.AtUri)
140 ctx = context.WithValue(ctx, "repoDescription", repo.Description)
141 ctx = context.WithValue(ctx, "repoAddedAt", repo.Created.Format(time.RFC3339))
142 next.ServeHTTP(w, req.WithContext(ctx))
143 })
144 }
145}
146
147// middleware that is tacked on top of /{user}/{repo}/pulls/{pull}
148func ResolvePull(s *State) middleware.Middleware {
149 return func(next http.Handler) http.Handler {
150 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
151 f, err := s.fullyResolvedRepo(r)
152 if err != nil {
153 log.Println("failed to fully resolve repo", err)
154 http.Error(w, "invalid repo url", http.StatusNotFound)
155 return
156 }
157
158 prId := chi.URLParam(r, "pull")
159 prIdInt, err := strconv.Atoi(prId)
160 if err != nil {
161 http.Error(w, "bad pr id", http.StatusBadRequest)
162 log.Println("failed to parse pr id", err)
163 return
164 }
165
166 pr, err := db.GetPull(s.db, f.RepoAt, prIdInt)
167 if err != nil {
168 log.Println("failed to get pull and comments", err)
169 return
170 }
171
172 ctx := context.WithValue(r.Context(), "pull", pr)
173
174 next.ServeHTTP(w, r.WithContext(ctx))
175 })
176 }
177}