Skip to content

Commit 10c78f7

Browse files
committed
Merge branch 'release/42.0.0'
2 parents a0191c8 + 8ed7eaf commit 10c78f7

File tree

13 files changed

+382
-25
lines changed

13 files changed

+382
-25
lines changed

.gitignore

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,32 @@
1-
msi.iws
2-
.idea/
3-
nbproject/
1+
.Build/
42
.DS_Store
53
.DS_Store?
6-
._*
74
.Spotlight-V100
85
.Trashes
9-
ehthumbs.db
10-
Thumbs.db
11-
.svn
6+
._*
7+
.idea/
8+
.php_cs.cache
9+
.phpunit.result.cache
1210
.sass-cache
1311
.sass-cache/
14-
npm-debug.log
15-
dynamicReturnTypeMeta.json
16-
Resources/Private/Build/node_modules/
17-
node_modules/
18-
.Build/
19-
composer.lock
20-
package-lock.json
21-
.php_cs.cache
12+
.svn
13+
/.config/
14+
/.env.local
2215
/.php-cs-fixer.cache
23-
.phpunit.result.cache
24-
var/
25-
/docker-compose.yml
26-
/config/
2716
/.phpunit.cache/
28-
/.env.local
17+
/CLAUDE.md
2918
/Tests/Acceptance/_output/*
3019
/Tests/Acceptance/_support/_generated/
31-
/CLAUDE.md
20+
/config/
21+
/docker-compose.yml
22+
Resources/Private/Build/node_modules/
23+
Thumbs.db
24+
composer.lock
25+
dynamicReturnTypeMeta.json
26+
ehthumbs.db
27+
msi.iws
28+
nbproject/
29+
node_modules/
30+
npm-debug.log
31+
package-lock.json
32+
var/
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
namespace In2code\Lux\Domain\Cache;
5+
6+
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
7+
8+
/**
9+
* Cache layer for rate limiting
10+
*
11+
* Stores request counters per fingerprint with 60-second TTL
12+
* Uses TYPO3 cache framework for automatic expiration and backend flexibility
13+
*/
14+
class RateLimiterCache
15+
{
16+
public const CACHE_KEY = 'luxratelimiter';
17+
public const TTL = 60; // 60 seconds = 1 minute window
18+
19+
protected FrontendInterface $cache;
20+
21+
public function __construct(FrontendInterface $cache)
22+
{
23+
$this->cache = $cache;
24+
}
25+
26+
public function getCount(string $fingerprintHash): int
27+
{
28+
$cacheIdentifier = $this->getCacheIdentifier($fingerprintHash);
29+
$data = $this->cache->get($cacheIdentifier);
30+
return (int)($data['count'] ?? 0);
31+
}
32+
33+
public function incrementAndGet(string $fingerprintHash): int
34+
{
35+
$cacheIdentifier = $this->getCacheIdentifier($fingerprintHash);
36+
$currentCount = $this->getCount($fingerprintHash);
37+
$newCount = $currentCount + 1;
38+
39+
// Store with 60-second TTL
40+
$this->cache->set(
41+
$cacheIdentifier,
42+
['count' => $newCount, 'timestamp' => time()],
43+
[self::CACHE_KEY],
44+
self::TTL
45+
);
46+
47+
return $newCount;
48+
}
49+
50+
/**
51+
* Generate cache identifier from fingerprint hash
52+
* Format: ratelimit_[first-16-chars-of-hash]
53+
*
54+
* @param string $fingerprintHash
55+
* @return string
56+
*/
57+
protected function getCacheIdentifier(string $fingerprintHash): string
58+
{
59+
$shortHash = substr($fingerprintHash, 0, 16);
60+
return 'ratelimit_' . $shortHash;
61+
}
62+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
namespace In2code\Lux\Domain\Tracker;
5+
6+
use In2code\Lux\Domain\Cache\RateLimiterCache;
7+
use In2code\Lux\Events\StopAnyProcessBeforePersistenceEvent;
8+
use In2code\Lux\Exception\RateLimitException;
9+
use In2code\Lux\Utility\ConfigurationUtility;
10+
use Psr\Log\LoggerInterface;
11+
use TYPO3\CMS\Core\Configuration\Exception\ExtensionConfigurationExtensionNotConfiguredException;
12+
use TYPO3\CMS\Core\Configuration\Exception\ExtensionConfigurationPathDoesNotExistException;
13+
14+
class RateLimiter
15+
{
16+
protected RateLimiterCache $cache;
17+
protected LoggerInterface $logger;
18+
19+
public function __construct(
20+
RateLimiterCache $cache,
21+
LoggerInterface $logger
22+
) {
23+
$this->cache = $cache;
24+
$this->logger = $logger;
25+
}
26+
27+
public function __invoke(StopAnyProcessBeforePersistenceEvent $event)
28+
{
29+
if ($this->isRateLimitingEnabled()) {
30+
$fingerprint = $event->getFingerprint();
31+
$fingerprintHash = $fingerprint->getValue();
32+
33+
if ($fingerprintHash !== '') {
34+
$currentCount = $this->cache->incrementAndGet($fingerprintHash);
35+
if ($currentCount > $this->getRateLimit()) {
36+
$this->logRateLimitExceeded($fingerprintHash, $currentCount, $this->getRateLimit());
37+
throw new RateLimitException(
38+
'Rate limit exceeded: ' . $currentCount . ' requests in last minute',
39+
1768214806
40+
);
41+
}
42+
}
43+
}
44+
}
45+
46+
protected function isRateLimitingEnabled(): bool
47+
{
48+
try {
49+
return ConfigurationUtility::isRateLimitingEnabled();
50+
} catch (ExtensionConfigurationExtensionNotConfiguredException | ExtensionConfigurationPathDoesNotExistException $exception) {
51+
return true;
52+
}
53+
}
54+
55+
protected function getRateLimit(): int
56+
{
57+
try {
58+
return ConfigurationUtility::getRateLimitRequestsPerMinute();
59+
} catch (ExtensionConfigurationExtensionNotConfiguredException | ExtensionConfigurationPathDoesNotExistException $exception) {
60+
return ConfigurationUtility::RATE_LIMIT;
61+
}
62+
}
63+
64+
protected function logRateLimitExceeded(string $fingerprintHash, int $currentCount, int $limit): void
65+
{
66+
if (ConfigurationUtility::isExceptionLoggingActivated()) {
67+
$this->logger->warning('Rate limit exceeded', [
68+
'fingerprint' => $fingerprintHash,
69+
'count' => $currentCount,
70+
'limit' => $limit,
71+
'component' => 'RateLimiter',
72+
]);
73+
}
74+
}
75+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
namespace In2code\Lux\Exception;
5+
6+
use Exception;
7+
8+
class RateLimitException extends Exception
9+
{
10+
}

Classes/Utility/ConfigurationUtility.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212

1313
class ConfigurationUtility
1414
{
15+
public const RATE_LIMIT = 20;
16+
1517
/**
1618
* @return string
1719
* @throws ExtensionConfigurationExtensionNotConfiguredException
@@ -217,6 +219,32 @@ public static function isExceptionLoggingActivated(): bool
217219
return ($extensionConfig['enableExceptionLogging'] ?? '0') === '1';
218220
}
219221

222+
/**
223+
* Check if rate limiting is enabled
224+
*
225+
* @return bool
226+
* @throws ExtensionConfigurationExtensionNotConfiguredException
227+
* @throws ExtensionConfigurationPathDoesNotExistException
228+
*/
229+
public static function isRateLimitingEnabled(): bool
230+
{
231+
$extensionConfig = self::getExtensionConfiguration();
232+
return ($extensionConfig['enableRateLimiting'] ?? '1') === '1';
233+
}
234+
235+
/**
236+
* Get rate limit (requests per minute)
237+
*
238+
* @return int
239+
* @throws ExtensionConfigurationExtensionNotConfiguredException
240+
* @throws ExtensionConfigurationPathDoesNotExistException
241+
*/
242+
public static function getRateLimitRequestsPerMinute(): int
243+
{
244+
$extensionConfig = self::getExtensionConfiguration();
245+
return (int)($extensionConfig['rateLimitRequestsPerMinute'] ?? self::RATE_LIMIT);
246+
}
247+
220248
/**
221249
* @return bool
222250
* @throws ExtensionConfigurationExtensionNotConfiguredException

Configuration/Services.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,16 @@ services:
1717
arguments:
1818
$cache: '@cache.luxcachelayer'
1919

20+
cache.luxratelimiter:
21+
class: TYPO3\CMS\Core\Cache\Frontend\FrontendInterface
22+
factory: [ '@TYPO3\CMS\Core\Cache\CacheManager', 'getCache' ]
23+
arguments: [ 'luxratelimiter' ]
24+
25+
In2code\Lux\Domain\Cache\RateLimiterCache:
26+
public: true
27+
arguments:
28+
$cache: '@cache.luxratelimiter'
29+
2030
In2code\Lux\Command\LuxAnonymizeCommand:
2131
tags:
2232
- name: 'console.command'
@@ -243,6 +253,12 @@ services:
243253
identifier: 'lux/stopTracking'
244254
event: In2code\Lux\Events\StopAnyProcessBeforePersistenceEvent
245255

256+
In2code\Lux\Domain\Tracker\RateLimiter:
257+
tags:
258+
- name: 'event.listener'
259+
identifier: 'lux/rateLimiter'
260+
event: In2code\Lux\Events\StopAnyProcessBeforePersistenceEvent
261+
246262
In2code\Lux\Domain\Tracker\UtmTracker:
247263
public: true
248264
tags:

Documentation/Technical/Changelog/Index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
| Version | Date | State | TYPO3 | Description |
1111
|------------|------------|----------|---------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
12+
| 42.0.0 | 2026-01-12 | Task | `12.4 + 13.4` | Add ratelimit functionality against bot request flooding |
1213
| 41.1.1 | 2026-01-06 | Bugfix | `12.4 + 13.4` | Fix pagebrowser on some detail views in backend, don't create pagevisit entries for non-existing pages |
1314
| 41.1.0 | 2025-12-23 | Task | `12.4 + 13.4` | CSS update for a better campaign layout |
1415
| 41.0.0 | 2025-12-18 | Task | `12.4 + 13.4` | IP services were replaced (iplist.cc + ipapi.com with ipapi.is + ipapi.co) see for more details [IpAddresses.md](../../Privacy/IpAddresses.md) |

Documentation/Technical/Installation/Index.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Example composer.json file:
2020
"require": {
2121
"php": "^8.1",
2222
"typo3/cms": "^13.4",
23-
"in2code/lux": "^38.0",
23+
"in2code/lux": "^42.0",
2424
}
2525
}
2626
```
@@ -66,6 +66,8 @@ If you click on the settings symbol for extension lux, you can change some basic
6666
| Advanced: Lead picture | Decide if TYPO3 should try to find an image of a lead by searching on gravatar.com (with hashed email) or on bing image search by given email domain (not full address). |
6767
| Advanced: Show render time | For an easier debugging all views in backend can be shown with render times. This is only visible for backend administrators. |
6868
| Advanced: Use cache layer | If you are facing performance issues with lux backend modules or with the page overview view (quick analysis), you can cache views (e.g. for 24h) when turning the feature on. In addition there is a command that can be executed via scheduler task to warmup caches (e.g. every night). |
69+
| Advanced: Enable rate limiting | Protect tracking endpoints from abuse by limiting requests per fingerprint. Recommended to keep enabled. |
70+
| Advanced: Rate limit requests per minute | Maximum number of tracking requests allowed per fingerprint per minute. Default is 100. |
6971

7072
#### 3. Add TypoScript
7173

Makefile

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,17 @@ composer-install:
7373
mkdir -p $(SQLDUMPSDIR)
7474
mkdir -p $(WEBROOT)/$(TYPO3_CACHE_DIR)
7575

76+
## Create .config/claude-code directory and download latest claude-code-instructions
77+
.create-claude-config-dir:
78+
echo "$(EMOJI_dividers) Creating .config/claude-code directory"
79+
mkdir -p .config/claude-code
80+
echo "$(EMOJI_receive) Downloading latest claude-code-instructions from GitHub"
81+
curl -L https://github.com/in2code-de/claude-code-instructions/archive/refs/heads/main.zip -o /tmp/claude-code-instructions.zip
82+
unzip -o /tmp/claude-code-instructions.zip -d /tmp/
83+
cp -r /tmp/claude-code-instructions-main/* .config/claude-code/
84+
rm -rf /tmp/claude-code-instructions.zip /tmp/claude-code-instructions-main
85+
echo "$(EMOJI_thumbsup) Claude Code instructions installed"
86+
7687
## Install mkcert on this computer, skips installation if already present
7788
.install-mkcert:
7889
if [[ "$$OSTYPE" == "linux-gnu" ]]; then \
@@ -174,7 +185,7 @@ typo3-clearcache:
174185
tar xvfz ../../.project/data/fileadmin.tar.gz
175186

176187
## To start an existing project incl. rsync from fileadmin, uploads and database dump
177-
install-project: .lfs-fetch .link-compose-file destroy .add-hosts-entry .init-docker .fix-mount-perms composer-install .typo3-add-site .typo3-add-dockerconfig .provision-fileadmin mysql-restore typo3-comparedb typo3-clearcache .typo3-setupinstall lux-demodata
188+
install-project: .lfs-fetch .link-compose-file destroy .add-hosts-entry .init-docker .fix-mount-perms .create-claude-config-dir composer-install .typo3-add-site .typo3-add-dockerconfig .provision-fileadmin mysql-restore typo3-comparedb typo3-clearcache .typo3-setupinstall lux-demodata
178189
echo "---------------------"
179190
echo ""
180191
echo "The project is online $(EMOJI_thumbsup)"

0 commit comments

Comments
 (0)