fix(cycle): raise the calendar feed fetch limits

A link subscribed on a phone, a tablet and a computer at a five-minute
refresh came close to the 30 fetches an hour a link was allowed, and a
household behind one address shared 120. The per-link bucket is now 120
an hour and the per-address one 600. The token is a random secret checked
by its HMAC, so guessing links is not what these limits stop.
This commit is contained in:
Marc-André Bombeck committed 2026-11-07 01:33:19 +01:00
1 parent 78611277fe
commit aea9796170
3 files changed
+14 -8

No files matched your search

+2 -2
View File
@@ -145,7 +145,7 @@ describe("serveCycleFeed", () => {
expect(limits.surface).toHaveBeenCalledWith(
expect.anything(),
"cycle-feed",
120,
600,
3_600_000,
);
});
@@ -156,7 +156,7 @@ describe("serveCycleFeed", () => {
expect(res.status).toBe(429);
expect(limits.token).toHaveBeenCalledWith(
`cycle-feed:${hashToken(TOKEN)}`,
30,
120,
3_600_000,
);
expect(built.build).not.toHaveBeenCalled();
+11 -5
View File
@@ -56,12 +56,18 @@ import { parseFeedTokenSegment } from "./token";
/**
* Per source address, across every feed. Generous because the big calendar
* providers poll from a pool of addresses that churns; the per-token bucket
* below is the one that bounds a single link.
* providers poll from a pool of addresses that churns, and a household behind
* one address may hold several links on several devices; the per-token bucket
* below is the one that bounds a single link. The token is a random secret
* checked by its HMAC, so guessing links is not what these limits stop.
*/
const SURFACE_LIMIT_PER_HOUR = 120;
/** Per link. A provider polling every few minutes still fits. */
const TOKEN_LIMIT_PER_HOUR = 30;
const SURFACE_LIMIT_PER_HOUR = 600;
/**
* Per link. One link subscribed on a phone, a tablet and a computer, each
* refreshing every five minutes, is 36 fetches an hour; 120 leaves room for
* a fourth device and for retries.
*/
const TOKEN_LIMIT_PER_HOUR = 120;
const HOUR_MS = 60 * 60 * 1000;
/** The longest User-Agent kept for the status read. */
@@ -510,7 +510,7 @@ describe("fetching the feed", () => {
it("throttles a well-formed unknown token through the anonymous limit", async () => {
let throttled = false;
for (let i = 0; i < 130 && !throttled; i++) {
for (let i = 0; i < 610 && !throttled; i++) {
const res = await fetchFeed(`hlf_${"1".repeat(48)}.ics`);
if (res.status === 429) throttled = true;
else expect(res.status).toBe(404);