mirror of
https://github.com/projectsend/projectsend.git
synced 2026-09-22 19:43:24 +00:00
a45eae315c
password.confirm redirected every write to the confirm-password screen. A redirect cannot carry a POST body, and Redirector::guest() only remembers the exact URL of a GET, so after confirming, the user landed back on an empty form and the action never ran. On the API token forms that meant typing the name, the scopes and the expiry again. An Inertia request now gets a 423 marked X-Password-Confirmation. A dialog mounted around every page catches it, asks for the password over the current page, and sends the refused request again with the same data and callbacks, so the form finishes as if nothing happened. The check itself is still the framework's. Plain form posts and JSON clients are answered as before, and accounts with no local password are offered a way to set one, as the confirm screen does.
62 lines
2.4 KiB
PHP
62 lines
2.4 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Modules\Identity\Http\Middleware;
|
|
|
|
use App\Modules\Identity\AuthSource;
|
|
use Closure;
|
|
use Illuminate\Auth\Middleware\RequirePassword;
|
|
use Illuminate\Http\Request;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
|
|
/**
|
|
* `password.confirm`, answered in place for the app's own screens.
|
|
*
|
|
* The framework's version redirects to the confirm-password screen and
|
|
* relies on the "intended" URL to come back. For a GET that works. For
|
|
* the writes this guards it cannot: Redirector::guest() only remembers
|
|
* the exact URL of a GET, so a POST comes back to the page it was sent
|
|
* from, freshly rendered, and whatever was typed into the form -- a
|
|
* token's name and scopes, a release reason -- is gone, along with the
|
|
* action itself.
|
|
*
|
|
* So an Inertia request gets a 423 instead, which the browser turns into
|
|
* a password dialog over the page it is on (password-confirmation-dialog.tsx).
|
|
* Nothing navigates, the form keeps its state, and once the password is
|
|
* proved the same request is sent again. The check itself is the
|
|
* framework's, unchanged: this only decides what the refusal looks like.
|
|
* Anything else -- a plain form post, a JSON client -- is answered
|
|
* exactly as before.
|
|
*/
|
|
class RequirePasswordConfirmation extends RequirePassword
|
|
{
|
|
/**
|
|
* Marks the 423 as this refusal and not any other, so the browser does
|
|
* not open a password dialog in answer to something else.
|
|
*/
|
|
public const HEADER = 'X-Password-Confirmation';
|
|
|
|
public function handle($request, Closure $next, $redirectToRoute = null, $passwordTimeoutSeconds = null)
|
|
{
|
|
if ($request->header('X-Inertia') && $this->shouldConfirmPassword($request, $passwordTimeoutSeconds)) {
|
|
return $this->inertiaRefusal($request);
|
|
}
|
|
|
|
return parent::handle($request, $next, $redirectToRoute, $passwordTimeoutSeconds);
|
|
}
|
|
|
|
private function inertiaRefusal(Request $request): Response
|
|
{
|
|
$user = $request->user();
|
|
|
|
return $this->responseFactory->json([
|
|
'message' => 'Password confirmation required.',
|
|
// The same question the confirm-password screen asks: an account
|
|
// provisioned by a provider has no password to type, and the
|
|
// dialog has to offer it a way to set one instead.
|
|
'has_local_password' => $user?->auth_source === AuthSource::Local,
|
|
], 423, [self::HEADER => 'required']);
|
|
}
|
|
}
|