Repository navigation
Answer 404 for a path this server does not serve - #1406
Merged
Merged
Conversation
The global FallbackPolicy requires an authenticated user for any endpoint
without an explicit [Authorize] -- deliberately, so a forgotten attribute
cannot mean anonymous access. It also applied to requests that matched
NO endpoint, and there the 401 it produced is a lie: the request was
refused for want of a route, not a session, and adding one would not have
helped.
That cost a real diagnosis. An invite link built from a misconfigured
App:PublicBaseUrl pointed at the API host rather than the SPA, so opening
it asked this server for /invite/{token} -- a path only the SPA has. The
401 sent the investigation hunting a broken authorization rule on an
endpoint that was already [AllowAnonymous] and working.
Implemented as a fallback ENDPOINT rather than middleware before
UseAuthorization, because only the endpoint form composes with branch
middleware. An adversarial review caught the first attempt: Hangfire
mounts its dashboard with app.Map, which registers no endpoint, so a
middleware keyed on "GetEndpoint() == null" swallowed /hangfire whole and
made UseCodeSpaceHangfire dead code on the API role. Routing reaches a
fallback only after every real endpoint and every earlier branch has
declined, so the special case disappears rather than needing an allowlist.
The boot banner also names where invite and reset links will point, read
from the raw configuration key rather than through PublicBaseUrlSetting:
that type throws outside Development when the key is unset and is
resolved lazily today, so constructing it at startup would move the
failure to boot for both roles -- and Main catches it into a Fatal log
and returns 0, so the container would exit cleanly and every restart
would look like a healthy stop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Opening an invite link returned 401, which read as "you need to sign in" — for a person who has no account yet, that is exactly the wrong thing to say, and it sent the investigation after an authorization bug that did not exist.
InvitationsControlleris[AllowAnonymous]and works:The cause is the global
FallbackPolicy: it requires an authenticated user for endpoints without an explicit[Authorize]— deliberately — and it also applies when nothing matched. There the 401 is a lie; the request was refused for want of a route.The immediate trigger was
App:PublicBaseUrlnaming the API host instead of the SPA, soInviteUrlmintedhttps://<api-host>/invite/{token}. The boot banner now prints where links will point, so that is visible before anyone sends one.Why a fallback endpoint, not middleware
The first attempt was middleware before
UseAuthorizationkeyed onGetEndpoint() == null. An adversarial review (17 agents, 13 candidate findings, 7 confirmed) caught two regressions in it, both verified by live A/B against this exact pipeline:UseHangfireDashboardisapp.Mapbranch middleware and registers no endpoint, soGetEndpoint()is null for/hangfireand the middleware swallowed it — makingUseCodeSpaceHangfiredead code on the API role, with no test able to see it (the E2E factory runs the Worker role, which mounts no dashboard).PublicBaseUrlSetting, which throws outside Development when the key is unset. That setting is resolved lazily today, only by the two request-scoped services that mint links. Worse,Maincatches it into aLog.Fataland returns 0, so the container would exit cleanly and every crash-loop restart would look like a healthy stop.A fallback endpoint has neither problem: routing reaches it only after every real endpoint and every earlier branch has declined, so the special case disappears instead of needing an allowlist that would rot.
Known limitation
A path that matches a route template with the wrong HTTP method still answers 401, not 405 — routing selects the framework's method-rejection endpoint, which carries empty metadata and so meets the FallbackPolicy. Same bug class, rarer, not addressed here.
Test plan
UnroutedRequestE2ETests(5) over the real HTTP surface — including that a matched endpoint is still challenged, so the FallbackPolicy is not weakenedThe_not_found_answer_does_not_swallow_branch_mounted_paths— reverting to the middleware form reds it, confirming it catches the design the review rejected