Skip to content

Commit e955052

Browse files
committed
feat(heartbeats): Implement more advanced rate limiting on API
1 parent 463cc59 commit e955052

File tree

1 file changed

+25
-9
lines changed

1 file changed

+25
-9
lines changed

frontend/functions/src/index.ts

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ const minaClient = new Client({ network: 'testnet' });
2323

2424
admin.initializeApp();
2525

26-
// Rate limit duration between heartbeats from the same submitter (15 seconds)
27-
const HEARTBEAT_RATE_LIMIT_MS = 15000;
26+
// Rate limit configuration: sliding window
27+
const WINDOW_SIZE_MS = 60000; // 1 minute window
28+
const MAX_REQUESTS_PER_WINDOW = 6;
2829

2930
function validateSignature(
3031
data: string,
@@ -96,21 +97,36 @@ export const handleValidationAndStore = onCall(
9697
const newHeartbeatRef = db.collection('heartbeats').doc();
9798

9899
await db.runTransaction(async (transaction) => {
99-
const doc = await transaction.get(rateLimitRef);
100+
const rateLimitDoc = await transaction.get(rateLimitRef);
100101
const now = Date.now();
101-
const cutoff = now - HEARTBEAT_RATE_LIMIT_MS;
102+
const windowStart = now - WINDOW_SIZE_MS;
102103

103-
if (doc.exists) {
104-
const lastCall = doc.data()?.['lastCall'];
105-
if (lastCall > cutoff) {
104+
if (rateLimitDoc.exists) {
105+
const data = rateLimitDoc.data();
106+
const previousTimestamps: number[] = data?.timestamps || [];
107+
const currentWindowTimestamps = previousTimestamps.filter(ts => ts > windowStart);
108+
109+
currentWindowTimestamps.push(now);
110+
111+
if (currentWindowTimestamps.length > MAX_REQUESTS_PER_WINDOW) {
106112
throw new functions.https.HttpsError(
107113
'resource-exhausted',
108-
'Rate limit exceeded for this public key',
114+
'Rate limit exceeded',
109115
);
110116
}
117+
118+
transaction.set(rateLimitRef, {
119+
timestamps: currentWindowTimestamps,
120+
lastCall: FieldValue.serverTimestamp(),
121+
});
122+
} else {
123+
// First request for this public key
124+
transaction.set(rateLimitRef, {
125+
timestamps: [now],
126+
lastCall: FieldValue.serverTimestamp(),
127+
});
111128
}
112129

113-
transaction.set(rateLimitRef, { lastCall: FieldValue.serverTimestamp() }, { merge: true });
114130
transaction.create(newHeartbeatRef, {
115131
...data,
116132
createTime: FieldValue.serverTimestamp(),

0 commit comments

Comments
 (0)