Skip to content

feat(auth): add Zod validation for OAuth callback endpoints - #593

Merged
Harxhit merged 4 commits into
Dev-Card:mainfrom
ramnnn2006:fix/oauth-callback-zod-validation
Jun 17, 2026
Merged

feat(auth): add Zod validation for OAuth callback endpoints#593
Harxhit merged 4 commits into
Dev-Card:mainfrom
ramnnn2006:fix/oauth-callback-zod-validation

Conversation

@ramnnn2006

Copy link
Copy Markdown
Contributor

Summary

Adds Zod validation to /auth/github/callback and /auth/google/callback so code and state are validated before any token exchange or DB calls happen. Previously there were scattered manual if (!code) checks, this replaces them with a single safeParse at the top of each handler.

Closes #539


Type of Change

  • Bug fix
  • New feature
  • Refactor (no functional change)
  • UI / Design change
  • Tests only
  • Documentation
  • Infrastructure / DevOps
  • Security

What Changed

  • Added oauthCallbackSchema to auth.validation.ts with code and state as required non-empty strings
  • Updated both callback handlers in auth.ts to use `safnual guards
  • Added auth-callback.test.ts with 14 tests covering missing/empty code, missing/empty state, no cookie, and cookie mismatch for both
    endpoints

How to Test

  1. Run pnpm -r run test — 14 new tests in `auth-callbac
  2. Hit GET /auth/github/callback with no code param — expect 400 with Invalid callback parameters
  3. Hit with valid code and state but no oauth_statevalid or missing OAuth state — possible CSRF attack`

Checklist

  • My code follows the project's coding style (`pnpm -
  • TypeScript compiles without errors (pnpm -r run typecheck).
  • I have added or updated tests for the changes I mad
  • All tests pass locally (pnpm -r run test).
  • I have updated documentation where necessary.
  • No new console.log or debug statements left in the code.
  • Breaking changes are documented in this PR descript

Screenshots / Recordings

N/A

Validates code and state query params in /auth/github/callback and
/auth/google/callback before any token exchange or DB work happens.
Adds oauthCallbackSchema to validators.ts and tests covering missing/
empty code, missing/empty state, and state cookie mismatch scenarios.
Copilot AI review requested due to automatic review settings June 17, 2026 09:38
@vercel

vercel Bot commented Jun 17, 2026

Copy link
Copy Markdown

@ramnnn2006 is attempting to deploy a commit to the Prashantkumar Khatri's projects Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions github-actions Bot added backend gssoc:approved Required label for every approved PR. Gives the base +50 points and enables contribution tracking. labels Jun 17, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Hi @ramnnn2006,

Thanks for opening this pull request.

This PR has been automatically classified based on the files modified.

Applied Labels

  • gssoc:approved
  • backend

Primary Review Area

  • backend

Reviewer

@Harxhit has been identified as the primary reviewer for this pull request.

If you have any questions regarding the affected area or implementation details, feel free to reach out to the assigned reviewer.

Thank you for your contribution!

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds Zod-based validation for OAuth callback query parameters (GitHub + Google) and introduces tests to ensure invalid callback requests fail early with consistent 400 responses.

Changes:

  • Introduced oauthCallbackSchema to validate code and state on OAuth callbacks.
  • Updated GitHub/Google callback routes to use safeParse() and return structured validation errors.
  • Added Vitest coverage for invalid callback parameter and OAuth state-cookie scenarios.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
apps/backend/src/validations/auth.validation.ts Adds a Zod schema for OAuth callback query params.
apps/backend/src/routes/auth.ts Uses the new schema to validate callback querystrings and standardize 400 responses.
apps/backend/src/tests/auth-callback.test.ts Adds regression tests for Zod validation + state cookie enforcement.
Comments suppressed due to low confidence (1)

apps/backend/src/validations/auth.validation.ts:1

  • Schema naming is inconsistent (oAuthStartSchema vs oauthCallbackSchema). Standardizing on a single convention (e.g., oauthStartSchema/oauthCallbackSchema or oAuthStartSchema/oAuthCallbackSchema) will make imports and discoverability more predictable.
import { z } from 'zod';

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +17 to +20
export const oauthCallbackSchema = z.object({
code: z.string().min(1, 'Authorization code is required'),
state: z.string().min(1, 'State parameter is required'),
}); No newline at end of file
Comment thread apps/backend/src/routes/auth.ts Outdated
Comment on lines +101 to +102
app.get('/github/callback', async (request: FastifyRequest<{ Querystring: OAuthCallbackQuery }>, reply: FastifyReply) => {
//TODO: Add zod validation here
const { code, state } = request.query;
const parsed = oauthCallbackSchema.safeParse(request.query);
Comment thread apps/backend/src/routes/auth.ts Outdated
Comment on lines +102 to +106
const parsed = oauthCallbackSchema.safeParse(request.query);
if (!parsed.success) {
return reply.status(400).send({ error: 'Invalid callback parameters', details: parsed.error.flatten() });
}
const { code, state } = parsed.data;
@github-actions

github-actions Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

CI — All Checks Passed

Backend — PASS

Check Result
Lint PASS
Test PASS
Typecheck PASS

Mobile — SKIP

Check Result
Lint -
Test -

Web — SKIP

Check Result
Build -

Last updated: Wed, 17 Jun 2026 11:50:35 GMT

- rename oauthCallbackSchema to oAuthCallbackSchema to match naming convention
- add .trim() to code and state fields to reject whitespace-only values
- export OAuthCallbackQuery type from auth.validation.ts and remove duplicate local interface
@ramnnn2006

Copy link
Copy Markdown
Contributor Author

@Harxhit could u check , ive resolved the conflictsnow!

@Harxhit Harxhit left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — adds Zod validation to the OAuth callbacks.

Clean, well-targeted change that does exactly what the title says: replaces the two //TODO: Add zod validation here placeholders with a shared oAuthCallbackSchema, drops the local OAuthCallbackQuery interface in favor of z.infer, and adds solid rejection-path tests (14 cases, ran green locally).

Verdict: approve with minor changes. The only item I'd actually act on is the CSRF cookie not being cleared on the validation-failure branch (inline below). Everything else is optional polish.

Inline comments follow.

Comment thread apps/backend/src/routes/auth.ts Outdated
const { code, state } = request.query;
const parsed = oAuthCallbackSchema.safeParse(request.query);
if (!parsed.success) {
return reply.status(400).send({ error: 'Invalid callback parameters', details: parsed.error.flatten() });

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CSRF cookie not cleared on validation failure (behavior change).

Previously the !code check ran after reply.clearCookie('oauth_state', ...) (line 106). Now validation returns early here, so a request with a valid+matching state but missing/empty code returns 400 without clearing oauth_state. The single-use CSRF token then lingers in the browser until the next successful callback or expiry.

Not exploitable (it's still compared on the next attempt), but a small hygiene regression. Consider clearing the cookie before returning on this branch, or moving clearCookie ahead of both checks once storedState is read.

Same applies to the Google callback below (line ~316).

Separately: this path also catches the user-denial redirect (?error=access_denied, no code), which now surfaces as a generic "Invalid callback parameters". Pre-existing, but since you're here it could be worth handling request.query.error explicitly. Optional.

Comment thread apps/backend/src/routes/auth.ts Outdated
const { code, state } = request.query;
const parsed = oAuthCallbackSchema.safeParse(request.query);
if (!parsed.success) {
return reply.status(400).send({ error: 'Invalid callback parameters', details: parsed.error.flatten() });

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same cookie-not-cleared-on-validation-failure point as the GitHub callback applies here.

Also: the parse + state-check preamble is now byte-identical across both callbacks. Optional, but a small shared helper (or a preHandler) like validateOAuthCallback(request, reply) returning { code, state } would keep the two providers from drifting.

state: z.string().trim().min(1, 'State parameter is required'),
});

export type OAuthCallbackQuery = z.infer<typeof oAuthCallbackSchema>; No newline at end of file

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Schema looks good — z.string().trim().min(1) is appropriately defensive and the messages are clear.

Nit: file is missing a trailing newline (diff shows \ No newline at end of file). Add one for consistency / to avoid lint noise.

await app.close();
});

it('400 — missing code rejects with validation error', async () => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rejection coverage is strong and nicely symmetric across both providers (missing/empty code, missing/empty state, no-cookie, mismatched-cookie, field-level details).

Gap: there's no happy-path assertion — valid code + matching state proceeding past validation (e.g. mocking fetch to assert clearCookie fires / token exchange is attempted). Without it the suite can't catch a regression that wrongly rejects valid callbacks. buildTestApp already wires the prisma/redis mocks, so it's set up for this. Optional for this PR's scope.

Copilot AI review requested due to automatic review settings June 17, 2026 11:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

Comment thread apps/backend/src/routes/auth.ts
Comment on lines +323 to +326
if (!storedState || state !== storedState) {
return reply.status(400).send({ error: 'Invalid or missing OAuth state — possible CSRF attack' });
}
reply.clearCookie('oauth_state', { path: '/' });
Comment thread apps/backend/src/routes/auth.ts Outdated
Comment on lines +100 to +101
reply.clearCookie('oauth_state', { path: '/' });
return reply.status(400).send({ error: 'Invalid callback parameters', details: parsed.error.flatten() });
Comment thread apps/backend/src/__tests__/auth-callback.test.ts

@Harxhit Harxhit left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Approving

@Harxhit
Harxhit merged commit 52e4df1 into Dev-Card:main Jun 17, 2026
5 of 6 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

Congratulations @ramnnn2006 on getting PR #593 merged!

Thank you for your contribution to the project.

To receive the appropriate GSSoC labels and recognition, please mention @Harxhit in the #get-labels channel on our Discord server and share your merged PR link.

@Harxhit Harxhit added level:advanced Complex contribution involving deeper technical work. (+55 pts) quality:clean PR is well-structured, readable, and follows good practices. (×1.2 multiplier) type:performance Performance optimization (+15 pts) type:security Security-related fixes/improvements (+20 pts) type:refactor Code refactoring/cleanup (+10 pts) type:bug Bug fixes (+10 pts) labels Jun 18, 2026
Harxhit pushed a commit to Harxhit/DevCard that referenced this pull request Jun 21, 2026
…#593)

* feat(auth): add Zod validation for OAuth callback endpoints

Validates code and state query params in /auth/github/callback and
/auth/google/callback before any token exchange or DB work happens.
Adds oauthCallbackSchema to validators.ts and tests covering missing/
empty code, missing/empty state, and state cookie mismatch scenarios.

* fix(auth): address review feedback on OAuth callback validation

- rename oauthCallbackSchema to oAuthCallbackSchema to match naming convention
- add .trim() to code and state fields to reject whitespace-only values
- export OAuthCallbackQuery type from auth.validation.ts and remove duplicate local interface

* fix(auth): clear oauth_state cookie on validation failure and add trailing newline

* fix(auth): clear oauth_state cookie on all failure paths and drop details from 400 response
ShantKhatri pushed a commit to ShantKhatri/DevCard that referenced this pull request Jun 23, 2026
…#593)

* feat(auth): add Zod validation for OAuth callback endpoints

Validates code and state query params in /auth/github/callback and
/auth/google/callback before any token exchange or DB work happens.
Adds oauthCallbackSchema to validators.ts and tests covering missing/
empty code, missing/empty state, and state cookie mismatch scenarios.

* fix(auth): address review feedback on OAuth callback validation

- rename oauthCallbackSchema to oAuthCallbackSchema to match naming convention
- add .trim() to code and state fields to reject whitespace-only values
- export OAuthCallbackQuery type from auth.validation.ts and remove duplicate local interface

* fix(auth): clear oauth_state cookie on validation failure and add trailing newline

* fix(auth): clear oauth_state cookie on all failure paths and drop details from 400 response
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend gssoc:approved Required label for every approved PR. Gives the base +50 points and enables contribution tracking. level:advanced Complex contribution involving deeper technical work. (+55 pts) quality:clean PR is well-structured, readable, and follows good practices. (×1.2 multiplier) type:bug Bug fixes (+10 pts) type:performance Performance optimization (+15 pts) type:refactor Code refactoring/cleanup (+10 pts) type:security Security-related fixes/improvements (+20 pts)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Zod Validation for OAuth Callback Endpoints

3 participants