Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
237 changes: 170 additions & 67 deletions src/pages/Contributors/Contributors.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,78 @@ const ContributorsPage = () => {
useEffect(() => {
const fetchContributors = async () => {
try {
const response = await axios.get(GITHUB_REPO_CONTRIBUTORS_URL, {
withCredentials: false,
});
// REAL API CALL
const response = await axios.get(
GITHUB_REPO_CONTRIBUTORS_URL,
{
withCredentials: false,
}
);

setContributors(response.data);
} catch {
setError("Failed to fetch contributors. Please try again later.");

/*
// ==================================================
// TEMPORARY TESTING CODE
// Uncomment this block ONLY if you want to test
// the rate limit UI manually.
// ==================================================

// // throw {
// // isAxiosError: true,
// // response: {
// // status: 403,
// // headers: {
// // "x-ratelimit-remaining": 0,
// // "x-ratelimit-reset": 1750000000,
// // },
// // },
// // };

// ==================================================
// */
} catch (error) {
if (axios.isAxiosError(error)) {

// GitHub API Rate Limit Error
if (error.response?.status === 403) {

const remaining =
error.response.headers["x-ratelimit-remaining"];

const reset =
error.response.headers["x-ratelimit-reset"];

let resetMessage = "";

if (reset) {
const resetTime = new Date(
Number(reset) * 1000
);

resetMessage =
` Try again after ${resetTime.toLocaleTimeString()}.`;
}

setError(
`GitHub API rate limit exceeded. Remaining requests: ${remaining}.${resetMessage}`
);
Comment on lines +70 to +91
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.

⚠️ Potential issue | 🟑 Minor | ⚑ Quick win

🧩 Analysis chain

🌐 Web query:

In GitHub REST API, are all HTTP 403 responses considered rate-limit errors? What is the recommended way to distinguish rate-limit responses (e.g., x-ratelimit-remaining/reset headers and response message) from other forbidden responses?

πŸ’‘ Result:

No, not all HTTP 403 (Forbidden) responses from the GitHub REST API are caused by rate-limit errors [1][2]. A 403 status code is a general response indicating that the server refuses to authorize the request, which can occur due to insufficient permissions, invalid credentials, or other access-related issues, in addition to both primary and secondary rate limiting [1][3][4]. To distinguish a rate-limit error from other types of 403 Forbidden responses, you should examine the response body and specific HTTP headers [1][3][5]. Recommended approach to identify and handle rate limits: 1. Check the response body: GitHub explicitly includes information in the error message when a rate limit has been exceeded [1][3]. For example, messages regarding secondary rate limits will explicitly state that a secondary rate limit was exceeded [1][3]. 2. Check for the Retry-After header: If this header is present, it is a strong indicator that you have hit a rate limit (typically a secondary rate limit), and the value specifies how many seconds you must wait before retrying [1][3][5]. 3. Check x-ratelimit-remaining: If the x-ratelimit-remaining header is 0, it confirms that your primary rate limit has been exhausted [1][3][5]. The x-ratelimit-reset header will provide the UTC epoch time when your limit will reset [1][3][5]. If a 403 response does not contain these specific rate-limit indicators, it is likely a standard authorization or permission error [2][6][4]. In such cases, retrying will not resolve the issue and may be considered abusive behavior [1][5]. For rate-limit errors, follow the prescribed backoff strategiesβ€”such as waiting until the time specified by the x-ratelimit-reset header or using exponential backoff if a retry-after header is presentβ€”to remain compliant and avoid potential banning of your integration [1][3][5].

Citations:


Avoid mislabeling all GitHub 403s as rate limits (and prevent undefined in error text)

At src/pages/Contributors/Contributors.tsx (lines 70-91), the code treats every 403 as a rate-limit issue, but GitHub can return 403 for many non-rate-limit authorization/permission problems. Also, it interpolates x-ratelimit-remaining directly, which can show undefined to users when the header isn’t present.

Proposed hardening
-          if (error.response?.status === 403) {
-            const remaining =
-              error.response.headers["x-ratelimit-remaining"];
-            const reset =
-              error.response.headers["x-ratelimit-reset"];
+          if (error.response?.status === 403) {
+            const remainingHeader =
+              error.response.headers["x-ratelimit-remaining"];
+            const resetHeader =
+              error.response.headers["x-ratelimit-reset"];
+            const message = String(error.response.data?.message ?? "").toLowerCase();
+            const isRateLimit =
+              remainingHeader === "0" || message.includes("rate limit");
+
+            if (!isRateLimit) {
+              setError("Access forbidden. Please verify permissions and try again.");
+              return;
+            }

             let resetMessage = "";
-
-            if (reset) {
+            if (resetHeader && !Number.isNaN(Number(resetHeader))) {
               const resetTime = new Date(
-                Number(reset) * 1000
+                Number(resetHeader) * 1000
               );
               resetMessage =
                 ` Try again after ${resetTime.toLocaleTimeString()}.`;
             }

             setError(
-              `GitHub API rate limit exceeded. Remaining requests: ${remaining}.${resetMessage}`
+              `GitHub API rate limit exceeded. Remaining requests: ${remainingHeader ?? "unknown"}.${resetMessage}`
             );
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (error.response?.status === 403) {
const remaining =
error.response.headers["x-ratelimit-remaining"];
const reset =
error.response.headers["x-ratelimit-reset"];
let resetMessage = "";
if (reset) {
const resetTime = new Date(
Number(reset) * 1000
);
resetMessage =
` Try again after ${resetTime.toLocaleTimeString()}.`;
}
setError(
`GitHub API rate limit exceeded. Remaining requests: ${remaining}.${resetMessage}`
);
if (error.response?.status === 403) {
const remainingHeader =
error.response.headers["x-ratelimit-remaining"];
const resetHeader =
error.response.headers["x-ratelimit-reset"];
const message = String(error.response.data?.message ?? "").toLowerCase();
const isRateLimit =
remainingHeader === "0" || message.includes("rate limit");
if (!isRateLimit) {
setError("Access forbidden. Please verify permissions and try again.");
return;
}
let resetMessage = "";
if (resetHeader && !Number.isNaN(Number(resetHeader))) {
const resetTime = new Date(
Number(resetHeader) * 1000
);
resetMessage =
` Try again after ${resetTime.toLocaleTimeString()}.`;
}
setError(
`GitHub API rate limit exceeded. Remaining requests: ${remainingHeader ?? "unknown"}.${resetMessage}`
);
πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/Contributors/Contributors.tsx` around lines 70 - 91, The code
currently assumes any HTTP 403 is a GitHub rate-limit and directly interpolates
headers that may be absent; update the 403 branch in the Contributors component
to first check for the presence of rate-limit headers
(error.response.headers["x-ratelimit-remaining"] and ["x-ratelimit-reset"]) and
only treat it as a rate-limit when the headers exist, otherwise setError to a
generic 403 authorization/permission message that includes safe fallbacks (e.g.,
statusText or error.message) instead of interpolating undefined; when building
the rate-limit message use a default value (like "unknown" or "N/A") for
remaining and only compute/resetMessage if x-ratelimit-reset is present before
calling setError.


} else {

setError(
"Failed to fetch contributors. Please try again later."
);

}

} else {

setError(
"An unexpected error occurred."
);

}
} finally {
setLoading(false);
}
Expand All @@ -47,92 +113,129 @@ const ContributorsPage = () => {
fetchContributors();
}, []);

// Loading UI
if (loading) {
return (
<Box sx={{ display: "flex", justifyContent: "center", mt: 4 }}>
<Box
sx={{
display: "flex",
justifyContent: "center",
mt: 4,
}}
>
<CircularProgress />
</Box>
);
}

// Error UI
if (error) {
return (
<Box sx={{ mt: 4 }}>
<Alert severity="error">{error}</Alert>
<Alert severity="error">
{error}
</Alert>
</Box>
);
}

return (
<div className="bg-white dark:bg-gray-900 text-black dark:text-white min-h-screen p-4 mt-4">
<Container>
<Typography sx={{ pb: 2 }} variant="h4" align="center" gutterBottom>
<Typography
sx={{ pb: 2 }}
variant="h4"
align="center"
gutterBottom
>
🀝 Contributors
</Typography>

<Grid container spacing={4}>
{contributors.map((contributor) => (
<Grid item xs={12} sm={6} md={3} key={contributor.id}>
<Card
sx={{
textAlign: "center",
p: 2,
borderRadius: "10px",
border: "1px solid #E0E0E0",
boxShadow: "0 4px 10px rgba(0,0,0,0.1)",
transition: "transform 0.3s ease-in-out",
"&:hover": {
transform: "scale(1.05)",
boxShadow: "0 8px 15px rgba(0,0,0,0.2)",
borderColor: "#C0C0C0",
outlineColor: "#B3B3B3",
},
<Grid
item
xs={12}
sm={6}
md={3}
key={contributor.id}
>
<Card
sx={{
textAlign: "center",
p: 2,
borderRadius: "10px",
border: "1px solid #E0E0E0",
boxShadow:
"0 4px 10px rgba(0,0,0,0.1)",
transition:
"transform 0.3s ease-in-out",

"&:hover": {
transform: "scale(1.05)",
boxShadow:
"0 8px 15px rgba(0,0,0,0.2)",
borderColor: "#C0C0C0",
outlineColor: "#B3B3B3",
},
}}
>
<Link
to={`/contributor/${contributor.login}`}
style={{
textDecoration: "none",
}}
>
<Link
to={`/contributor/${contributor.login}`}
style={{ textDecoration: "none" }}
<Avatar
src={contributor.avatar_url}
alt={contributor.login}
sx={{
width: 100,
height: 100,
mx: "auto",
mb: 2,
}}
/>

<CardContent>
<Typography
variant="h6"
sx={{
fontWeight: "bold",
}}
>
{contributor.login}
</Typography>

<Typography
variant="body2"
color="text.secondary"
>
<Avatar
src={contributor.avatar_url}
alt={contributor.login}
sx={{ width: 100, height: 100, mx: "auto", mb: 2 }}
/>
<CardContent>
<Typography variant="h6" sx={{ fontWeight: "bold" }}>
{contributor.login}
</Typography>

<Typography variant="body2" color="text.secondary">
{contributor.contributions} Contributions
</Typography>
{/*
<Typography variant="body2" sx={{ mt: 2 }}>
Thank you for your valuable contributions to our
community!
</Typography> */}
</CardContent>
</Link>

<Box sx={{ mt: 2 }}>
<Button
variant="contained"
startIcon={<FaGithub />}
href={contributor.html_url}
target="_blank"
sx={{
backgroundColor: "#333333",
textTransform: "none",
color: "#FFFFFF",
"&:hover": {
backgroundColor: "#555555",
},
}}
>
GitHub
</Button>
</Box>
</Card>
{contributor.contributions} Contributions
</Typography>
</CardContent>
</Link>

<Box sx={{ mt: 2 }}>
<Button
variant="contained"
startIcon={<FaGithub />}
href={contributor.html_url}
target="_blank"
sx={{
Comment on lines +223 to +225
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.

⚠️ Potential issue | 🟠 Major | ⚑ Quick win

Add rel on the external link opened in a new tab.

Line 224 uses target="_blank" without rel="noopener noreferrer", which is a security gap.

Proposed fix
                   <Button
                     variant="contained"
                     startIcon={<FaGithub />}
                     href={contributor.html_url}
                     target="_blank"
+                    rel="noopener noreferrer"
                     sx={{
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
href={contributor.html_url}
target="_blank"
sx={{
href={contributor.html_url}
target="_blank"
rel="noopener noreferrer"
sx={{
πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/Contributors/Contributors.tsx` around lines 223 - 225, The anchor
rendered for contributors uses target="_blank" on href={contributor.html_url}
(in Contributors.tsx) without a rel attribute; update the Link/anchor element
that sets href={contributor.html_url} and target="_blank" to also include
rel="noopener noreferrer" to prevent tab-napping and improve security (ensure
the change is applied where the contributor link is created, e.g., the element
rendering contributor.html_url).

backgroundColor: "#333333",
textTransform: "none",
color: "#FFFFFF",

"&:hover": {
backgroundColor: "#555555",
},
}}
>
GitHub
</Button>
</Box>
</Card>
</Grid>
))}
</Grid>
Expand All @@ -141,4 +244,4 @@ const ContributorsPage = () => {
);
};

export default ContributorsPage;
export default ContributorsPage;
Loading