kai-api traverser

This commit is contained in:
zkldi
2021-05-20 07:32:04 +01:00
parent 7196ce0e9b
commit 55c34ca4fe
@@ -0,0 +1,86 @@
import { KtLogger } from "../../../../types";
import ScoreImportFatalError from "../../../framework/score-importing/score-import-error";
import nodeFetch from "../../../../fetch";
import { KaiAuthDocument } from "kamaitachi-common";
/**
* Traverses a Kai-like personal_bests api.
* @param baseUrl The base URL to fetch requests from. - https://google.com
* @param subUrl The endpoint for the scores - /api/v1/personal_bests
* @param authDoc The users' authentication document.
* @param fetch This is so `fetch` can be mocked with something
* that wont make http requests during tests.
*/
export async function* TraverseKaiAPI(
baseUrl: string,
subUrl: string,
authDoc: KaiAuthDocument,
logger: KtLogger,
fetch = nodeFetch
) {
let fetchMoreData = true;
let url = `${baseUrl}${subUrl}`;
while (fetchMoreData) {
// Rough SSRF check - KAI apis could potentially get us to perform
// SSRF. We check if the origin of the next request is exactly the
// original, expected, base URL.
const origin = new URL(url).origin;
if (origin !== baseUrl) {
// this might be critical, to be honest.
logger.severe(`${baseUrl} attempted SSRF with url ${url}?`);
throw new ScoreImportFatalError(500, `${baseUrl} returned invalid data.`);
}
let json;
// wrap all this in a try catch just incase the fetch or the res
// json call fails.
try {
// eslint-disable-next-line no-await-in-loop
const res = await fetch(url, {
headers: {
Authorization: `Bearer ${authDoc.token}`,
},
});
// eslint-disable-next-line no-await-in-loop
json = await res.json();
} catch (err) {
logger.error(`Recieved invalid response from ${url}.`, { err });
throw new ScoreImportFatalError(
500,
`Recieved invalid response from ${url}. Are they down?`
);
}
if (json._links === null || typeof json._links !== "object") {
logger.error(`Recieved invalid JSON from ${url}. Invalid _links.`);
throw new ScoreImportFatalError(
500,
`Recieved no _links prop from ${url}. This is not an error with Kamaitachi.`
);
}
if (typeof json._links._next === "string") {
url = json._links._next;
} else if (json._links._next === null) {
// exit the loop after this, we're on the last page.
fetchMoreData = false;
} else {
logger.error(`Recieved invalid response from ${url}. Invalid _links._next.`);
throw new ScoreImportFatalError(500, `Recieved invalid _links._next prop from ${url}.`);
}
if (!Array.isArray(json._items)) {
throw new ScoreImportFatalError(500, `Recieved invalid _items from ${url}.`);
}
// yield everything out of the score array
for (const score of json._items) {
yield score as unknown;
}
}
}