Skip to content

Commit 09caff5

Browse files
authored
fix(api): reject reversed custom streak date ranges (JhaSourav07#1644)
## Description Fixes JhaSourav07#1643 ## Pillar - [x] 🛠️ Other (Bug fix, refactoring, docs) ## What changed The custom `from` and `to` parameters on `/api/streak` were validated independently, so a reversed range such as `from=2025-12-31&to=2025-01-01` passed schema validation and reached the GitHub fetch path. This PR adds object-level validation to `streakParamsSchema` so requests with both dates present must satisfy: ```ts Date.parse(from) <= Date.parse(to) ``` If the range is reversed, the route now returns a 400 validation response before making any GitHub API request. I also added a route regression test that verifies the reversed range is rejected and `fetchGitHubContributions` is not called. This is a GSSoC 2026 API bug fix contribution. Please add the relevant GSSoC labels if they are missing. ## Visual Preview N/A — this is API validation behavior. ## Local checks ```bash npm run format:check npm run lint npm run test -- app/api/streak/route.test.ts ``` `npm run lint` reports one existing warning in `components/ReturnToTop.test.tsx`; there are no lint errors. ## Checklist before requesting a review: - [x] I have read the `CONTRIBUTING.md` file. - [x] I have tested these changes locally. - [x] I have run `npm run format` and `npm run lint` locally and resolved all errors. - [x] My commit follows the Conventional Commits format. - [x] I have updated `README.md` if I added a new theme or URL parameter. - [x] I have starred the repo. - [x] I have made sure that I have only one commit to merge in this PR. - [x] The SVG output matches the CommitPulse quality standard.
2 parents 3185a1e + 01c9f14 commit 09caff5

3 files changed

Lines changed: 30 additions & 1 deletion

File tree

app/api/streak/route.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,19 @@ describe('GET /api/streak', () => {
531531
});
532532
});
533533

534+
it('returns 400 when custom from date is after custom to date', async () => {
535+
const response = await GET(
536+
makeRequest({ user: 'octocat', from: '2025-12-31', to: '2025-01-01' })
537+
);
538+
const body = await response.json();
539+
540+
expect(response.status).toBe(400);
541+
expect(body.details.fieldErrors.to[0]).toContain(
542+
'"to" date must be after or equal to "from" date'
543+
);
544+
expect(fetchGitHubContributions).not.toHaveBeenCalled();
545+
});
546+
534547
it('functions normally when the year parameter is missing', async () => {
535548
const response = await GET(makeRequest({ user: 'octocat' }));
536549

app/api/wrapped/route.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,14 @@ describe('GET /api/wrapped', () => {
150150
expect(response.status).toBe(200);
151151
expect(body).toContain('rx="15"');
152152
});
153+
154+
it('hides the wrapped badge background when hide_background=1 is passed', async () => {
155+
const response = await GET(makeRequest({ user: 'octocat', hide_background: '1' }));
156+
const body = await response.text();
157+
158+
expect(response.status).toBe(200);
159+
expect(body).toContain('fill="transparent"');
160+
});
153161
});
154162

155163
describe('cache-control header', () => {

lib/validations.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ function dimensionParam(name: string, min: number, max: number) {
6060

6161
const GITHUB_USERNAME_REGEX = /^[a-zA-Z0-9](?:[a-zA-Z0-9]|-(?=[a-zA-Z0-9]))*$/;
6262

63-
export const streakParamsSchema = z.object({
63+
const baseStreakParamsSchema = z.object({
6464
// Required — missing user surfaces as "Missing" to match existing tests
6565
user: z
6666
.string({ error: 'Missing user parameter' })
@@ -243,6 +243,14 @@ export const streakParamsSchema = z.object({
243243
entrance: z.enum(['rise', 'fade', 'slide', 'none']).catch('rise').default('rise'),
244244
});
245245

246+
export const streakParamsSchema = baseStreakParamsSchema.refine(
247+
(data) => !data.from || !data.to || Date.parse(data.from) <= Date.parse(data.to),
248+
{
249+
message: '"to" date must be after or equal to "from" date',
250+
path: ['to'],
251+
}
252+
);
253+
246254
export const githubParamsSchema = z.object({
247255
username: z
248256
.string({ error: 'Missing "username" parameter' })

0 commit comments

Comments
 (0)