security: Host-Whitelist & Legacy-API-Key-Übergabe härten (H5, M11)
- trustHosts erlaubt nur die konfigurierten Domains (plus localhost/IP für Health-Checks) und verwirft fremde Host-Header, sodass X-Forwarded-Host keine URL-Generierung mehr vergiften kann (H5). - Legacy-API-Keys nur noch per Header (Bearer/X-ApiKey); die alte Query-/Body-Übergabe wird nicht mehr zur Auth akzeptiert, erhält aber weiterhin den 410-Migrationshinweis statt eines opaken 401 (M11). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
000ae93e3b
commit
299837d2ba
9 changed files with 207 additions and 21 deletions
|
|
@ -20,6 +20,8 @@ class LegacyCompanyController extends Controller
|
|||
|
||||
public function store(Request $request): JsonResponse
|
||||
{
|
||||
abort_unless($request->user()->tokenCan('press-releases:write'), 403);
|
||||
|
||||
try {
|
||||
$validated = $request->validate([
|
||||
'company' => ['required', 'array'],
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ class LegacyContactController extends Controller
|
|||
|
||||
public function store(Request $request, int $company): JsonResponse
|
||||
{
|
||||
abort_unless($request->user()->tokenCan('press-releases:write'), 403);
|
||||
|
||||
$company = Company::withoutGlobalScopes()
|
||||
->whereKey($company)
|
||||
->accessibleBy($request->user())
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ use Symfony\Component\HttpFoundation\Response;
|
|||
|
||||
/**
|
||||
* Legacy-API-Kompatibilität (Übernahme bestehender Integrationen,
|
||||
* 06.07.2026): Die Altportale authentifizierten per `X-ApiKey`-Header bzw.
|
||||
* `api_key`-Parameter. Importierte Legacy-Keys (legacy:import-api-keys)
|
||||
* 06.07.2026): Die Altportale authentifizierten per `X-ApiKey`-Header.
|
||||
* Importierte Legacy-Keys (legacy:import-api-keys)
|
||||
* sind gültige Sanctum-Tokens — hier wird die alte Übergabeform in einen
|
||||
* Bearer-Header übersetzt, damit `auth:sanctum` sie versteht. Unbekannte
|
||||
* Keys erhalten weiterhin 410 mit Migrationshinweis.
|
||||
|
|
@ -27,6 +27,14 @@ class RejectLegacyApiKeys
|
|||
$legacyKey = $this->legacyKey($request);
|
||||
|
||||
if ($legacyKey === null) {
|
||||
// Kein Header-Key: ein in Query/Body übergebener Alt-Schlüssel wird
|
||||
// NICHT für die Auth akzeptiert (Zugangsdaten dürfen nicht in URLs/
|
||||
// Logs landen), erhält aber weiterhin den Migrationshinweis statt
|
||||
// eines nichtssagenden 401 — sofern kein regulärer Bearer-Token da ist.
|
||||
if (blank($request->bearerToken()) && $this->hasParameterKey($request)) {
|
||||
return $this->migrationHintResponse();
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
|
|
@ -41,27 +49,40 @@ class RejectLegacyApiKeys
|
|||
return $next($request);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'message' => 'This API key is not valid (anymore). Please generate a token in your customer account.',
|
||||
'migration_url' => url('/customer/tokens'),
|
||||
'docs_url' => url('/docs/api/v1'),
|
||||
], 410);
|
||||
return $this->migrationHintResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy-Schlüssel aus den historischen Übergabeformen: `X-ApiKey`
|
||||
* (dokumentierter Header der Altportale), `X-Api-Key` (verbreitete
|
||||
* Schreibvariante) oder `api_key` als Query-/Body-Parameter.
|
||||
* Legacy-Schlüssel ausschließlich aus Headern lesen. Query- und
|
||||
* Body-Parameter werden bewusst nicht akzeptiert, damit Zugangsdaten
|
||||
* nicht in URLs, Proxy-Logs oder Referrer-Headern landen.
|
||||
*/
|
||||
private function legacyKey(Request $request): ?string
|
||||
{
|
||||
$key = trim((string) (
|
||||
$request->header('X-ApiKey')
|
||||
?: $request->header('X-Api-Key')
|
||||
?: $request->query('api_key')
|
||||
?: $request->input('api_key')
|
||||
));
|
||||
|
||||
return $key === '' ? null : $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft, ob ein Alt-Schlüssel per Query- oder Body-Parameter übergeben
|
||||
* wurde. Wird nur genutzt, um den Migrationshinweis auszulösen — der Wert
|
||||
* wird nie zur Authentifizierung verwendet.
|
||||
*/
|
||||
private function hasParameterKey(Request $request): bool
|
||||
{
|
||||
return filled($request->query('api_key')) || filled($request->input('api_key'));
|
||||
}
|
||||
|
||||
private function migrationHintResponse(): Response
|
||||
{
|
||||
return response()->json([
|
||||
'message' => 'This API key is not valid (anymore). Please generate a token in your customer account and send it as a Bearer token or X-ApiKey header.',
|
||||
'migration_url' => url('/customer/tokens'),
|
||||
'docs_url' => url('/docs/api/v1'),
|
||||
], 410);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue