getKey(); if (RateLimiter::tooManyAttempts($throttleKey, 3)) { return false; } RateLimiter::hit($throttleKey, 600); $expiresAt = now()->addMinutes(self::EXPIRES_IN_MINUTES); Session::put(self::SESSION_KEY, [ 'user_id' => $user->getKey(), 'provider' => $provider, 'provider_id' => $providerId, 'expires_at' => $expiresAt->getTimestamp(), ]); $confirmUrl = URL::temporarySignedRoute('oauth.google.confirm-link', $expiresAt, [ 'user' => $user->getKey(), 'provider_id' => $providerId, ]); Mail::to($user->email)->queue((new SocialAccountLinkConfirmation( user: $user, confirmUrl: $confirmUrl, expiresAt: $expiresAt->format('H:i'), ))->afterCommit()); return true; } /** * Speichert die vorgemerkte Verknüpfung, wenn sie zu dieser Session, * diesem Konto und dieser Provider-Identität gehört. */ public function confirm(User $user, string $providerId): bool { $pending = Session::pull(self::SESSION_KEY); if (! is_array($pending) || (int) ($pending['user_id'] ?? 0) !== (int) $user->getKey() || ! hash_equals((string) ($pending['provider_id'] ?? ''), $providerId) || (int) ($pending['expires_at'] ?? 0) < now()->getTimestamp()) { return false; } if (filled($user->oauth_provider_id)) { return hash_equals((string) $user->oauth_provider_id, $providerId); } $this->social->link($user, (string) $pending['provider'], $providerId); return true; } }