···
13
+
"github.com/bluesky-social/indigo/atproto/identity"
14
+
"github.com/go-chi/chi/v5"
15
+
"tangled.sh/tangled.sh/core/appview"
16
+
"tangled.sh/tangled.sh/core/appview/db"
"tangled.sh/tangled.sh/core/appview/oauth"
18
+
"tangled.sh/tangled.sh/core/appview/pages"
"tangled.sh/tangled.sh/core/appview/pagination"
20
+
"tangled.sh/tangled.sh/core/appview/reporesolver"
21
+
"tangled.sh/tangled.sh/core/rbac"
13
-
type Middleware func(http.Handler) http.Handler
24
+
type Middleware struct {
27
+
enforcer rbac.Enforcer
28
+
repoResolver *reporesolver.RepoResolver
29
+
resolver *appview.Resolver
33
+
func New(oauth *oauth.OAuth, db *db.DB, enforcer rbac.Enforcer, repoResolver *reporesolver.RepoResolver, resolver *appview.Resolver, pages *pages.Pages) Middleware {
38
+
repoResolver: repoResolver,
44
+
type middlewareFunc func(http.Handler) http.Handler
15
-
func AuthMiddleware(a *oauth.OAuth) Middleware {
46
+
func AuthMiddleware(a *oauth.OAuth) middlewareFunc {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
redirectFunc := func(w http.ResponseWriter, r *http.Request) {
···
next.ServeHTTP(w, r.WithContext(ctx))
106
+
func (mw Middleware) knotRoleMiddleware(group string) middlewareFunc {
107
+
return func(next http.Handler) http.Handler {
108
+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
109
+
// requires auth also
110
+
actor := mw.oauth.GetUser(r)
112
+
// we need a logged in user
113
+
log.Printf("not logged in, redirecting")
114
+
http.Error(w, "Forbiden", http.StatusUnauthorized)
117
+
domain := chi.URLParam(r, "domain")
119
+
http.Error(w, "malformed url", http.StatusBadRequest)
123
+
ok, err := mw.enforcer.E.HasGroupingPolicy(actor.Did, group, domain)
124
+
if err != nil || !ok {
125
+
// we need a logged in user
126
+
log.Printf("%s does not have perms of a %s in domain %s", actor.Did, group, domain)
127
+
http.Error(w, "Forbiden", http.StatusUnauthorized)
131
+
next.ServeHTTP(w, r)
136
+
func (mw Middleware) KnotOwner() middlewareFunc {
137
+
return mw.knotRoleMiddleware("server:owner")
140
+
func (mw Middleware) RepoPermissionMiddleware(requiredPerm string) middlewareFunc {
141
+
return func(next http.Handler) http.Handler {
142
+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
143
+
// requires auth also
144
+
actor := mw.oauth.GetUser(r)
146
+
// we need a logged in user
147
+
log.Printf("not logged in, redirecting")
148
+
http.Error(w, "Forbiden", http.StatusUnauthorized)
151
+
f, err := mw.repoResolver.Resolve(r)
153
+
http.Error(w, "malformed url", http.StatusBadRequest)
157
+
ok, err := mw.enforcer.E.Enforce(actor.Did, f.Knot, f.DidSlashRepo(), requiredPerm)
158
+
if err != nil || !ok {
159
+
// we need a logged in user
160
+
log.Printf("%s does not have perms of a %s in repo %s", actor.Did, requiredPerm, f.OwnerSlashRepo())
161
+
http.Error(w, "Forbiden", http.StatusUnauthorized)
165
+
next.ServeHTTP(w, r)
170
+
func StripLeadingAt(next http.Handler) http.Handler {
171
+
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
172
+
path := req.URL.EscapedPath()
173
+
if strings.HasPrefix(path, "/@") {
174
+
req.URL.RawPath = "/" + strings.TrimPrefix(path, "/@")
176
+
next.ServeHTTP(w, req)
180
+
func (mw Middleware) ResolveIdent() middlewareFunc {
181
+
excluded := []string{"favicon.ico"}
183
+
return func(next http.Handler) http.Handler {
184
+
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
185
+
didOrHandle := chi.URLParam(req, "user")
186
+
if slices.Contains(excluded, didOrHandle) {
187
+
next.ServeHTTP(w, req)
191
+
id, err := mw.resolver.ResolveIdent(req.Context(), didOrHandle)
193
+
// invalid did or handle
194
+
log.Println("failed to resolve did/handle:", err)
195
+
w.WriteHeader(http.StatusNotFound)
199
+
ctx := context.WithValue(req.Context(), "resolvedId", *id)
201
+
next.ServeHTTP(w, req.WithContext(ctx))
206
+
func (mw Middleware) ResolveRepo() middlewareFunc {
207
+
return func(next http.Handler) http.Handler {
208
+
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
209
+
repoName := chi.URLParam(req, "repo")
210
+
id, ok := req.Context().Value("resolvedId").(identity.Identity)
212
+
log.Println("malformed middleware")
213
+
w.WriteHeader(http.StatusInternalServerError)
217
+
repo, err := db.GetRepo(mw.db, id.DID.String(), repoName)
219
+
// invalid did or handle
220
+
log.Println("failed to resolve repo")
221
+
mw.pages.Error404(w)
225
+
ctx := context.WithValue(req.Context(), "knot", repo.Knot)
226
+
ctx = context.WithValue(ctx, "repoAt", repo.AtUri)
227
+
ctx = context.WithValue(ctx, "repoDescription", repo.Description)
228
+
ctx = context.WithValue(ctx, "repoAddedAt", repo.Created.Format(time.RFC3339))
229
+
next.ServeHTTP(w, req.WithContext(ctx))
234
+
// middleware that is tacked on top of /{user}/{repo}/pulls/{pull}
235
+
func (mw Middleware) ResolvePull() middlewareFunc {
236
+
return func(next http.Handler) http.Handler {
237
+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
238
+
f, err := mw.repoResolver.Resolve(r)
240
+
log.Println("failed to fully resolve repo", err)
241
+
http.Error(w, "invalid repo url", http.StatusNotFound)
245
+
prId := chi.URLParam(r, "pull")
246
+
prIdInt, err := strconv.Atoi(prId)
248
+
http.Error(w, "bad pr id", http.StatusBadRequest)
249
+
log.Println("failed to parse pr id", err)
253
+
pr, err := db.GetPull(mw.db, f.RepoAt, prIdInt)
255
+
log.Println("failed to get pull and comments", err)
259
+
ctx := context.WithValue(r.Context(), "pull", pr)
261
+
if pr.IsStacked() {
262
+
stack, err := db.GetStack(mw.db, pr.StackId)
264
+
log.Println("failed to get stack", err)
267
+
abandonedPulls, err := db.GetAbandonedPulls(mw.db, pr.StackId)
269
+
log.Println("failed to get abandoned pulls", err)
273
+
ctx = context.WithValue(ctx, "stack", stack)
274
+
ctx = context.WithValue(ctx, "abandonedPulls", abandonedPulls)
277
+
next.ServeHTTP(w, r.WithContext(ctx))
282
+
// this should serve the go-import meta tag even if the path is technically
283
+
// a 404 like tangled.sh/oppi.li/go-git/v5
284
+
func (mw Middleware) GoImport() middlewareFunc {
285
+
return func(next http.Handler) http.Handler {
286
+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
287
+
f, err := mw.repoResolver.Resolve(r)
289
+
log.Println("failed to fully resolve repo", err)
290
+
http.Error(w, "invalid repo url", http.StatusNotFound)
294
+
fullName := f.OwnerHandle() + "/" + f.RepoName
296
+
if r.Header.Get("User-Agent") == "Go-http-client/1.1" {
297
+
if r.URL.Query().Get("go-get") == "1" {
298
+
html := fmt.Sprintf(
299
+
`<meta name="go-import" content="tangled.sh/%s git https://tangled.sh/%s"/>`,
303
+
w.Header().Set("Content-Type", "text/html")
304
+
w.Write([]byte(html))
309
+
next.ServeHTTP(w, r)