feat: add the API key check and settings for the chatbot content feed

This commit is contained in:
fromme committed 2026-10-08 09:35:55 +02:00
1 parent 41d188a8cc
commit b4a9156df6
3 files changed
+125

No files matched your search

@@ -0,0 +1,65 @@
<?php
namespace EP\EpProducts\ContentFeed;
use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* The chatbot content feed's extension configuration.
*
* Missing keys fall back to "feed is on, but covers nothing": a key added to ext_conf_template.txt
* is absent until the extension configuration is saved in the backend, and an empty feed is
* refused by the consumer rather than read as "everything was deleted".
*/
class ContentFeedConfiguration
{
/**
* @var array
*/
private $configuration;
public function __construct(ExtensionConfiguration $extensionConfiguration)
{
$this->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<string, int> 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;
}
}
@@ -0,0 +1,45 @@
<?php
namespace EP\EpProducts\Http;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
/**
* Checks the shared key of the CMS backchannel endpoints (content feed, CMS index).
*
* An empty or missing `cmsApiKey` denies every request. ExtensionConfiguration::get() returns the
* stored configuration verbatim, so a newly added key is absent until someone saves the extension
* configuration in the backend - defaulting to "allow" there would publish the whole catalogue.
*/
class ApiKeyAuthenticator
{
/**
* @var ExtensionConfiguration
*/
private $extensionConfiguration;
public function __construct(ExtensionConfiguration $extensionConfiguration)
{
$this->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);
}
}
@@ -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