Refactor auth middleware (#36848)

Principles: let the caller decide what it needs, but not let the
framework (middleware) guess what it should do.

Then a lot of hacky code can be removed. And some FIXMEs can be fixed.

This PR introduces a new kind of middleware: "PreMiddleware", it will be
executed before all other middlewares on the same routing level, then a
route can declare its options for other middlewares.

By the way, allow the workflow badge to be accessed by Basic or OAuth2
auth.

Fixes: https://github.com/go-gitea/gitea/pull/36830
Fixes: https://github.com/go-gitea/gitea/issues/36859
This commit is contained in:
wxiaoguang
2026-03-08 17:59:46 +08:00
committed by GitHub
parent a0996cb229
commit 3f1ef703d5
25 changed files with 338 additions and 444 deletions
+65 -22
View File
@@ -18,6 +18,13 @@ import (
"github.com/go-chi/chi/v5"
)
// PreMiddlewareProvider is a special middleware provider which will be executed
// before other middlewares on the same "routing" level (AfterRouting/Group/Methods/Any, but not BeforeRouting).
// A route can do something (e.g.: set middleware options) at the place where it is declared,
// and the code will be executed before other middlewares which are added before the declaration.
// Use cases: mark a route with some meta info, set some options for middlewares, etc.
type PreMiddlewareProvider func(next http.Handler) http.Handler
// Bind binding an obj to a handler's context data
func Bind[T any](_ T) http.HandlerFunc {
return func(resp http.ResponseWriter, req *http.Request) {
@@ -41,7 +48,10 @@ func GetForm(dataStore reqctx.RequestDataStore) any {
// Router defines a route based on chi's router
type Router struct {
chiRouter *chi.Mux
chiRouter *chi.Mux
afterRouting []any
curGroupPrefix string
curMiddlewares []any
}
@@ -52,8 +62,9 @@ func NewRouter() *Router {
return &Router{chiRouter: r}
}
// Use supports two middlewares
func (r *Router) Use(middlewares ...any) {
// BeforeRouting adds middlewares which will be executed before the request path gets routed
// It should only be used for framework-level global middlewares when it needs to change request method & path.
func (r *Router) BeforeRouting(middlewares ...any) {
for _, m := range middlewares {
if !isNilOrFuncNil(m) {
r.chiRouter.Use(toHandlerProvider(m))
@@ -61,7 +72,13 @@ func (r *Router) Use(middlewares ...any) {
}
}
// Group mounts a sub-Router along a `pattern` string.
// AfterRouting adds middlewares which will be executed after the request path gets routed
// It can see the routed path and resolved path parameters
func (r *Router) AfterRouting(middlewares ...any) {
r.afterRouting = append(r.afterRouting, middlewares...)
}
// Group mounts a sub-router along a "pattern" string.
func (r *Router) Group(pattern string, fn func(), middlewares ...any) {
previousGroupPrefix := r.curGroupPrefix
previousMiddlewares := r.curMiddlewares
@@ -93,36 +110,54 @@ func isNilOrFuncNil(v any) bool {
return r.Kind() == reflect.Func && r.IsNil()
}
func wrapMiddlewareAndHandler(curMiddlewares, h []any) ([]func(http.Handler) http.Handler, http.HandlerFunc) {
handlerProviders := make([]func(http.Handler) http.Handler, 0, len(curMiddlewares)+len(h)+1)
for _, m := range curMiddlewares {
if !isNilOrFuncNil(m) {
handlerProviders = append(handlerProviders, toHandlerProvider(m))
func wrapMiddlewareAppendPre(all []middlewareProvider, middlewares []any) []middlewareProvider {
for _, m := range middlewares {
if h, ok := m.(PreMiddlewareProvider); ok && h != nil {
all = append(all, toHandlerProvider(middlewareProvider(h)))
}
}
return all
}
func wrapMiddlewareAppendNormal(all []middlewareProvider, middlewares []any) []middlewareProvider {
for _, m := range middlewares {
if _, ok := m.(PreMiddlewareProvider); !ok && !isNilOrFuncNil(m) {
all = append(all, toHandlerProvider(m))
}
}
return all
}
func wrapMiddlewareAndHandler(useMiddlewares, curMiddlewares, h []any) (_ []middlewareProvider, _ http.HandlerFunc, hasPreMiddlewares bool) {
if len(h) == 0 {
panic("no endpoint handler provided")
}
for i, m := range h {
if !isNilOrFuncNil(m) {
handlerProviders = append(handlerProviders, toHandlerProvider(m))
} else if i == len(h)-1 {
panic("endpoint handler can't be nil")
}
if isNilOrFuncNil(h[len(h)-1]) {
panic("endpoint handler can't be nil")
}
handlerProviders := make([]middlewareProvider, 0, len(useMiddlewares)+len(curMiddlewares)+len(h)+1)
handlerProviders = wrapMiddlewareAppendPre(handlerProviders, useMiddlewares)
handlerProviders = wrapMiddlewareAppendPre(handlerProviders, curMiddlewares)
handlerProviders = wrapMiddlewareAppendPre(handlerProviders, h)
hasPreMiddlewares = len(handlerProviders) > 0
handlerProviders = wrapMiddlewareAppendNormal(handlerProviders, useMiddlewares)
handlerProviders = wrapMiddlewareAppendNormal(handlerProviders, curMiddlewares)
handlerProviders = wrapMiddlewareAppendNormal(handlerProviders, h)
middlewares := handlerProviders[:len(handlerProviders)-1]
handlerFunc := handlerProviders[len(handlerProviders)-1](nil).ServeHTTP
mockPoint := RouterMockPoint(MockAfterMiddlewares)
if mockPoint != nil {
middlewares = append(middlewares, mockPoint)
}
return middlewares, handlerFunc
return middlewares, handlerFunc, hasPreMiddlewares
}
// Methods adds the same handlers for multiple http "methods" (separated by ",").
// If any method is invalid, the lower level router will panic.
func (r *Router) Methods(methods, pattern string, h ...any) {
middlewares, handlerFunc := wrapMiddlewareAndHandler(r.curMiddlewares, h)
middlewares, handlerFunc, _ := wrapMiddlewareAndHandler(r.afterRouting, r.curMiddlewares, h)
fullPattern := r.getPattern(pattern)
if strings.Contains(methods, ",") {
methods := strings.SplitSeq(methods, ",")
@@ -134,15 +169,19 @@ func (r *Router) Methods(methods, pattern string, h ...any) {
}
}
// Mount attaches another Router along ./pattern/*
// Mount attaches another Router along "/pattern/*"
func (r *Router) Mount(pattern string, subRouter *Router) {
subRouter.Use(r.curMiddlewares...)
r.chiRouter.Mount(r.getPattern(pattern), subRouter.chiRouter)
handlerProviders := make([]middlewareProvider, 0, len(r.afterRouting)+len(r.curMiddlewares))
handlerProviders = wrapMiddlewareAppendPre(handlerProviders, r.afterRouting)
handlerProviders = wrapMiddlewareAppendPre(handlerProviders, r.curMiddlewares)
handlerProviders = wrapMiddlewareAppendNormal(handlerProviders, r.afterRouting)
handlerProviders = wrapMiddlewareAppendNormal(handlerProviders, r.curMiddlewares)
r.chiRouter.With(handlerProviders...).Mount(r.getPattern(pattern), subRouter.chiRouter)
}
// Any delegate requests for all methods
func (r *Router) Any(pattern string, h ...any) {
middlewares, handlerFunc := wrapMiddlewareAndHandler(r.curMiddlewares, h)
middlewares, handlerFunc, _ := wrapMiddlewareAndHandler(r.afterRouting, r.curMiddlewares, h)
r.chiRouter.With(middlewares...).HandleFunc(r.getPattern(pattern), handlerFunc)
}
@@ -178,12 +217,16 @@ func (r *Router) Patch(pattern string, h ...any) {
// ServeHTTP implements http.Handler
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// TODO: need to move it to the top-level common middleware, otherwise each "Mount" will cause it to be executed multiple times, which is inefficient.
r.normalizeRequestPath(w, req, r.chiRouter)
}
// NotFound defines a handler to respond whenever a route could not be found.
func (r *Router) NotFound(h http.HandlerFunc) {
r.chiRouter.NotFound(h)
middlewares, handlerFunc, _ := wrapMiddlewareAndHandler(r.afterRouting, r.curMiddlewares, []any{h})
r.chiRouter.NotFound(func(w http.ResponseWriter, r *http.Request) {
executeMiddlewaresHandler(w, r, middlewares, handlerFunc)
})
}
func (r *Router) normalizeRequestPath(resp http.ResponseWriter, req *http.Request, next http.Handler) {