cache = $cacheFactory->createLocking('whiteboard_sync'); } #[NoAdminRequired] #[NoCSRFRequired] #[PublicPage] public function show(int $fileId): DataResponse { try { $jwt = $this->getJwtFromRequest(); $userId = $this->jwtService->getUserIdFromJWT($jwt); $user = $this->getUserFromIdServiceFactory->create($userId)->getUser(); $file = $this->getFileServiceFactory->create($user, $fileId)->getFile(); $data = $this->contentService->getContent($file); return new DataResponse(['data' => $data]); } catch (Exception $e) { return $this->exceptionService->handleException($e); } } #[NoAdminRequired] #[NoCSRFRequired] #[PublicPage] public function update(int $fileId, array $data): DataResponse { $lockKey = "sync_lock_{$fileId}"; $lockValue = uniqid(); $lockTTL = 5; // 5 seconds // Simple distributed lock if (!$this->cache->add($lockKey, $lockValue, $lockTTL)) { return new DataResponse(['status' => 'conflict'], 409); } try { $jwt = $this->getJwtFromRequest(); $userId = $this->jwtService->getUserIdFromJWT($jwt); $user = $this->getUserFromIdServiceFactory->create($userId)->getUser(); $file = $this->getFileServiceFactory->create($user, $fileId)->getFile(); $this->contentService->updateContent($file, $data); return new DataResponse(['status' => 'success']); } catch (Exception $e) { $this->logger->error('Error syncing whiteboard data: ' . $e->getMessage()); return $this->exceptionService->handleException($e); } finally { if ($this->cache->get($lockKey) === $lockValue) { $this->cache->remove($lockKey); } } } #[NoAdminRequired] #[NoCSRFRequired] #[PublicPage] public function getLib(): DataResponse { try { $jwt = $this->getJwtFromRequest(); $this->jwtService->getUserIdFromJWT($jwt); $data = $this->libraryService->getUserLib(); return new DataResponse(['data' => $data]); } catch (Exception $e) { return $this->exceptionService->handleException($e); } } #[NoAdminRequired] #[NoCSRFRequired] #[PublicPage] public function updateLib(): DataResponse { try { $jwt = $this->getJwtFromRequest(); $userId = $this->jwtService->getUserIdFromJWT($jwt); $items = $this->request->getParam('items', []); $this->libraryService->updateUserLib($userId, $items); return new DataResponse(['status' => 'success']); } catch (Exception $e) { return $this->exceptionService->handleException($e); } } private function getJwtFromRequest(): string { $authHeader = $this->request->getHeader('Authorization'); if (sscanf($authHeader, 'Bearer %s', $jwt) !== 1) { $this->logger->error('Invalid JWT format in Authorization header'); throw new UnauthorizedException(); } return (string)$jwt; } private function getUserIdFromRequest(): string { return $this->request->getHeader('X-Whiteboard-User'); } private function validateBackendSharedToken(int $fileId): void { $backendSharedToken = $this->request->getHeader('X-Whiteboard-Auth'); if (!$backendSharedToken || !$this->verifySharedToken($backendSharedToken, $fileId)) { $this->logger->error('Invalid backend shared token', [ 'file_id' => $fileId, 'token_present' => !empty($backendSharedToken) ]); throw new InvalidUserException('Invalid backend shared token'); } } private function verifySharedToken(string $token, int $fileId): bool { [$roomId, $timestamp, $signature] = explode(':', $token); if ($roomId !== (string)$fileId) { return false; } $sharedSecret = $this->configService->getWhiteboardSharedSecret(); $payload = "$roomId:$timestamp"; $expectedSignature = hash_hmac('sha256', $payload, $sharedSecret); return hash_equals($expectedSignature, $signature); } }