Files
projectsend/app/Http/Controllers/Auth/ConfirmablePasswordController.php
T
ignacionelson 3a3fd5358d Let directory accounts confirm their password
The confirm-password screen hid its field from every account that was
not Local, and told it to set a password instead. That is right for an
account a provider created, which has no password anybody has seen. It
is wrong for a directory account: its password is the directory's,
PasswordVerification accepts it, and /settings/password refuses to let
it set another. So an LDAP account could not get past the confirmation
at all, and everything behind it, turning on two-factor included, was
out of reach. The new dialog copied the same question.

Both now ask whether the account came from a provider, and the prop is
called has_password, which is what it means. The dialog also clears
the typed password when it closes or once it has been used, instead of
keeping it in component state.
2026-09-21 18:13:24 -03:00

76 lines
2.8 KiB
PHP

<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Modules\Identity\AuthSource;
use App\Modules\Identity\PasswordVerification;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response as HttpResponse;
use Illuminate\Validation\ValidationException;
use Inertia\Inertia;
use Inertia\Response;
class ConfirmablePasswordController extends Controller
{
/**
* Show the confirm password page.
*/
public function show(Request $request): Response
{
$user = $request->user();
assert($user !== null);
return Inertia::render('auth/confirm-password', [
// An account provisioned by a provider has no password to
// confirm with — its stored hash is a generated string nobody
// has seen. The screen offers to set one instead of asking for
// it, which is the only way past this for those accounts, and
// this screen stands in front of two-factor enrolment.
//
// Social, not "anything but Local": a directory account has a
// password -- the directory's -- and store() accepts it. Asking
// whether the account was Local told those accounts to set one
// here instead, which /settings/password refuses them, and left
// them no way past this screen at all.
'has_password' => $user->auth_source !== AuthSource::Social,
]);
}
/**
* Confirm the user's password.
*
* Through PasswordVerification, so this asks the same question the
* sign-in form asks: is this the account's password, from wherever
* that account's password lives. Checking only the local hash refused
* every directory-provisioned account the password it actually has --
* their local hash is a Str::password(64) nobody has ever seen -- and
* this screen stands in front of enrolling in two-factor, so those
* accounts could not enrol at all.
*
* Asked for JSON, it answers with a bare 204: that is the password
* dialog (RequirePasswordConfirmation), which stays on the page and
* sends the refused request again itself, so there is nowhere to go.
*/
public function store(Request $request, PasswordVerification $passwords): RedirectResponse|HttpResponse
{
$user = $request->user();
assert($user !== null);
if (! $passwords->verify($user, (string) $request->string('password'))) {
throw ValidationException::withMessages([
'password' => __('auth.password'),
]);
}
$request->session()->put('auth.password_confirmed_at', time());
if ($request->expectsJson()) {
return response()->noContent();
}
return redirect()->intended(route('dashboard', absolute: false));
}
}