176 lines
4.6 KiB
PHP
176 lines
4.6 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Arr;
|
|
use Illuminate\Support\Facades\Cookie;
|
|
use Illuminate\Support\Facades\URL;
|
|
use Symfony\Component\HttpFoundation\Cookie as HttpFoundationCookie;
|
|
|
|
class LandingAuthOverrideService
|
|
{
|
|
public function authButtonsHidden(Request $request): bool
|
|
{
|
|
if (! config('landing.hide_auth_buttons', false)) {
|
|
return false;
|
|
}
|
|
|
|
return ! $this->allowsAuthentication($request);
|
|
}
|
|
|
|
public function allowsAuthentication(Request $request): bool
|
|
{
|
|
if (! config('landing.hide_auth_buttons', false)) {
|
|
return true;
|
|
}
|
|
|
|
if ($request->boolean('force')) {
|
|
return true;
|
|
}
|
|
|
|
if ($this->hasOverrideCookie($request)) {
|
|
return true;
|
|
}
|
|
|
|
if (! $this->hasValidSignedOverride($request)) {
|
|
return false;
|
|
}
|
|
|
|
$this->queueOverrideCookie();
|
|
|
|
return true;
|
|
}
|
|
|
|
public function generateSignedUrl(int $days): string
|
|
{
|
|
$path = $this->signedPath(now()->addDays($days));
|
|
|
|
return rtrim(config('app.url'), '/').$path;
|
|
}
|
|
|
|
public function signedPath(\DateTimeInterface|\DateInterval|int $expiration): string
|
|
{
|
|
$parameters = [
|
|
$this->queryParameter() => 1,
|
|
'expires' => $this->availableAt($expiration),
|
|
];
|
|
|
|
ksort($parameters);
|
|
|
|
$signature = hash_hmac('sha256', $this->originalString('/', $parameters), $this->signingKey());
|
|
|
|
return '/?'.Arr::query($parameters + ['signature' => $signature]);
|
|
}
|
|
|
|
private function queueOverrideCookie(): void
|
|
{
|
|
Cookie::queue($this->makeOverrideCookie());
|
|
}
|
|
|
|
private function makeOverrideCookie(): HttpFoundationCookie
|
|
{
|
|
return Cookie::make(
|
|
$this->cookieName(),
|
|
'1',
|
|
(int) config('landing.auth_override.cookie_minutes', 60 * 24 * 7),
|
|
'/',
|
|
config('session.domain'),
|
|
config('session.secure'),
|
|
true,
|
|
false,
|
|
config('session.same_site', 'lax'),
|
|
);
|
|
}
|
|
|
|
private function hasValidSignedOverride(Request $request): bool
|
|
{
|
|
if (! $request->boolean($this->queryParameter())) {
|
|
return false;
|
|
}
|
|
|
|
if ($this->signatureHasExpired($request)) {
|
|
return false;
|
|
}
|
|
|
|
$parameters = $request->query();
|
|
unset($parameters['signature']);
|
|
|
|
foreach (config('landing.auth_override.ignore_signature_query_parameters', []) as $ignoredParameter) {
|
|
unset($parameters[$ignoredParameter]);
|
|
}
|
|
|
|
ksort($parameters);
|
|
|
|
$signature = (string) $request->query('signature', '');
|
|
|
|
return hash_equals(
|
|
hash_hmac('sha256', $this->originalString($request->getPathInfo(), $parameters), $this->signingKey()),
|
|
$signature,
|
|
);
|
|
}
|
|
|
|
private function signatureHasExpired(Request $request): bool
|
|
{
|
|
$expires = $request->query('expires');
|
|
|
|
if (! is_numeric($expires)) {
|
|
return true;
|
|
}
|
|
|
|
return (int) $expires < now()->getTimestamp();
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $parameters
|
|
*/
|
|
private function originalString(string $path, array $parameters): string
|
|
{
|
|
$normalizedPath = $path === '' ? '/' : $path;
|
|
$query = Arr::query($parameters);
|
|
|
|
return rtrim($normalizedPath.'?'.$query, '?');
|
|
}
|
|
|
|
private function signingKey(): string
|
|
{
|
|
$key = app('config')->get('app.key');
|
|
|
|
if (! is_string($key) || $key === '') {
|
|
$url = URL::to('/');
|
|
|
|
throw new \RuntimeException("Unable to sign landing auth URL for {$url} without app.key.");
|
|
}
|
|
|
|
return $key;
|
|
}
|
|
|
|
private function availableAt(\DateTimeInterface|\DateInterval|int $delay): int
|
|
{
|
|
if ($delay instanceof \DateTimeInterface) {
|
|
return $delay->getTimestamp();
|
|
}
|
|
|
|
if ($delay instanceof \DateInterval) {
|
|
return now()->add($delay)->getTimestamp();
|
|
}
|
|
|
|
return now()->addSeconds($delay)->getTimestamp();
|
|
}
|
|
|
|
private function hasOverrideCookie(Request $request): bool
|
|
{
|
|
return filter_var($request->cookie($this->cookieName()), FILTER_VALIDATE_BOOL);
|
|
}
|
|
|
|
private function queryParameter(): string
|
|
{
|
|
return (string) config('landing.auth_override.query_parameter', 'signup');
|
|
}
|
|
|
|
private function cookieName(): string
|
|
{
|
|
return (string) config('landing.auth_override.cookie_name', 'landing_auth_override');
|
|
}
|
|
}
|