Refactor permissions for IRs

This commit is contained in:
zkldi
2021-11-05 17:00:27 +00:00
parent 46cc8689ad
commit e71efc7c02
10 changed files with 188 additions and 82 deletions
@@ -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,
},
+23 -17
View File
@@ -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");
@@ -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 (
@@ -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();
});
@@ -73,6 +73,8 @@ const ValidateHeaders: RequestHandler = (req, res, next) => {
return next();
};
router.use(ValidateHeaders);
/**
* Saves a SDVX Konaste score.
*
+17 -6
View File
@@ -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;
+22 -27
View File
@@ -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"
]
}
]
+3
View File
@@ -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">;
@@ -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
}
@@ -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"
]
}