fix: disable api imports if not in list

This commit is contained in:
zk
2026-06-02 16:26:32 +00:00
parent 24d1848df8
commit 15c522840d
2 changed files with 65 additions and 3 deletions
@@ -6,6 +6,52 @@ import { afterAll, describe, expect, it } from "vitest";
afterAll(() => CloseServerConnection());
describe("POST /api/v1/import/from-api", () => {
it("returns 400 when the import type is not enabled on this instance", async () => {
const { id: userId } = await seedUser({
username: "from_api_disabled_type",
withCredential: true,
withSettings: true,
});
await seedApiToken({
token: "from_api_disabled",
userId,
submitScore: true,
});
const res = await mockApi
.post("/api/v1/import/from-api")
.set("Authorization", "Bearer from_api_disabled")
.send({ importType: "api/cg-dev-sdvx" });
expect(res.status).toBe(400);
expect(res.body.success).toBe(false);
expect(String(res.body.description)).toMatch(/not enabled on this instance/iu);
});
it("returns 400 for non-API import types", async () => {
const { id: userId } = await seedUser({
username: "from_api_wrong_type",
withCredential: true,
withSettings: true,
});
await seedApiToken({
token: "from_api_wrong",
userId,
submitScore: true,
});
const res = await mockApi
.post("/api/v1/import/from-api")
.set("Authorization", "Bearer from_api_wrong")
.send({ importType: "file/batch-manual" });
expect(res.status).toBe(400);
expect(res.body.success).toBe(false);
expect(String(res.body.description)).toMatch(/Invalid import type/iu);
});
});
describe("import/orphans auth", () => {
it("returns 403 for unauthenticated GET list", async () => {
const res = await mockApi.get("/api/v1/import/orphans");
@@ -20,7 +20,8 @@ import { Random20Hex } from "#utils/misc";
import { FormatUserDoc, GetUserWithIDGuaranteed } from "#utils/user";
import { ExpectedErr } from "bliss";
import { p } from "prudence";
import { type FileUploadImportTypes, type V3Game } from "tachi-common";
import { type APIImportTypes, type FileUploadImportTypes, type V3Game } from "tachi-common";
import { apiImportTypes } from "tachi-common/constants/import-types";
import { API_V1_ROUTER } from "../_singleton";
@@ -28,6 +29,22 @@ const ParseMultipartScoredata = CreateMulterSingleUploadMiddleware("scoreData",
const fileImportTypes = TachiConfig.IMPORT_TYPES.filter((e) => e.startsWith("file/"));
const enabledApiImportTypes = TachiConfig.IMPORT_TYPES.filter((e): e is APIImportTypes =>
e.startsWith("api/"),
);
function assertEnabledApiImportType(importType: string): APIImportTypes {
if (!apiImportTypes.includes(importType as APIImportTypes)) {
throw new ExpectedErr(400, `Invalid import type: ${importType}`);
}
if (!enabledApiImportTypes.includes(importType as APIImportTypes)) {
throw new ExpectedErr(400, `Import type "${importType}" is not enabled on this instance.`);
}
return importType as APIImportTypes;
}
/**
* Import scores from a file. Expects the post request to be multipart,
* and to provide a scoreData file.
@@ -91,8 +108,7 @@ API_V1_ROUTER.rawAdd(
* @name POST /api/v1/import/from-api
*/
API_V1_ROUTER.add("POST /import/from-api", async ({ input, req }) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const importType = input.importType as any;
const importType = assertEnabledApiImportType(input.importType);
const importID = Random20Hex();
const userID = req[SYMBOL_TACHI_API_AUTH].userID!;
const userIntent = req.header("X-User-Intent")?.toLowerCase() === "true";