fix: Scalar OpenAPI spec URL behind nginx reverse proxy #19
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fix/scalar-reverse-proxy-pathprefix"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
The Scalar UI at
https://umbrel.kagaku.space:8080/doujinshi/scalargenerates:No prefix → browser fetches
/openapi/v1.json→ nginx doesn't route it → 404.Root cause
nginx strips the prefix (
rewrite ^/doujinshi/?(.*)$ /$1 break;), so the app sees/scalar. WithDOUJIN_MANAGER_PATH_PREFIXunset, the Scalar HTML uses a bare/openapi/v1.jsonURL. But the old env var couldn't be used to fix this either — it only prefixed the Scalar/OpenAPI routes, not the API routes, so setting it would break every/api/*call.Fix
Replace the
DOUJIN_MANAGER_PATH_PREFIXenv var with ASP.NET Core'sForwardedHeadersmiddleware. nginx already sendsX-Forwarded-Prefix: /doujinshi— the middleware reads it and setsRequest.PathBase = /doujinshi.ScalarUi.GetHtml(PathBase)now builds the correctdata-url="/doujinshi/openapi/v1.json".The app always routes on bare paths (
/api,/scalar,/openapi). The prefix lives only inPathBase, used solely for generating the Scalardata-url.Request flow after the fix
GET /doujinshi/scalarGET /scalar+X-Forwarded-Prefix: /doujinshiForwardedHeadersmiddlewareRequest.PathBase = /doujinshidata-url="/doujinshi/openapi/v1.json"✅GET /doujinshi/openapi/v1.jsonGET /openapi/v1.json→ served ✅GET /doujinshi/api/doujinsGET /api/doujins→ endpoint ✅Files changed (6 files, +108 / -27)
Program.csForwardedHeadersOptions(For+Proto+Prefix),UseForwardedHeaders(), remove env-var logic, bare routesScalarUi.csPathBaseinstead of hardcodedopenApiRoute, trim trailing slashdocker-compose.ymlDOUJIN_MANAGER_PATH_PREFIXenv varDEPLOYMENT.mdREADME.mdADR 0013nginx config
No changes needed — your nginx already sends the right header:
No env var needed
Just remove the
DOUJIN_MANAGER_PATH_PREFIXfrom your container config (if it was set). TheX-Forwarded-Prefixheader is now the single source of truth.All 238 tests pass.
Summary
Summary
Coverage
DoujinManager.ApplicationCore - 84.8%
DoujinManager.Infrastructure - 91.6%
pshot
DoujinManager.RestAdapter - 83.9%
E5CB12CEAC5BBBEA65844E1C05F9ADA4DA38B848075C41304FC6A1ABF77__ValidationAttr
ibuteCache
DoujinManager.Server - 20.9%
🤖 Hermes automated review: minor comments
Reviewed the full diff (6 files, +108/−27) for
fix/scalar-reverse-proxy-pathprefix→main, head21535ad9.Core logic is correct and well-reasoned. The switch from the env-var approach to
ForwardedHeadersmiddleware is the right fix — nginx strips the prefix viarewrite(so the app must route on bare/api,/scalar,/openapi) whileX-Forwarded-Prefixcarries the original sub-path intoRequest.PathBasefor URL generation only. Verified:Program.cs:90—app.UseForwardedHeaders()is registered beforeUseRestAdapterMiddleware()(correct pipeline ordering; ForwardedHeaders must run early).Program.cs:105— bareMapOpenApi("/openapi/{documentName}.json")matches the stripped path the app actually receives.ScalarUi.cs:23—pathBase.TrimEnd('/')+$"{baseSegment}/openapi/v1.json"produces/openapi/v1.json(root, empty PathBase) or/doujinshi/openapi/v1.json(proxied) — both correct, no double-slash.HttpUtility.HtmlAttributeEncode(specUrl)properly guards thedata-urlattribute value.Static security scan: clean — no secrets, shell injection,
eval/exec, or SQL in added lines.Minor (non-blocking) notes
ScalarUi.cs(newGetHtml(pathBase)logic) — no unit test. The method is now pure and trivially testable, yetScalarUi.cssits at 0% coverage. A small parameterized test pinning the three branches (empty string →/openapi/v1.json,/doujinshi→/doujinshi/openapi/v1.json,/doujinshi/trailing slash → same as no trailing slash) would lock in the prefix behavior against future regressions.Program.cs— trusting all proxies is a deliberate but un-lockedable posture.KnownNetworks.Clear()+KnownProxies.Clear()trustsX-Forwarded-*headers from any source. This is the standard ASP.NET Core container pattern and is safe when the container is only reachable via nginx (your deployment), but it means the container port must never be published directly to an untrusted network — otherwiseX-Forwarded-For,X-Forwarded-Proto, andX-Forwarded-Prefixall become client-spoofable. Consider a one-line note inDEPLOYMENT.md§9 ("do not publish the container port directly; the app trusts forwarded headers").Neither blocks merge.
Verification: CI/CD: forgejo-actions coverage comment #318 is current for head
21535ad9(line 83.4%, branch 49.8%; generated 15:45:23 UTC, after the head commit).ci.ymlposts coverage only on successful build+test, so build+test passed for this head SHA; local build/test skipped per CI-evidence policy.Automated daily review. I never merge PRs.