mirror of
https://github.com/yusing/godoxy.git
synced 2026-02-23 01:04:53 +01:00
refactor(middleware): replace path prefix checks with function-based approach
Replace simple path prefix-based enforcement/bypass mechanism with a more flexible function-based approach. This allows for more complex conditions to determine when middleware should be enforced or bypassed. - Add checkReqFunc and checkRespFunc types for flexible condition checking - Replace enforcedPathPrefixes with separate enforce and bypass check functions - Add static asset path detection for automatic bypassing - Separate request and response check logic for better granularity
This commit is contained in:
@@ -2,16 +2,19 @@ package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/yusing/godoxy/internal/auth"
|
||||
"github.com/yusing/godoxy/internal/route/rules"
|
||||
httputils "github.com/yusing/goutils/http"
|
||||
)
|
||||
|
||||
type Bypass []rules.RuleOn
|
||||
|
||||
type (
|
||||
checkReqFunc func(r *http.Request) bool
|
||||
checkRespFunc func(resp *http.Response) bool
|
||||
)
|
||||
|
||||
func (b Bypass) ShouldBypass(w http.ResponseWriter, r *http.Request) bool {
|
||||
for _, rule := range b {
|
||||
if rule.Check(w, r) {
|
||||
@@ -29,32 +32,93 @@ type checkBypass struct {
|
||||
modReq RequestModifier
|
||||
modRes ResponseModifier
|
||||
|
||||
// when request path matches any of these prefixes, bypass is not applied
|
||||
enforcedPathPrefixes []string
|
||||
modReqCheckEnforceFuncs []checkReqFunc
|
||||
modReqCheckBypassFuncs []checkReqFunc
|
||||
|
||||
modResCheckEnforceFuncs []checkRespFunc
|
||||
modResCheckBypassFuncs []checkRespFunc
|
||||
}
|
||||
|
||||
func (c *checkBypass) isEnforced(r *http.Request) bool {
|
||||
for _, prefix := range c.enforcedPathPrefixes {
|
||||
if strings.HasPrefix(r.URL.Path, prefix) {
|
||||
var (
|
||||
_ RequestModifier = (*checkBypass)(nil)
|
||||
_ ResponseModifier = (*checkBypass)(nil)
|
||||
)
|
||||
|
||||
// shouldModReqEnforce checks if the modify request should be enforced.
|
||||
//
|
||||
// Returns true if any of the check functions returns true.
|
||||
func (c *checkBypass) shouldModReqEnforce(r *http.Request) bool {
|
||||
for _, f := range c.modReqCheckEnforceFuncs {
|
||||
if f(r) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// shouldModResEnforce checks if the modify response should be enforced.
|
||||
//
|
||||
// Returns true if any of the check functions returns true.
|
||||
func (c *checkBypass) shouldModResEnforce(resp *http.Response) bool {
|
||||
for _, f := range c.modResCheckEnforceFuncs {
|
||||
if f(resp) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// shouldModReqBypass checks if the modify request should be bypassed.
|
||||
//
|
||||
// If enforce checks return true, the bypass checks are not performed.
|
||||
// Otherwise, if any of the bypass checks returns true
|
||||
// or user defined bypass rules return true, the request is bypassed.
|
||||
func (c *checkBypass) shouldModReqBypass(w http.ResponseWriter, r *http.Request) bool {
|
||||
if c.shouldModReqEnforce(r) {
|
||||
return false
|
||||
}
|
||||
for _, f := range c.modReqCheckBypassFuncs {
|
||||
if f(r) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return c.bypass.ShouldBypass(w, r)
|
||||
}
|
||||
|
||||
// shouldModResBypass checks if the modify response should be bypassed.
|
||||
//
|
||||
// If enforce checks return true, the bypass checks are not performed.
|
||||
// Otherwise, if any of the bypass checks returns true
|
||||
// or user defined bypass rules return true, the response is bypassed.
|
||||
func (c *checkBypass) shouldModResBypass(resp *http.Response) bool {
|
||||
if c.shouldModResEnforce(resp) {
|
||||
return false
|
||||
}
|
||||
for _, f := range c.modResCheckBypassFuncs {
|
||||
if f(resp) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return c.bypass.ShouldBypass(httputils.ResponseAsRW(resp), resp.Request)
|
||||
}
|
||||
|
||||
// before modifies the request if the request should be modified.
|
||||
//
|
||||
// Returns true if the request is not done, false otherwise.
|
||||
func (c *checkBypass) before(w http.ResponseWriter, r *http.Request) (proceedNext bool) {
|
||||
if c.modReq == nil || (!c.isEnforced(r) && c.bypass.ShouldBypass(w, r)) {
|
||||
if c.modReq == nil || c.shouldModReqBypass(w, r) {
|
||||
return true
|
||||
}
|
||||
log.Debug().Str("middleware", c.name).Str("url", r.Host+r.URL.Path).Msg("modifying request")
|
||||
// log.Debug().Str("middleware", c.name).Str("url", r.Host+r.URL.Path).Msg("modifying request")
|
||||
return c.modReq.before(w, r)
|
||||
}
|
||||
|
||||
// modifyResponse modifies the response if the response should be modified.
|
||||
func (c *checkBypass) modifyResponse(resp *http.Response) error {
|
||||
if c.modRes == nil || (!c.isEnforced(resp.Request) && c.bypass.ShouldBypass(httputils.ResponseAsRW(resp), resp.Request)) {
|
||||
if c.modRes == nil || c.shouldModResBypass(resp) {
|
||||
return nil
|
||||
}
|
||||
log.Debug().Str("middleware", c.name).Str("url", resp.Request.Host+resp.Request.URL.Path).Msg("modifying response")
|
||||
// log.Debug().Str("middleware", c.name).Str("url", resp.Request.Host+resp.Request.URL.Path).Msg("modifying response")
|
||||
return c.modRes.modifyResponse(resp)
|
||||
}
|
||||
|
||||
@@ -63,23 +127,46 @@ func (m *Middleware) withCheckBypass() any {
|
||||
modReq, _ := m.impl.(RequestModifier)
|
||||
modRes, _ := m.impl.(ResponseModifier)
|
||||
return &checkBypass{
|
||||
name: m.Name(),
|
||||
bypass: m.Bypass,
|
||||
enforcedPathPrefixes: getEnforcedPathPrefixes(modReq, modRes),
|
||||
modReq: modReq,
|
||||
modRes: modRes,
|
||||
name: m.Name(),
|
||||
bypass: m.Bypass,
|
||||
modReq: modReq,
|
||||
modRes: modRes,
|
||||
modReqCheckEnforceFuncs: getModReqCheckEnforceFuncs(modReq),
|
||||
modReqCheckBypassFuncs: getModReqCheckBypassFuncs(modReq),
|
||||
modResCheckEnforceFuncs: getModResCheckEnforceFuncs(modRes),
|
||||
modResCheckBypassFuncs: getModResCheckBypassFuncs(modRes),
|
||||
}
|
||||
}
|
||||
return m.impl
|
||||
}
|
||||
|
||||
func getEnforcedPathPrefixes(modReq RequestModifier, modRes ResponseModifier) []string {
|
||||
if modReq == nil && modRes == nil {
|
||||
func getModReqCheckEnforceFuncs(modReq RequestModifier) (checks []checkReqFunc) {
|
||||
if modReq == nil {
|
||||
return nil
|
||||
}
|
||||
if _, ok := modReq.(*oidcMiddleware); ok {
|
||||
checks = append(checks, isOIDCAuthPath)
|
||||
}
|
||||
return checks
|
||||
}
|
||||
|
||||
func getModReqCheckBypassFuncs(modReq RequestModifier) (checks []checkReqFunc) {
|
||||
if modReq == nil {
|
||||
return nil
|
||||
}
|
||||
switch modReq.(type) {
|
||||
case *oidcMiddleware:
|
||||
return []string{auth.OIDCAuthBasePath}
|
||||
case *oidcMiddleware, *forwardAuthMiddleware, *crowdsecMiddleware, *hCaptcha:
|
||||
checks = append(checks, isStaticAssetPath)
|
||||
}
|
||||
return checks
|
||||
}
|
||||
|
||||
func getModResCheckEnforceFuncs(modRes ResponseModifier) []checkRespFunc {
|
||||
// TODO: add enforce checks for response modifiers if needed.
|
||||
return nil
|
||||
}
|
||||
|
||||
func getModResCheckBypassFuncs(modRes ResponseModifier) []checkRespFunc {
|
||||
// TODO: add bypass checks for response modifiers if needed.
|
||||
return nil
|
||||
}
|
||||
|
||||
74
internal/net/gphttp/middleware/bypass_static.go
Normal file
74
internal/net/gphttp/middleware/bypass_static.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/yusing/godoxy/internal/route/rules"
|
||||
)
|
||||
|
||||
func must[T any](v T, err error) T {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
var staticAssetsPaths = map[string]struct{}{
|
||||
// Web app manifests
|
||||
"/manifest.json": {},
|
||||
"/manifest.webmanifest": {},
|
||||
// Service workers
|
||||
"/sw.js": {},
|
||||
"/registerSW.js": {},
|
||||
// Favicons
|
||||
"/favicon.ico": {},
|
||||
"/favicon.png": {},
|
||||
"/favicon.svg": {},
|
||||
// Apple icons
|
||||
"/apple-icon.png": {},
|
||||
"/apple-touch-icon.png": {},
|
||||
"/apple-touch-icon-precomposed.png": {},
|
||||
// Microsoft / browser config
|
||||
"/browserconfig.xml": {},
|
||||
// Safari pinned tab
|
||||
"/safari-pinned-tab.svg": {},
|
||||
// Crawlers / SEO
|
||||
"/robots.txt": {},
|
||||
"/sitemap.xml": {},
|
||||
"/sitemap_index.xml": {},
|
||||
"/ads.txt": {},
|
||||
}
|
||||
|
||||
var staticAssetsGlobs = []rules.Matcher{
|
||||
// Workbox (PWA)
|
||||
must(rules.GlobMatcher("/workbox-window.prod.es5-*.js", false)),
|
||||
must(rules.GlobMatcher("/workbox-*.js", false)),
|
||||
// Favicon variants (e.g. favicon-32x32.png)
|
||||
must(rules.GlobMatcher("/favicon-*.png", false)),
|
||||
// Web app manifest icons
|
||||
must(rules.GlobMatcher("/web-app-manifest-*.png", false)),
|
||||
// Android Chrome icons
|
||||
must(rules.GlobMatcher("/android-chrome-*.png", false)),
|
||||
// Apple touch icon variants
|
||||
must(rules.GlobMatcher("/apple-touch-icon-*.png", false)),
|
||||
// Microsoft tile icons
|
||||
must(rules.GlobMatcher("/mstile-*.png", false)),
|
||||
// Generic PWA / app icons
|
||||
must(rules.GlobMatcher("/pwa-*.png", false)),
|
||||
must(rules.GlobMatcher("/icon-*.png", false)),
|
||||
// Sitemaps (e.g. sitemap-1.xml, sitemap-posts.xml)
|
||||
must(rules.GlobMatcher("/sitemap-*.xml", false)),
|
||||
}
|
||||
|
||||
func isStaticAssetPath(r *http.Request) bool {
|
||||
if _, ok := staticAssetsPaths[r.URL.Path]; ok {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, matcher := range staticAssetsGlobs {
|
||||
if matcher(r.URL.Path) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -28,6 +28,10 @@ type oidcMiddleware struct {
|
||||
|
||||
var OIDC = NewMiddleware[oidcMiddleware]()
|
||||
|
||||
func isOIDCAuthPath(r *http.Request) bool {
|
||||
return strings.HasPrefix(r.URL.Path, auth.OIDCAuthBasePath)
|
||||
}
|
||||
|
||||
func (amw *oidcMiddleware) finalize() error {
|
||||
if !auth.IsOIDCEnabled() {
|
||||
log.Error().Msg("OIDC not enabled but OIDC middleware is used")
|
||||
|
||||
Reference in New Issue
Block a user