mirror of
https://github.com/projectsend/projectsend.git
synced 2026-09-20 18:43:20 +00:00
ff9ad10742
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.
61 lines
2.1 KiB
PHP
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));
|
|
}
|
|
}
|