api/api-keys: add allowedServices to limit or extend access

it's useful for limiting access to services per key, or for overriding default list of enabled services with "all"
This commit is contained in:
wukko 2025-06-26 22:20:09 +06:00
parent d69100c68d
commit 3243564f77
No known key found for this signature in database
GPG Key ID: 3E30B3F26C7B4AA2
4 changed files with 44 additions and 5 deletions

View File

@ -245,7 +245,10 @@ export const runAPI = async (express, app, __dirname, isPrimary = true) => {
return fail(res, "error.api.invalid_body");
}
const parsed = extract(normalizedRequest.url);
const parsed = extract(
normalizedRequest.url,
APIKeys.getAllowedServices(req.rateLimitKey),
);
if (!parsed) {
return fail(res, "error.api.link.invalid");

View File

@ -11,6 +11,7 @@ const forceLocalProcessingOptions = ["never", "session", "always"];
const youtubeHlsOptions = ["never", "key", "always"];
export const loadEnvs = (env = process.env) => {
const allServices = new Set(Object.keys(services));
const disabledServices = env.DISABLED_SERVICES?.split(',') || [];
const enabledServices = new Set(Object.keys(services).filter(e => {
if (!disabledServices.includes(e)) {
@ -64,6 +65,7 @@ export const loadEnvs = (env = process.env) => {
instanceCount: (env.API_INSTANCE_COUNT && parseInt(env.API_INSTANCE_COUNT)) || 1,
keyReloadInterval: 900,
allServices,
enabledServices,
customInnertubeClient: env.CUSTOM_INNERTUBE_CLIENT,

View File

@ -199,7 +199,7 @@ export function normalizeURL(url) {
);
}
export function extract(url) {
export function extract(url, enabledServices = env.enabledServices) {
if (!(url instanceof URL)) {
url = new URL(url);
}
@ -210,7 +210,7 @@ export function extract(url) {
return { error: "link.invalid" };
}
if (!env.enabledServices.has(host)) {
if (!enabledServices.has(host)) {
// show a different message when youtube is disabled on official instances
// as it only happens when shit hits the fan
if (new URL(env.apiURL).hostname.endsWith(".imput.net") && host === "youtube") {

View File

@ -15,7 +15,7 @@ const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12
let keys = {}, reader = null;
const ALLOWED_KEYS = new Set(['name', 'ips', 'userAgents', 'limit']);
const ALLOWED_KEYS = new Set(['name', 'ips', 'userAgents', 'limit', 'allowedServices']);
/* Expected format pseudotype:
** type KeyFileContents = Record<
@ -24,7 +24,8 @@ const ALLOWED_KEYS = new Set(['name', 'ips', 'userAgents', 'limit']);
** name?: string,
** limit?: number | "unlimited",
** ips?: CIDRString[],
** userAgents?: string[]
** userAgents?: string[],
** allowedServices?: "all" | string[],
** }
** >;
*/
@ -77,6 +78,19 @@ const validateKeys = (input) => {
throw "`userAgents` in details contains an invalid user agent: " + invalid_ua;
}
}
if (details.allowedServices) {
const isArray = Array.isArray(details.allowedServices);
if (isArray) {
const invalid_services = details.allowedServices.find(service => !env.allServices.has(service));
if (invalid_services) {
throw "`allowedServices` in details contains an invalid service";
}
} else if (details.allowedServices !== "all") {
throw "details object contains value for `allowedServices` which is not an array or `all`";
}
}
});
}
@ -112,6 +126,14 @@ const formatKeys = (keyData) => {
if (data.userAgents) {
formatted[key].userAgents = data.userAgents.map(generateWildcardRegex);
}
if (data.allowedServices) {
if (Array.isArray(data.allowedServices)) {
formatted[key].allowedServices = new Set(data.allowedServices);
} else {
formatted[key].allowedServices = data.allowedServices;
}
}
}
return formatted;
@ -230,3 +252,15 @@ export const setup = (url) => {
});
}
}
export const getAllowedServices = (key) => {
if (typeof key !== "string") return;
const allowedServices = keys[key.toLowerCase()]?.allowedServices;
if (!allowedServices) return;
if (allowedServices === "all") {
return env.allServices;
}
return allowedServices;
}