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', ); });