Files
projectsend/app/Http/Controllers/Auth/ConfirmablePasswordController.php
T
ignacionelson ff9ad10742 Let an account that signs in through a provider set a password
Reported by Ricardo Cazati, who had to turn compulsory two-factor off to
get his colleagues working.

An account provisioned by a provider carries a generated password nobody
has ever seen. The password screen asked for the current one before it
would set a new one, so those accounts could never have a password of
their own — and enrolling in two-factor is behind a password
confirmation, so they could not enrol either. With
`TwoFactorEnforcement` set, the enforcement middleware sent them to
enrol, enrolling sent them to confirm a password they do not have, and
every other screen — including the one that would have given them one —
redirected back. No way in and no way out.

- The password screen asks for the current one only where there is one,
  and says "Set a password" otherwise. Setting it moves the account to
  `local`, the line NewPasswordController already writes when such an
  account resets its password: the hash is now what signs it in, and the
  settings screens read that off this column.
- An LDAP account is refused outright rather than handed a password that
  signs nothing in — its password lives in the directory.
- The enforcement middleware lets the password screen through, the way it
  already lets the confirm-password screen through, so the loop has an
  exit.
- The confirm-password screen offers to set one instead of asking for a
  password that does not exist.
2026-09-18 13:16:20 -03:00

61 lines
2.1 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\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.
'has_local_password' => $user->auth_source === AuthSource::Local,
]);
}
/**
* 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.
*/
public function store(Request $request, PasswordVerification $passwords): RedirectResponse
{
$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());
return redirect()->intended(route('dashboard', absolute: false));
}
}