From b4a9156df61dd24a2572add456ef77f86238b185 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Fromme?= Date: Thu, 8 Oct 2026 09:08:46 +0200 Subject: [PATCH] feat: add the API key check and settings for the chatbot content feed --- .../ContentFeed/ContentFeedConfiguration.php | 65 +++++++++++++++++++ .../Classes/Http/ApiKeyAuthenticator.php | 45 +++++++++++++ .../ext/ep_products/ext_conf_template.txt | 15 +++++ 3 files changed, 125 insertions(+) create mode 100644 public/typo3conf/ext/ep_products/Classes/ContentFeed/ContentFeedConfiguration.php create mode 100644 public/typo3conf/ext/ep_products/Classes/Http/ApiKeyAuthenticator.php diff --git a/public/typo3conf/ext/ep_products/Classes/ContentFeed/ContentFeedConfiguration.php b/public/typo3conf/ext/ep_products/Classes/ContentFeed/ContentFeedConfiguration.php new file mode 100644 index 00000000..7aa5b486 --- /dev/null +++ b/public/typo3conf/ext/ep_products/Classes/ContentFeed/ContentFeedConfiguration.php @@ -0,0 +1,65 @@ +configuration = (array) $extensionConfiguration->get('ep_products'); + } + + public function isEnabled(): bool + { + return (bool) ($this->configuration['contentFeedEnabled'] ?? true); + } + + /** + * @return string[] site identifiers + */ + public function getSites(): array + { + return GeneralUtility::trimExplode(',', (string) ($this->configuration['contentFeedSites'] ?? ''), true); + } + + /** + * @return int[] page uids whose whole subtree stays out of the feed + */ + public function getExcludedPages(): array + { + return GeneralUtility::intExplode(',', (string) ($this->configuration['contentFeedExcludedPages'] ?? ''), true); + } + + /** + * @return array site identifier => news detail page uid + */ + public function getNewsDetailPages(): array + { + $pages = []; + + foreach (GeneralUtility::trimExplode(',', (string) ($this->configuration['contentFeedNewsDetailPages'] ?? ''), true) as $entry) { + $parts = GeneralUtility::trimExplode(':', $entry, true); + + if (2 === count($parts) && (int) $parts[1] > 0) { + $pages[$parts[0]] = (int) $parts[1]; + } + } + + return $pages; + } +} diff --git a/public/typo3conf/ext/ep_products/Classes/Http/ApiKeyAuthenticator.php b/public/typo3conf/ext/ep_products/Classes/Http/ApiKeyAuthenticator.php new file mode 100644 index 00000000..3d87c03a --- /dev/null +++ b/public/typo3conf/ext/ep_products/Classes/Http/ApiKeyAuthenticator.php @@ -0,0 +1,45 @@ +extensionConfiguration = $extensionConfiguration; + } + + public function isAuthorized(ServerRequestInterface $request): bool + { + $configured = (string) ($this->extensionConfiguration->get('ep_products')['cmsApiKey'] ?? ''); + + if ('' === $configured) { + return false; + } + + // Header first: query-string keys end up in access logs. `key` stays accepted for parity + // with the existing /api/hotel and /api/product calls. + $provided = $request->getHeaderLine('X-Api-Key'); + + if ('' === $provided) { + $provided = (string) ($request->getQueryParams()['key'] ?? ''); + } + + return hash_equals($configured, $provided); + } +} diff --git a/public/typo3conf/ext/ep_products/ext_conf_template.txt b/public/typo3conf/ext/ep_products/ext_conf_template.txt index 011ad451..428a16a2 100644 --- a/public/typo3conf/ext/ep_products/ext_conf_template.txt +++ b/public/typo3conf/ext/ep_products/ext_conf_template.txt @@ -75,3 +75,18 @@ productImportSftpHostKey = # cat=SftpImport; type=int+; label=SFTP connection timeout in seconds productImportSftpTimeout = 15 + +# cat=ContentFeed; type=string; label=API key for the CMS backchannel endpoints (chatbot content feed, CMS index); sent as X-Api-Key. Empty denies every request. +cmsApiKey = + +# cat=ContentFeed; type=boolean; label=Serve the chatbot content feed (off: all feed endpoints answer 503) +contentFeedEnabled = 1 + +# cat=ContentFeed; type=string; label=Site identifiers included in the chatbot content feed (comma-separated) +contentFeedSites = ep-reisen,ser,ski-boarderweek,ski-wochenenden,snowzone + +# cat=ContentFeed; type=string; label=Page uids whose subtrees stay out of the chatbot content feed (comma-separated; 634 = group booking links) +contentFeedExcludedPages = 634 + +# cat=ContentFeed; type=string; label=News detail page per site for the chatbot content feed (identifier:pageUid, comma-separated) +contentFeedNewsDetailPages = ep-reisen:1392