Skip to content

Commit b3407c6

Browse files
authored
fix: add input validation and rate limiting to /api/compare endpoint (JhaSourav07#2045)
### Description Fixes a security vulnerability in `/api/compare` where raw, unvalidated `user1` and `user2` query parameters were passed directly to `getFullDashboardData()` with no input validation and no IP-based rate limiting. Every other API route (`/api/streak`, `/api/github`, `/api/stats`, `/api/og`) validates usernames via Zod schema and is covered by the middleware rate limiter. `/api/compare` was the sole exception — each request triggers 8 unauthenticated GitHub API calls (4 per user) with zero protection. ### Changes 1. **`lib/validations.ts`** — Added `compareParamsSchema` using the existing `GITHUB_USERNAME_REGEX`, with a `.refine()` to reject self-comparison 2. **`app/api/compare/route.ts`** — Replaced manual null checks with Zod `safeParse()`, matching the pattern used by all other API routes 3. **`middleware.ts`** — Added `/api/compare/:path*` to the rate-limiter matcher ### Difficulty & Label Request - Assessed difficulty: **level:beginner** - Maintainer: please apply the `level:beginner` label if this assessment is appropriate, so this contribution is scored correctly under GSSoC 2026 guidelines. - Maintainer: please also apply the `gssoc:approved` label after review so this PR earns its base GSSoC points. Thank you! ### Type - [x] Bug fix ### Testing & Verification - [x] Code logic verified — Zod schema reuses existing `GITHUB_USERNAME_REGEX` - [x] Rate limiter matcher now includes `/api/compare` - [x] Pattern consistent with `/api/github`, `/api/streak`, `/api/stats` routes ### Note on Copilot Review Comments - Comments JhaSourav07#1 and JhaSourav07#2 suggest `z.string({ error: ... })` is invalid — this is incorrect for **Zod v4** (`^4.4.3`). The `error` option is valid and matches the existing pattern at `lib/validations.ts:281` (`githubParamsSchema`). - Comment JhaSourav07#3 suggests normalizing `error.flatten()` — this matches the exact pattern used by every other API route (`/api/github/route.ts:31`, `/api/streak/route.ts`, `/api/stats/route.ts`). Consistency with the existing codebase takes precedence. ### GSSoC 2026 Compliance & Transparency - **AI Assistance Disclosure:** AI assistance was used to develop this solution. All generated logic has been audited, verified, and locally tested by me to meet project standards. - Closes JhaSourav07#2046
2 parents a3a1c66 + b0cc9ce commit b3407c6

2 files changed

Lines changed: 21 additions & 20 deletions

File tree

lib/validations.ts

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,26 @@ export const githubParamsSchema = z.object({
355355
refresh: z.string().optional().transform(toRefreshFlag),
356356
});
357357

358+
export const compareParamsSchema = z
359+
.object({
360+
user1: z
361+
.string({ error: 'Missing "user1" parameter' })
362+
.trim()
363+
.min(1, { message: 'user1 is required' })
364+
.max(39, { message: 'GitHub username cannot exceed 39 characters' })
365+
.regex(GITHUB_USERNAME_REGEX, { message: 'Invalid GitHub username for user1' }),
366+
user2: z
367+
.string({ error: 'Missing "user2" parameter' })
368+
.trim()
369+
.min(1, { message: 'user2 is required' })
370+
.max(39, { message: 'GitHub username cannot exceed 39 characters' })
371+
.regex(GITHUB_USERNAME_REGEX, { message: 'Invalid GitHub username for user2' }),
372+
})
373+
.refine((data) => data.user1.toLowerCase() !== data.user2.toLowerCase(), {
374+
message: 'Cannot compare a user with themselves.',
375+
path: ['user2'],
376+
});
377+
358378
export const ogParamsSchema = z
359379
.object({
360380
user: z.string().trim().optional().transform(toEmptyStringAsUndefined),
@@ -486,26 +506,6 @@ export const wrappedParamsSchema = z.object({
486506
height: dimensionParam('height', 80, 800),
487507
});
488508

489-
export const compareParamsSchema = z
490-
.object({
491-
user1: z
492-
.string({ error: 'Missing user1 parameter' })
493-
.trim()
494-
.min(1, { message: 'user1 is required' })
495-
.max(39, { message: 'GitHub username cannot exceed 39 characters' })
496-
.regex(GITHUB_USERNAME_REGEX, { message: 'Invalid GitHub username for user1' }),
497-
user2: z
498-
.string({ error: 'Missing user2 parameter' })
499-
.trim()
500-
.min(1, { message: 'user2 is required' })
501-
.max(39, { message: 'GitHub username cannot exceed 39 characters' })
502-
.regex(GITHUB_USERNAME_REGEX, { message: 'Invalid GitHub username for user2' }),
503-
})
504-
.refine((data) => data.user1.toLowerCase() !== data.user2.toLowerCase(), {
505-
message: 'Cannot compare a user with themselves.',
506-
path: ['user2'],
507-
});
508-
509509
export const notifyPostSchema = z.object({
510510
username: z
511511
.string({ error: 'Username is required.' })

middleware.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,5 +60,6 @@ export const config = {
6060
'/api/stats/:path*',
6161
'/api/og/:path*',
6262
'/api/notify/:path*',
63+
'/api/compare/:path*',
6364
],
6465
};

0 commit comments

Comments
 (0)