›
byrcsc/laravel-dev-login · 1.x
Five independent checks decide whether the dev login page exists, and all five have to agree.
The package puts a page on your application that logs anybody in without a
password. Whether that page exists is decided by five separate checks. None of
them is "it was installed with --dev", and none of them can be satisfied by
another one being satisfied.
Three are answered while the application boots, and they decide whether the routes register at all. One is a refusal that stops the boot. One is answered per request, because it depends on something no config file knows.
'enabled' => (bool) env('DEV_LOGIN_ENABLED', false),Off by default. Turning it on is something a developer does on purpose, in
their own .env, on their own machine.
It is deliberately separate from the environment allowlist below. Being in
local is never on its own enough to expose this page, so both have to say
yes.
'environments' => ['local', 'testing'],The environments the package may run in at all, matched case-sensitively
against app()->environment().
Staging is not on the list on purpose. A team that wants it writes it in, which is an edit somebody reviews rather than a default nobody notices:
'environments' => ['local', 'testing', 'staging'],Config this check cannot read as a list is treated as an empty list. A malformed value shuts the gate rather than opening it.
production is not a value this list accepts. Naming it while the enable flag
is on throws DevLoginEnabledInProduction at boot:
Dev login is enabled while APP_ENV=production. Refusing to boot. Remove
DEV_LOGIN_ENABLED from the production environment, or set it to false.The production check is asked separately from the allowlist, so the list
cannot be edited into an override. It also matches case-insensitively, unlike
the allowlist, so an APP_ENV=Production cannot slide past it.
This is loud on purpose. An application that reaches production with the flag on has a deployment problem, and it should find out from a failed boot rather than from a stranger on the login page. A silent no-op is indistinguishable from a package that is working.
The refusal stops artisan as well as web requests, because both boot the
same container. The way out is the environment file, not a cache command.
When the flag, the allowlist, or the production check disagrees, the routes
are never registered. There is no GET /dev-login to forbid.
Every refusal is therefore a 404 rather than a 403. A 403 tells a stranger the page is there.
'allowed_hosts' => ['localhost', '127.0.0.1', '*.test'],This is the one check that does not trust your application's own
configuration. Everything above can be switched on by an .env that reaches
the wrong machine. This is matched against the host the request actually
arrived on, by middleware registered under the alias dev-login.host.
Matching rules:
localhost does not match localhost.evil.com.*. matches subdomains only. *.test covers acme.test and
tenant.acme.test, but never test itself, and never acme.test.evil.com.* matches nothing. Wildcards only ever stand for the subdomain
part, so a pattern that is neither an exact host nor *.something is a host
nobody has.A host that fails aborts with 404, for the same reason the boot-time gates leave no route behind.
The page shows app()->environment() as a yellow badge next to your
application name, above a line saying that anybody who can reach the page can
sign in as any profile on it.
That is aimed at the person looking at the screen rather than at any request, and it is why the page is not themeable into something that could be mistaken for your real login. Publishing the views is the only customization the package offers, and there is no theming config.
<x-dev-login::profiles /> can be rendered from a route your application owns,
where the package's own route registration is not involved. The component
therefore asks the boot-time gates and the host gate again on its own account,
and renders an empty string when either says no.
A list of ways into an application is not something to render on a host nobody allowed.
Gatekeeper is the class behind all of this, resolvable from the container:
use ByRcsc\LaravelDevLogin\Gatekeeper;
$gatekeeper = app(Gatekeeper::class);
$gatekeeper->passes(); // all three boot-time gates agree
$gatekeeper->mustRefuseToBoot(); // enabled, and in production
$gatekeeper->hostIsAllowed('acme.test'); // the per-request gate, for one hostpasses() is what route registration hangs on, and what the component checks
before rendering.