forked from tangled.org/core
this repo has no description
1package state 2 3import ( 4 "context" 5 "fmt" 6 "log" 7 "net/http" 8 "strconv" 9 "strings" 10 "time" 11 12 "slices" 13 14 "github.com/bluesky-social/indigo/atproto/identity" 15 "github.com/go-chi/chi/v5" 16 "tangled.sh/tangled.sh/core/appview/db" 17 "tangled.sh/tangled.sh/core/appview/middleware" 18) 19 20func knotRoleMiddleware(s *State, group string) middleware.Middleware { 21 return func(next http.Handler) http.Handler { 22 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 23 // requires auth also 24 actor := s.oauth.GetUser(r) 25 if actor == nil { 26 // we need a logged in user 27 log.Printf("not logged in, redirecting") 28 http.Error(w, "Forbiden", http.StatusUnauthorized) 29 return 30 } 31 domain := chi.URLParam(r, "domain") 32 if domain == "" { 33 http.Error(w, "malformed url", http.StatusBadRequest) 34 return 35 } 36 37 ok, err := s.enforcer.E.HasGroupingPolicy(actor.Did, group, domain) 38 if err != nil || !ok { 39 // we need a logged in user 40 log.Printf("%s does not have perms of a %s in domain %s", actor.Did, group, domain) 41 http.Error(w, "Forbiden", http.StatusUnauthorized) 42 return 43 } 44 45 next.ServeHTTP(w, r) 46 }) 47 } 48} 49 50func KnotOwner(s *State) middleware.Middleware { 51 return knotRoleMiddleware(s, "server:owner") 52} 53 54func RepoPermissionMiddleware(s *State, requiredPerm string) middleware.Middleware { 55 return func(next http.Handler) http.Handler { 56 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 57 // requires auth also 58 actor := s.oauth.GetUser(r) 59 if actor == nil { 60 // we need a logged in user 61 log.Printf("not logged in, redirecting") 62 http.Error(w, "Forbiden", http.StatusUnauthorized) 63 return 64 } 65 f, err := s.fullyResolvedRepo(r) 66 if err != nil { 67 http.Error(w, "malformed url", http.StatusBadRequest) 68 return 69 } 70 71 ok, err := s.enforcer.E.Enforce(actor.Did, f.Knot, f.DidSlashRepo(), requiredPerm) 72 if err != nil || !ok { 73 // we need a logged in user 74 log.Printf("%s does not have perms of a %s in repo %s", actor.Did, requiredPerm, f.OwnerSlashRepo()) 75 http.Error(w, "Forbiden", http.StatusUnauthorized) 76 return 77 } 78 79 next.ServeHTTP(w, r) 80 }) 81 } 82} 83 84func StripLeadingAt(next http.Handler) http.Handler { 85 return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 86 path := req.URL.EscapedPath() 87 if strings.HasPrefix(path, "/@") { 88 req.URL.RawPath = "/" + strings.TrimPrefix(path, "/@") 89 } 90 next.ServeHTTP(w, req) 91 }) 92} 93 94func ResolveIdent(s *State) middleware.Middleware { 95 excluded := []string{"favicon.ico"} 96 97 return func(next http.Handler) http.Handler { 98 return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 99 didOrHandle := chi.URLParam(req, "user") 100 if slices.Contains(excluded, didOrHandle) { 101 next.ServeHTTP(w, req) 102 return 103 } 104 105 id, err := s.resolver.ResolveIdent(req.Context(), didOrHandle) 106 if err != nil { 107 // invalid did or handle 108 log.Println("failed to resolve did/handle:", err) 109 w.WriteHeader(http.StatusNotFound) 110 return 111 } 112 113 ctx := context.WithValue(req.Context(), "resolvedId", *id) 114 115 next.ServeHTTP(w, req.WithContext(ctx)) 116 }) 117 } 118} 119 120func ResolveRepo(s *State) middleware.Middleware { 121 return func(next http.Handler) http.Handler { 122 return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 123 repoName := chi.URLParam(req, "repo") 124 id, ok := req.Context().Value("resolvedId").(identity.Identity) 125 if !ok { 126 log.Println("malformed middleware") 127 w.WriteHeader(http.StatusInternalServerError) 128 return 129 } 130 131 repo, err := db.GetRepo(s.db, id.DID.String(), repoName) 132 if err != nil { 133 // invalid did or handle 134 log.Println("failed to resolve repo") 135 s.pages.Error404(w) 136 return 137 } 138 139 ctx := context.WithValue(req.Context(), "knot", repo.Knot) 140 ctx = context.WithValue(ctx, "repoAt", repo.AtUri) 141 ctx = context.WithValue(ctx, "repoDescription", repo.Description) 142 ctx = context.WithValue(ctx, "repoAddedAt", repo.Created.Format(time.RFC3339)) 143 next.ServeHTTP(w, req.WithContext(ctx)) 144 }) 145 } 146} 147 148// middleware that is tacked on top of /{user}/{repo}/pulls/{pull} 149func ResolvePull(s *State) middleware.Middleware { 150 return func(next http.Handler) http.Handler { 151 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 152 f, err := s.fullyResolvedRepo(r) 153 if err != nil { 154 log.Println("failed to fully resolve repo", err) 155 http.Error(w, "invalid repo url", http.StatusNotFound) 156 return 157 } 158 159 prId := chi.URLParam(r, "pull") 160 prIdInt, err := strconv.Atoi(prId) 161 if err != nil { 162 http.Error(w, "bad pr id", http.StatusBadRequest) 163 log.Println("failed to parse pr id", err) 164 return 165 } 166 167 pr, err := db.GetPull(s.db, f.RepoAt, prIdInt) 168 if err != nil { 169 log.Println("failed to get pull and comments", err) 170 return 171 } 172 173 ctx := context.WithValue(r.Context(), "pull", pr) 174 175 if pr.IsStacked() { 176 stack, err := db.GetStack(s.db, pr.StackId) 177 if err != nil { 178 log.Println("failed to get stack", err) 179 return 180 } 181 abandonedPulls, err := db.GetAbandonedPulls(s.db, pr.StackId) 182 if err != nil { 183 log.Println("failed to get abandoned pulls", err) 184 return 185 } 186 187 ctx = context.WithValue(ctx, "stack", stack) 188 ctx = context.WithValue(ctx, "abandonedPulls", abandonedPulls) 189 } 190 191 next.ServeHTTP(w, r.WithContext(ctx)) 192 }) 193 } 194} 195 196// this should serve the go-import meta tag even if the path is technically 197// a 404 like tangled.sh/oppi.li/go-git/v5 198func GoImport(s *State) middleware.Middleware { 199 return func(next http.Handler) http.Handler { 200 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 201 f, err := s.fullyResolvedRepo(r) 202 if err != nil { 203 log.Println("failed to fully resolve repo", err) 204 http.Error(w, "invalid repo url", http.StatusNotFound) 205 return 206 } 207 208 fullName := f.OwnerHandle() + "/" + f.RepoName 209 210 if r.Header.Get("User-Agent") == "Go-http-client/1.1" { 211 if r.URL.Query().Get("go-get") == "1" { 212 html := fmt.Sprintf( 213 `<meta name="go-import" content="tangled.sh/%s git https://tangled.sh/%s"/>`, 214 fullName, 215 fullName, 216 ) 217 w.Header().Set("Content-Type", "text/html") 218 w.Write([]byte(html)) 219 return 220 } 221 } 222 223 next.ServeHTTP(w, r) 224 }) 225 } 226}