Confirm a console password against the console's own record
parent
f54d00b3e7
commit
513cadc5ce
|
|
@ -50,6 +50,11 @@ class Mail extends Component
|
||||||
*/
|
*/
|
||||||
public bool $usable = true;
|
public bool $usable = true;
|
||||||
|
|
||||||
|
protected function confirmationGuard(): string
|
||||||
|
{
|
||||||
|
return 'operator';
|
||||||
|
}
|
||||||
|
|
||||||
public function mount(): void
|
public function mount(): void
|
||||||
{
|
{
|
||||||
$this->authorize('mail.manage');
|
$this->authorize('mail.manage');
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,11 @@ class Secrets extends Component
|
||||||
/** Result of the last connection test, for display only. */
|
/** Result of the last connection test, for display only. */
|
||||||
public ?array $check = null;
|
public ?array $check = null;
|
||||||
|
|
||||||
|
protected function confirmationGuard(): string
|
||||||
|
{
|
||||||
|
return 'operator';
|
||||||
|
}
|
||||||
|
|
||||||
public function mount(): void
|
public function mount(): void
|
||||||
{
|
{
|
||||||
$this->authorize('secrets.manage');
|
$this->authorize('secrets.manage');
|
||||||
|
|
|
||||||
|
|
@ -39,9 +39,23 @@ trait ConfirmsPassword
|
||||||
return $at > 0 && (time() - $at) < $this->confirmationWindow();
|
return $at > 0 && (time() - $at) < $this->confirmationWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Which guard's record this component confirms against.
|
||||||
|
*
|
||||||
|
* Overridden to 'operator' by the console components. A default of 'web'
|
||||||
|
* keeps the portal working unchanged — and an override is explicit, where
|
||||||
|
* sniffing the request would be one more thing to get wrong on a page that
|
||||||
|
* gates access to credentials.
|
||||||
|
*/
|
||||||
|
protected function confirmationGuard(): string
|
||||||
|
{
|
||||||
|
return 'web';
|
||||||
|
}
|
||||||
|
|
||||||
public function confirmPassword(): void
|
public function confirmPassword(): void
|
||||||
{
|
{
|
||||||
$key = 'confirm-password:'.auth()->id().'|'.request()->ip();
|
$account = auth()->guard($this->confirmationGuard())->user();
|
||||||
|
$key = 'confirm-password:'.$this->confirmationGuard().'|'.$account?->getAuthIdentifier().'|'.request()->ip();
|
||||||
|
|
||||||
if (RateLimiter::tooManyAttempts($key, 5)) {
|
if (RateLimiter::tooManyAttempts($key, 5)) {
|
||||||
$this->addError('confirmablePassword', __('auth.throttle', [
|
$this->addError('confirmablePassword', __('auth.throttle', [
|
||||||
|
|
@ -51,7 +65,7 @@ trait ConfirmsPassword
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! Hash::check($this->confirmablePassword, auth()->user()->password)) {
|
if ($account === null || ! Hash::check($this->confirmablePassword, $account->password)) {
|
||||||
RateLimiter::hit($key, 60);
|
RateLimiter::hit($key, 60);
|
||||||
$this->addError('confirmablePassword', __('admin_settings.password_wrong'));
|
$this->addError('confirmablePassword', __('admin_settings.password_wrong'));
|
||||||
$this->reset('confirmablePassword');
|
$this->reset('confirmablePassword');
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,11 @@ class EditMailbox extends ModalComponent
|
||||||
/** Whether this mailbox logs in before it sends — Codex R15#4, P1b. */
|
/** Whether this mailbox logs in before it sends — Codex R15#4, P1b. */
|
||||||
public bool $authenticates = true;
|
public bool $authenticates = true;
|
||||||
|
|
||||||
|
protected function confirmationGuard(): string
|
||||||
|
{
|
||||||
|
return 'operator';
|
||||||
|
}
|
||||||
|
|
||||||
public function mount(string $uuid): void
|
public function mount(string $uuid): void
|
||||||
{
|
{
|
||||||
$this->authorize('mail.manage');
|
$this->authorize('mail.manage');
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Livewire\Admin\Secrets;
|
||||||
|
use App\Models\Operator;
|
||||||
|
use App\Models\User;
|
||||||
|
use Livewire\Livewire;
|
||||||
|
|
||||||
|
beforeEach(fn () => config()->set('admin_access.secrets_key', 'base64:'.base64_encode(random_bytes(32))));
|
||||||
|
|
||||||
|
it('confirms a console password against the operator record, not a portal user', function () {
|
||||||
|
$operator = Operator::factory()->role('Owner')->create(['password' => 'operator-passwort']);
|
||||||
|
|
||||||
|
Livewire::actingAs($operator, 'operator')->test(Secrets::class)
|
||||||
|
->set('confirmablePassword', 'operator-passwort')
|
||||||
|
->call('confirmPassword')
|
||||||
|
->assertHasNoErrors();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects a password that belongs to a portal user with the same address', function () {
|
||||||
|
// The trap: two tables, one address. Confirming against the wrong one would
|
||||||
|
// let a customer's password unlock the console.
|
||||||
|
$operator = Operator::factory()->role('Owner')->create([
|
||||||
|
'email' => 'doppelt@clupilot.test', 'password' => 'operator-passwort',
|
||||||
|
]);
|
||||||
|
User::factory()->create(['email' => 'doppelt@clupilot.test', 'password' => 'kunden-passwort']);
|
||||||
|
|
||||||
|
Livewire::actingAs($operator, 'operator')->test(Secrets::class)
|
||||||
|
->set('confirmablePassword', 'kunden-passwort')
|
||||||
|
->call('confirmPassword')
|
||||||
|
->assertHasErrors('confirmablePassword');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('still confirms a portal password on the web guard', function () {
|
||||||
|
$user = User::factory()->create(['password' => 'kunden-passwort']);
|
||||||
|
|
||||||
|
Livewire::actingAs($user)->test(App\Livewire\Settings::class)
|
||||||
|
->set('confirmablePassword', 'kunden-passwort')
|
||||||
|
->call('confirmPassword')
|
||||||
|
->assertHasNoErrors();
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue