new funcionality
This commit is contained in:
115
tests/Feature/Auth/PasswordGrantTwoFactorTest.php
Normal file
115
tests/Feature/Auth/PasswordGrantTwoFactorTest.php
Normal file
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Auth;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use PragmaRX\Google2FA\Google2FA;
|
||||
use Tests\TestCase;
|
||||
|
||||
class PasswordGrantTwoFactorTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
private string $clientId;
|
||||
|
||||
private string $clientSecret;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$owner = User::factory()->approved()->create();
|
||||
$response = $this->actingAs($owner)->postJson('/web-api/clients', [
|
||||
'name' => 'Password Grant Test Client',
|
||||
'grant_type' => 'password',
|
||||
]);
|
||||
$response->assertCreated();
|
||||
|
||||
$this->clientId = $response->json('client.id');
|
||||
$this->clientSecret = $response->json('plain_secret');
|
||||
}
|
||||
|
||||
public function test_password_grant_still_works_for_users_without_two_factor(): void
|
||||
{
|
||||
User::factory()->approved()->create([
|
||||
'email' => 'no2fa@example.com',
|
||||
'password' => Hash::make('password123'),
|
||||
]);
|
||||
|
||||
$this->requestToken('no2fa@example.com', 'password123')
|
||||
->assertOk()->assertJsonStructure(['access_token']);
|
||||
}
|
||||
|
||||
public function test_password_grant_is_rejected_without_otp_when_two_factor_is_enabled(): void
|
||||
{
|
||||
User::factory()->approved()->create([
|
||||
'email' => 'has2fa@example.com',
|
||||
'password' => Hash::make('password123'),
|
||||
'two_factor_secret' => (new Google2FA())->generateSecretKey(),
|
||||
'two_factor_confirmed_at' => now(),
|
||||
]);
|
||||
|
||||
$this->requestToken('has2fa@example.com', 'password123')->assertStatus(400);
|
||||
}
|
||||
|
||||
public function test_password_grant_is_rejected_with_wrong_otp(): void
|
||||
{
|
||||
User::factory()->approved()->create([
|
||||
'email' => 'has2fa@example.com',
|
||||
'password' => Hash::make('password123'),
|
||||
'two_factor_secret' => (new Google2FA())->generateSecretKey(),
|
||||
'two_factor_confirmed_at' => now(),
|
||||
]);
|
||||
|
||||
$this->requestToken('has2fa@example.com', 'password123', '000000')->assertStatus(400);
|
||||
}
|
||||
|
||||
public function test_password_grant_succeeds_with_valid_totp_code(): void
|
||||
{
|
||||
$secret = (new Google2FA())->generateSecretKey();
|
||||
User::factory()->approved()->create([
|
||||
'email' => 'has2fa@example.com',
|
||||
'password' => Hash::make('password123'),
|
||||
'two_factor_secret' => $secret,
|
||||
'two_factor_confirmed_at' => now(),
|
||||
]);
|
||||
|
||||
$otp = (new Google2FA())->getCurrentOtp($secret);
|
||||
|
||||
$this->requestToken('has2fa@example.com', 'password123', $otp)
|
||||
->assertOk()->assertJsonStructure(['access_token']);
|
||||
}
|
||||
|
||||
public function test_password_grant_succeeds_with_a_recovery_code_and_consumes_it(): void
|
||||
{
|
||||
$user = User::factory()->approved()->create([
|
||||
'email' => 'has2fa@example.com',
|
||||
'password' => Hash::make('password123'),
|
||||
'two_factor_secret' => (new Google2FA())->generateSecretKey(),
|
||||
'two_factor_recovery_codes' => ['AAAA-1111', 'BBBB-2222'],
|
||||
'two_factor_confirmed_at' => now(),
|
||||
]);
|
||||
|
||||
$this->requestToken('has2fa@example.com', 'password123', 'AAAA-1111')->assertOk();
|
||||
|
||||
$this->assertNotContains('AAAA-1111', $user->fresh()->two_factor_recovery_codes);
|
||||
|
||||
// El mismo código de recuperación no sirve una segunda vez.
|
||||
$this->requestToken('has2fa@example.com', 'password123', 'AAAA-1111')->assertStatus(400);
|
||||
}
|
||||
|
||||
private function requestToken(string $email, string $password, ?string $otp = null)
|
||||
{
|
||||
return $this->postJson('/oauth/token', array_filter([
|
||||
'grant_type' => 'password',
|
||||
'client_id' => $this->clientId,
|
||||
'client_secret' => $this->clientSecret,
|
||||
'username' => $email,
|
||||
'password' => $password,
|
||||
'otp' => $otp,
|
||||
'scope' => 'users:read',
|
||||
], fn ($v) => $v !== null));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user