Describe the bug
In apps/dashboard-api/src/controllers/dbExport.controller.js, the daily export quota key is incremented and then given a TTL in two separate Redis operations:
const newCount = await redis.incr(key);
if (newCount === 1) {
await redis.expire(key, 86400);
}
If the Node.js process crashes, is restarted, or loses the Redis connection between the INCR and the EXPIRE calls, the key is created but never given an expiry. The counter for that project/day persists indefinitely, permanently blocking the project from making further export requests on that day — or preventing exports forever if the TTL is never applied.
To Reproduce
- Send an export request that creates the counter key for the first time (
newCount === 1).
- Simulate a crash between INCR and EXPIRE (e.g., kill -9 the process, or introduce a mock that throws before EXPIRE).
- Restart the server.
- Send export requests. The key still exists with
value = 1 and no TTL, so the limit check passes normally — but if you kill before EXPIRE on every first request, the key has no TTL.
- Over multiple days the counter accumulates without resetting.
Expected behavior
The increment and TTL assignment should be atomic. Use SET key 1 EX 86400 NX for the first write, then INCR for subsequent ones. Or use the incrWithTtlAtomic helper already present in @urbackend/common:
const newCount = await incrWithTtlAtomic(redis, key, 86400, 1);
if (newCount > maxExports) {
await redis.decr(key);
return next(new AppError(429, `Daily export limit reached...`));
}
Additional context
File: apps/dashboard-api/src/controllers/dbExport.controller.js
Labels: bug, nsoc26, level2
Describe the bug
In
apps/dashboard-api/src/controllers/dbExport.controller.js, the daily export quota key is incremented and then given a TTL in two separate Redis operations:If the Node.js process crashes, is restarted, or loses the Redis connection between the
INCRand theEXPIREcalls, the key is created but never given an expiry. The counter for that project/day persists indefinitely, permanently blocking the project from making further export requests on that day — or preventing exports forever if the TTL is never applied.To Reproduce
newCount === 1).value = 1and no TTL, so the limit check passes normally — but if you kill before EXPIRE on every first request, the key has no TTL.Expected behavior
The increment and TTL assignment should be atomic. Use
SET key 1 EX 86400 NXfor the first write, thenINCRfor subsequent ones. Or use the incrWithTtlAtomic helper already present in@urbackend/common:Additional context
File:
apps/dashboard-api/src/controllers/dbExport.controller.jsLabels:
bug,nsoc26,level2