refactor: move access guards logic into reusable functions (#4535)

* refactor: move access guards logic into reusable functions

* Update src/common/access.js

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Alexandr <qwerty541zxc@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Ophelia Goldstein
2025-10-25 16:46:06 +02:00
committed by martin-mfg
co-authored by Copilot Alexandr
parent 3bcd26a6a9
commit b687bf30bb
5 changed files with 121 additions and 100 deletions
+15 -16
View File
@@ -1,7 +1,6 @@
// @ts-check
import { CONSTANTS, renderError, parseBoolean } from "../src/common/utils.js";
import { gistWhitelist } from "../src/common/envs.js";
import { isLocaleAvailable } from "../src/translations.js";
import { renderGistCard } from "../src/cards/gist.js";
import { fetchGist } from "../src/fetchers/gist.js";
@@ -11,6 +10,7 @@ import {
setCacheHeaders,
setErrorCacheHeaders,
} from "../src/common/cache.js";
import { guardAccess } from "../src/common/access.js";
export default async (req, res) => {
const {
@@ -30,21 +30,20 @@ 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,
},
),
);
const access = guardAccess({
res,
id,
type: "gist",
colors: {
title_color,
text_color,
bg_color,
border_color,
theme,
},
});
if (!access.isPassed) {
return access.result;
}
if (locale && !isLocaleAvailable(locale)) {
+15 -34
View File
@@ -1,13 +1,12 @@
// @ts-check
import { renderRepoCard } from "../src/cards/repo.js";
import { blacklist } from "../src/common/blacklist.js";
import { guardAccess } from "../src/common/access.js";
import {
resolveCacheSeconds,
setCacheHeaders,
setErrorCacheHeaders,
} from "../src/common/cache.js";
import { whitelist } from "../src/common/envs.js";
import {
CONSTANTS,
parseArray,
@@ -44,38 +43,20 @@ 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 (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,
},
),
);
const access = guardAccess({
res,
id: username,
type: "username",
colors: {
title_color,
text_color,
bg_color,
border_color,
theme,
},
});
if (!access.isPassed) {
return access.result;
}
if (locale && !isLocaleAvailable(locale)) {
+15 -34
View File
@@ -1,13 +1,12 @@
// @ts-check
import { renderTopLanguages } from "../src/cards/top-languages.js";
import { blacklist } from "../src/common/blacklist.js";
import { guardAccess } from "../src/common/access.js";
import {
resolveCacheSeconds,
setCacheHeaders,
setErrorCacheHeaders,
} from "../src/common/cache.js";
import { whitelist } from "../src/common/envs.js";
import {
CONSTANTS,
parseArray,
@@ -46,38 +45,20 @@ export default async (req, res) => {
} = req.query;
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 (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,
},
),
);
const access = guardAccess({
res,
id: username,
type: "username",
colors: {
title_color,
text_color,
bg_color,
border_color,
theme,
},
});
if (!access.isPassed) {
return access.result;
}
if (locale && !isLocaleAvailable(locale)) {
+15 -16
View File
@@ -7,7 +7,6 @@ import {
parseBoolean,
renderError,
} from "../src/common/utils.js";
import { whitelist } from "../src/common/envs.js";
import { fetchWakatimeStats } from "../src/fetchers/wakatime.js";
import { isLocaleAvailable } from "../src/translations.js";
import { storeRequest } from "../src/common/database.js";
@@ -16,6 +15,7 @@ import {
setCacheHeaders,
setErrorCacheHeaders,
} from "../src/common/cache.js";
import { guardAccess } from "../src/common/access.js";
export default async (req, res) => {
const {
@@ -45,21 +45,20 @@ 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,
},
),
);
const access = guardAccess({
res,
id: username,
type: "wakatime",
colors: {
title_color,
text_color,
bg_color,
border_color,
theme,
},
});
if (!access.isPassed) {
return access.result;
}
if (locale && !isLocaleAvailable(locale)) {
+61
View File
@@ -0,0 +1,61 @@
// @ts-check
import { renderError } from "./utils.js";
import { blacklist } from "./blacklist.js";
import { whitelist, gistWhitelist } from "./envs.js";
const NOT_WHITELISTED_USERNAME_MESSAGE = "This username is not whitelisted";
const NOT_WHITELISTED_GIST_MESSAGE = "This gist ID is not whitelisted";
const BLACKLISTED_MESSAGE = "This username is blacklisted";
/**
* Guards access using whitelist/blacklist.
*
* @param {Object} args The parameters object.
* @param {Object} args.res The response object.
* @param {string} args.id Resource identifier (username or gist id).
* @param {"username"|"gist"|"wakatime"} args.type The type of identifier.
* @param {{ title_color?: string, text_color?: string, bg_color?: string, border_color?: string, theme?: string }} args.colors Color options for the error card.
* @returns {{ isPassed: boolean, result?: any }} The result object indicating success or failure.
*/
const guardAccess = ({ res, id, type, colors }) => {
if (!["username", "gist", "wakatime"].includes(type)) {
throw new Error(
'Invalid type. Expected "username", "gist", or "wakatime".',
);
}
const currentWhitelist = type === "gist" ? gistWhitelist : whitelist;
const notWhitelistedMsg =
type === "gist"
? NOT_WHITELISTED_GIST_MESSAGE
: NOT_WHITELISTED_USERNAME_MESSAGE;
if (Array.isArray(currentWhitelist) && !currentWhitelist.includes(id)) {
const result = res.send(
renderError(notWhitelistedMsg, "Please deploy your own instance", {
...colors,
show_repo_link: false,
}),
);
return { isPassed: false, result };
}
if (
type === "username" &&
currentWhitelist === undefined &&
blacklist.includes(id)
) {
const result = res.send(
renderError(BLACKLISTED_MESSAGE, "Please deploy your own instance", {
...colors,
show_repo_link: false,
}),
);
return { isPassed: false, result };
}
return { isPassed: true };
};
export { guardAccess };