Files
portal/app/tests/Feature/Auth/AuthFlowIntegrationTest.php
T

154 lines
6.9 KiB
PHP
Raw Normal View History

<?php
declare(strict_types=1);
use App\Models\Tenant;
use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Facades\Password;
use PragmaRX\Google2FA\Google2FA;
uses(DatabaseTransactions::class);
/**
* Reset the Auth manager's default guard and cached guard instances back to
* the 'web' SessionGuard.
*
* Necessary because auth:sanctum middleware calls Auth::shouldUse('sanctum')
* on every successfully-authenticated request, which permanently changes
* config('auth.defaults.guard') to 'sanctum' in the shared test application
* instance. Laravel feature tests reuse the same $this->app between HTTP calls,
* so this pollution persists across requests. Any subsequent call to
* Auth::login() (which internally calls Auth::guard()->login()) then resolves
* to the Sanctum RequestGuard — which has no login() method — and throws a
* BadMethodCallException.
*
* The reset must happen *before* any request whose controller calls Auth::login()
* without an explicit guard argument (i.e. login and 2fa/verify routes).
*/
function resetAuthToWebGuard(): void
{
app('auth')->forgetGuards();
app('auth')->setDefaultDriver('web');
}
it('full auth-flow writes all expected auth_log events', function () {
Notification::fake();
$tenant = Tenant::factory()->create();
// ── Step 1: Register ─────────────────────────────────────────────────────
$this->postJson('/api/auth/register', [
'email' => 'flow-test@example.ru',
'password' => 'secure-pass-1234',
'accept_offer' => true,
'accept_pdn' => true,
])->assertStatus(201);
// logs: register_success
$user = User::where('email', 'flow-test@example.ru')->first();
expect($user)->not->toBeNull();
// ── Step 2: Login (no 2FA yet) — establish session auth ──────────────────
// No prior auth:sanctum request, so no reset needed here.
$this->postJson('/api/auth/login', [
'email' => 'flow-test@example.ru',
'password' => 'secure-pass-1234',
])->assertOk();
// logs: login_success (first direct login, 2FA not yet enabled)
// ── Step 3: 2FA init (session-authenticated via web guard) ───────────────
// auth:sanctum middleware → shouldUse('sanctum') → default becomes 'sanctum'
$this->postJson('/api/2fa/init')->assertOk();
// logs: 2fa_setup_init
$secret = session('auth.pending_totp_secret');
expect($secret)->not->toBeNull();
// ── Step 4: 2FA confirm ───────────────────────────────────────────────────
$google2fa = new Google2FA;
$code = $google2fa->getCurrentOtp($secret);
// auth:sanctum middleware → shouldUse('sanctum') again
$this->postJson('/api/2fa/confirm', ['code' => $code])->assertOk();
// logs: 2fa_setup_confirmed (totp_enabled now true)
// ── Step 5: Logout ────────────────────────────────────────────────────────
// auth:sanctum middleware → shouldUse('sanctum') again
$this->postJson('/api/auth/logout')->assertOk();
// logs: logout
// ── Step 6: Login with 2FA enabled ────────────────────────────────────────
// auth.defaults.guard is now 'sanctum' from previous auth:sanctum requests.
// Reset to 'web' so Auth::login() inside AuthController::login() finds the
// SessionGuard (which implements login()) rather than the RequestGuard.
resetAuthToWebGuard();
$this->postJson('/api/auth/login', [
'email' => 'flow-test@example.ru',
'password' => 'secure-pass-1234',
])->assertOk();
// requires_2fa=true, pending_user_id stored in session
// ── Step 7: 2FA verify — completes login ─────────────────────────────────
// No auth:sanctum request happened since the last reset, so no reset needed.
$validCode = $google2fa->getCurrentOtp($secret);
$this->postJson('/api/auth/2fa/verify', ['code' => $validCode])->assertOk();
// logs: 2fa_verify_success
// ── Step 8: 2FA disable (session-authenticated from step 7) ──────────────
// auth:sanctum middleware → shouldUse('sanctum') again
$this->postJson('/api/2fa/disable', ['password' => 'secure-pass-1234'])->assertOk();
// logs: 2fa_disabled
// ── Step 9: Logout ────────────────────────────────────────────────────────
// auth:sanctum middleware → shouldUse('sanctum') again
$this->postJson('/api/auth/logout')->assertOk();
// ── Step 10: Login without 2FA — direct login_success ────────────────────
// Reset again: auth.defaults.guard is 'sanctum' from Step 8+9 auth:sanctum.
resetAuthToWebGuard();
$this->postJson('/api/auth/login', [
'email' => 'flow-test@example.ru',
'password' => 'secure-pass-1234',
])->assertOk();
// logs: login_success (direct login, 2FA now disabled)
// ── Step 11: Forgot password ──────────────────────────────────────────────
$this->postJson('/api/auth/logout')->assertOk();
$this->postJson('/api/auth/forgot', [
'email' => 'flow-test@example.ru',
])->assertOk();
// logs: password_reset_requested
// ── Step 12: Reset password ───────────────────────────────────────────────
$token = Password::createToken($user);
$this->postJson('/api/auth/reset-password', [
'token' => $token,
'email' => 'flow-test@example.ru',
'password' => 'new-secure-pass-5678',
'password_confirmation' => 'new-secure-pass-5678',
])->assertOk();
// logs: password_reset_completed
// ── Assert all expected events were recorded for this user ────────────────
$events = DB::table('auth_log')
->where('user_id', $user->id)
->pluck('event')
->all();
expect($events)->toContain(
'register_success',
'2fa_setup_init',
'2fa_setup_confirmed',
'logout',
'login_success',
'2fa_verify_success',
'2fa_disabled',
'password_reset_requested',
'password_reset_completed',
);
});