newsroom: Verwaltungszugang „Diesen Newsroom verwalten" (Roadmap 1b.2)
Neue Seite /newsroom/{slug}/verwalten auf den Web-Portalen: schickt den
Anmeldelink an die hinterlegte Firmen-E-Mail (sonst Pressekontakte), zeigt
sie nur maskiert, und funktioniert auch für Firmen ohne veröffentlichte
Meldung. Konto, Zuordnung und Login laufen durch ContactAccessService —
neu dort: Herkunft (profile_claim auf dem Token) und Ziel-Firma, auf deren
Kundenseite der Login landet. 410-entfernte bleiben Tombstone, aufgegangene
Slugs führen zum Überlebenden (MergedNewsroomLookup, geteilt mit der
Newsroom-Seite). 3 Links je Newsroom und Tag, 15 je IP und Stunde.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a3e0042055
commit
0c1c072487
15 changed files with 665 additions and 37 deletions
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace App\Services\Auth;
|
||||
|
||||
use App\Enums\ConversionSource;
|
||||
use App\Mail\MagicLoginLink;
|
||||
use App\Models\Company;
|
||||
use App\Models\Contact;
|
||||
|
|
@ -41,7 +42,7 @@ class ContactAccessService
|
|||
* - Mehrfach hinterlegt → ein Konto, ALLE Firmen/Kontakte zugeordnet.
|
||||
* - Deaktiviertes Bestandskonto / unbekannte E-Mail → keine Aktion.
|
||||
*/
|
||||
public function requestMagicAccess(string $email, ?string $ip = null): bool
|
||||
public function requestMagicAccess(string $email, ?string $ip = null, ?ConversionSource $source = null, ?int $landingCompanyId = null): bool
|
||||
{
|
||||
$email = mb_strtolower(trim($email));
|
||||
$contacts = $this->contactsFor($email);
|
||||
|
|
@ -60,7 +61,7 @@ class ContactAccessService
|
|||
}
|
||||
|
||||
$this->linkMemberships($user, $this->companyIdsFrom($contacts, $companies), $contacts);
|
||||
$this->sendLoginLink($user, $ip);
|
||||
$this->sendLoginLink($user, $ip, $source, $landingCompanyId);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -199,9 +200,14 @@ class ContactAccessService
|
|||
/**
|
||||
* Versendet einen regulären Login-Magic-Link an den Account.
|
||||
*/
|
||||
private function sendLoginLink(User $user, ?string $ip): void
|
||||
private function sendLoginLink(User $user, ?string $ip, ?ConversionSource $source = null, ?int $landingCompanyId = null): void
|
||||
{
|
||||
$generated = $this->magicLinks->createLoginLink($user, $ip);
|
||||
$generated = $this->magicLinks->createLoginLink(
|
||||
$user,
|
||||
$ip,
|
||||
source: $source,
|
||||
payload: $landingCompanyId !== null ? ['company_id' => $landingCompanyId] : [],
|
||||
);
|
||||
|
||||
Mail::to($user->email)->queue((new MagicLoginLink(
|
||||
user: $user,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ use Illuminate\Support\Str;
|
|||
class MagicLinkGenerator
|
||||
{
|
||||
/**
|
||||
* @param array<string, mixed> $payload z. B. `company_id`, auf dessen Firmenseite der Login landen soll (Roadmap 1b.2)
|
||||
* @return array{magic_link: MagicLink, plain_token: string, expires_at: Carbon}
|
||||
*/
|
||||
public function createLoginLink(
|
||||
|
|
@ -19,6 +20,7 @@ class MagicLinkGenerator
|
|||
?string $requestedIp = null,
|
||||
int $ttlMinutes = 15,
|
||||
?ConversionSource $source = null,
|
||||
array $payload = [],
|
||||
): array {
|
||||
$plainToken = Str::random(64);
|
||||
$tokenHash = hash('sha256', $plainToken);
|
||||
|
|
@ -40,6 +42,7 @@ class MagicLinkGenerator
|
|||
// führt über zwei Domains und einen Mailclient, die Sitzung reißt
|
||||
// dabei genau an der interessanten Stelle (Roadmap 0.4).
|
||||
'source' => $source?->value,
|
||||
'payload' => $payload ?: null,
|
||||
'expires_at' => $expiresAt,
|
||||
'ip_requested' => $requestedIp,
|
||||
]);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue