4 Commits (70fa136e222008b76e04f96cc232a36ed36a9812)
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
233fc73430 |
Let the console keep its recovery hostnames without breaking route caching
tests / pest (push) Successful in 7m22s
Details
tests / assets (push) Successful in 24s
Details
tests / release (push) Successful in 4s
Details
Registering the console once per accepted hostname reused one set of route names, and Laravel refuses to serialise two routes under the same name — so `artisan optimize` failed on the live server the moment the separation was switched on, mid-deploy. Only the canonical registration carries the `admin.` names now; the alternates answer under `admin.viaN.*`. They exist to be MATCHED — they are the addresses someone locked out reaches for — never to have URLs generated for them, so route() keeps producing the canonical hostname. That renaming then broke every exact route-name check, which is how the console navigation decides what is active: reached through a recovery address, nothing in the sidebar was marked, exactly when something is already going wrong. The check goes through AdminArea now and matches either form. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|
|
|
492b1925fb |
Land a sign-in where it was performed, not always in the customer portal
tests / pest (push) Successful in 7m42s
Details
tests / assets (push) Successful in 21s
Details
tests / release (push) Successful in 6s
Details
Fortify sends every successful sign-in to config('fortify.home') — /dashboard.
That is why an operator signing in ended up in the customer portal, and once
the console has a hostname to itself it stops being merely confusing:
/dashboard does not exist on that host, so signing in would land on a 404.
Three outcomes now, decided by where the sign-in happened and who signed in:
the console for an operator on the console, the portal everywhere else, and for
a non-operator on the console the session is taken away again at the moment it
was created — a guard that merely refuses each page afterwards leaves that
session in the browser.
Four things the review caught, each of which would have left a hole:
- Fortify never reaches LoginResponse when a two-factor challenge was involved.
Binding only that one left exactly the accounts most likely to be operators
on the old behaviour, so both exits are bound and share one decision.
- The JSON branch ran before the authorization check, so a JSON client kept a
session a browser would have lost.
- The two exits do not share a JSON success contract — an ordinary sign-in
answers {"two_factor":false}, a completed challenge answers an empty 204 —
and merging them breaks a client keying off the status code.
- In shared mode everyone posts to /login, so the request never looks like the
console even when /admin is the destination. The intended URL is read too.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
|
|
|
de6821b53e |
Move the console off /admin, give the status page its own address, and measure monitoring
tests / pest (push) Successful in 7m43s
Details
tests / assets (push) Successful in 19s
Details
tests / release (push) Successful in 5s
Details
Three things the owner asked for, and one the review found underneath them. The console leaves /admin. It has its own route file now, registered before routes/web.php because once it has a hostname to itself it sits at the ROOT of that host — where `/` and `/settings` also exist for the portal, and the first matching route wins. Where it mounts is one decision in one place: `/` on the console hostname where it has one to itself, `/admin` on any host otherwise. Names stay admin.* in both modes, so nothing that builds a URL has to know. Exclusivity is its own switch, not a consequence of having a hostname. The first attempt made "this host is the console" follow from ADMIN_HOSTS, and a development machine lists its own IP there so the console works without DNS — which took the public site and the portal off that machine entirely. The switch also registers the console on every listed hostname, canonical last, so the alternates that exist as recovery paths keep working. The status page moves out of /legal, where it sat beside the imprint and the terms. Nothing about the current health of the platform is a legal document. It is a real page now: portal, instances, provisioning and backups, each derived from records, aggregate only, and a component with no signal reports "unknown" rather than "operational". That last rule is what exposed the real bug. monitoring_targets.status was written once — 'up', at provisioning — and nothing ever updated it. Both the console's notices and the new public page read it, so an outage would have been published as healthy indefinitely. There is a sync job now, on a five-minute schedule, and a checked_at column so a verdict can go stale instead of standing forever. The monitoring contract had to grow a third state for that to be honest. isHealthy() answers true when no monitoring is configured and false when the monitor is unreachable; recording that boolean would have published either a fleet-wide all-clear nobody measured or a fleet-wide outage that was really one broken monitor. health() returns null for both, the recorder leaves the old verdict to go stale, and isHealthy() keeps its forgiving semantics for the one caller that wants them — the provisioning acceptance check, which must not fail a delivery because monitoring is not set up. Backups are counted from the instances that need protecting rather than from the backup rows that exist, so an instance with no schedule at all cannot be missing from the arithmetic that declares the estate protected. Also: the update panel polls itself, offers the button only when there is something to install, and opens the log while a run is in progress. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|
|
|
b860ce2e9b |
Give the three console guards one answer to "is this the console?"
tests / pest (push) Successful in 7m44s
Details
tests / assets (push) Successful in 19s
Details
tests / release (push) Successful in 3s
Details
The hostname guard, the network allowlist and the public-site switch each decided for themselves, by testing the path against admin/*. That works only while the console sits under /admin. It is also a trap: the moment the console moves to the root of its own hostname, PublicSiteGate stops recognising it, and with the public site hidden the console answers 503 to the very person trying to sign in and switch it back on. The guard does not fail loudly — it silently stops matching. AdminArea is now the single answer. It has two modes and no third: a console hostname is configured, in which case the console IS that host and answers at its root; or nothing is configured, in which case the console stays under /admin on any host exactly as before, so upgrading cannot lock anyone out of a system that was working. RestrictAdminHost gains the half it was missing. Binding console routes to a hostname does not stop the customer routes from answering there too, because they are registered without one — so the console's hostname would still serve portal pages wherever the paths did not collide. It now enforces both directions, with the endpoints both sides genuinely share written out as a list rather than inferred. Nothing changes yet for an installation with no ADMIN_HOSTS set, which is every development machine and every fresh checkout. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |