- R-01: ApiNewsroomAssignment hängt an eine bestehende Firma nur noch Owner, Mitglieder oder Nutzer aus NEWSROOM_API_ASSIGNMENT_USER_IDS; Pivot mit granted_via = api; Befehl company-memberships:api-assignments (Liste, --remove mit Vorschau) - K-04: OAuth-Verifikation eines offenen Kontos verwirft Passwort, remember_token und Sanctum-Tokens; SessionRevocation + Middleware EnsureSessionIsNotRevoked widerruft alte Sessions (Redis-tauglich) - K-08: LoginCompletion für Passwort, Magic Link und Google — last_login_at und Anmeldemethode auch nach 2FA, Landeziel über die Challenge, Google ohne erzwungenes remember, magic-login ohne guest für eigene Links - B-10: eigener Magic-Link-Zweck image_confirmation (Migration) - K-07: legalAcceptanceVersions #[Locked], Versionswechsel beim Absenden wird abgelehnt, Gate-Abfragen je Request zwischengespeichert Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
93 lines
3.1 KiB
PHP
93 lines
3.1 KiB
PHP
<?php
|
||
|
||
namespace App\Services\Auth;
|
||
|
||
use App\Models\User;
|
||
use Illuminate\Support\Str;
|
||
|
||
/**
|
||
* 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,
|
||
private readonly CustomerAccountProvisioner $accountProvisioner,
|
||
private readonly SessionRevocation $sessions,
|
||
) {}
|
||
|
||
/**
|
||
* 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::findByEmail($email);
|
||
|
||
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).
|
||
//
|
||
// Vorab-Übernahme (Audit K-04): Wer die Adresse unverifiziert
|
||
// registriert hat, war nicht zwingend der Inhaber des Postfachs. Alles,
|
||
// was er dabei gesetzt hat, verfällt: Passwort, Remember-Cookie,
|
||
// API-Tokens und offene Sessions. Der Inhaber meldet sich per Google
|
||
// oder Magic Link an und setzt bei Bedarf selbst ein Passwort.
|
||
if ($user->email_verified_at === null) {
|
||
$user->forceFill([
|
||
'email_verified_at' => now(),
|
||
'is_active' => true,
|
||
'password' => null,
|
||
]);
|
||
$user->setRememberToken(Str::random(60));
|
||
$user->save();
|
||
|
||
$user->tokens()->delete();
|
||
$this->sessions->revokeAllFor($user);
|
||
|
||
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
|
||
{
|
||
return $this->accountProvisioner->provisionVerifiedCustomer(
|
||
$email,
|
||
$name !== null ? trim($name) : '',
|
||
[
|
||
'oauth_provider' => $provider,
|
||
'oauth_provider_id' => $providerId,
|
||
],
|
||
channel: $provider,
|
||
);
|
||
}
|
||
}
|