fix(oidc): correct behavior when working with bypass rules

- Introduced a new handler for unknown paths in the OIDCProvider to prevent fallback to the default login page.
- Forced OIDC middleware to treat unknown path as logic path to redirect to login property when bypass rules is declared.
- Refactored OIDC path constants.
- Updated checkBypass middleware to enforce path prefixes for bypass rules, ensuring proper request handling.
This commit is contained in:
yusing
2025-11-13 15:13:20 +08:00
parent f6dcc8f118
commit 219eedf3c5
3 changed files with 49 additions and 7 deletions

View File

@@ -31,6 +31,8 @@ type (
endSessionURL *url.URL endSessionURL *url.URL
allowedUsers []string allowedUsers []string
allowedGroups []string allowedGroups []string
onUnknownPathHandler http.HandlerFunc
} }
IDTokenClaims struct { IDTokenClaims struct {
@@ -64,8 +66,9 @@ func (auth *OIDCProvider) getAppScopedCookieName(baseName string) string {
const ( const (
OIDCAuthInitPath = "/" OIDCAuthInitPath = "/"
OIDCPostAuthPath = "/auth/callback" OIDCAuthBasePath = "/auth"
OIDCLogoutPath = "/auth/logout" OIDCPostAuthPath = OIDCAuthBasePath + "/callback"
OIDCLogoutPath = OIDCAuthBasePath + "/logout"
) )
var ( var (
@@ -177,6 +180,10 @@ func (auth *OIDCProvider) SetScopes(scopes []string) {
auth.oauthConfig.Scopes = scopes auth.oauthConfig.Scopes = scopes
} }
func (auth *OIDCProvider) SetOnUnknownPathHandler(handler http.HandlerFunc) {
auth.onUnknownPathHandler = handler
}
// optRedirectPostAuth returns an oauth2 option that sets the "redirect_uri" // optRedirectPostAuth returns an oauth2 option that sets the "redirect_uri"
// parameter of the authorization URL to the post auth path of the current // parameter of the authorization URL to the post auth path of the current
// request host. // request host.
@@ -213,6 +220,10 @@ func (auth *OIDCProvider) HandleAuth(w http.ResponseWriter, r *http.Request) {
case OIDCLogoutPath: case OIDCLogoutPath:
auth.LogoutHandler(w, r) auth.LogoutHandler(w, r)
default: default:
if auth.onUnknownPathHandler != nil {
auth.onUnknownPathHandler(w, r)
return
}
http.Redirect(w, r, OIDCAuthInitPath, http.StatusFound) http.Redirect(w, r, OIDCAuthInitPath, http.StatusFound)
} }
} }

View File

@@ -2,7 +2,9 @@ package middleware
import ( import (
"net/http" "net/http"
"strings"
"github.com/yusing/godoxy/internal/auth"
"github.com/yusing/godoxy/internal/route/rules" "github.com/yusing/godoxy/internal/route/rules"
) )
@@ -21,17 +23,29 @@ type checkBypass struct {
bypass Bypass bypass Bypass
modReq RequestModifier modReq RequestModifier
modRes ResponseModifier modRes ResponseModifier
// when request path matches any of these prefixes, bypass is not applied
enforcedPathPrefixes []string
}
func (c *checkBypass) isEnforced(r *http.Request) bool {
for _, prefix := range c.enforcedPathPrefixes {
if strings.HasPrefix(r.URL.Path, prefix) {
return true
}
}
return false
} }
func (c *checkBypass) before(w http.ResponseWriter, r *http.Request) (proceedNext bool) { func (c *checkBypass) before(w http.ResponseWriter, r *http.Request) (proceedNext bool) {
if c.modReq == nil || c.bypass.ShouldBypass(w, r) { if c.modReq == nil || (!c.isEnforced(r) && c.bypass.ShouldBypass(w, r)) {
return true return true
} }
return c.modReq.before(w, r) return c.modReq.before(w, r)
} }
func (c *checkBypass) modifyResponse(resp *http.Response) error { func (c *checkBypass) modifyResponse(resp *http.Response) error {
if c.modRes == nil || c.bypass.ShouldBypass(rules.ResponseAsRW(resp), resp.Request) { if c.modRes == nil || (!c.isEnforced(resp.Request) && c.bypass.ShouldBypass(rules.ResponseAsRW(resp), resp.Request)) {
return nil return nil
} }
return c.modRes.modifyResponse(resp) return c.modRes.modifyResponse(resp)
@@ -42,10 +56,22 @@ func (m *Middleware) withCheckBypass() any {
modReq, _ := m.impl.(RequestModifier) modReq, _ := m.impl.(RequestModifier)
modRes, _ := m.impl.(ResponseModifier) modRes, _ := m.impl.(ResponseModifier)
return &checkBypass{ return &checkBypass{
bypass: m.Bypass, bypass: m.Bypass,
modReq: modReq, enforcedPathPrefixes: getEnforcedPathPrefixes(modReq, modRes),
modRes: modRes, modReq: modReq,
modRes: modRes,
} }
} }
return m.impl return m.impl
} }
func getEnforcedPathPrefixes(modReq RequestModifier, modRes ResponseModifier) []string {
if modReq == nil && modRes == nil {
return nil
}
switch modReq.(type) {
case *oidcMiddleware:
return []string{auth.OIDCAuthBasePath}
}
return nil
}

View File

@@ -74,6 +74,11 @@ func (amw *oidcMiddleware) initSlow() error {
} }
// If no custom credentials, authProvider remains the global one // If no custom credentials, authProvider remains the global one
// Always trigger login on unknown paths.
// This prevents falling back to the default login page, which applies bypass rules.
// Without this, redirecting to the global login page could circumvent the intended route restrictions.
authProvider.SetOnUnknownPathHandler(authProvider.LoginHandler)
// Apply per-route user/group restrictions (these always override global) // Apply per-route user/group restrictions (these always override global)
if len(amw.AllowedUsers) > 0 { if len(amw.AllowedUsers) > 0 {
authProvider.SetAllowedUsers(amw.AllowedUsers) authProvider.SetAllowedUsers(amw.AllowedUsers)