From 450c88978f626a8e01f2002eb2a35336dadf626d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A0=EC=B0=BD=EC=9A=B1?= Date: Sun, 12 Jul 2026 00:35:41 +0900 Subject: [PATCH] fix(cloud): session-id regeneration on login (fixation) + pristine test output --- cloud/src/Auth/AuthController.php | 2 ++ cloud/src/Auth/Session.php | 3 +++ cloud/src/Ingest/EventValidator.php | 6 +++--- cloud/src/Ingest/ImageProcessor.php | 1 - cloud/tests/ImageProcessorTest.php | 2 +- cloud/tests/IngestControllerTest.php | 2 +- 6 files changed, 10 insertions(+), 6 deletions(-) diff --git a/cloud/src/Auth/AuthController.php b/cloud/src/Auth/AuthController.php index 88fb997..8648bb7 100644 --- a/cloud/src/Auth/AuthController.php +++ b/cloud/src/Auth/AuthController.php @@ -34,6 +34,7 @@ final class AuthController return Response::json(['error' => 'invalid_credentials'], 401); } $this->users->clearFailures((int)$u['id']); + $sess->regenerate(); $sess->set('user_id', (int)$u['id']); $sess->set('username', $u['username']); $sess->set('role', $u['role']); @@ -45,6 +46,7 @@ final class AuthController public function logout(SessionStore $sess): Response { $sess->clear(); + $sess->regenerate(); return Response::json(['ok' => true], 200); } } diff --git a/cloud/src/Auth/Session.php b/cloud/src/Auth/Session.php index 995a7e9..bed9234 100644 --- a/cloud/src/Auth/Session.php +++ b/cloud/src/Auth/Session.php @@ -6,6 +6,7 @@ interface SessionStore { public function get(string $key): mixed; public function set(string $key, mixed $value): void; public function clear(): void; + public function regenerate(): void; } final class ArraySessionStore implements SessionStore { @@ -13,6 +14,7 @@ final class ArraySessionStore implements SessionStore { public function get(string $k): mixed { return $this->d[$k] ?? null; } public function set(string $k, mixed $v): void { $this->d[$k] = $v; } public function clear(): void { $this->d = []; } + public function regenerate(): void { $this->d = []; } } final class PhpSessionStore implements SessionStore { @@ -22,4 +24,5 @@ final class PhpSessionStore implements SessionStore { public function get(string $k): mixed { return $_SESSION[$k] ?? null; } public function set(string $k, mixed $v): void { $_SESSION[$k] = $v; } public function clear(): void { $_SESSION = []; session_destroy(); } + public function regenerate(): void { session_regenerate_id(true); } } diff --git a/cloud/src/Ingest/EventValidator.php b/cloud/src/Ingest/EventValidator.php index 9f26d07..6267f65 100644 --- a/cloud/src/Ingest/EventValidator.php +++ b/cloud/src/Ingest/EventValidator.php @@ -23,9 +23,9 @@ final class EventValidator else foreach ($snaps as $s) if (!is_string($s)) { $errors[] = 'bad:snapshot_item'; break; } // Type guards for required fields - if (!is_string($p['device_id'])) $errors[] = 'bad:device_id'; - if (!is_string($p['camera_id'])) $errors[] = 'bad:camera_id'; - if (!is_numeric($p['occurred_at'])) $errors[] = 'bad:occurred_at'; + if (isset($p['device_id']) && !is_string($p['device_id'])) $errors[] = 'bad:device_id'; + if (isset($p['camera_id']) && !is_string($p['camera_id'])) $errors[] = 'bad:camera_id'; + if (isset($p['occurred_at']) && !is_numeric($p['occurred_at'])) $errors[] = 'bad:occurred_at'; // Type guards for optional fields if (isset($p['track_id']) && !is_numeric($p['track_id'])) $errors[] = 'bad:track_id'; diff --git a/cloud/src/Ingest/ImageProcessor.php b/cloud/src/Ingest/ImageProcessor.php index f2e4fff..c295d4e 100644 --- a/cloud/src/Ingest/ImageProcessor.php +++ b/cloud/src/Ingest/ImageProcessor.php @@ -13,7 +13,6 @@ final class ImageProcessor ob_start(); $ok = imagejpeg($im, null, $quality); $bytes = (string)ob_get_clean(); - imagedestroy($im); if (!$ok || $bytes === '') return ['ok' => false]; return [ 'ok' => true, diff --git a/cloud/tests/ImageProcessorTest.php b/cloud/tests/ImageProcessorTest.php index d21dfef..5da9e0d 100644 --- a/cloud/tests/ImageProcessorTest.php +++ b/cloud/tests/ImageProcessorTest.php @@ -8,7 +8,7 @@ final class ImageProcessorTest extends TestCase { private function tinyJpegBase64(): string { $im = imagecreatetruecolor(8, 8); - ob_start(); imagejpeg($im); $bytes = ob_get_clean(); imagedestroy($im); + ob_start(); imagejpeg($im); $bytes = ob_get_clean(); return base64_encode($bytes); } public function testReencodeProducesJpeg(): void { diff --git a/cloud/tests/IngestControllerTest.php b/cloud/tests/IngestControllerTest.php index 88ab23f..4a3ea7b 100644 --- a/cloud/tests/IngestControllerTest.php +++ b/cloud/tests/IngestControllerTest.php @@ -24,7 +24,7 @@ final class IngestControllerTest extends TestCase $c = new IngestController($cfg, $dev, $nonce, $rl, $events, $snaps); return [$c, $events, $snaps, $nonce]; } - private function jpegB64(): string { $im = imagecreatetruecolor(8, 8); ob_start(); imagejpeg($im); $b = ob_get_clean(); imagedestroy($im); return base64_encode($b); } + private function jpegB64(): string { $im = imagecreatetruecolor(8, 8); ob_start(); imagejpeg($im); $b = ob_get_clean(); return base64_encode($b); } private function signedReq(string $secret, array $payload, string $nonce = 'n1', string $ts = '1000'): Request { $body = json_encode($payload, JSON_UNESCAPED_UNICODE);