presseportale/tests/Feature/Auth/GoogleLoginTest.php
Kevin Adametz 0ab53bfa5e auth: Firmenrechte nur aus nachgewiesener Adresse, 2FA auf allen Login-Wegen (Audit Block 1, Deploy A)
Audit vom 11.09. gegen HEAD geprüft, Entwicklungsplan dazu unter
dev/2026-09-11-audit/ENTWICKLUNGSPLAN.md. Dieser Commit ist Deploy A:
die sechs kleinen Punkte aus Block 1 ohne Schemaänderung.

F01: Der Fortify-Endpunkt PUT /user/profile-information tauschte die
E-Mail sofort und setzte die Verifizierung zurück; der Login-Sync vergab
danach Firmenrechte allein nach E-Mail-Treffer. Feature abgeschaltet
(die Kunden-UI hat den pending_email-Prozess), und der Sync verlangt
jetzt ein verifiziertes, aktives Konto – für alle Aufrufer.

F02: Google-Login übergibt Konten mit bestätigtem TOTP in die Challenge,
wie Passwort- und Magic-Link-Login.

F03: showRecoveryCodes ist gesperrt; ein Client-Update konnte die
Recovery-Codes entschlüsseln lassen.

F08: Nutzer-Detailroute unter can:users:manage (persistente Livewire-
Middleware), Gutschriften autorisieren zusätzlich in der Action.

F09: Gate::before greift nur für aktive Super-Admins; deaktivierte
fallen auf die Gates mit is_active-Prüfung zurück.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-18 10:08:55 +00:00

125 lines
4.5 KiB
PHP

<?php
use App\Models\User;
use Database\Seeders\RolesAndPermissionsSeeder;
use Laravel\Socialite\Contracts\Provider;
use Laravel\Socialite\Facades\Socialite;
use Laravel\Socialite\Two\User as SocialiteUser;
use PragmaRX\Google2FA\Google2FA;
use Tests\TestCase;
beforeEach(function () {
$this->seed(RolesAndPermissionsSeeder::class);
});
function fakeGoogleUser(string $id, string $email, string $name): void
{
$socialiteUser = (new SocialiteUser)->map([
'id' => $id,
'email' => $email,
'name' => $name,
]);
$provider = Mockery::mock(Provider::class);
$provider->shouldReceive('user')->andReturn($socialiteUser);
Socialite::shouldReceive('driver')->with('google')->andReturn($provider);
}
test('a new google user is created active, verified and as customer', function () {
/** @var TestCase $this */
fakeGoogleUser('g-new-1', 'new-google@example.test', 'Neu Google');
$this->get(route('oauth.google.callback'))
->assertRedirect(route('me.dashboard', absolute: false));
$user = User::query()->where('email', 'new-google@example.test')->firstOrFail();
expect($user->is_active)->toBeTrue();
expect($user->hasVerifiedEmail())->toBeTrue();
expect($user->hasRole('customer'))->toBeTrue();
expect($user->oauth_provider)->toBe('google');
expect($user->oauth_provider_id)->toBe('g-new-1');
expect($user->gdpr_consent_at)->not->toBeNull();
$this->assertAuthenticatedAs($user);
});
test('an existing user is linked by email without creating a duplicate', function () {
/** @var TestCase $this */
$existing = User::factory()->create(['email' => 'existing-google@example.test', 'is_active' => true]);
$existing->assignRole('customer');
fakeGoogleUser('g-existing-1', 'Existing-Google@example.test', 'Existing');
$this->get(route('oauth.google.callback'))
->assertRedirect(route('me.dashboard', absolute: false));
expect(User::query()->where('email', 'existing-google@example.test')->count())->toBe(1);
$existing->refresh();
expect($existing->oauth_provider)->toBe('google');
expect($existing->oauth_provider_id)->toBe('g-existing-1');
$this->assertAuthenticatedAs($existing);
});
test('a deactivated verified account is not reactivated by google login', function () {
/** @var TestCase $this */
$existing = User::factory()->create(['email' => 'deactivated@example.test', 'is_active' => false]);
$existing->assignRole('customer');
fakeGoogleUser('g-deact-1', 'deactivated@example.test', 'Deactivated');
$this->get(route('oauth.google.callback'))
->assertRedirect(route('login'));
expect($existing->fresh()->is_active)->toBeFalse();
$this->assertGuest();
});
test('a pending unverified registrant is onboarded via google', function () {
/** @var TestCase $this */
$pending = User::factory()->unverified()->create([
'email' => 'pending@example.test',
'is_active' => false,
]);
fakeGoogleUser('g-pending-1', 'pending@example.test', 'Pending');
$this->get(route('oauth.google.callback'))
->assertRedirect(route('me.dashboard', absolute: false));
$pending->refresh();
expect($pending->is_active)->toBeTrue();
expect($pending->hasVerifiedEmail())->toBeTrue();
expect($pending->hasRole('customer'))->toBeTrue();
});
test('a failed google callback redirects back to login with an error', function () {
/** @var TestCase $this */
$provider = Mockery::mock(Provider::class);
$provider->shouldReceive('user')->andThrow(new RuntimeException('invalid state'));
Socialite::shouldReceive('driver')->with('google')->andReturn($provider);
$this->get(route('oauth.google.callback'))
->assertRedirect(route('login'));
$this->assertGuest();
});
test('google login hands an account with confirmed two-factor to the challenge (Audit F02)', function () {
/** @var TestCase $this */
$user = User::factory()->create(['email' => 'totp-google@example.test', 'is_active' => true]);
$user->assignRole('customer');
$user->forceFill([
'two_factor_secret' => encrypt(app(Google2FA::class)->generateSecretKey()),
'two_factor_confirmed_at' => now(),
])->save();
fakeGoogleUser('g-totp-1', 'totp-google@example.test', 'Totp');
$this->get(route('oauth.google.callback'))
->assertRedirect(route('two-factor.challenge'));
$this->assertGuest();
expect(session('login.id'))->toBe($user->id);
expect($user->fresh()->last_login_at)->toBeNull();
});