Merge pull request 'CORS-Preflight beantworten, damit das Projekte-Modal speichern kann' (#31) from fds2610-bot/monitor:main into main
Reviewed-on: https://codeberg.org/fds2610/monitor/pulls/31
This commit is contained in:
commit
40c48ed46a
3 changed files with 41 additions and 0 deletions
|
|
@ -119,6 +119,14 @@ final class Application
|
||||||
|
|
||||||
public function handle(Request $request): Response
|
public function handle(Request $request): Response
|
||||||
{
|
{
|
||||||
|
// CORS-Preflight generisch beantworten, bevor der Router laeuft: OPTIONS ist fuer
|
||||||
|
// keinen Pfad registriert, der Router wuerde also MethodNotAllowedException (405)
|
||||||
|
// werfen. Der Browser sendet den eigentlichen POST/PATCH/DELETE dann gar nicht mehr
|
||||||
|
// und meldet nur einen Netzwerkfehler - genau das brach das Projekte-Modal am PO.
|
||||||
|
if ($request->method === 'OPTIONS') {
|
||||||
|
return Response::preflight();
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$match = $this->router->match($request);
|
$match = $this->router->match($request);
|
||||||
} catch (MethodNotAllowedException) {
|
} catch (MethodNotAllowedException) {
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,15 @@ final class Response
|
||||||
return new self($status, ['error' => $message]);
|
return new self($status, ['error' => $message]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Antwort auf einen CORS-Preflight (OPTIONS): 204 ohne Body, die eigentliche Auskunft
|
||||||
|
* stehen in den Access-Control-Headern aus send().
|
||||||
|
*/
|
||||||
|
public static function preflight(): self
|
||||||
|
{
|
||||||
|
return new self(204, null);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param \Closure $writer - wird von send() mit gesetzten SSE-Headern aufgerufen und
|
* @param \Closure $writer - wird von send() mit gesetzten SSE-Headern aufgerufen und
|
||||||
* schreibt selbst per echo/flush() an den Client (typischerweise eine Polling-Schleife).
|
* schreibt selbst per echo/flush() an den Client (typischerweise eine Polling-Schleife).
|
||||||
|
|
@ -58,6 +67,13 @@ final class Response
|
||||||
// liefern ohnehin nur oeffentliche GET-Daten bzw. verlangen fuer POST/PATCH einen
|
// liefern ohnehin nur oeffentliche GET-Daten bzw. verlangen fuer POST/PATCH einen
|
||||||
// gueltigen API-Key, ein offener Origin ist daher unkritisch.
|
// gueltigen API-Key, ein offener Origin ist daher unkritisch.
|
||||||
header('Access-Control-Allow-Origin: *');
|
header('Access-Control-Allow-Origin: *');
|
||||||
|
// Schreibende Aufrufe schicken X-Admin-Key/X-Api-Key und Content-Type: application/json.
|
||||||
|
// Beides macht aus dem fetch() einen "preflighted request": Der Browser fragt vorher per
|
||||||
|
// OPTIONS nach und bricht mit einem reinen Netzwerkfehler ab, wenn die erlaubten Methoden
|
||||||
|
// und Header fehlen - ohne dass der eigentliche POST je gesendet wird.
|
||||||
|
header('Access-Control-Allow-Methods: GET, POST, PATCH, DELETE, OPTIONS');
|
||||||
|
header('Access-Control-Allow-Headers: Content-Type, X-Admin-Key, X-Api-Key');
|
||||||
|
header('Access-Control-Max-Age: 86400');
|
||||||
|
|
||||||
if ($this->streamWriter !== null) {
|
if ($this->streamWriter !== null) {
|
||||||
http_response_code($this->status);
|
http_response_code($this->status);
|
||||||
|
|
@ -80,6 +96,11 @@ final class Response
|
||||||
}
|
}
|
||||||
|
|
||||||
http_response_code($this->status);
|
http_response_code($this->status);
|
||||||
|
|
||||||
|
if ($this->status === 204) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
header('Content-Type: application/json; charset=utf-8');
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
echo json_encode($this->data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
echo json_encode($this->data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -132,6 +132,18 @@ final class ApplicationTest extends TestCase
|
||||||
self::assertIsArray($response->body()['repos']);
|
self::assertIsArray($response->body()['repos']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der X-Admin-Key-Header macht aus dem fetch() im Projekte-Modal einen preflighted
|
||||||
|
* request. Wird OPTIONS nicht mit 2xx beantwortet, sendet der Browser den POST nie und
|
||||||
|
* das Modal zeigt nur "Netzwerkfehler beim Abrufen der Ressource".
|
||||||
|
*/
|
||||||
|
public function testCorsPreflightOnAdminEndpointSucceeds(): void
|
||||||
|
{
|
||||||
|
$response = $this->app->handle(Request::create('OPTIONS', '/api/v1/repo-configs'));
|
||||||
|
|
||||||
|
self::assertSame(204, $response->status);
|
||||||
|
}
|
||||||
|
|
||||||
public function testCreateRepoConfigRequiresAdminKey(): void
|
public function testCreateRepoConfigRequiresAdminKey(): void
|
||||||
{
|
{
|
||||||
$response = $this->app->handle(Request::create('POST', '/api/v1/repo-configs', body: [
|
$response = $this->app->handle(Request::create('POST', '/api/v1/repo-configs', body: [
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue