From e71efc7c02b449d3d2b68b1fa6883002e22162b3 Mon Sep 17 00:00:00 2001 From: zkldi Date: Fri, 5 Nov 2021 17:00:27 +0000 Subject: [PATCH] Refactor permissions for IRs --- .../ir/barbatos/converter.test.ts | 5 -- server/src/server/middleware/auth.ts | 40 +++++----- .../src/server/router/ir/beatoraja/router.ts | 5 +- .../router/ir/kshook/sv3c/router.test.ts | 74 +++++++++++++++++++ .../server/router/ir/kshook/sv3c/router.ts | 2 + server/src/server/router/ir/router.ts | 23 ++++-- .../src/test-utils/mock-db/charts-sdvx.json | 49 ++++++------ server/src/test-utils/test-data.ts | 3 + .../test-data/kshook-sv3c/base.json | 24 ++++++ .../test-data/tachi/sdvx-albida-chart.json | 45 +++++------ 10 files changed, 188 insertions(+), 82 deletions(-) create mode 100644 server/src/server/router/ir/kshook/sv3c/router.test.ts create mode 100644 server/src/test-utils/test-data/kshook-sv3c/base.json diff --git a/server/src/lib/score-import/import-types/ir/barbatos/converter.test.ts b/server/src/lib/score-import/import-types/ir/barbatos/converter.test.ts index dfc40aa4a..2c9dce86e 100644 --- a/server/src/lib/score-import/import-types/ir/barbatos/converter.test.ts +++ b/server/src/lib/score-import/import-types/ir/barbatos/converter.test.ts @@ -33,11 +33,6 @@ t.test("#ConverterIRBarbatos", (t) => { playtype: "Single", levelNum: 10, level: "10", - flags: { - "IN BASE GAME": true, - OMNIMIX: false, - "N-1": false, - }, data: { inGameID: 1, }, diff --git a/server/src/server/middleware/auth.ts b/server/src/server/middleware/auth.ts index 3ec538751..a21bc2e11 100644 --- a/server/src/server/middleware/auth.ts +++ b/server/src/server/middleware/auth.ts @@ -158,22 +158,28 @@ export const RequirePermissions = return next(); }; -export const RequireNotGuest: RequestHandler = (req, res, next) => { - if (!req[SYMBOL_TachiAPIAuth]) { - logger.error(`RequirePermissions middleware was hit without any TachiAPIData?`); - return res.status(500).json({ - success: false, - description: "An internal error has occured.", - }); - } +const CreateRequireNotGuest = + (errorKeyName: string): RequestHandler => + (req, res, next) => { + if (!req[SYMBOL_TachiAPIAuth]) { + logger.error(`RequirePermissions middleware was hit without any TachiAPIData?`); + return res.status(500).json({ + success: false, + description: "An internal error has occured.", + }); + } - if (!req[SYMBOL_TachiAPIAuth].userID) { - logger.info(`Request to ${req.method} ${req.url} was attempted by guest.`); - return res.status(401).json({ - success: false, - description: "This endpoint requires authentication.", - }); - } + if (req[SYMBOL_TachiAPIAuth].userID === null) { + logger.info(`Request to ${req.method} ${req.url} was attempted by guest.`); + return res.status(401).json({ + success: false, + [errorKeyName]: "This endpoint requires authentication.", + }); + } - return next(); -}; + return next(); + }; + +export const RequireNotGuest: RequestHandler = CreateRequireNotGuest("description"); + +export const FervidexStyleRequireNotGuest: RequestHandler = CreateRequireNotGuest("error"); diff --git a/server/src/server/router/ir/beatoraja/router.ts b/server/src/server/router/ir/beatoraja/router.ts index 93b2d83dc..39e2b8626 100644 --- a/server/src/server/router/ir/beatoraja/router.ts +++ b/server/src/server/router/ir/beatoraja/router.ts @@ -5,6 +5,7 @@ import CreateLogCtx, { KtLogger } from "lib/logger/logger"; import { ExpressWrappedScoreImportMain } from "lib/score-import/framework/express-wrapper"; import { ParseBeatorajaSingle } from "lib/score-import/import-types/ir/beatoraja/parser"; import { ServerConfig } from "lib/setup/config"; +import { RequireNotGuest } from "server/middleware/auth"; import { UpdateClassIfGreater } from "utils/class"; import { GetUserWithIDGuaranteed } from "utils/user"; import { ValidateIRClientVersion } from "./auth"; @@ -21,7 +22,7 @@ router.use(ValidateIRClientVersion); * * @name POST /ir/beatoraja/submit-score */ -router.post("/submit-score", async (req, res) => { +router.post("/submit-score", RequireNotGuest, async (req, res) => { const userDoc = await GetUserWithIDGuaranteed(req[SYMBOL_TachiAPIAuth]!.userID!); const ParserFunction = (logger: KtLogger) => ParseBeatorajaSingle(req.body, userDoc.id, logger); @@ -91,7 +92,7 @@ router.post("/submit-score", async (req, res) => { * * @name POST /ir/beatoraja/submit-course */ -router.post("/submit-course", async (req, res) => { +router.post("/submit-course", RequireNotGuest, async (req, res) => { const charts = req.body.course?.charts; if ( diff --git a/server/src/server/router/ir/kshook/sv3c/router.test.ts b/server/src/server/router/ir/kshook/sv3c/router.test.ts new file mode 100644 index 000000000..f24ac4ac9 --- /dev/null +++ b/server/src/server/router/ir/kshook/sv3c/router.test.ts @@ -0,0 +1,74 @@ +import t from "tap"; +import { InsertFakeTokenWithAllPerms } from "test-utils/fake-auth"; +import mockApi from "test-utils/mock-api"; +import ResetDBState from "test-utils/resets"; +import { TestingKsHookSV3CScore } from "test-utils/test-data"; + +t.test("POST /ir/kshook/sv3c/score/save", (t) => { + t.beforeEach(ResetDBState); + t.beforeEach(InsertFakeTokenWithAllPerms("mock_token")); + + t.test("Should import a valid score to the database.", async (t) => { + const res = await mockApi + .post("/ir/kshook/sv3c/score/save") + .set("Authorization", "Bearer mock_token") + .set("User-Agent", "kshook/0.1.0") + .set("X-Software-Model", "QCV:J:C:A:2021100600") + .send(TestingKsHookSV3CScore); + + t.equal(res.status, 200); + t.equal(res.body.success, true); + t.equal(res.body.body.scoreIDs.length, 1, "Should import one score."); + t.equal(res.body.body.errors.length, 0, "Should have 0 failed scores."); + + t.end(); + }); + + t.test("Should reject scores with invalid software models.", async (t) => { + const res = await mockApi + .post("/ir/kshook/sv3c/score/save") + .set("Authorization", "Bearer mock_token") + .set("User-Agent", "kshook/0.1.0") + .set("X-Software-Model", "LDJ:J:C:A:2021100600") + .send(TestingKsHookSV3CScore); + + t.equal(res.status, 400, "Should reject an import with invalid software model."); + t.type(res.body.error, "string", "Should have an error message."); + + const res2 = await mockApi + .post("/ir/kshook/sv3c/score/save") + .set("Authorization", "Bearer mock_token") + .set("User-Agent", "kshook/0.1.0") + .send(TestingKsHookSV3CScore); + + t.equal(res2.status, 400, "Should reject an import with no software model."); + t.type(res2.body.error, "string", "Should have an error message."); + + t.end(); + }); + + t.test("Should reject scores with invalid auth.", async (t) => { + const res = await mockApi + .post("/ir/kshook/sv3c/score/save") + .set("Authorization", "Bearer foo") + .set("User-Agent", "kshook/0.1.0") + .set("X-Software-Model", "QCV:J:C:A:2021100600") + .send(TestingKsHookSV3CScore); + + t.equal(res.status, 401, "Should reject an import with invalid authentication."); + t.type(res.body.error, "string", "Should have an error message."); + + const res2 = await mockApi + .post("/ir/kshook/sv3c/score/save") + .set("User-Agent", "kshook/0.1.0") + .set("X-Software-Model", "QCV:J:C:A:2021100600") + .send(TestingKsHookSV3CScore); + + t.equal(res2.status, 401, "Should reject an import with no authentication."); + t.type(res2.body.error, "string", "Should have an error message."); + + t.end(); + }); + + t.end(); +}); diff --git a/server/src/server/router/ir/kshook/sv3c/router.ts b/server/src/server/router/ir/kshook/sv3c/router.ts index 856ff19e1..0f2843ac4 100644 --- a/server/src/server/router/ir/kshook/sv3c/router.ts +++ b/server/src/server/router/ir/kshook/sv3c/router.ts @@ -73,6 +73,8 @@ const ValidateHeaders: RequestHandler = (req, res, next) => { return next(); }; +router.use(ValidateHeaders); + /** * Saves a SDVX Konaste score. * diff --git a/server/src/server/router/ir/router.ts b/server/src/server/router/ir/router.ts index d5ac0ab54..a53ac1ab4 100644 --- a/server/src/server/router/ir/router.ts +++ b/server/src/server/router/ir/router.ts @@ -7,14 +7,19 @@ import uscIR from "./usc/router"; import beatorajaIR from "./beatoraja/router"; import ksHookIR from "./kshook/router"; import { RequireBokutachi, RequireKamaitachi } from "../../middleware/type-require"; -import { SetFervidexStyleRequestPermissions, SetRequestPermissions } from "../../middleware/auth"; +import { + FervidexStyleRequireNotGuest, + RequireNotGuest, + SetFervidexStyleRequestPermissions, + SetRequestPermissions, +} from "../../middleware/auth"; const router: Router = Router({ mergeParams: true }); // Common IRs -router.use("/direct-manual", SetRequestPermissions, directManualIR); -router.use("/kshook", SetFervidexStyleRequestPermissions, ksHookIR); +router.use("/direct-manual", SetRequestPermissions, RequireNotGuest, directManualIR); +router.use("/kshook", SetFervidexStyleRequestPermissions, FervidexStyleRequireNotGuest, ksHookIR); // Bokutachi IRs @@ -26,8 +31,14 @@ router.use("/beatoraja", SetRequestPermissions, RequireBokutachi, beatorajaIR); // Kamaitachi IRs -router.use("/barbatos", SetRequestPermissions, RequireKamaitachi, barbatosIR); -router.use("/chunitachi", SetRequestPermissions, RequireKamaitachi, chunitachiIR); -router.use("/fervidex", SetFervidexStyleRequestPermissions, RequireKamaitachi, fervidexIR); +router.use("/barbatos", SetRequestPermissions, RequireNotGuest, RequireKamaitachi, barbatosIR); +router.use("/chunitachi", SetRequestPermissions, RequireNotGuest, RequireKamaitachi, chunitachiIR); +router.use( + "/fervidex", + SetFervidexStyleRequestPermissions, + FervidexStyleRequireNotGuest, + RequireKamaitachi, + fervidexIR +); export default router; diff --git a/server/src/test-utils/mock-db/charts-sdvx.json b/server/src/test-utils/mock-db/charts-sdvx.json index 4cbbac926..98e1f850e 100644 --- a/server/src/test-utils/mock-db/charts-sdvx.json +++ b/server/src/test-utils/mock-db/charts-sdvx.json @@ -1,29 +1,24 @@ [ - { - "rgcID": null, - "chartID": "5088a4d0e1ee9d0cc2f625934306e45b1a60699b", - "difficulty": "ADV", - "songID": 1, - "playtype": "Single", - "levelNum": 10, - "level": "10", - "flags": { - "IN BASE GAME": true, - "OMNIMIX": false, - "N-1": false - }, - "data": { - "inGameID": 1, - "uscEquiv": null, - "arcChartID": "EOZ7FixCDpv" - }, - "isPrimary": true, - "versions": [ - "booth", - "inf", - "gw", - "heaven", - "vivid" - ] - } + { + "rgcID": null, + "chartID": "5088a4d0e1ee9d0cc2f625934306e45b1a60699b", + "difficulty": "ADV", + "songID": 1, + "playtype": "Single", + "levelNum": 10, + "level": "10", + "data": { + "inGameID": 1, + "arcChartID": "EOZ7FixCDpv" + }, + "isPrimary": true, + "versions": [ + "booth", + "inf", + "gw", + "heaven", + "vivid", + "konaste" + ] + } ] \ No newline at end of file diff --git a/server/src/test-utils/test-data.ts b/server/src/test-utils/test-data.ts index f7fca6aea..9b94f9d0d 100644 --- a/server/src/test-utils/test-data.ts +++ b/server/src/test-utils/test-data.ts @@ -21,6 +21,7 @@ import { import { DryScore } from "lib/score-import/framework/common/types"; import { BarbatosScore } from "lib/score-import/import-types/ir/barbatos/types"; import { USCClientScore } from "../server/router/ir/usc/types"; +import { KsHookSV3CScore } from "lib/score-import/import-types/ir/kshook-sv3c/types"; const file = (name: string) => path.join(__dirname, "/test-data", name); @@ -38,6 +39,8 @@ export const TestingIIDXSPScore = GetKTDataJSON( "./tachi/iidx-score.json" ) as ScoreDocument<"iidx:SP">; +export const TestingKsHookSV3CScore = GetKTDataJSON("./kshook-sv3c/base.json") as KsHookSV3CScore; + export const Testing511SPA = GetKTDataJSON("./tachi/iidx-511spa.json") as ChartDocument<"iidx:SP">; export const Testing511Song = GetKTDataJSON("./tachi/iidx-511-song.json") as SongDocument<"iidx">; diff --git a/server/src/test-utils/test-data/kshook-sv3c/base.json b/server/src/test-utils/test-data/kshook-sv3c/base.json new file mode 100644 index 000000000..6140145c4 --- /dev/null +++ b/server/src/test-utils/test-data/kshook-sv3c/base.json @@ -0,0 +1,24 @@ +{ + "appeal_id": 2001, + "btn_rate": 182, + "clear": "CLEAR_EXCESSIVE", + "critical": 1184, + "difficulty": "DIFFICULTY_ADVANCED", + "early": 36, + "error": 30, + "gauge": 71, + "grade": "GRADE_AA_PLUS", + "late": 10, + "long_rate": 195, + "max_chain": 158, + "music_id": 1, + "near": 46, + "player_name": "EE", + "rate": "RATE_EXCESSIVE", + "score": 9579365, + "skill_frame": "SKILL_FRAME_NONE", + "skill_level": "SKILL_LEVEL_NONE", + "skill_name": 5, + "track_no": 0, + "vol_rate": 193 +} \ No newline at end of file diff --git a/server/src/test-utils/test-data/tachi/sdvx-albida-chart.json b/server/src/test-utils/test-data/tachi/sdvx-albida-chart.json index a06cca42f..327cdd34a 100644 --- a/server/src/test-utils/test-data/tachi/sdvx-albida-chart.json +++ b/server/src/test-utils/test-data/tachi/sdvx-albida-chart.json @@ -1,27 +1,22 @@ { - "rgcID": null, - "chartID": "5088a4d0e1ee9d0cc2f625934306e45b1a60699b", - "difficulty": "ADV", - "songID": 1, - "playtype": "Single", - "levelNum": 10, - "level": "10", - "flags": { - "IN BASE GAME": true, - "OMNIMIX": false, - "N-1": false - }, - "data": { - "inGameID": 1, - "uscEquiv": null, - "arcChartID": "EOZ7FixCDpv" - }, - "isPrimary": true, - "versions": [ - "booth", - "inf", - "gw", - "heaven", - "vivid" - ] + "rgcID": null, + "chartID": "5088a4d0e1ee9d0cc2f625934306e45b1a60699b", + "difficulty": "ADV", + "songID": 1, + "playtype": "Single", + "levelNum": 10, + "level": "10", + "data": { + "inGameID": 1, + "arcChartID": "EOZ7FixCDpv" + }, + "isPrimary": true, + "versions": [ + "booth", + "inf", + "gw", + "heaven", + "vivid", + "konaste" + ] } \ No newline at end of file