Commit Graph

3 Commits (77bd30ca566bfe98252561fe76df24c86e334ec1)

Author SHA1 Message Date
nexxo 77bd30ca56 Answer on every name for the website, and send them all to one
tests / pest (push) Failing after 7m23s Details
tests / assets (push) Successful in 22s Details
tests / release (push) Has been skipped Details
SITE_HOST takes a comma-separated list now, the first name canonical. It serves
the site; every other name in the list redirects there permanently, path and
query intact. An apex domain and its www are two names for one thing, and
answering on both without picking one splits search rankings and cookies
between them.

The redirect is a host-bound catch-all rather than middleware. Middleware that
only some routes carry is the same half-measure this file already made once,
and a catch-all registered without a host would swallow every path in the
application — which is what the third test is there to stop.

The test harness needed fixing too, and the fix is the finding. Router::dispatch
on a standalone router does not rebind the container's request, so a route
reading one — through the helper OR through an injected parameter, both of which
resolve from the container — gets the test's request rather than the one being
answered. The redirect silently lost its query string in the test while working
in production. dispatchOn() binds it where the framework would.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 01:29:33 +02:00
nexxo 21762284a7 Bind the portal to its own hostname too, so the website cannot serve it
tests / pest (push) Failing after 7m48s Details
tests / assets (push) Successful in 23s Details
tests / release (push) Has been skipped Details
The previous release bound the landing page and stopped there. The website duly
vanished from the portal — and the portal stayed reachable from the website:
www.clupilot.com/dashboard, /login, /settings all kept working. Half a
separation is no separation, and it was reported back within the hour.

APP_HOST binds every portal route, Fortify's own POST actions included through
fortify.domain — a sign-in form on the website's hostname would otherwise post
to a route that answers there. SITE_HOST now takes the legal pages and
robots.txt with the landing page rather than leaving them everywhere; binding
them is also what makes route('legal.impressum') produce a www URL from inside
a queued mail, where there is no request to take a hostname from.

There is no clever middleware here and there should not be. Route::domain() is
the mechanism: a route registered for one host does not exist on another, and a
request for it gets the 404 it deserves. The only deliberate exception is "/" on
the portal host, which redirects to the dashboard or the sign-in page — that is
what people type from memory, and turning it into an error page to make a point
about hostnames helps nobody. The Stripe webhook and /up stay host-agnostic:
Stripe posts to whichever URL it was given, and a mismatch there loses payments.

Both are opt-in and empty by default, which every development machine reached
by a bare IP depends on.

The tests now check BOTH directions for every path rather than only the one
that was reported. That is the mistake this commit exists to correct.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 01:06:50 +02:00
nexxo 49a3ca2e33 Keep the shop window off the portal's front door
tests / pest (push) Failing after 7m35s Details
tests / assets (push) Successful in 19s Details
tests / release (push) Has been skipped Details
The landing page was registered host-agnostically, so app.clupilot.com served
it: marketing copy, a pricing table and a "sign up" call to action, at the
address where a customer's servers live. Reported exactly that way.

SITE_HOST binds the website to its own hostname. Every other host — the portal,
a bare IP — answers "/" with the product: the dashboard for somebody signed in,
the sign-in page for everybody else. Never a 404 there: "/" is what people type
from memory, and turning that into an error page to make a point about
hostnames helps nobody. Empty keeps today's behaviour, which every development
machine reached by IP depends on.

The console is unaffected — its routes are registered first and win on their
own host, the ordering the status page above already documents.

The legal pages are deliberately not bound. An imprint has to be reachable from
wherever the reader is standing, and a mail footer links to it from whichever
host sent the mail.

Also drops "just reply to this message" from two mails. A reply lands in the
billing or provisioning mailbox rather than in the support queue somebody
actually works through — so it now says to raise it in the portal, where it
gets a place in that queue instead of getting lost.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 00:26:22 +02:00