Stop refusing sign-in over a scheme other than the public URL's
The auth routes no longer return 400 when the resolved request scheme differs from MEALQUEST_PUBLIC_URL's. If Docker's published-port path presents its bridge gateway instead of Traefik as the direct peer, the forwarded headers are rightly ignored, but sign-in must still work: the OIDC redirect URI and the Secure cookies are fixed by MEALQUEST_PUBLIC_URL, not by the request. internal/auth is back to its state before the trusted-proxy change; client IP and scheme resolution stays in the router. Add TestLoginThroughRouter: it wires the real auth flow and session manager through server.NewRouter, trusting Traefik only, and checks that a login from Traefik and from an untrusted peer both redirect with the redirect URI from MEALQUEST_PUBLIC_URL and set a Secure login cookie. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+1
-33
@@ -19,7 +19,6 @@ import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/forwarded"
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/secureurl"
|
||||
)
|
||||
|
||||
@@ -84,10 +83,7 @@ type Auth struct {
|
||||
// endSessionURL is the provider's end_session_endpoint; logout ends
|
||||
// there so the provider's own session ends too.
|
||||
endSessionURL string
|
||||
// publicScheme is the public URL's scheme; requests to the auth routes
|
||||
// must have arrived over it.
|
||||
publicScheme string
|
||||
logger *slog.Logger
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
// New discovers the provider at cfg.Issuer and returns the login flow. It
|
||||
@@ -159,7 +155,6 @@ func New(ctx context.Context, cfg Config, sessions Sessions, logger *slog.Logger
|
||||
cookie: newLoginCookie(cfg.SecretKey),
|
||||
sessions: sessions,
|
||||
endSessionURL: discovered.EndSessionURI,
|
||||
publicScheme: cfg.PublicURL.Scheme,
|
||||
logger: logger,
|
||||
}, nil
|
||||
}
|
||||
@@ -198,39 +193,12 @@ func checkRedirect(req *http.Request, via []*http.Request) error {
|
||||
}
|
||||
|
||||
// Mount registers GET LoginPath, GET CallbackPath and POST LogoutPath on r.
|
||||
// The routes need the request's resolved scheme in the context (see package
|
||||
// forwarded) and refuse requests that did not arrive over the public URL's
|
||||
// scheme.
|
||||
func (a *Auth) Mount(r chi.Router) {
|
||||
r = r.With(a.requirePublicScheme)
|
||||
r.Get(LoginPath, a.login)
|
||||
r.Get(CallbackPath, a.callback)
|
||||
r.Post(LogoutPath, a.logout)
|
||||
}
|
||||
|
||||
// requirePublicScheme refuses a request whose resolved scheme differs from
|
||||
// the public URL's. The login and session cookies are Secure, and the
|
||||
// redirect URI is always built from the public URL, so a sign-in reaching the
|
||||
// service over another scheme cannot work: behind a TLS-terminating proxy
|
||||
// this means the proxy's X-Forwarded-Proto was not honoured, because the
|
||||
// proxy is not a trusted peer.
|
||||
func (a *Auth) requirePublicScheme(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
scheme, ok := forwarded.Scheme(r.Context())
|
||||
switch {
|
||||
case !ok:
|
||||
a.logger.ErrorContext(r.Context(), "auth route reached without a resolved request scheme")
|
||||
http.Error(w, "internal error", http.StatusInternalServerError)
|
||||
case scheme != a.publicScheme:
|
||||
a.logger.WarnContext(r.Context(), "auth request scheme differs from the public URL; is the proxy a trusted peer?",
|
||||
"scheme", scheme, "public_scheme", a.publicScheme)
|
||||
http.Error(w, "sign-in must use the service's public URL", http.StatusBadRequest)
|
||||
default:
|
||||
next.ServeHTTP(w, r)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// EndSessionURL is the provider's end_session_endpoint.
|
||||
func (a *Auth) EndSessionURL() string { return a.endSessionURL }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user