auth/security: API-Firmenzuordnung nur für Mitglieder oder Allowlist, Google-Vorabübernahme geschlossen, gemeinsamer Login-Abschluss (Umsetzungsaudit 29.09.: R-01, K-04, K-07, K-08, B-10)

- 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>
This commit is contained in:
Kevin Adametz 2026-10-02 13:09:00 +00:00
parent 3dbc3fa673
commit 1ccb4a8479
29 changed files with 1287 additions and 73 deletions

View file

@ -0,0 +1,46 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
/**
* Umsetzungsaudit 29.09.2026, B-10: eigener Zweck für den Link „Bild
* bestätigen". Ein späterer Login-Link entwertet nur Links mit Zweck
* `login`; der Bestätigungslink bleibt bis zu seinem Ablauf gültig.
* Der Wert wird ans Ende der Liste angehängt (MySQL: INPLACE, ohne Kopie).
*/
return new class extends Migration
{
public function up(): void
{
Schema::table('magic_links', function (Blueprint $table) {
$table->enum('purpose', [
'login',
'press_release_access',
'invoice_access',
'initial_setup',
'press_release_report',
'image_confirmation',
])->default('login')->change();
});
}
public function down(): void
{
// Links des entfallenden Zwecks zuerst räumen, sonst scheitert die
// Rückverengung des Enums an bestehenden Zeilen.
DB::table('magic_links')->where('purpose', 'image_confirmation')->delete();
Schema::table('magic_links', function (Blueprint $table) {
$table->enum('purpose', [
'login',
'press_release_access',
'invoice_access',
'initial_setup',
'press_release_report',
])->default('login')->change();
});
}
};