Security: JSON-Login durchläuft die is_active-/Verifizierungschecks
RoleAwareLoginResponse gab bei wantsJson() sofort 204 zurück – VOR den Sicherheitschecks. Ein XHR/JSON-Login eines verifiziert-inaktiven Accounts erhielt damit eine Session ohne Logout. Checks laufen jetzt zuerst: verifiziert-inaktiv → Logout + Session-Invalidate + 403 (JSON) bzw. Login mit Fehler (HTML); unverifiziert → 204 (JSON) bzw. Notice (HTML); danach der Erfolgsfall. Tests: JSON-Login eines inaktiven Accounts (403, guest), JSON-Login eines aktiven Users (204, authentifiziert). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
f4ca452c6b
commit
ae79d5bee4
3 changed files with 52 additions and 16 deletions
|
|
@ -11,34 +11,46 @@ use Laravel\Fortify\Contracts\LoginResponse as LoginResponseContract;
|
|||
use Laravel\Fortify\Contracts\TwoFactorLoginResponse as TwoFactorLoginResponseContract;
|
||||
|
||||
/**
|
||||
* Einheitliche Antwort für den Fortify-POST-Login UND den Abschluss der
|
||||
* 2FA-Challenge. Spiegelt dieselbe Policy wie der Volt-Login:
|
||||
* - unverifiziert → Verifizierungs-Notice
|
||||
* - verifiziert, aber inaktiv → Login blockiert (Logout + Fehler)
|
||||
* Einheitliche Antwort für den Fortify-POST-Login (HTML und JSON/XHR) UND den
|
||||
* Abschluss der 2FA-Challenge. Spiegelt dieselbe Policy wie der Volt-Login:
|
||||
* - verifiziert, aber inaktiv → Session beenden, KEIN Login (auch für JSON)
|
||||
* - unverifiziert → Verifizierungs-Notice (JSON: 204, verified-Middleware schützt)
|
||||
* - sonst rollengerechter, 403-sicherer Redirect (intended nur wenn erreichbar)
|
||||
*
|
||||
* Wichtig: Die Sicherheitsprüfungen laufen VOR dem JSON-Kurzschluss, damit ein
|
||||
* XHR-Login keine Session für einen deaktivierten Account erhält.
|
||||
*/
|
||||
class RoleAwareLoginResponse implements LoginResponseContract, TwoFactorLoginResponseContract
|
||||
{
|
||||
public function toResponse($request): RedirectResponse|JsonResponse
|
||||
{
|
||||
if ($request instanceof Request && $request->wantsJson()) {
|
||||
return new JsonResponse('', 204);
|
||||
}
|
||||
|
||||
$user = $request->user();
|
||||
$wantsJson = $request instanceof Request && $request->wantsJson();
|
||||
|
||||
if ($user && ! $user->hasVerifiedEmail()) {
|
||||
return redirect()->route('verification.notice');
|
||||
}
|
||||
|
||||
if ($user && ! $user->is_active) {
|
||||
// Sicherheits-Boundary zuerst: ein verifizierter, aber deaktivierter
|
||||
// Account darf keine Session behalten – egal ob HTML oder JSON.
|
||||
if ($user && $user->hasVerifiedEmail() && ! $user->is_active) {
|
||||
Auth::guard('web')->logout();
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
|
||||
return redirect()->route('login')->withErrors([
|
||||
'email' => __('Ihr Konto ist nicht aktiv. Bitte wenden Sie sich an den Support.'),
|
||||
]);
|
||||
$message = __('Ihr Konto ist nicht aktiv. Bitte wenden Sie sich an den Support.');
|
||||
|
||||
return $wantsJson
|
||||
? new JsonResponse(['message' => $message], 403)
|
||||
: redirect()->route('login')->withErrors(['email' => $message]);
|
||||
}
|
||||
|
||||
// Unverifizierte Selbst-Registrierer sind authentifiziert, aber die
|
||||
// verified-Middleware sperrt geschützte Routen. HTML → Notice, JSON → 204.
|
||||
if ($user && ! $user->hasVerifiedEmail()) {
|
||||
return $wantsJson
|
||||
? new JsonResponse('', 204)
|
||||
: redirect()->route('verification.notice');
|
||||
}
|
||||
|
||||
if ($wantsJson) {
|
||||
return new JsonResponse('', 204);
|
||||
}
|
||||
|
||||
$default = LoginRedirect::homeFor($user);
|
||||
|
|
|
|||
|
|
@ -90,6 +90,8 @@ Aus einer gezielten Auth-Prüfung umgesetzt:
|
|||
|
||||
**Hinweis:** `fortify.views => false` bleibt; die Challenge wird vom Volt-Frontend bereitgestellt, die Verifizierung übernimmt Fortify.
|
||||
|
||||
**Nachschärfung (Review 16.06., Teil 3):** Der `wantsJson()`-Kurzschluss in `RoleAwareLoginResponse` lief zuvor VOR den Sicherheitschecks – ein XHR/JSON-Login eines verifiziert-inaktiven Accounts erhielt so eine Session ohne Logout. Jetzt laufen die Prüfungen zuerst: verifiziert-inaktiv → `Auth::logout()` + Session-Invalidate + **403** (JSON) bzw. Login mit Fehler (HTML); unverifiziert → 204 (JSON) bzw. Notice (HTML). Erst danach der 204-/Redirect-Erfolgsfall.
|
||||
|
||||
---
|
||||
|
||||
## 7. Deployment-Reihenfolge (Migrationen dieser Phase)
|
||||
|
|
|
|||
|
|
@ -65,6 +65,28 @@ test('the fortify login post blocks inactive but verified users', function () {
|
|||
$this->assertGuest();
|
||||
});
|
||||
|
||||
test('a json login does not grant a session to an inactive verified account', function () {
|
||||
/** @var TestCase $this */
|
||||
$user = User::factory()->create(['is_active' => false]);
|
||||
|
||||
$this->postJson('/login', ['email' => $user->email, 'password' => 'password'])
|
||||
->assertStatus(403);
|
||||
|
||||
$this->assertGuest();
|
||||
});
|
||||
|
||||
test('a json login for an active user succeeds with 204', function () {
|
||||
/** @var TestCase $this */
|
||||
$this->seed(RolesAndPermissionsSeeder::class);
|
||||
$customer = User::factory()->create(['is_active' => true]);
|
||||
$customer->assignRole('customer');
|
||||
|
||||
$this->postJson('/login', ['email' => $customer->email, 'password' => 'password'])
|
||||
->assertNoContent();
|
||||
|
||||
$this->assertAuthenticatedAs($customer);
|
||||
});
|
||||
|
||||
test('the fortify login post keeps a customer out of the admin area on stale intended', function () {
|
||||
/** @var TestCase $this */
|
||||
$this->seed(RolesAndPermissionsSeeder::class);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue