feat: implement whitelist for self-hosted instances (#3939)
* whitelist for username based endpoints * added gist whitelist * review * fix --------- Co-authored-by: Alexandr <qwerty541zxc@gmail.com>
This commit is contained in:
+18
@@ -4,6 +4,7 @@ import {
|
||||
renderError,
|
||||
parseBoolean,
|
||||
} from "../src/common/utils.js";
|
||||
import { gistWhitelist } from "../src/common/whitelist.js";
|
||||
import { isLocaleAvailable } from "../src/translations.js";
|
||||
import { renderGistCard } from "../src/cards/gist.js";
|
||||
import { fetchGist } from "../src/fetchers/gist.js";
|
||||
@@ -26,6 +27,23 @@ export default async (req, res) => {
|
||||
|
||||
res.setHeader("Content-Type", "image/svg+xml");
|
||||
|
||||
if (gistWhitelist && !gistWhitelist.includes(id)) {
|
||||
return res.send(
|
||||
renderError(
|
||||
"This gist ID is not whitelisted",
|
||||
"Please deploy your own instance",
|
||||
{
|
||||
title_color,
|
||||
text_color,
|
||||
bg_color,
|
||||
border_color,
|
||||
theme,
|
||||
show_repo_link: false,
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (locale && !isLocaleAvailable(locale)) {
|
||||
return res.send(
|
||||
renderError("Something went wrong", "Language not found", {
|
||||
|
||||
+31
-8
@@ -1,5 +1,6 @@
|
||||
import { renderStatsCard } from "../src/cards/stats.js";
|
||||
import { blacklist } from "../src/common/blacklist.js";
|
||||
import { whitelist } from "../src/common/whitelist.js";
|
||||
import {
|
||||
clampValue,
|
||||
CONSTANTS,
|
||||
@@ -41,15 +42,37 @@ export default async (req, res) => {
|
||||
} = req.query;
|
||||
res.setHeader("Content-Type", "image/svg+xml");
|
||||
|
||||
if (blacklist.includes(username)) {
|
||||
if (whitelist && !whitelist.includes(username)) {
|
||||
return res.send(
|
||||
renderError("Something went wrong", "This username is blacklisted", {
|
||||
title_color,
|
||||
text_color,
|
||||
bg_color,
|
||||
border_color,
|
||||
theme,
|
||||
}),
|
||||
renderError(
|
||||
"This username is not whitelisted",
|
||||
"Please deploy your own instance",
|
||||
{
|
||||
title_color,
|
||||
text_color,
|
||||
bg_color,
|
||||
border_color,
|
||||
theme,
|
||||
show_repo_link: false,
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (whitelist === undefined && blacklist.includes(username)) {
|
||||
return res.send(
|
||||
renderError(
|
||||
"This username is blacklisted",
|
||||
"Please deploy your own instance",
|
||||
{
|
||||
title_color,
|
||||
text_color,
|
||||
bg_color,
|
||||
border_color,
|
||||
theme,
|
||||
show_repo_link: false,
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+31
-8
@@ -1,5 +1,6 @@
|
||||
import { renderRepoCard } from "../src/cards/repo.js";
|
||||
import { blacklist } from "../src/common/blacklist.js";
|
||||
import { whitelist } from "../src/common/whitelist.js";
|
||||
import {
|
||||
clampValue,
|
||||
CONSTANTS,
|
||||
@@ -29,15 +30,37 @@ export default async (req, res) => {
|
||||
|
||||
res.setHeader("Content-Type", "image/svg+xml");
|
||||
|
||||
if (blacklist.includes(username)) {
|
||||
if (whitelist && !whitelist.includes(username)) {
|
||||
return res.send(
|
||||
renderError("Something went wrong", "This username is blacklisted", {
|
||||
title_color,
|
||||
text_color,
|
||||
bg_color,
|
||||
border_color,
|
||||
theme,
|
||||
}),
|
||||
renderError(
|
||||
"This username is not whitelisted",
|
||||
"Please deploy your own instance",
|
||||
{
|
||||
title_color,
|
||||
text_color,
|
||||
bg_color,
|
||||
border_color,
|
||||
theme,
|
||||
show_repo_link: false,
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (whitelist === undefined && blacklist.includes(username)) {
|
||||
return res.send(
|
||||
renderError(
|
||||
"This username is blacklisted",
|
||||
"Please deploy your own instance",
|
||||
{
|
||||
title_color,
|
||||
text_color,
|
||||
bg_color,
|
||||
border_color,
|
||||
theme,
|
||||
show_repo_link: false,
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+31
-8
@@ -1,5 +1,6 @@
|
||||
import { renderTopLanguages } from "../src/cards/top-languages.js";
|
||||
import { blacklist } from "../src/common/blacklist.js";
|
||||
import { whitelist } from "../src/common/whitelist.js";
|
||||
import {
|
||||
clampValue,
|
||||
CONSTANTS,
|
||||
@@ -36,15 +37,37 @@ export default async (req, res) => {
|
||||
} = req.query;
|
||||
res.setHeader("Content-Type", "image/svg+xml");
|
||||
|
||||
if (blacklist.includes(username)) {
|
||||
if (whitelist && !whitelist.includes(username)) {
|
||||
return res.send(
|
||||
renderError("Something went wrong", "This username is blacklisted", {
|
||||
title_color,
|
||||
text_color,
|
||||
bg_color,
|
||||
border_color,
|
||||
theme,
|
||||
}),
|
||||
renderError(
|
||||
"This username is not whitelisted",
|
||||
"Please deploy your own instance",
|
||||
{
|
||||
title_color,
|
||||
text_color,
|
||||
bg_color,
|
||||
border_color,
|
||||
theme,
|
||||
show_repo_link: false,
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (whitelist === undefined && blacklist.includes(username)) {
|
||||
return res.send(
|
||||
renderError(
|
||||
"This username is blacklisted",
|
||||
"Please deploy your own instance",
|
||||
{
|
||||
title_color,
|
||||
text_color,
|
||||
bg_color,
|
||||
border_color,
|
||||
theme,
|
||||
show_repo_link: false,
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
parseBoolean,
|
||||
renderError,
|
||||
} from "../src/common/utils.js";
|
||||
import { whitelist } from "../src/common/whitelist.js";
|
||||
import { fetchWakatimeStats } from "../src/fetchers/wakatime.js";
|
||||
import { isLocaleAvailable } from "../src/translations.js";
|
||||
|
||||
@@ -36,6 +37,23 @@ export default async (req, res) => {
|
||||
|
||||
res.setHeader("Content-Type", "image/svg+xml");
|
||||
|
||||
if (whitelist && !whitelist.includes(username)) {
|
||||
return res.send(
|
||||
renderError(
|
||||
"This username is not whitelisted",
|
||||
"Please deploy your own instance",
|
||||
{
|
||||
title_color,
|
||||
text_color,
|
||||
bg_color,
|
||||
border_color,
|
||||
theme,
|
||||
show_repo_link: false,
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (locale && !isLocaleAvailable(locale)) {
|
||||
return res.send(
|
||||
renderError("Something went wrong", "Language not found", {
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
- [On Vercel](#on-vercel)
|
||||
- [:film\_projector: Check Out Step By Step Video Tutorial By @codeSTACKr](#film_projector-check-out-step-by-step-video-tutorial-by-codestackr)
|
||||
- [On other platforms](#on-other-platforms)
|
||||
- [Disable rate limit protections](#disable-rate-limit-protections)
|
||||
- [Available environment variables](#available-environment-variables)
|
||||
- [Keep your fork up to date](#keep-your-fork-up-to-date)
|
||||
- [:sparkling\_heart: Support the project](#sparkling_heart-support-the-project)
|
||||
</details>
|
||||
@@ -797,11 +797,13 @@ Since the GitHub API only allows 5k requests per hour, my `https://github-readme
|
||||
5. You're done 🎉
|
||||
</details>
|
||||
|
||||
## Disable rate limit protections
|
||||
## Available environment variables
|
||||
|
||||
GitHub Readme Stats contains several Vercel environment variables that can be used to remove the rate limit protections:
|
||||
GitHub Readme Stats provides several environment variables that can be used to customize the behavior of your self-hosted instance. These include:
|
||||
|
||||
* `CACHE_SECONDS`: This environment variable takes precedence over our cache minimum and maximum values and can circumvent these values for self-hosted Vercel instances.
|
||||
* `CACHE_SECONDS`: This takes precedence over our cache minimum and maximum values and can circumvent these values for self-hosted instances.
|
||||
* `WHITELIST`: A comma-separated list of GitHub usernames that are allowed to access your instance. If this variable is not set, all usernames are allowed.
|
||||
* `GIST_WHITELIST`: A comma-separated list of GitHub gist IDs that are allowed to be accessed on your instance. If this variable is not set, all gist IDs are allowed.
|
||||
|
||||
See [the Vercel documentation](https://vercel.com/docs/concepts/projects/environment-variables) on adding these environment variables to your Vercel instance.
|
||||
|
||||
|
||||
+8
-1
@@ -355,6 +355,12 @@ const UPSTREAM_API_ERRORS = [
|
||||
* @param {string} message Main error message.
|
||||
* @param {string} secondaryMessage The secondary error message.
|
||||
* @param {object} options Function options.
|
||||
* @param {string=} options.title_color Card title color.
|
||||
* @param {string=} options.text_color Card text color.
|
||||
* @param {string=} options.bg_color Card background color.
|
||||
* @param {string=} options.border_color Card border color.
|
||||
* @param {string=} options.theme Card theme.
|
||||
* @param {boolean=} options.show_repo_link Whether to show repo link or not.
|
||||
* @returns {string} The SVG markup.
|
||||
*/
|
||||
const renderError = (message, secondaryMessage = "", options = {}) => {
|
||||
@@ -364,6 +370,7 @@ const renderError = (message, secondaryMessage = "", options = {}) => {
|
||||
bg_color,
|
||||
border_color,
|
||||
theme = "default",
|
||||
show_repo_link = true,
|
||||
} = options;
|
||||
|
||||
// returns theme based colors with proper overrides and defaults
|
||||
@@ -388,7 +395,7 @@ const renderError = (message, secondaryMessage = "", options = {}) => {
|
||||
ERROR_CARD_LENGTH - 1
|
||||
}" height="99%" rx="4.5" fill="${bgColor}" stroke="${borderColor}"/>
|
||||
<text x="25" y="45" class="text">Something went wrong!${
|
||||
UPSTREAM_API_ERRORS.includes(secondaryMessage)
|
||||
UPSTREAM_API_ERRORS.includes(secondaryMessage) || !show_repo_link
|
||||
? ""
|
||||
: " file an issue at https://tiny.one/readme-stats"
|
||||
}</text>
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
const whitelist = process.env.WHITELIST
|
||||
? process.env.WHITELIST.split(",")
|
||||
: undefined;
|
||||
|
||||
const gistWhitelist = process.env.GIST_WHITELIST
|
||||
? process.env.GIST_WHITELIST.split(",")
|
||||
: undefined;
|
||||
|
||||
export { whitelist, gistWhitelist };
|
||||
export default whitelist;
|
||||
+5
-1
@@ -307,7 +307,11 @@ describe("Test /api/", () => {
|
||||
|
||||
expect(res.setHeader).toBeCalledWith("Content-Type", "image/svg+xml");
|
||||
expect(res.send).toBeCalledWith(
|
||||
renderError("Something went wrong", "This username is blacklisted"),
|
||||
renderError(
|
||||
"This username is blacklisted",
|
||||
"Please deploy your own instance",
|
||||
{ show_repo_link: false },
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
+5
-1
@@ -156,7 +156,11 @@ describe("Test /api/pin", () => {
|
||||
|
||||
expect(res.setHeader).toBeCalledWith("Content-Type", "image/svg+xml");
|
||||
expect(res.send).toBeCalledWith(
|
||||
renderError("Something went wrong", "This username is blacklisted"),
|
||||
renderError(
|
||||
"This username is blacklisted",
|
||||
"Please deploy your own instance",
|
||||
{ show_repo_link: false },
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -184,7 +184,11 @@ describe("Test /api/top-langs", () => {
|
||||
|
||||
expect(res.setHeader).toBeCalledWith("Content-Type", "image/svg+xml");
|
||||
expect(res.send).toBeCalledWith(
|
||||
renderError("Something went wrong", "This username is blacklisted"),
|
||||
renderError(
|
||||
"This username is blacklisted",
|
||||
"Please deploy your own instance",
|
||||
{ show_repo_link: false },
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user