|
@@ -25,6 +25,7 @@ const rateLimiter = new RateLimiterMemory({
|
|
duration: 10,
|
|
duration: 10,
|
|
});
|
|
});
|
|
|
|
|
|
|
|
+const authTokens = getAuthTokens();
|
|
|
|
|
|
export async function init()
|
|
export async function init()
|
|
{
|
|
{
|
|
@@ -47,16 +48,22 @@ export async function init()
|
|
// rate limiting
|
|
// rate limiting
|
|
app.use(async (req, res, next) => {
|
|
app.use(async (req, res, next) => {
|
|
const fmt = req?.query?.format ? String(req.query.format) : undefined;
|
|
const fmt = req?.query?.format ? String(req.query.format) : undefined;
|
|
|
|
+ const { authorization } = req.headers;
|
|
|
|
+ const authHeader = authorization?.startsWith("Bearer") ? authorization.substring(7) : authorization;
|
|
|
|
|
|
res.setHeader("API-Info", `geniURL v${packageJson.version} (${packageJson.homepage})`);
|
|
res.setHeader("API-Info", `geniURL v${packageJson.version} (${packageJson.homepage})`);
|
|
|
|
|
|
|
|
+ if(authHeader && authTokens.has(authHeader))
|
|
|
|
+ return next();
|
|
|
|
+
|
|
rateLimiter.consume(req.ip)
|
|
rateLimiter.consume(req.ip)
|
|
.catch((err) => {
|
|
.catch((err) => {
|
|
if(err instanceof RateLimiterRes) {
|
|
if(err instanceof RateLimiterRes) {
|
|
res.set("Retry-After", String(Math.ceil(err.msBeforeNext / 1000)));
|
|
res.set("Retry-After", String(Math.ceil(err.msBeforeNext / 1000)));
|
|
return respond(res, 429, { message: "You are being rate limited" }, fmt);
|
|
return respond(res, 429, { message: "You are being rate limited" }, fmt);
|
|
}
|
|
}
|
|
- else return respond(res, 500, { message: "Internal error in rate limiting middleware. Please try again later." }, fmt);
|
|
|
|
|
|
+ else
|
|
|
|
+ return respond(res, 500, { message: "Internal error in rate limiting middleware. Please try again later." }, fmt);
|
|
})
|
|
})
|
|
.finally(next);
|
|
.finally(next);
|
|
});
|
|
});
|
|
@@ -82,17 +89,21 @@ function registerEndpoints()
|
|
app.get("/search", async (req, res) => {
|
|
app.get("/search", async (req, res) => {
|
|
try
|
|
try
|
|
{
|
|
{
|
|
- const { q, artist, song, format: fmt } = req.query;
|
|
|
|
|
|
+ const { q, artist, song, format: fmt, threshold: thr } = req.query;
|
|
|
|
|
|
- const format = fmt ? String(fmt) : "json";
|
|
|
|
|
|
+ const format: string = fmt ? String(fmt) : "json";
|
|
|
|
+ const threshold = isNaN(Number(thr)) ? undefined : Number(thr);
|
|
|
|
|
|
if(hasArg(q) || (hasArg(artist) && hasArg(song)))
|
|
if(hasArg(q) || (hasArg(artist) && hasArg(song)))
|
|
{
|
|
{
|
|
- const meta = await getMeta(q ? {
|
|
|
|
- q: String(q),
|
|
|
|
- } : {
|
|
|
|
- artist: String(artist),
|
|
|
|
- song: String(song),
|
|
|
|
|
|
+ const meta = await getMeta({
|
|
|
|
+ ...(q ? {
|
|
|
|
+ q: String(q),
|
|
|
|
+ } : {
|
|
|
|
+ artist: String(artist),
|
|
|
|
+ song: String(song),
|
|
|
|
+ }),
|
|
|
|
+ threshold,
|
|
});
|
|
});
|
|
|
|
|
|
if(!meta || meta.all.length < 1)
|
|
if(!meta || meta.all.length < 1)
|
|
@@ -115,17 +126,21 @@ function registerEndpoints()
|
|
app.get("/search/top", async (req, res) => {
|
|
app.get("/search/top", async (req, res) => {
|
|
try
|
|
try
|
|
{
|
|
{
|
|
- const { q, artist, song, format: fmt } = req.query;
|
|
|
|
|
|
+ const { q, artist, song, format: fmt, threshold: thr } = req.query;
|
|
|
|
|
|
- const format = fmt ? String(fmt) : "json";
|
|
|
|
|
|
+ const format: string = fmt ? String(fmt) : "json";
|
|
|
|
+ const threshold = isNaN(Number(thr)) ? undefined : Number(thr);
|
|
|
|
|
|
if(hasArg(q) || (hasArg(artist) && hasArg(song)))
|
|
if(hasArg(q) || (hasArg(artist) && hasArg(song)))
|
|
{
|
|
{
|
|
- const meta = await getMeta(q ? {
|
|
|
|
- q: String(q),
|
|
|
|
- } : {
|
|
|
|
- artist: String(artist),
|
|
|
|
- song: String(song),
|
|
|
|
|
|
+ const meta = await getMeta({
|
|
|
|
+ ...(q ? {
|
|
|
|
+ q: String(q),
|
|
|
|
+ } : {
|
|
|
|
+ artist: String(artist),
|
|
|
|
+ song: String(song),
|
|
|
|
+ }),
|
|
|
|
+ threshold,
|
|
});
|
|
});
|
|
|
|
|
|
if(!meta || !meta.top)
|
|
if(!meta || !meta.top)
|
|
@@ -148,6 +163,12 @@ function registerEndpoints()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+/**
|
|
|
|
+ * Responds to an incoming request
|
|
|
|
+ * @param type Type of response or status code
|
|
|
|
+ * @param data The data to send in the response body
|
|
|
|
+ * @param format json / xml
|
|
|
|
+ */
|
|
function respond(res: Response, type: ResponseType | number, data: Stringifiable | Record<string, unknown>, format = "json", matchesAmt = 0)
|
|
function respond(res: Response, type: ResponseType | number, data: Stringifiable | Record<string, unknown>, format = "json", matchesAmt = 0)
|
|
{
|
|
{
|
|
let statusCode = 500;
|
|
let statusCode = 500;
|
|
@@ -167,7 +188,7 @@ function respond(res: Response, type: ResponseType | number, data: Stringifiable
|
|
error = false;
|
|
error = false;
|
|
matches = matchesAmt;
|
|
matches = matchesAmt;
|
|
statusCode = 200;
|
|
statusCode = 200;
|
|
- resData = { ...data };
|
|
|
|
|
|
+ resData = typeof data === "string" ? data : { ...data };
|
|
break;
|
|
break;
|
|
case "clientError":
|
|
case "clientError":
|
|
error = true;
|
|
error = true;
|
|
@@ -187,7 +208,7 @@ function respond(res: Response, type: ResponseType | number, data: Stringifiable
|
|
error = false;
|
|
error = false;
|
|
matches = matchesAmt ?? 0;
|
|
matches = matchesAmt ?? 0;
|
|
statusCode = type;
|
|
statusCode = type;
|
|
- resData = { ...data };
|
|
|
|
|
|
+ resData = typeof data === "string" ? data : { ...data };
|
|
}
|
|
}
|
|
break;
|
|
break;
|
|
}
|
|
}
|
|
@@ -204,5 +225,18 @@ function respond(res: Response, type: ResponseType | number, data: Stringifiable
|
|
const finalData = format === "xml" ? jsonToXml.parse("data", resData) : resData;
|
|
const finalData = format === "xml" ? jsonToXml.parse("data", resData) : resData;
|
|
|
|
|
|
res.setHeader("Content-Type", mimeType);
|
|
res.setHeader("Content-Type", mimeType);
|
|
- res.status(statusCode).send(finalData);
|
|
|
|
|
|
+ res.status(statusCode)
|
|
|
|
+ .send(finalData);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+function getAuthTokens() {
|
|
|
|
+ const envVal = process.env["AUTH_TOKENS"];
|
|
|
|
+ let tokens: string[] = [];
|
|
|
|
+
|
|
|
|
+ if(!envVal || envVal.length === 0)
|
|
|
|
+ tokens = [];
|
|
|
|
+ else
|
|
|
|
+ tokens = envVal.split(/,/g);
|
|
|
|
+
|
|
|
|
+ return new Set<string>(tokens);
|
|
}
|
|
}
|