Skip to content

Commit fc745d2

Browse files
implement rate limiting for public profile endpoints (#72)
Signed-off-by: Parth Patidar <parth11.patidar@gmail.com>
1 parent 96ea639 commit fc745d2

4 files changed

Lines changed: 51 additions & 2 deletions

File tree

apps/backend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"@fastify/helmet": "^12.0.0",
2424
"@fastify/jwt": "^9.0.0",
2525
"@fastify/multipart": "^9.0.0",
26+
"@fastify/rate-limit": "^10.3.0",
2627
"@fastify/static": "^8.0.0",
2728
"@prisma/client": "^6.0.0",
2829
"dotenv": "^16.4.0",

apps/backend/src/app.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import jwt from '@fastify/jwt';
55
import cookie from '@fastify/cookie';
66
import multipart from '@fastify/multipart';
77
import fastifyStatic from '@fastify/static';
8+
import rateLimit from '@fastify/rate-limit';
89
import path from 'path';
910
import { fileURLToPath } from 'url';
1011

@@ -62,6 +63,10 @@ export async function buildApp() {
6263

6364
await app.register(cookie);
6465
await app.register(multipart, { limits: { fileSize: 5 * 1024 * 1024 } }); // 5MB
66+
await app.register(rateLimit, {
67+
max: 100,
68+
timeWindow: '1 minute',
69+
});
6570

6671
// Static file serving for uploads
6772
await app.register(fastifyStatic, {

apps/backend/src/routes/public.ts

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,14 @@ type UsernameCardPublicProfileResponse = {
6262

6363
export async function publicRoutes(app: FastifyInstance) {
6464
// ─── Public Profile ───
65+
app.get('/:username', {
66+
config: {
67+
rateLimit: {
68+
max: 100,
69+
timeWindow: '1 minute'
70+
}
71+
}
72+
}, async (request: FastifyRequest<{ Params: { username: string } }>, reply: FastifyReply) => {
6573
/**
6674
* GET /api/public/:username
6775
* Returns the public profile information for a user.
@@ -162,7 +170,14 @@ export async function publicRoutes(app: FastifyInstance) {
162170
*/
163171
// ─── Shared Card View (Direct) ───
164172

165-
app.get('/card/:cardId', async (request: FastifyRequest<{ Params: { cardId: string } }>, reply: FastifyReply) => {
173+
app.get('/card/:cardId', {
174+
config: {
175+
rateLimit: {
176+
max: 100,
177+
timeWindow: '1 minute'
178+
}
179+
}
180+
}, async (request: FastifyRequest<{ Params: { cardId: string } }>, reply: FastifyReply) => {
166181
const { cardId } = request.params;
167182

168183
const card = await app.prisma.card.findUnique({
@@ -203,6 +218,14 @@ export async function publicRoutes(app: FastifyInstance) {
203218
});
204219

205220
// ─── Public Card View ───
221+
app.get('/:username/card/:cardId', {
222+
config: {
223+
rateLimit: {
224+
max: 100,
225+
timeWindow: '1 minute'
226+
}
227+
}
228+
}, async (request: FastifyRequest<{ Params: { username: string; cardId: string } }>, reply: FastifyReply) => {
206229
/**
207230
* GET /api/public/:username/card/:cardId
208231
* Returns full owner profile + specific card data.
@@ -282,7 +305,14 @@ export async function publicRoutes(app: FastifyInstance) {
282305

283306
// ─── QR Code Generation ───
284307

285-
app.get('/:username/qr', async (request: FastifyRequest<{
308+
app.get('/:username/qr', {
309+
config: {
310+
rateLimit: {
311+
max: 50, // Lower limit for QR generation as it's more resource intensive
312+
timeWindow: '1 minute'
313+
}
314+
}
315+
}, async (request: FastifyRequest<{
286316
Params: { username: string };
287317
Querystring: { format?: string; size?: string };
288318
}>, reply: FastifyReply) => {

pnpm-lock.yaml

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)