NovinkaZverejnili sme 50+ Laravel balíkov ako open source
Custom AI apps, agents and automation — Roundly ConsultingRoundly
Všetky balíky

TwoFactor::fake() nahradí službu programovateľným dvojníkom bez kryptografie a vráti ho ako TwoFactorFake na asercie. Vidí ho fasáda, vložený TwoFactorService aj metódy modelu, takže tok 2FA otestujete bez zmrazovania času a bez výpočtu skutočných kódov:

use RoundlyConsulting\TwoFactor\Enums\TwoFactorMethod;
use RoundlyConsulting\TwoFactor\Facades\TwoFactor;

// Accept any code (the default) and assert the challenge was verified:
$fake = TwoFactor::fake()->accept();
$this->post('/login/2fa', ['code' => '123456'])->assertOk();
$fake->assertVerifiedFor($user);

// Reject every code:
TwoFactor::fake()->reject();
$this->post('/login/2fa', ['code' => '000000'])->assertStatus(422);

// Accept only a specific code:
TwoFactor::fake()->acceptCode('424242');

// Drive attempt(): pass via a recovery code, report 2 left, assert the method:
$fake = TwoFactor::fake()->acceptRecoveryCode()->withRemainingRecoveryCodes(2);
$this->post('/login/2fa', ['code' => 'ABCDE-12345'])->assertOk();
$fake->assertVerifiedVia(TwoFactorMethod::RecoveryCode);

// Fail as a replay (VerificationResult::$replayed === true):
TwoFactor::fake()->replay();

// Enrolment writes run for real on the fake's canned secret and codes, and are recorded:
$fake = TwoFactor::fake();
$this->post('/two-factor/enable')->assertOk();        // calls TwoFactor::for($user)->start()
$this->post('/two-factor/disable')->assertOk();       // or $user->disableTwoFactor()
$fake->assertStarted($user);
$fake->assertDisabled($user);
$fake->assertNothingRegenerated();

Čo fake robí

  • attempt() nepočíta žiadnu TOTP matematiku — vráti naprogramovaný výsledok a zaznamená používateľa, kód aj výsledok.
  • start(), confirm(), recoveryCodes()->regenerate() a disable() stále spúšťajú skutočné akcie, no nad pripraveným tajným kľúčom a záložnými kódmi fake a jeho programovateľným verify(); zaznamenajú sa až po úspechu — neúspešné potvrdenie sa nezaznamená.
  • status() a recoveryCodes()->remaining() čítajú priamo z modelu.

Programovateľné správanie

MetódaÚčinok
accept()Každé verify() aj attempt() prejde cez TOTP (predvolené).
acceptRecoveryCode()attempt() prejde, akoby sa spotreboval záložný kód.
reject()Každé verify() aj attempt() zlyhá.
replay()attempt() zlyhá ako opakovanie (replayed: true).
acceptCode($code)Prijme len presne tento kód, cez aktuálnu metódu.
withRemainingRecoveryCodes($n)Pevne nastaví hlásený počet. Bez neho fake hlási uložený počet používateľa, o jeden nižší, keď prejde záložný kód.
withSecret($secret)Tajný kľúč, ktorý vráti generateSecret().
withRecoveryCodes(...$codes)Kódy, ktoré vráti generateRecoveryCodes().

Asercie

Všetky asercie na vrátenom fake. Argument $user je pri assertStarted(), assertConfirmed(), assertRegenerated() a assertDisabled() voliteľný:

AserciaPrejde, keď
assertVerified()Aspoň jedno attempt() prešlo.
assertVerifiedFor($user)Pre tohto používateľa prešlo attempt().
assertVerifiedVia(TwoFactorMethod::RecoveryCode)attempt() prešlo cez túto TwoFactorMethod.
assertVerificationFailed()Aspoň jedno attempt() zlyhalo.
assertVerifyCount(2)Zaznamenal sa presne tento počet volaní attempt().
assertCodeAttempted('424242')Tento kód prešiel cez attempt() alebo plochú verify().
assertNothingVerified()Nezaznamenalo sa žiadne attempt().
assertStarted($user)start() uspelo — pre tohto používateľa, ak ho zadáte.
assertNothingStarted()Nespustila sa žiadna aktivácia.
assertConfirmed($user)confirm() uspelo — neúspešné potvrdenie sa nezaznamenáva.
assertNothingConfirmed()Nepotvrdila sa žiadna aktivácia.
assertRegenerated($user)Prebehlo recoveryCodes()->regenerate() — pre tohto používateľa, ak ho zadáte.
assertNothingRegenerated()Žiadne záložné kódy sa neobnovili.
assertDisabled($user)Prebehlo disable() — pre tohto používateľa, ak ho zadáte.
assertNothingDisabled()2FA sa nikomu nevyplo.

Celý test overenia pri prihlásení s aserciami overovania:

use RoundlyConsulting\TwoFactor\Enums\TwoFactorMethod;
use RoundlyConsulting\TwoFactor\Facades\TwoFactor;

it('lets the user through with the right code only', function (): void {
    $user = User::factory()->create();
    $fake = TwoFactor::fake()->acceptCode('424242');

    $this->actingAs($user)->post('/login/2fa', ['code' => '000000'])->assertStatus(422);
    $this->actingAs($user)->post('/login/2fa', ['code' => '424242'])->assertOk();

    $fake->assertVerifyCount(2);
    $fake->assertCodeAttempted('000000');
    $fake->assertVerificationFailed();
    $fake->assertVerified();
    $fake->assertVerifiedFor($user);
    $fake->assertVerifiedVia(TwoFactorMethod::Totp);
    $fake->assertNothingStarted();
    $fake->assertNothingConfirmed();
    $fake->assertNothingDisabled();
});

Asercie vyhadzujú TwoFactorAssertionFailedException — výnimku balíka, nie PHPUnit asserciu — takže fungujú v akomkoľvek test runneri. Fake je určený len na testy: registruje sa výlučne cez TwoFactor::fake() a nikdy nie v produkcii.

Testovanie so skutočnými kódmi

Ak chcete otestovať skutočnú kryptografiu, zmrazte čas cez Carbon::setTestNow() a aktuálny kód vypočítajte cez TwoFactor::currentCode():

use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Event;
use RoundlyConsulting\TwoFactor\Events\TwoFactorConfirmed;
use RoundlyConsulting\TwoFactor\Events\TwoFactorReplayDetected;
use RoundlyConsulting\TwoFactor\Facades\TwoFactor;

it('rejects the confirmation code replayed at first login', function (): void {
    Carbon::setTestNow(Carbon::createFromTimestamp(1_700_000_000));
    Event::fake();

    $user = User::factory()->create();
    $setup = TwoFactor::for($user)->start();
    $code = TwoFactor::currentCode($setup->secret);

    TwoFactor::for($user->fresh())->confirm($code);

    expect(TwoFactor::for($user->fresh())->status()->enabled)->toBeTrue();
    Event::assertDispatched(TwoFactorConfirmed::class);

    // The exact code used to confirm cannot double as the first login code.
    expect(TwoFactor::for($user->fresh())->attempt($code)->verified)->toBeFalse();
    Event::assertDispatched(TwoFactorReplayDetected::class);

    // A code at a later timestep still verifies.
    Carbon::setTestNow(Carbon::createFromTimestamp(1_700_000_060));
    expect(TwoFactor::for($user->fresh())->attempt(TwoFactor::currentCode($setup->secret))->verified)->toBeTrue();

    Carbon::setTestNow();
});

Prejavte lásku k open source

Tento balík je zadarmo pod licenciou MIT. Ak vám šetrí čas, jednorazový príspevok alebo členstvo na Patreone nám pomôže ho ďalej udržiavať, testovať a dokumentovať.

Ďalšie spôsoby podpory vrátane kryptomien

Odoslaním daru súhlasíte s našimi podmienkami prijímania darov.

Chcete to zabudovať do svojho produktu?

Naše balíky integrujeme do zákazkových Laravel a AI riešení. Napíšte nám, na čom pracujete, a ozveme sa do 48 hodín.