mirror of
https://github.com/zkldi/Tachi.git
synced 2026-10-07 22:38:02 +03:00
Vendored
+5
-1
@@ -10,10 +10,14 @@
|
||||
EAG_API_URL: "https://eag.example.com",
|
||||
MIN_API_URL: "https://min.example.com",
|
||||
ARC_API_URL: "https://arc.example.com",
|
||||
FLO_OAUTH2_INFO: {
|
||||
CLIENT_ID: "DUMMY_CLIENT_ID",
|
||||
CLIENT_SECRET: "DUMMY_CLIENT_SECRET",
|
||||
},
|
||||
ARC_AUTH_TOKEN: "unused",
|
||||
CDN_FILE_ROOT: "./local-cdn",
|
||||
PORT: 8080,
|
||||
TYPE: "omni",
|
||||
ENABLE_SERVER_HTTPS: false,
|
||||
RUN_OWN_CDN: true
|
||||
RUN_OWN_CDN: true,
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ export async function ParseArcDDR(
|
||||
`/api/v1/ddr/16/player_bests?profile_id=${authDoc.accountID}`,
|
||||
ServerConfig.ARC_AUTH_TOKEN,
|
||||
logger,
|
||||
null,
|
||||
fetch
|
||||
),
|
||||
context: {},
|
||||
|
||||
@@ -22,6 +22,7 @@ export async function ParseArcIIDX(
|
||||
`/api/v1/iidx/27/player_bests?profile_id=${authDoc.accountID}`,
|
||||
ServerConfig.ARC_AUTH_TOKEN,
|
||||
logger,
|
||||
null,
|
||||
fetch
|
||||
),
|
||||
context: {},
|
||||
|
||||
@@ -21,6 +21,7 @@ export async function ParseArcSDVX(
|
||||
`/api/v1/sdvx/5/player_bests?profile_id=${authDoc.accountID}`,
|
||||
ServerConfig.ARC_AUTH_TOKEN,
|
||||
logger,
|
||||
null,
|
||||
fetch
|
||||
),
|
||||
context: {},
|
||||
|
||||
@@ -4,7 +4,7 @@ import t from "tap";
|
||||
import { CloseAllConnections } from "test-utils/close-connections";
|
||||
import { MockBasicFetch, MockJSONFetch } from "test-utils/mock-fetch";
|
||||
import ResetDBState from "test-utils/resets";
|
||||
import { KaiTypeToBaseURL } from "utils/misc";
|
||||
import { KaiTypeToBaseURL } from "../utils";
|
||||
import { CreateKaiIIDXClassHandler } from "./class-handler";
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { IIDXDans } from "lib/constants/classes";
|
||||
import { ClassHandler } from "lib/score-import/framework/user-game-stats/types";
|
||||
import nodeFetch from "utils/fetch";
|
||||
import { KaiTypeToBaseURL } from "utils/misc";
|
||||
import { KaiTypeToBaseURL } from "../utils";
|
||||
|
||||
export async function CreateKaiIIDXClassHandler(
|
||||
kaiType: "FLO" | "EAG",
|
||||
|
||||
@@ -4,8 +4,9 @@ import { KaiAuthDocument } from "tachi-common";
|
||||
import { KaiContext } from "../types";
|
||||
import { TraverseKaiAPI } from "../traverse-api";
|
||||
import { ParserFunctionReturns } from "../../types";
|
||||
import { KaiTypeToBaseURL } from "utils/misc";
|
||||
import { KaiTypeToBaseURL } from "../utils";
|
||||
import { CreateKaiIIDXClassHandler } from "./class-handler";
|
||||
import { CreateKaiReauthFunction } from "../reauth";
|
||||
|
||||
export async function ParseKaiIIDX(
|
||||
service: "FLO" | "EAG",
|
||||
@@ -21,6 +22,7 @@ export async function ParseKaiIIDX(
|
||||
"/api/iidx/v2/play_history",
|
||||
authDoc.token,
|
||||
logger,
|
||||
CreateKaiReauthFunction(service, authDoc, logger, fetch),
|
||||
fetch
|
||||
),
|
||||
context: {
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
import deepmerge from "deepmerge";
|
||||
import db from "external/mongo/db";
|
||||
import CreateLogCtx from "lib/logger/logger";
|
||||
import { ServerConfig } from "lib/setup/config";
|
||||
import { KaiAuthDocument } from "tachi-common";
|
||||
import t from "tap";
|
||||
import { CloseAllConnections } from "test-utils/close-connections";
|
||||
import { MockJSONFetch } from "test-utils/mock-fetch";
|
||||
import ResetDBState from "test-utils/resets";
|
||||
import { CreateKaiReauthFunction } from "./reauth";
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
const authDoc: KaiAuthDocument = {
|
||||
refreshToken: "REFRESH_TOKEN",
|
||||
service: "FLO",
|
||||
token: "foobar",
|
||||
userID: 1,
|
||||
};
|
||||
|
||||
t.test("#CreateKaiReauthFunction", (t) => {
|
||||
t.beforeEach(ResetDBState);
|
||||
// eslint-disable-next-line no-return-await
|
||||
t.beforeEach(async () => await db["kai-auth-tokens"].remove({}));
|
||||
|
||||
if (!ServerConfig.FLO_OAUTH2_INFO) {
|
||||
throw new Error(
|
||||
`Panic in test - No dummy FLO_OAUTH2_INFO configured, and the test depends on some dummy data here.`
|
||||
);
|
||||
}
|
||||
|
||||
t.test("Should create a working reauthentication for the service.", async (t) => {
|
||||
const mockFetch = MockJSONFetch({
|
||||
[`${ServerConfig.FLO_API_URL}/oauth/token?refresh_token=${
|
||||
authDoc.refreshToken
|
||||
}&grant_type=refresh_token&client_secret=${
|
||||
ServerConfig.FLO_OAUTH2_INFO!.CLIENT_SECRET
|
||||
}&client_id=${ServerConfig.FLO_OAUTH2_INFO!.CLIENT_ID}`]: {
|
||||
refresh_token: "NEW_REFRESH_TOKEN",
|
||||
access_token: "NEW_ACCESS_TOKEN",
|
||||
},
|
||||
});
|
||||
|
||||
await db["kai-auth-tokens"].insert(deepmerge(authDoc, {}));
|
||||
|
||||
const reauthFn = CreateKaiReauthFunction("FLO", authDoc, logger, mockFetch);
|
||||
|
||||
t.equal(reauthFn.length, 0, "Should return a function with arity 0.");
|
||||
|
||||
const data = await reauthFn();
|
||||
|
||||
t.equal(data, "NEW_ACCESS_TOKEN");
|
||||
|
||||
const dbChange = await db["kai-auth-tokens"].findOne({
|
||||
userID: 1,
|
||||
service: "FLO",
|
||||
});
|
||||
|
||||
// should also update the db
|
||||
t.equal(dbChange?.refreshToken, "NEW_REFRESH_TOKEN");
|
||||
t.equal(dbChange?.token, "NEW_ACCESS_TOKEN");
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test("Should throw on fetch error.", async (t) => {
|
||||
// will fail
|
||||
const mockFetch = MockJSONFetch({});
|
||||
|
||||
await db["kai-auth-tokens"].insert(deepmerge(authDoc, {}));
|
||||
|
||||
const reauthFn = CreateKaiReauthFunction("FLO", authDoc, logger, mockFetch);
|
||||
|
||||
t.rejects(() => reauthFn(), {
|
||||
message: "An error has occured while attempting reauthentication.",
|
||||
});
|
||||
|
||||
const dbChange = await db["kai-auth-tokens"].findOne({
|
||||
userID: 1,
|
||||
service: "FLO",
|
||||
});
|
||||
|
||||
t.equal(
|
||||
dbChange?.refreshToken,
|
||||
authDoc.refreshToken,
|
||||
"DB should not be changed for refreshToken."
|
||||
);
|
||||
t.equal(dbChange?.token, authDoc.token, "DB should not be changed for token.");
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test("Should throw on invalid JSON response", async (t) => {
|
||||
const mockFetch = MockJSONFetch({
|
||||
[`${ServerConfig.FLO_API_URL}/oauth/token?refresh_token=${
|
||||
authDoc.refreshToken
|
||||
}&grant_type=refresh_token&client_secret=${
|
||||
ServerConfig.FLO_OAUTH2_INFO!.CLIENT_SECRET
|
||||
}&client_id=${ServerConfig.FLO_OAUTH2_INFO!.CLIENT_ID}`]: {
|
||||
// missing refresh_token
|
||||
access_token: "NEW_ACCESS_TOKEN",
|
||||
},
|
||||
});
|
||||
|
||||
await db["kai-auth-tokens"].insert(deepmerge(authDoc, {}));
|
||||
|
||||
const reauthFn = CreateKaiReauthFunction("FLO", authDoc, logger, mockFetch);
|
||||
|
||||
t.rejects(() => reauthFn(), {
|
||||
message: "An error has occured while attempting reauthentication.",
|
||||
});
|
||||
|
||||
const dbChange = await db["kai-auth-tokens"].findOne({
|
||||
userID: 1,
|
||||
service: "FLO",
|
||||
});
|
||||
|
||||
t.equal(
|
||||
dbChange?.refreshToken,
|
||||
authDoc.refreshToken,
|
||||
"DB should not be changed for refreshToken."
|
||||
);
|
||||
t.equal(dbChange?.token, authDoc.token, "DB should not be changed for token.");
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.teardown(CloseAllConnections);
|
||||
@@ -0,0 +1,97 @@
|
||||
import db from "external/mongo/db";
|
||||
import { KtLogger } from "lib/logger/logger";
|
||||
import ScoreImportFatalError from "lib/score-import/framework/score-importing/score-import-error";
|
||||
import { GetKaiTypeClientCredentials, KaiTypeToBaseURL } from "./utils";
|
||||
import nodeFetch from "utils/fetch";
|
||||
import p from "prudence";
|
||||
import { KaiAuthDocument } from "tachi-common";
|
||||
|
||||
const REAUTH_SCHEMA = {
|
||||
access_token: "string",
|
||||
refresh_token: "string",
|
||||
};
|
||||
|
||||
export function CreateKaiReauthFunction(
|
||||
kaiType: "FLO" | "EAG" | "MIN",
|
||||
authDoc: KaiAuthDocument,
|
||||
logger: KtLogger,
|
||||
fetch = nodeFetch
|
||||
) {
|
||||
const maybeCredentials = GetKaiTypeClientCredentials(kaiType);
|
||||
|
||||
/* istanbul ignore next */
|
||||
if (!maybeCredentials) {
|
||||
logger.error(
|
||||
`No CLIENT_ID or CLIENT_SECRET was configured for ${kaiType}. Cannot create reauth function.`
|
||||
);
|
||||
throw new ScoreImportFatalError(
|
||||
500,
|
||||
`Fatal error in performing authentication. This has been reported.`
|
||||
);
|
||||
}
|
||||
|
||||
const { CLIENT_ID, CLIENT_SECRET } = maybeCredentials;
|
||||
|
||||
return async () => {
|
||||
let res;
|
||||
try {
|
||||
res = await fetch(
|
||||
`${KaiTypeToBaseURL(kaiType)}/oauth/token?refresh_token=${
|
||||
authDoc.refreshToken
|
||||
}&grant_type=refresh_token&client_secret=${CLIENT_SECRET}&client_id=${CLIENT_ID}`
|
||||
);
|
||||
} catch (err) {
|
||||
logger.error(`Unexpected error while fetching reauth?`, { res, err });
|
||||
throw new ScoreImportFatalError(
|
||||
500,
|
||||
"An error has occured while attempting reauthentication."
|
||||
);
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
if (res.status !== 200) {
|
||||
logger.error(`Unexpected ${res.status} error while fetching reauth?`, { res });
|
||||
throw new ScoreImportFatalError(
|
||||
500,
|
||||
"An error has occured while attempting reauthentication."
|
||||
);
|
||||
}
|
||||
|
||||
let json;
|
||||
/* istanbul ignore next */
|
||||
try {
|
||||
json = await res.json();
|
||||
} catch (err) {
|
||||
logger.error(`Invalid JSON body in successful reauth response.`, { res, err });
|
||||
throw new ScoreImportFatalError(
|
||||
500,
|
||||
"An error has occured while attempting reauthentication."
|
||||
);
|
||||
}
|
||||
|
||||
const err = p(json, REAUTH_SCHEMA, {}, { allowExcessKeys: true, throwOnNonObject: false });
|
||||
|
||||
if (err) {
|
||||
logger.error(`Invalid JSON body in successful reauth response.`, { err, json });
|
||||
throw new ScoreImportFatalError(
|
||||
500,
|
||||
"An error has occured while attempting reauthentication."
|
||||
);
|
||||
}
|
||||
|
||||
await db["kai-auth-tokens"].update(
|
||||
{
|
||||
userID: authDoc.userID,
|
||||
service: authDoc.service,
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
refreshToken: json.refresh_token,
|
||||
token: json.access_token,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return json.access_token;
|
||||
};
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import t from "tap";
|
||||
import { CloseAllConnections } from "test-utils/close-connections";
|
||||
import { MockBasicFetch, MockJSONFetch } from "test-utils/mock-fetch";
|
||||
import ResetDBState from "test-utils/resets";
|
||||
import { KaiTypeToBaseURL } from "utils/misc";
|
||||
import { KaiTypeToBaseURL } from "../utils";
|
||||
import { CreateKaiSDVXClassHandler } from "./class-handler";
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { SDVXDans } from "lib/constants/classes";
|
||||
import { ClassHandler } from "lib/score-import/framework/user-game-stats/types";
|
||||
import nodeFetch from "utils/fetch";
|
||||
import { KaiTypeToBaseURL } from "utils/misc";
|
||||
import { KaiTypeToBaseURL } from "../utils";
|
||||
|
||||
export async function CreateKaiSDVXClassHandler(
|
||||
kaiType: "FLO" | "EAG" | "MIN",
|
||||
|
||||
@@ -4,8 +4,9 @@ import { KaiAuthDocument } from "tachi-common";
|
||||
import { KaiContext } from "../types";
|
||||
import { TraverseKaiAPI } from "../traverse-api";
|
||||
import { ParserFunctionReturns } from "../../types";
|
||||
import { KaiTypeToBaseURL } from "utils/misc";
|
||||
import { KaiTypeToBaseURL } from "../utils";
|
||||
import { CreateKaiSDVXClassHandler } from "./class-handler";
|
||||
import { CreateKaiReauthFunction } from "../reauth";
|
||||
|
||||
export async function ParseKaiSDVX(
|
||||
service: "FLO" | "EAG" | "MIN",
|
||||
@@ -21,6 +22,7 @@ export async function ParseKaiSDVX(
|
||||
"/api/sdvx/v1/play_history",
|
||||
authDoc.token,
|
||||
logger,
|
||||
CreateKaiReauthFunction(service, authDoc, logger, fetch),
|
||||
fetch
|
||||
),
|
||||
context: {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import t from "tap";
|
||||
import { CloseAllConnections } from "test-utils/close-connections";
|
||||
import { agta } from "test-utils/misc";
|
||||
import { MockJSONFetch } from "test-utils/mock-fetch";
|
||||
import { MockBasicFetch, MockJSONFetch } from "test-utils/mock-fetch";
|
||||
import ResetDBState from "test-utils/resets";
|
||||
import { NodeFetch } from "utils/fetch";
|
||||
import CreateLogCtx from "lib/logger/logger";
|
||||
@@ -31,7 +31,7 @@ t.test("#TraverseKaiAPI", (t) => {
|
||||
},
|
||||
});
|
||||
|
||||
const res = TraverseKaiAPI("http://url.com", "/sub", fakeAuth, logger, mockKaiAPI);
|
||||
const res = TraverseKaiAPI("http://url.com", "/sub", fakeAuth, logger, null, mockKaiAPI);
|
||||
|
||||
const elements = await agta(res);
|
||||
|
||||
@@ -55,7 +55,7 @@ t.test("#TraverseKaiAPI", (t) => {
|
||||
_items: [5, 6],
|
||||
},
|
||||
});
|
||||
const res = TraverseKaiAPI("http://url.com", "/sub", fakeAuth, logger, mockKaiAPI);
|
||||
const res = TraverseKaiAPI("http://url.com", "/sub", fakeAuth, logger, null, mockKaiAPI);
|
||||
|
||||
t.rejects(
|
||||
() => agta(res),
|
||||
@@ -67,7 +67,7 @@ t.test("#TraverseKaiAPI", (t) => {
|
||||
|
||||
t.test("Should throw on invalid response JSON", (t) => {
|
||||
const mockKaiAPI = (() => ({ json: null })) as unknown as NodeFetch;
|
||||
const res = TraverseKaiAPI("http://url.com", "/sub", fakeAuth, logger, mockKaiAPI);
|
||||
const res = TraverseKaiAPI("http://url.com", "/sub", fakeAuth, logger, null, mockKaiAPI);
|
||||
|
||||
t.rejects(() => agta(res));
|
||||
|
||||
@@ -78,7 +78,7 @@ t.test("#TraverseKaiAPI", (t) => {
|
||||
const mockKaiAPI = () => {
|
||||
throw new Error("Fake Request timeout...");
|
||||
};
|
||||
const res = TraverseKaiAPI("http://url.com", "/sub", fakeAuth, logger, mockKaiAPI);
|
||||
const res = TraverseKaiAPI("http://url.com", "/sub", fakeAuth, logger, null, mockKaiAPI);
|
||||
|
||||
t.rejects(() => agta(res));
|
||||
|
||||
@@ -92,7 +92,7 @@ t.test("#TraverseKaiAPI", (t) => {
|
||||
},
|
||||
});
|
||||
|
||||
const res = TraverseKaiAPI("http://url.com", "/sub", fakeAuth, logger, mockKaiAPI);
|
||||
const res = TraverseKaiAPI("http://url.com", "/sub", fakeAuth, logger, null, mockKaiAPI);
|
||||
|
||||
t.rejects(() => agta(res));
|
||||
|
||||
@@ -102,7 +102,7 @@ t.test("#TraverseKaiAPI", (t) => {
|
||||
},
|
||||
});
|
||||
|
||||
const res2 = TraverseKaiAPI("http://url.com", "/sub", fakeAuth, logger, mockKaiAPI2);
|
||||
const res2 = TraverseKaiAPI("http://url.com", "/sub", fakeAuth, logger, null, mockKaiAPI2);
|
||||
|
||||
t.rejects(() => agta(res2));
|
||||
|
||||
@@ -116,7 +116,7 @@ t.test("#TraverseKaiAPI", (t) => {
|
||||
},
|
||||
});
|
||||
|
||||
const res = TraverseKaiAPI("http://url.com", "/sub", fakeAuth, logger, mockKaiAPI);
|
||||
const res = TraverseKaiAPI("http://url.com", "/sub", fakeAuth, logger, null, mockKaiAPI);
|
||||
|
||||
t.rejects(() => agta(res));
|
||||
|
||||
@@ -128,7 +128,7 @@ t.test("#TraverseKaiAPI", (t) => {
|
||||
},
|
||||
});
|
||||
|
||||
const res2 = TraverseKaiAPI("http://url.com", "/sub", fakeAuth, logger, mockKaiAPI2);
|
||||
const res2 = TraverseKaiAPI("http://url.com", "/sub", fakeAuth, logger, null, mockKaiAPI2);
|
||||
|
||||
t.rejects(() => agta(res2));
|
||||
|
||||
@@ -145,13 +145,60 @@ t.test("#TraverseKaiAPI", (t) => {
|
||||
},
|
||||
});
|
||||
|
||||
const res = TraverseKaiAPI("http://url.com", "/sub", fakeAuth, logger, mockKaiAPI);
|
||||
const res = TraverseKaiAPI("http://url.com", "/sub", fakeAuth, logger, null, mockKaiAPI);
|
||||
|
||||
t.rejects(() => agta(res));
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test("Should throw on infinite loop", (t) => {
|
||||
const mockKaiAPI = MockJSONFetch({
|
||||
"http://url.com/sub": {
|
||||
_links: {
|
||||
_next: "http://url.com/sub",
|
||||
},
|
||||
_items: [1, 2, 3, 4],
|
||||
},
|
||||
});
|
||||
|
||||
const res = TraverseKaiAPI("http://url.com", "/sub", fakeAuth, logger, null, mockKaiAPI);
|
||||
|
||||
t.rejects(() => agta(res));
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test("Should attempt reauthentication if authentication fails.", async (t) => {
|
||||
const mockKaiAPI = MockBasicFetch({ status: 401 });
|
||||
|
||||
let hasAttemptedReauth = false;
|
||||
|
||||
const res = TraverseKaiAPI(
|
||||
"http://url.com",
|
||||
"/sub",
|
||||
fakeAuth,
|
||||
logger,
|
||||
// eslint-disable-next-line require-await
|
||||
async () => {
|
||||
hasAttemptedReauth = true;
|
||||
return "bar";
|
||||
},
|
||||
mockKaiAPI
|
||||
);
|
||||
|
||||
await t.rejects(
|
||||
// not redundant here
|
||||
// eslint-disable-next-line no-return-await
|
||||
async () => await agta(res),
|
||||
"Should correctly bail out of an endless reauth loop."
|
||||
);
|
||||
|
||||
t.equal(hasAttemptedReauth, true, "Should have called the reauth function.");
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
|
||||
@@ -4,9 +4,32 @@ import nodeFetch from "utils/fetch";
|
||||
import { VERSION_STR } from "lib/constants/version";
|
||||
import { ServerTypeInfo } from "lib/setup/config";
|
||||
|
||||
/**
|
||||
* A Kai Reauth function is an async function that returns a string
|
||||
* which represents the new token to use, OR throws a score import
|
||||
* fatal error if reauthentication fails.
|
||||
*/
|
||||
export type KaiAPIReauthFunction = () => Promise<string>;
|
||||
|
||||
/**
|
||||
* there is the (strange) case here where we could end up
|
||||
* infinitely requesting re-auth and, despite succeeding,
|
||||
* continually failing on new requests.
|
||||
* in this case, we check if we've exceeded the maximum amount of
|
||||
* reauths, and will throw if it is exceeded.
|
||||
*/
|
||||
const MAX_REAUTH_ATTEMPTS = 1;
|
||||
|
||||
/**
|
||||
* Defines the absolute maximum amount of loops this function can do.
|
||||
* A maliciously crafted API can cause us to enter an infinite loop and
|
||||
* blow up - we don't want that.
|
||||
*/
|
||||
const MAX_ITERATIONS = 500;
|
||||
|
||||
/**
|
||||
* Traverses a Kai-like personal_bests api.
|
||||
* @param baseUrl The base URL to fetch requests from. - https://google.com
|
||||
* @param baseUrl The base URL to fetch requests from. - like 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
|
||||
@@ -17,11 +40,15 @@ export async function* TraverseKaiAPI(
|
||||
subUrl: string,
|
||||
token: string,
|
||||
logger: KtLogger,
|
||||
reauthFunction: KaiAPIReauthFunction | null = null,
|
||||
fetch = nodeFetch
|
||||
) {
|
||||
let fetchMoreData = true;
|
||||
let url = `${baseUrl}${subUrl}`;
|
||||
|
||||
let reauthAttempts = 0;
|
||||
let currentIteration = 0;
|
||||
|
||||
while (fetchMoreData) {
|
||||
// Rough SSRF check - KAI-like apis could potentially get us to perform
|
||||
// SSRF. We check if the origin of the next request is exactly the
|
||||
@@ -35,21 +62,56 @@ export async function* TraverseKaiAPI(
|
||||
}
|
||||
|
||||
let json;
|
||||
let res;
|
||||
// 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, {
|
||||
res = await fetch(url, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"User-Agent": `${ServerTypeInfo.name}/${VERSION_STR}`,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error(`Recieved invalid response from ${url}.`, { err });
|
||||
throw new ScoreImportFatalError(
|
||||
500,
|
||||
`Recieved invalid response from ${url}. Are they down?`
|
||||
);
|
||||
}
|
||||
|
||||
// if we are unauthorised or forbidden we need to attempt
|
||||
// reauth
|
||||
if (res.status === 401 || res.status === 403) {
|
||||
reauthAttempts++;
|
||||
if (reauthAttempts > MAX_REAUTH_ATTEMPTS) {
|
||||
throw new ScoreImportFatalError(
|
||||
500,
|
||||
`Attempted to reauthenticate with ${baseUrl} more than ${MAX_REAUTH_ATTEMPTS} times. The problem is likely not on our end.`
|
||||
);
|
||||
}
|
||||
|
||||
if (!reauthFunction) {
|
||||
throw new ScoreImportFatalError(
|
||||
res.status,
|
||||
`Unable to authenticate with ${baseUrl} and reauthentication was not possible.`
|
||||
);
|
||||
}
|
||||
|
||||
// reassigning this param is fine.
|
||||
// eslint-disable-next-line no-param-reassign, no-await-in-loop
|
||||
token = await reauthFunction();
|
||||
|
||||
// then go to the start of the while loop.
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
json = await res.json();
|
||||
} catch (err) {
|
||||
logger.error(`Recieved invalid response from ${url}.`, { err });
|
||||
logger.error(`Recieved invalid (non-json) response from ${url}.`, { err });
|
||||
throw new ScoreImportFatalError(
|
||||
500,
|
||||
`Recieved invalid response from ${url}. Are they down?`
|
||||
@@ -65,6 +127,18 @@ export async function* TraverseKaiAPI(
|
||||
);
|
||||
}
|
||||
|
||||
currentIteration++;
|
||||
|
||||
if (currentIteration > MAX_ITERATIONS) {
|
||||
logger.error(
|
||||
`An infinite loop has occured - Terminating at MAX_ITERATIONS (${MAX_ITERATIONS}).`
|
||||
);
|
||||
throw new ScoreImportFatalError(
|
||||
508,
|
||||
"An infinite loop appears to have occured while synchronising with this api."
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof json._links._next === "string") {
|
||||
url = json._links._next;
|
||||
} else if (json._links._next === null) {
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ServerConfig } from "lib/setup/config";
|
||||
|
||||
export function KaiTypeToBaseURL(kaiType: "FLO" | "EAG" | "MIN") {
|
||||
if (kaiType === "FLO") {
|
||||
return ServerConfig.FLO_API_URL;
|
||||
} else if (kaiType === "EAG") {
|
||||
return ServerConfig.EAG_API_URL;
|
||||
} else if (kaiType === "MIN") {
|
||||
return ServerConfig.MIN_API_URL;
|
||||
}
|
||||
|
||||
throw new Error(`Invalid Kai Type ${kaiType} provided.`);
|
||||
}
|
||||
|
||||
export function GetKaiTypeClientCredentials(kaiType: "FLO" | "EAG" | "MIN") {
|
||||
if (kaiType === "FLO") {
|
||||
return ServerConfig.FLO_OAUTH2_INFO;
|
||||
} else if (kaiType === "EAG") {
|
||||
return ServerConfig.EAG_OAUTH2_INFO;
|
||||
}
|
||||
|
||||
return ServerConfig.MIN_OAUTH2_INFO;
|
||||
}
|
||||
@@ -37,7 +37,7 @@ function isValidURL(self: unknown) {
|
||||
}
|
||||
}
|
||||
|
||||
interface OAuth2Info {
|
||||
export interface OAuth2Info {
|
||||
CLIENT_ID: string;
|
||||
CLIENT_SECRET: string;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ export function MockJSONFetch(urlDataMap: Record<string, unknown>) {
|
||||
return (async (url: string) => {
|
||||
if (urlDataMap[url]) {
|
||||
return {
|
||||
status: 200,
|
||||
json: async () => urlDataMap[url],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -75,16 +75,4 @@ export function IsString(val: unknown): val is string {
|
||||
|
||||
export function DedupeArr<T>(arr: T[]): T[] {
|
||||
return [...new Set(arr)];
|
||||
}
|
||||
|
||||
export function KaiTypeToBaseURL(kaiType: "FLO" | "EAG" | "MIN") {
|
||||
if (kaiType === "FLO") {
|
||||
return ServerConfig.FLO_API_URL;
|
||||
} else if (kaiType === "EAG") {
|
||||
return ServerConfig.EAG_API_URL;
|
||||
} else if (kaiType === "MIN") {
|
||||
return ServerConfig.MIN_API_URL;
|
||||
}
|
||||
|
||||
throw new Error(`Invalid Kai Type ${kaiType} provided.`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user