WS-6: Google-Login via Laravel Socialite
- Socialite installiert; oauth_provider/oauth_provider_id an users (Migration). - GoogleController (redirect/callback) + SocialAuthService: De-Dup über E-Mail, neuer User aktiv + verifiziert + customer (Verifizierung über den Google- Kanal), offener Selbst-Registrierer wird onboardet, deaktivierter Account wird NICHT reaktiviert. Abschluss über die gemeinsame LoginRedirect-Logik (rollengerecht, 403-sicher). - Routen /auth/google/redirect + /auth/google/callback (guest), "Mit Google anmelden/registrieren"-Buttons auf Login und Register. - config/services.php google + .env.example-Keys; Sicherheits-/Deployment-Doku ergänzt (Keys, Redirect-URI, Migration). Tests: neuer User, De-Dup bestehender User, deaktivierter Account blockiert, unverifizierter Registrierer onboardet, fehlgeschlagener Callback. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
ae79d5bee4
commit
068a5a4b49
13 changed files with 715 additions and 1 deletions
62
app/Http/Controllers/Auth/GoogleController.php
Normal file
62
app/Http/Controllers/Auth/GoogleController.php
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\Auth\SocialAuthService;
|
||||
use App\Support\LoginRedirect;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Laravel\Socialite\Facades\Socialite;
|
||||
|
||||
/**
|
||||
* Google-Login (WS-6). Erst-Login legt – de-dupliziert über die E-Mail – einen
|
||||
* aktiven, verifizierten customer-Account an; bestehende Accounts werden nur
|
||||
* verknüpft. Deaktivierte Accounts werden nicht reaktiviert.
|
||||
*/
|
||||
class GoogleController extends Controller
|
||||
{
|
||||
public function redirect(): RedirectResponse
|
||||
{
|
||||
return Socialite::driver('google')->redirect();
|
||||
}
|
||||
|
||||
public function callback(Request $request, SocialAuthService $social): RedirectResponse
|
||||
{
|
||||
try {
|
||||
$googleUser = Socialite::driver('google')->user();
|
||||
} catch (\Throwable) {
|
||||
return redirect()->route('login')->withErrors([
|
||||
'email' => __('Die Anmeldung mit Google ist fehlgeschlagen. Bitte versuchen Sie es erneut.'),
|
||||
]);
|
||||
}
|
||||
|
||||
$user = $social->resolveUser(
|
||||
'google',
|
||||
(string) $googleUser->getId(),
|
||||
$googleUser->getEmail(),
|
||||
$googleUser->getName(),
|
||||
);
|
||||
|
||||
if (! $user || ! $user->is_active) {
|
||||
return redirect()->route('login')->withErrors([
|
||||
'email' => __('Ihr Konto ist nicht aktiv. Bitte wenden Sie sich an den Support.'),
|
||||
]);
|
||||
}
|
||||
|
||||
Auth::login($user, remember: true);
|
||||
$request->session()->regenerate();
|
||||
|
||||
$user->forceFill([
|
||||
'last_login_at' => now(),
|
||||
'last_login_ip' => $request->ip(),
|
||||
])->save();
|
||||
|
||||
return redirect(LoginRedirect::safeTarget(
|
||||
$user,
|
||||
$request->session()->pull('url.intended'),
|
||||
LoginRedirect::homeFor($user),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
@ -45,6 +45,8 @@ class User extends Authenticatable implements MustVerifyEmail
|
|||
'legacy_portal',
|
||||
'legacy_id',
|
||||
'password',
|
||||
'oauth_provider',
|
||||
'oauth_provider_id',
|
||||
'press_release_quota_used_this_month',
|
||||
];
|
||||
|
||||
|
|
|
|||
83
app/Services/Auth/SocialAuthService.php
Normal file
83
app/Services/Auth/SocialAuthService.php
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services\Auth;
|
||||
|
||||
use App\Enums\RegistrationType;
|
||||
use App\Models\User;
|
||||
|
||||
/**
|
||||
* Auflösung eines Social-Logins (Google, ggf. weitere) auf einen lokalen User.
|
||||
*
|
||||
* Identität ist die E-Mail (De-Dup darüber, keine Dubletten). Der Provider
|
||||
* bestätigt die E-Mail, daher gilt die Verifizierung über den Kanal als erfüllt
|
||||
* (Entscheidung 15.06.) – kein zusätzlicher E-Mail-Verifizierungsschritt.
|
||||
*/
|
||||
class SocialAuthService
|
||||
{
|
||||
public function __construct(private readonly UserRolePermissionSyncService $roleSync) {}
|
||||
|
||||
/**
|
||||
* Liefert den (ggf. neu angelegten) User zur Social-Identität. Ein
|
||||
* deaktivierter Bestands-Account wird NICHT reaktiviert – der Aufrufer prüft
|
||||
* danach is_active und blockiert.
|
||||
*/
|
||||
public function resolveUser(string $provider, string $providerId, ?string $email, ?string $name): ?User
|
||||
{
|
||||
$email = $email ? mb_strtolower(trim($email)) : null;
|
||||
|
||||
if ($email === null || $email === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$user = User::query()->whereRaw('LOWER(email) = ?', [$email])->first();
|
||||
|
||||
if (! $user) {
|
||||
return $this->createUser($provider, $providerId, $email, $name);
|
||||
}
|
||||
|
||||
$user->forceFill([
|
||||
'oauth_provider' => $provider,
|
||||
'oauth_provider_id' => $providerId,
|
||||
]);
|
||||
|
||||
// Noch nicht verifiziert (offener Selbst-Registrierer): der Provider
|
||||
// bestätigt die E-Mail → Onboarding abschließen (aktiv + customer).
|
||||
if ($user->email_verified_at === null) {
|
||||
$user->forceFill([
|
||||
'email_verified_at' => now(),
|
||||
'is_active' => true,
|
||||
])->save();
|
||||
|
||||
if ($user->roles()->doesntExist()) {
|
||||
$this->roleSync->assignRoleAndSyncPermissions($user, 'customer');
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
// Bereits verifiziert: nur Provider verknüpfen. is_active bleibt
|
||||
// unangetastet (deaktivierte Accounts werden nicht reaktiviert).
|
||||
$user->save();
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
private function createUser(string $provider, string $providerId, string $email, ?string $name): User
|
||||
{
|
||||
$name = $name !== null ? trim($name) : '';
|
||||
|
||||
$user = User::create([
|
||||
'name' => $name !== '' ? $name : $email,
|
||||
'email' => $email,
|
||||
'registration_type' => RegistrationType::Company->value,
|
||||
'is_active' => true,
|
||||
'oauth_provider' => $provider,
|
||||
'oauth_provider_id' => $providerId,
|
||||
]);
|
||||
|
||||
$user->forceFill(['email_verified_at' => now()])->save();
|
||||
$this->roleSync->assignRoleAndSyncPermissions($user, 'customer');
|
||||
|
||||
return $user;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue