user(); if (! app(SecurityConfirmation::class)->sendEmailCode($user)) { throw ValidationException::withMessages([ 'securityEmailCode' => __('Zu viele Codes angefordert. Bitte warten Sie einige Minuten.'), ]); } $this->securityEmailCodeSent = true; } /** * Art des Nachweises für die Anzeige: fresh, two_factor, password oder email_code. */ public function securityProofKind(User $user): string { return match (true) { app(SecurityConfirmation::class)->isFresh($user) => 'fresh', $user->hasEnabledTwoFactorAuthentication() => 'two_factor', (string) $user->password !== '' => 'password', default => 'email_code', }; } protected function assertSecurityProof(User $user): void { $confirmation = app(SecurityConfirmation::class); if ($confirmation->isFresh($user)) { return; } if ($user->hasEnabledTwoFactorAuthentication()) { $this->assertSecondFactor($user); } elseif ((string) $user->password !== '') { try { $this->validate(['securityPassword' => ['required', 'string', 'current_password']]); } finally { $this->reset('securityPassword'); } } else { $this->assertEmailCode($user); } $confirmation->markConfirmed($user); } /** * Prüft `securityCode` als TOTP (Fortify-Provider, replay-geschützt) oder * als Wiederherstellungs-Code (wird dabei verbraucht). Fünf Fehlversuche * pro Minute, damit eine Session die sechs Stellen nicht durchprobiert. */ protected function assertSecondFactor(User $user): void { $this->validate(['securityCode' => ['required', 'string']]); $code = trim($this->securityCode); $this->reset('securityCode'); $throttleKey = 'security-proof:'.$user->getKey(); if (RateLimiter::tooManyAttempts($throttleKey, 5)) { throw ValidationException::withMessages([ 'securityCode' => __('Zu viele Fehlversuche. Bitte warten Sie eine Minute.'), ]); } $secret = decrypt($user->two_factor_secret); if (app(TwoFactorAuthenticationProvider::class)->verify($secret, $code)) { RateLimiter::clear($throttleKey); return; } if (in_array($code, $user->recoveryCodes() ?? [], true)) { $user->replaceRecoveryCode($code); RateLimiter::clear($throttleKey); return; } RateLimiter::hit($throttleKey, 60); throw ValidationException::withMessages([ 'securityCode' => __('Der Code ist ungültig. Geben Sie einen aktuellen Code aus Ihrer Authenticator-App oder einen Wiederherstellungs-Code ein.'), ]); } private function assertEmailCode(User $user): void { $this->validate(['securityEmailCode' => ['required', 'string']], [ 'securityEmailCode.required' => __('Bitte fordern Sie einen Bestätigungscode per E-Mail an und geben Sie ihn ein.'), ]); $code = $this->securityEmailCode; $this->reset('securityEmailCode'); if (! app(SecurityConfirmation::class)->verifyEmailCode($user, $code)) { throw ValidationException::withMessages([ 'securityEmailCode' => __('Der Code ist ungültig oder abgelaufen. Bitte fordern Sie einen neuen Code an.'), ]); } $this->securityEmailCodeSent = false; } }