diff --git a/backend/api-renamed/gist.js b/backend/api-renamed/gist.js index ab14a7a3..e7be76b4 100644 --- a/backend/api-renamed/gist.js +++ b/backend/api-renamed/gist.js @@ -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)) { diff --git a/backend/api-renamed/pin.js b/backend/api-renamed/pin.js index 4151fb25..d7f18ea8 100644 --- a/backend/api-renamed/pin.js +++ b/backend/api-renamed/pin.js @@ -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)) { diff --git a/backend/api-renamed/top-langs.js b/backend/api-renamed/top-langs.js index f13c6263..efb0de9c 100644 --- a/backend/api-renamed/top-langs.js +++ b/backend/api-renamed/top-langs.js @@ -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)) { diff --git a/backend/api-renamed/wakatime.js b/backend/api-renamed/wakatime.js index 68b97ff4..5ea41255 100644 --- a/backend/api-renamed/wakatime.js +++ b/backend/api-renamed/wakatime.js @@ -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)) { diff --git a/backend/src/common/access.js b/backend/src/common/access.js new file mode 100644 index 00000000..959e17af --- /dev/null +++ b/backend/src/common/access.js @@ -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 };