diff --git a/internal/auth/oidc.go b/internal/auth/oidc.go index 44b90608..6352e898 100644 --- a/internal/auth/oidc.go +++ b/internal/auth/oidc.go @@ -31,6 +31,8 @@ type ( endSessionURL *url.URL allowedUsers []string allowedGroups []string + + onUnknownPathHandler http.HandlerFunc } IDTokenClaims struct { @@ -64,8 +66,9 @@ func (auth *OIDCProvider) getAppScopedCookieName(baseName string) string { const ( OIDCAuthInitPath = "/" - OIDCPostAuthPath = "/auth/callback" - OIDCLogoutPath = "/auth/logout" + OIDCAuthBasePath = "/auth" + OIDCPostAuthPath = OIDCAuthBasePath + "/callback" + OIDCLogoutPath = OIDCAuthBasePath + "/logout" ) var ( @@ -177,6 +180,10 @@ func (auth *OIDCProvider) SetScopes(scopes []string) { auth.oauthConfig.Scopes = scopes } +func (auth *OIDCProvider) SetOnUnknownPathHandler(handler http.HandlerFunc) { + auth.onUnknownPathHandler = handler +} + // optRedirectPostAuth returns an oauth2 option that sets the "redirect_uri" // parameter of the authorization URL to the post auth path of the current // request host. @@ -213,6 +220,10 @@ func (auth *OIDCProvider) HandleAuth(w http.ResponseWriter, r *http.Request) { case OIDCLogoutPath: auth.LogoutHandler(w, r) default: + if auth.onUnknownPathHandler != nil { + auth.onUnknownPathHandler(w, r) + return + } http.Redirect(w, r, OIDCAuthInitPath, http.StatusFound) } } diff --git a/internal/net/gphttp/middleware/bypass.go b/internal/net/gphttp/middleware/bypass.go index b8baf776..6b4801dd 100644 --- a/internal/net/gphttp/middleware/bypass.go +++ b/internal/net/gphttp/middleware/bypass.go @@ -2,7 +2,9 @@ package middleware import ( "net/http" + "strings" + "github.com/yusing/godoxy/internal/auth" "github.com/yusing/godoxy/internal/route/rules" ) @@ -21,17 +23,29 @@ type checkBypass struct { bypass Bypass modReq RequestModifier 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) { - if c.modReq == nil || c.bypass.ShouldBypass(w, r) { + if c.modReq == nil || (!c.isEnforced(r) && c.bypass.ShouldBypass(w, r)) { return true } return c.modReq.before(w, r) } 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 c.modRes.modifyResponse(resp) @@ -42,10 +56,22 @@ func (m *Middleware) withCheckBypass() any { modReq, _ := m.impl.(RequestModifier) modRes, _ := m.impl.(ResponseModifier) return &checkBypass{ - bypass: m.Bypass, - modReq: modReq, - modRes: modRes, + bypass: m.Bypass, + enforcedPathPrefixes: getEnforcedPathPrefixes(modReq, modRes), + modReq: modReq, + modRes: modRes, } } 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 +} diff --git a/internal/net/gphttp/middleware/oidc.go b/internal/net/gphttp/middleware/oidc.go index bf21a7cb..74c341d7 100644 --- a/internal/net/gphttp/middleware/oidc.go +++ b/internal/net/gphttp/middleware/oidc.go @@ -74,6 +74,11 @@ func (amw *oidcMiddleware) initSlow() error { } // 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) if len(amw.AllowedUsers) > 0 { authProvider.SetAllowedUsers(amw.AllowedUsers)