Skip to content

prove github creds via graphql (faster, but token always required) - #4

Open
Chmarusso wants to merge 1 commit into
mainfrom
use-graphql
Open

prove github creds via graphql (faster, but token always required)#4
Chmarusso wants to merge 1 commit into
mainfrom
use-graphql

Conversation

@Chmarusso

Copy link
Copy Markdown
Contributor

fixes issue with repos that have a lot contributors (pagination)

@vercel

vercel Bot commented Nov 19, 2025

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
github-contribution-verifier-demo Ready Ready Preview Comment Nov 19, 2025 7:08pm

@coderabbitai

coderabbitai Bot commented Nov 19, 2025

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This pull request refactors API request handling to support both GraphQL and REST endpoints. A new utility module (lib/graphql-transformer.ts) provides functions to transform GraphQL responses and extract repository information. The prove endpoint now branches between GraphQL and REST based on request content. The upload-proof and verify-all endpoints leverage the new transformers for simplified data extraction. The main page component strengthens type safety for state variables and implements GraphQL-based verification with query construction and response parsing. Error handling and logging are updated across affected routes.

Pre-merge checks

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title accurately captures the main change: migrating from REST to GraphQL for GitHub credential verification, with the tradeoff that tokens are now required.
Description check ✅ Passed The description is related to the changeset and identifies a specific problem being solved: handling repos with many contributors through pagination improvements.

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (6)
app/api/prove/route.ts (2)

3-4: Align maxDuration with comment or platform limit

The comment says “up to 90 seconds” but maxDuration is set to 160. This mismatch is confusing and could suggest an unsupported runtime config. Consider either updating the comment to match the chosen value or lowering maxDuration if 90s is the actual platform limit.


13-31: Guard GraphQL path with clearer validation (optional)

The GraphQL branch currently triggers whenever body.query is truthy, and silently allows missing githubToken (GitHub will then 401). If this endpoint is used beyond your own UI, you may want to explicitly validate that body.query is a non‑empty string and that a token is present when taking the GraphQL path, returning a 400 for bad input instead of delegating all failures to GitHub.

lib/graphql-transformer.ts (1)

196-221: Harden extractRepoInfo against non‑string requestBody.query

requestBody.query.match(...) and requestBody.query.substring(...) assume query is a string. If an upstream change ever sends a non‑string (e.g., object or null), this will throw before reaching the outer catch. A small type guard would make this more robust.

-        // If no variables, try to extract from GraphQL query string (search query format)
-        if ((!repoOwner || !repoName) && requestBody.query) {
+        // If no variables, try to extract from GraphQL query string (search query format)
+        if ((!repoOwner || !repoName) && typeof requestBody.query === 'string' && requestBody.query) {
           // Parse search query like: repo:owner/name is:pr is:merged author:username
           // Match pattern: repo:owner/name (handles cases where name might have special chars)
           const searchQueryMatch = requestBody.query.match(/repo:([^\/\s"']+)\/([^\s"']+)/);
           if (searchQueryMatch) {
             repoOwner = searchQueryMatch[1];
             // Remove any trailing characters that might be part of the query (like quotes or spaces)
             repoName = searchQueryMatch[2].replace(/["']/g, '').trim();
             console.log(`Extracted repo info from search query: ${repoOwner}/${repoName}`);
           } else {
-            console.log('Could not extract repo from search query:', requestBody.query.substring(0, 100));
+            console.log('Could not extract repo from search query:', requestBody.query.substring(0, 100));
           }
         }
app/page.tsx (3)

12-18: Type improvements look good; consider tightening type

The stricter typing for presentation and result.data.contributionData is a nice step away from any. As a further (optional) improvement, you could narrow type from string to a union like 'prove' | 'verify' to get exhaustiveness checking where you branch on result.type.


50-65: Prefer GraphQL variables for the search query instead of string interpolation

searchQuery is interpolated directly into the GraphQL string (query: "${searchQuery}"). While owner/repo/username are constrained on GitHub, building queries this way is brittle (quoting/escaping issues) and harder to evolve. Consider switching to a parameterized query with a $searchQuery variable and sending it via the variables field (which /api/prove already supports):

const graphqlQuery = `
  query MergedPrCount($searchQuery: String!) {
    search(
      query: $searchQuery
      type: ISSUE
      first: 1
    ) {
      issueCount
    }
  }
`;

// and in the fetch body:
body: JSON.stringify({
  query: graphqlQuery,
  variables: { searchQuery },
  githubToken: githubToken.trim(),
});

This avoids manual quoting and keeps the query template cleaner.


125-173: GraphQL response parsing duplicates server transformer logic

The parsing here (checking data.search.issueCount, repository.pullRequests.nodes, totalCount, and array length) closely mirrors transformGraphQLPRResponse in lib/graphql-transformer.ts. To reduce drift and bugs if GitHub’s shapes change, you might consider reusing that transformer on the client as well (it already accepts an object, not only strings) and mapping its contributions field into your contributionData.total.

import { transformGraphQLPRResponse } from '@/lib/graphql-transformer';

// ...
if (data.response && data.response.body) {
  try {
    const contributor = transformGraphQLPRResponse(
      JSON.parse(data.response.body),
      username.trim()
    );
    if (!contributor || contributor.contributions === 0) {
      setError(`No merged PRs found for @${username.trim()} in repository`);
      return;
    }
    contributionData = {
      username: contributor.username,
      total: contributor.contributions,
      avatar: contributor.avatar,
    };
  } catch {
    setError('Failed to parse contribution data');
    return;
  }
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 05c9cb1 and dfe2d7e.

📒 Files selected for processing (5)
  • app/api/prove/route.ts (2 hunks)
  • app/api/upload-proof/route.ts (3 hunks)
  • app/api/verify-all/route.ts (2 hunks)
  • app/page.tsx (9 hunks)
  • lib/graphql-transformer.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
app/api/upload-proof/route.ts (1)
lib/graphql-transformer.ts (2)
  • extractRepoInfo (192-255)
  • transformGraphQLPRResponse (44-187)
app/api/verify-all/route.ts (1)
lib/graphql-transformer.ts (2)
  • extractRepoInfo (192-255)
  • transformGraphQLPRResponse (44-187)
app/api/prove/route.ts (1)
next.config.ts (1)
  • headers (6-18)
🔇 Additional comments (4)
app/api/upload-proof/route.ts (1)

3-4: Good consolidation of parsing via shared transformers

Using extractRepoInfo(verifyData) and transformGraphQLPRResponse(verifyData.response.body, username.trim()) removes duplicated parsing logic and centralizes handling of GraphQL vs legacy REST/commits formats. This should make supporting new response shapes and pagination much easier going forward.

Also applies to: 72-95

lib/graphql-transformer.ts (1)

65-185: Transformer logic looks solid across GraphQL and legacy shapes

The control flow in transformGraphQLPRResponse covers all expected formats (search.issueCount, PR totalCount, PR nodes, contributors array, commits array) and always falls back to null on empty or error cases, which matches how the callers treat “no contributions found”. This is a good centralization of the parsing logic.

app/api/verify-all/route.ts (1)

81-84: Nice reuse of shared transformers to simplify aggregation

Using extractRepoInfo and transformGraphQLPRResponse here makes the “verify all” flow much simpler and keeps contributor shaping consistent with /api/upload-proof. The repoUrl derivation from {owner, name} is also a good touch for downstream display.

Also applies to: 87-105

app/page.tsx (1)

36-45: Client-side validation and button disabling align well with new GraphQL flow

Requiring both username and GitHub token before calling handleProve, and wiring that into the button’s disabled state, nicely prevents invalid GraphQL prove requests from ever hitting the backend and surfaces clear error messages to the user.

Also applies to: 310-316

Comment thread app/api/prove/route.ts
Comment on lines +21 to +37
// Construct headers for GraphQL request
const headers = [
"User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36",
"Content-Type: application/json",
"Accept: application/vnd.github+json"
]
};
];

if (body.githubToken) {
headers.push(`Authorization: Bearer ${body.githubToken}`);
}

requestBody = {
url: graphqlUrl,
method: 'POST',
headers: headers,
body: graphqlBody
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Do not log GitHub PAT in requestBody

requestBody.headers includes Authorization: Bearer ${body.githubToken}, and console.log('Request body:', JSON.stringify(requestBody, null, 2)); will emit that PAT into server logs. This is high‑risk credential leakage; please avoid logging the full request body or explicitly strip/redact the Authorization header before logging.

-    console.log('Request body:', JSON.stringify(requestBody, null, 2));
+    // Avoid logging full requestBody because it may contain sensitive headers (e.g., GitHub PATs).
+    // If needed for debugging, log only non-sensitive metadata:
+    console.debug('Request body summary:', {
+      url: requestBody.url,
+      method: requestBody.method,
+      headerCount: Array.isArray(requestBody.headers) ? requestBody.headers.length : undefined,
+    });

Also applies to: 61-62

🤖 Prompt for AI Agents
In app/api/prove/route.ts around lines 21 to 37 (and also where requestBody is
logged around lines 61-62), the requestBody is built with an Authorization
header containing the GitHub PAT which must not be emitted to logs; update
logging to never print the full requestBody or to redact sensitive headers by
removing or replacing the Authorization header value before any
JSON.stringify/console.log (e.g., clone the requestBody for logging and set
cloned.headers to include a redacted Authorization or filter it out entirely),
and ensure any other logs that could include body.githubToken are removed or
redacted.

Comment on lines 58 to 61
const verifyData = await verifyResponse.json();

console.log('Verify data:', verifyData);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Avoid logging full verification payloads (GitHub PAT leakage risk)

console.log('Verify data:', verifyData); combined with the fact that verifyData carries the original request (including headers) means GitHub personal access tokens sent via /api/prove will end up in server logs. That’s a serious secret‑exposure risk; please remove or strictly sanitize these logs (e.g., drop headers, especially Authorization) before logging.

-    console.log('Verify data:', verifyData);
+    // Avoid logging full verifyData because it may contain sensitive headers (e.g., GitHub PATs).
+    // console.debug('Verify summary:', {
+    //   hasRequest: !!verifyData.request,
+    //   hasResponse: !!verifyData.response,
+    // });

Also applies to: 79-83

🤖 Prompt for AI Agents
In app/api/upload-proof/route.ts around lines 58-61 (and also remove or fix
similar logging at 79-83), the code logs the full verifyData object which
contains request headers and can leak GitHub PATs; remove the full console.log
or replace it with a sanitized log that omits headers (especially Authorization)
and any sensitive fields, e.g. log only safe metadata like response status or
specific non‑sensitive properties; ensure any future logging explicitly strips
or redacts headers.Authorization before output.

Comment on lines 79 to 84
console.log('Verify response structure:', JSON.stringify(verifyData, null, 2));

// Extract repository info from the verified response
let repoOwner: string | null = null;
let repoName: string | null = null;
try {
// Try multiple locations where the URL might be
let urlToMatch = null;

// Check different possible locations
if (verifyData?.response?.url) {
urlToMatch = verifyData.response.url;
} else if (verifyData?.request?.url) {
urlToMatch = verifyData.request.url;
} else if (verifyData?.response?.request) {
urlToMatch = verifyData.response.request;
}

console.log('URL to match:', urlToMatch);

if (urlToMatch) {
const urlMatch = urlToMatch.match(/\/repos\/([^\/]+)\/([^\/]+)\/contributors/);
if (urlMatch) {
repoOwner = urlMatch[1];
repoName = urlMatch[2];
console.log(`Extracted repo: ${repoOwner}/${repoName}`);
}
}
} catch (e) {
console.log('Could not extract repo info from response:', e);
}
const { owner: repoOwner, name: repoName } = extractRepoInfo(verifyData);
console.log(`Extracted repo: ${repoOwner}/${repoName}`);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Sanitize or remove logs that include full verification payloads

Between console.log('Verify response structure:', JSON.stringify(verifyData, null, 2)); and logging of responseBody, you’re likely emitting the original request (including GitHub Authorization headers from the webproof) into server logs. That PAT is sensitive and should not be logged. Recommend either removing these logs or logging only high‑level metadata (e.g., filenames, repo owner/name, counts) without headers.

-        console.log('Verify response structure:', JSON.stringify(verifyData, null, 2));
+        // Avoid logging full verifyData; it may contain sensitive headers (e.g., GitHub PATs).
+        console.debug('Verify response summary:', {
+          file,
+          hasRequest: !!verifyData.request,
+          hasResponse: !!verifyData.response,
+        });
@@
-          const responseBody = verifyData.response.body;
-          const contributorData = transformGraphQLPRResponse(responseBody, targetUsername || undefined);
+          const responseBody = verifyData.response.body;
+          const contributorData = transformGraphQLPRResponse(responseBody, targetUsername || undefined);

Also applies to: 87-91

🤖 Prompt for AI Agents
In app/api/verify-all/route.ts around lines 79-84 (and similarly 87-91), remove
or replace the console.log that prints the full verifyData/responseBody JSON
(which may contain sensitive GitHub Authorization headers/PATs); instead log
only high-level, non-sensitive metadata such as repo owner/name, filenames or
counts, and strip any headers or auth fields before logging. Ensure any helper
that extracts repo info runs before logging and only sanitized fields are
emitted to logs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant