Skip to content

Packaged binary: GET / returns 401 instead of the SPA shell (bare root not served under global auth fallback) #1181

Description

@Chris0Jeky

Summary

The published self-contained desktop binary serves the SPA for every path except the bare root /, which returns HTTP 401 ({"errorCode":"Unauthorized"}) instead of index.html. / is the URL the first-run flow logs and the browser auto-opens to, so a user navigating to http://localhost:5000 gets a JSON 401 error on first launch.

Discovered while implementing the #1123 AC2 post-publish smoke test (release-desktop.yml). The AC2 smoke asserts GET / returns the SPA, so this currently blocks a green release run (the gate working as intended).

Reproduction (local, win-x64 self-contained single-file publish)

Run the published exe (cwd = publish dir) with throwaway secrets, then:

GET /index.html      -> 200   (static files serve the shell)
GET /board           -> 200   (MapFallbackToFile SPA fallback, AllowAnonymous)
GET /login           -> 200   (SPA fallback)
GET /                -> 401   (bare root)
GET /health/ready    -> 200

Reproduces identically in both ASPNETCORE_ENVIRONMENT=Production (default) and Development, so it is environment-independent. The packaged SPA-serving path has never shipped, so this was never exercised before.

Root cause

backend/src/Taskdeck.Api/Extensions/PipelineConfiguration.cs uses UseDefaultFiles() + UseStaticFiles() + app.MapFallbackToFile("index.html").AllowAnonymous(). The SPA fallback's catch-all route ({*path:nonfile}) does not match the empty root path, so / matches no endpoint and is rejected by the global SetFallbackPolicy(RequireAuthenticatedUser) (#1132 AC4) -> 401. Non-empty client routes (/board) do match the fallback and are served. UseDefaultFiles is not rewriting / -> /index.html in this configuration.

Existing coverage gap: backend/tests/Taskdeck.Api.Tests/FallbackPolicyTests.cs only exercises a non-empty route (/some/client/route); the bare root / is untested.

Recommended fix

Ensure the bare root serves the SPA shell anonymously, e.g. add alongside the existing fallback:

// Bare root must serve the SPA shell; the {*path:nonfile} fallback doesn't match the empty path.
app.MapFallbackToFile("/", "index.html").AllowAnonymous();

(or otherwise make / resolve to index.html). Add a GET / -> 200 + index.html regression test to FallbackPolicyTests mirroring the existing SPA-fallback test.

Impact

Metadata

Metadata

Assignees

No one assigned

    Labels

    Priority VMeta-tracking, archival consistency, historical context.

    Projects

    Status
    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions