Skip to content

fix(indiekit): derive application URL from each request - #918

Merged
paulrobertlloyd merged 1 commit into
mainfrom
fix/application-url-per-request
Aug 24, 2026
Merged

fix(indiekit): derive application URL from each request#918
paulrobertlloyd merged 1 commit into
mainfrom
fix/application-url-per-request

Conversation

@rmdes

@rmdes rmdes commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

Fixes #906.

Indiekit.applicationUrl, as you suggested.

// index.js — read where it cannot yet have been overwritten
this.applicationUrl = config.application?.url;

// lib/middleware/locals.js
application.url = Indiekit.applicationUrl || getUrl(request);

Capturing it in the constructor is the part that does the work. application is config.application, so the middleware writing application.url destroys the value it later needs to consult — which is how ||= lost the ability to tell "configured" from "cached from whoever asked first". A copy taken before any request arrives keeps them distinguishable, and the request-derived case now behaves like collections, localeUsed, package and shortcuts on the lines above it.

The impact is wider than the issue title suggests

I only framed this as missing CSS and JS. Tracing the readers, application.url also builds the IndieAuth client_id and redirect_uri:

// lib/indieauth.js:87-89
const applicationUrl = getCanonicalUrl(application.url);
const { href: clientId }    = new URL("id", applicationUrl);
const { href: callbackUrl } = new URL("session/auth", applicationUrl);

So a latched host doesn't only render an unstyled page — it sends the sign-in flow's client_id and redirect_uri to a different origin from the one being browsed, where the session cookie isn't shared. Same root cause, but it reaches authentication, not just presentation. It also feeds the JF2 feed URL (lib/controllers/feed.js:3) and image-resize URLs (frontend/lib/filters/url.js:66).

Worth knowing rather than discovering later; happy to note it on the issue too.

Tests

Two, both written against the old code first and watched fail:

  • "Derives application URL from each request" — two requests with different Host headers; asserts the second reflects its own host. Failed with actual: 'http://127.0.0.1:3000' where the second request sent localhost:3000, which is the bug reproduced in a unit test.
  • "Prefers a configured application URL over the request" — asserts the configured value survives, so the reverse-proxy behaviour documented in configuration/application.md is kept.

packages/indiekit: 111 pass, 0 fail across 46 suites. prettier --check clean, eslint clean on the changed files.

One note on coverage: I didn't add a test for the constructor assignment itself. Nothing currently constructs Indiekit in the unit tests, and its siblings this.locale and this.mongodbUrl are unasserted for the same reason — the behaviour is covered at the middleware boundary instead. Say if you'd rather have one and I'll add it.

Naming

lib/indieauth.js:87 already declares a local const applicationUrl. Different scope, no conflict, but flagging it since it's the same name a few files away.

eslint also reports a pre-existing import-x/order error in packages/indiekit/bin/cli.js, untouched by this branch.

`application.url ||= getUrl(request)` memoised into `Indiekit.config.application`,
an object that outlives the request, so the first request a process ever received
fixed the host used by every response after it. A health check, a container probe
or a curl in a start script could set it before anyone opened a browser.

`application.url` is documented as configurable for reverse proxies, defaulting to
the request. Once `getUrl()` had written to it there was no way to tell the two
apart, so the fallback became permanent.

Read the configured value in the constructor, where it cannot yet have been
overwritten, and derive from the request otherwise — matching how `collections`,
`localeUsed`, `package` and `shortcuts` are assigned on the lines above.

This affects more than styling. `application.url` builds the absolute asset URLs
in the rendered page, and also the IndieAuth `client_id` and `redirect_uri` in
`lib/indieauth.js`, so a latched host sent the sign-in flow to a different origin
than the one being browsed.
@paulrobertlloyd
paulrobertlloyd merged commit 2e20ad0 into main Aug 24, 2026
1 check passed
@paulrobertlloyd
paulrobertlloyd deleted the fix/application-url-per-request branch August 24, 2026 19:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Admin interface loses its CSS and JS when opened on a different hostname than the first request used

2 participants