56 lines
1.9 KiB
PHP
56 lines
1.9 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Middleware;
|
|
|
|
use App\Support\Settings;
|
|
use Closure;
|
|
use Illuminate\Http\Request;
|
|
use Symfony\Component\HttpFoundation\IpUtils;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
|
|
/**
|
|
* Hides the marketing site and the customer portal while the product is still
|
|
* being built, without hiding it from us.
|
|
*
|
|
* Switched from the console (site.public). Anyone coming through the management
|
|
* VPN, and any signed-in operator, sees the real thing; everyone else — crawlers
|
|
* included — gets a placeholder.
|
|
*
|
|
* Answers 503 with Retry-After and X-Robots-Tag rather than 200: a 200 would
|
|
* invite search engines to index the placeholder as the site's content, and
|
|
* getting that out of an index again is much harder than keeping it out.
|
|
*/
|
|
class PublicSiteGate
|
|
{
|
|
/**
|
|
* Paths that must keep working regardless: the console itself (otherwise
|
|
* the switch could not be flipped back), Livewire's endpoint that drives it,
|
|
* Stripe's webhook, and the health check.
|
|
*/
|
|
private const ALWAYS_ALLOWED = ['admin', 'admin/*', 'livewire/*', 'webhooks/*', 'up', 'robots.txt'];
|
|
|
|
public function handle(Request $request, Closure $next): Response
|
|
{
|
|
if ($request->is(...self::ALWAYS_ALLOWED) || Settings::bool('site.public', true)) {
|
|
return $next($request);
|
|
}
|
|
|
|
if ($this->fromManagementNetwork($request) || $request->user()?->isOperator()) {
|
|
return $next($request);
|
|
}
|
|
|
|
return response()->view('coming-soon', [], 503)->withHeaders([
|
|
'Retry-After' => 3600,
|
|
'X-Robots-Tag' => 'noindex, nofollow, noarchive',
|
|
'Cache-Control' => 'no-store',
|
|
]);
|
|
}
|
|
|
|
private function fromManagementNetwork(Request $request): bool
|
|
{
|
|
$ranges = (array) config('admin_access.trusted_ranges', []);
|
|
|
|
return $ranges !== [] && IpUtils::checkIp((string) $request->ip(), $ranges);
|
|
}
|
|
}
|