fix(auth): let OAuth-only users enable 2FA without a password confirmation

Users with a linked OAuth identity or no password cannot confirm a
password they do not know. The password.confirm middleware now follows
User::requiresPasswordConfirmation(), like destructive actions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Andras BacsaiandClaude Opus 5.5 committed 2026-10-02 16:01:10 +02:00
1 parent 3dabda5c56
commit f75ffd329f
3 files changed
+56 -1

No files matched your search

+1 -1
View File
@@ -17,6 +17,7 @@ use App\Http\Middleware\EnsureTeamMcpEnabled;
use App\Http\Middleware\EnsureTokenBelongsToCurrentTeamMember;
use App\Http\Middleware\PreventRequestsDuringMaintenance;
use App\Http\Middleware\RedirectIfAuthenticated;
use App\Http\Middleware\RequirePassword;
use App\Http\Middleware\TrimStrings;
use App\Http\Middleware\TrustProxies;
use App\Http\Middleware\ValidateSignature;
@@ -24,7 +25,6 @@ use App\Http\Middleware\VerifyCsrfToken;
use Illuminate\Auth\Middleware\AuthenticateWithBasicAuth;
use Illuminate\Auth\Middleware\Authorize;
use Illuminate\Auth\Middleware\EnsureEmailIsVerified;
use Illuminate\Auth\Middleware\RequirePassword;
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull;
+22
View File
@@ -0,0 +1,22 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Auth\Middleware\RequirePassword as Middleware;
class RequirePassword extends Middleware
{
/**
* Users with a linked OAuth identity, or without a password, cannot confirm
* a password they do not know. They follow the same rule as destructive
* actions (see User::requiresPasswordConfirmation()).
*/
protected function shouldConfirmPassword($request, $passwordTimeoutSeconds = null)
{
if ($request->user()?->requiresPasswordConfirmation() === false) {
return false;
}
return parent::shouldConfirmPassword($request, $passwordTimeoutSeconds);
}
}
@@ -3,6 +3,7 @@
use App\Models\InstanceSettings;
use App\Models\OauthIdentity;
use App\Models\OauthSetting;
use App\Models\Team;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Blade;
@@ -173,3 +174,35 @@ describe('confirmation modal', function () {
->and($html)->not->toContain('Confirm with Google');
});
});
describe('two-factor setup', function () {
beforeEach(function () {
$this->team = Team::factory()->create(['show_boarding' => false]);
});
it('lets a user with a linked identity and no password enable 2fa without a password confirmation', function () {
$user = User::factory()->create();
$user->forceFill(['password' => null])->save();
$this->team->members()->attach($user, ['role' => 'owner']);
linkGoogleIdentity($user, 'google-id');
$this->actingAs($user)
->withSession(['currentTeam' => $this->team])
->post(route('two-factor.enable'))
->assertSessionHas('status', 'two-factor-authentication-enabled');
expect($user->fresh()->two_factor_secret)->not->toBeNull();
});
it('still asks a password user without a linked identity to confirm the password', function () {
$user = User::factory()->create(['password' => Hash::make('secret-password')]);
$this->team->members()->attach($user, ['role' => 'owner']);
$this->actingAs($user)
->withSession(['currentTeam' => $this->team])
->post(route('two-factor.enable'))
->assertRedirect(route('password.confirm'));
expect($user->fresh()->two_factor_secret)->toBeNull();
});
});