Merge pull request #605 from TNG-dev/zkldi/issue-604

This commit is contained in:
zkldi
2022-07-20 21:56:48 +01:00
committed by GitHub
9 changed files with 130 additions and 88 deletions
@@ -12,7 +12,7 @@ t.test("#ParseFervidexStatic", (t) => {
t.test("Should parse static data from body", (t) => {
const res = ParseFervidexStatic(
FervidexStaticBase,
{ model: "LDJ:J:B:A:2020092900" },
{ model: "LDJ:J:B:A:2020092900", shouldImportScores: true },
logger
);
@@ -45,12 +45,42 @@ t.test("#ParseFervidexStatic", (t) => {
game: "iidx",
});
t.type(res.classHandler, "function", "Should have a function ready for class handling");
t.end();
});
t.test("Should not have anything in its iterable if shouldImportScores is false.", (t) => {
const res = ParseFervidexStatic(
FervidexStaticBase,
{ model: "LDJ:J:B:A:2020092900", shouldImportScores: false },
logger
);
t.strictSame(res.iterable, []);
t.hasStrict(res, {
context: { version: "27" },
game: "iidx",
});
t.type(
res.classHandler,
"function",
"Should have a function ready for class handling, regardless of shouldImportScores being false"
);
t.end();
});
t.test("Should throw an error if no body.scores is present", (t) => {
t.throws(
() => ParseFervidexStatic({}, { model: "LDJ:J:B:A:2020092900" }, logger),
() =>
ParseFervidexStatic(
{},
{ model: "LDJ:J:B:A:2020092900", shouldImportScores: true },
logger
),
"Invalid body.scores"
);
@@ -62,7 +92,7 @@ t.test("#ParseFervidexStatic", (t) => {
() =>
ParseFervidexStatic(
{ scores: { nonsenseKey: {} } },
{ model: "LDJ:J:B:A:2020092900" },
{ model: "LDJ:J:B:A:2020092900", shouldImportScores: true },
logger
),
"Invalid songID nonsenseKey"
@@ -72,7 +102,7 @@ t.test("#ParseFervidexStatic", (t) => {
() =>
ParseFervidexStatic(
{ scores: { 1000: null } },
{ model: "LDJ:J:B:A:2020092900" },
{ model: "LDJ:J:B:A:2020092900", shouldImportScores: true },
logger
),
"Invalid score with songID 1000"
@@ -86,7 +116,7 @@ t.test("#ParseFervidexStatic", (t) => {
() =>
ParseFervidexStatic(
{ scores: { 1000: { spn: null } } },
{ model: "LDJ:J:B:A:2020092900" },
{ model: "LDJ:J:B:A:2020092900", shouldImportScores: true },
logger
),
"Invalid score with songID 1000"
@@ -96,7 +126,7 @@ t.test("#ParseFervidexStatic", (t) => {
() =>
ParseFervidexStatic(
{ scores: { 1000: { spn: undefined } } },
{ model: "LDJ:J:B:A:2020092900" },
{ model: "LDJ:J:B:A:2020092900", shouldImportScores: true },
logger
),
"Invalid score with songID 1000"
@@ -106,7 +136,7 @@ t.test("#ParseFervidexStatic", (t) => {
() =>
ParseFervidexStatic(
{ scores: { 1000: { spn: "foo" } } },
{ model: "LDJ:J:B:A:2020092900" },
{ model: "LDJ:J:B:A:2020092900", shouldImportScores: true },
logger
),
"Invalid score with songID 1000"
@@ -124,7 +154,7 @@ t.test("#ParseFervidexStatic", (t) => {
1000: { spn: { ex_score: -1, miss_count: null, clear_type: 0 } },
},
},
{ model: "LDJ:J:B:A:2020092900" },
{ model: "LDJ:J:B:A:2020092900", shouldImportScores: true },
logger
),
"Invalid score with songID 1000 at chart spn"
@@ -138,7 +168,7 @@ t.test("#ParseFervidexStatic", (t) => {
1000: { spn: { ex_score: 1000, miss_count: "foo", clear_type: 0 } },
},
},
{ model: "LDJ:J:B:A:2020092900" },
{ model: "LDJ:J:B:A:2020092900", shouldImportScores: true },
logger
),
"Invalid score with songID 1000 at chart spn"
@@ -152,7 +182,7 @@ t.test("#ParseFervidexStatic", (t) => {
1000: { spn: { ex_score: 1000, miss_count: null, clear_type: -1 } },
},
},
{ model: "LDJ:J:B:A:2020092900" },
{ model: "LDJ:J:B:A:2020092900", shouldImportScores: true },
logger
),
"Invalid score with songID 1000 at chart spn"
@@ -170,7 +200,7 @@ t.test("#ParseFervidexStatic", (t) => {
1000: { spx: { ex_score: 1000, miss_count: null, clear_type: 0 } },
},
},
{ model: "LDJ:J:B:A:2020092900" },
{ model: "LDJ:J:B:A:2020092900", shouldImportScores: true },
logger
),
"Invalid chart spx"
@@ -6,8 +6,7 @@ import p from "prudence";
import { IsRecord } from "utils/misc";
import { FormatPrError } from "utils/prudence";
import type { ParserFunctionReturns } from "../../common/types";
import type { FerHeaders } from "../fervidex/parser";
import type { FervidexStaticContext, FervidexStaticScore } from "./types";
import type { FervidexStaticContext, FervidexStaticHeaders, FervidexStaticScore } from "./types";
import type { KtLogger } from "lib/logger/logger";
import type { PrudenceSchema } from "prudence";
@@ -19,10 +18,21 @@ const PR_FERVIDEX_STATIC: PrudenceSchema = {
export function ParseFervidexStatic(
body: Record<string, unknown>,
headers: FerHeaders,
headers: FervidexStaticHeaders,
logger: KtLogger
): ParserFunctionReturns<FervidexStaticScore, FervidexStaticContext> {
const version = SoftwareIDToVersion(headers.model, logger);
const classHandler = CreateFerStaticClassHandler(body);
// if we shouldn't import scores, just sync up dans.
if (!headers.shouldImportScores) {
return {
context: { version },
game: "iidx",
iterable: [],
classHandler,
};
}
const staticScores = body.scores;
@@ -80,6 +90,6 @@ export function ParseFervidexStatic(
context: { version },
game: "iidx",
iterable: scores,
classHandler: CreateFerStaticClassHandler(body),
classHandler,
};
}
@@ -13,3 +13,8 @@ export interface FervidexStaticScore {
export interface FervidexStaticContext {
version: GPTSupportedVersions["iidx:DP" | "iidx:SP"];
}
export interface FervidexStaticHeaders {
model: string;
shouldImportScores: boolean;
}
@@ -13,7 +13,7 @@ import p from "prudence";
import { ParseEA3SoftID } from "utils/ea3id";
import { FormatPrError, optNull } from "utils/prudence";
import type { ParserFunctionReturns } from "../../common/types";
import type { FervidexContext, FervidexScore } from "./types";
import type { FerHeaders as FervidexHeaders, FervidexContext, FervidexScore } from "./types";
import type { KtLogger } from "lib/logger/logger";
import type { PrudenceSchema, ValidSchemaValue } from "prudence";
@@ -108,13 +108,9 @@ export function SoftwareIDToVersion(model: string, logger: KtLogger) {
}
}
export interface FerHeaders {
model: string;
}
export function ParseFervidexSingle(
body: Record<string, unknown>,
headers: FerHeaders,
headers: FervidexHeaders,
logger: KtLogger
): ParserFunctionReturns<FervidexScore, FervidexContext> {
const version = SoftwareIDToVersion(headers.model, logger);
@@ -65,3 +65,7 @@ export interface FervidexContext {
version: GPTSupportedVersions["iidx:DP" | "iidx:SP"];
timeReceived: number;
}
export interface FerHeaders {
model: string;
}
@@ -490,7 +490,7 @@ t.test("POST /ir/fervidex/profile/submit", (t) => {
t.end();
});
t.test("Should disallow requests from non INF2 if forceStaticImport is false.", async (t) => {
t.test("Should allow requests from non INF2 if forceStaticImport is false.", async (t) => {
await db["fer-settings"].update({ userID: 1 }, { $set: { forceStaticImport: false } });
const res = await mockApi
@@ -500,7 +500,13 @@ t.test("POST /ir/fervidex/profile/submit", (t) => {
.set("X-Software-Model", "LDJ:J:B:A:2020092900")
.send(FervidexStaticBase);
t.equal(res.statusCode, 400, "Should be rejected, as FSI is not set.");
t.equal(res.statusCode, 202, "Should be allowed, but no scores should be imported");
const scoresNow = await db.scores.count({
service: "Fervidex Static",
});
t.strictSame(scoresNow, 0, "Should have imported no scores");
t.end();
});
+54 -63
View File
@@ -72,66 +72,6 @@ const ValidateFervidexHeader: RequestHandler = (req, res, next) => {
next();
};
const RequireInf2ModelHeaderOrForceStatic: RequestHandler = async (req, res, next) => {
const settings = await db["fer-settings"].findOne({
userID: req[SYMBOL_TACHI_API_AUTH].userID!,
});
if (settings?.forceStaticImport === true) {
logger.debug(`User ${settings.userID} had forceStaticImport set, allowing request.`);
// Force static import should ideally only ever be used once. If left on, a users profile
// will get innundated with a bunch of pb imports on every game-load. This is not what
// people want.
// FSI should ideally just be used once to get unreachable scores onto Kamaitachi. Otherwise
// they're doing something wrong.
await db["fer-settings"].update(
{
userID: settings.userID,
},
{
$set: {
forceStaticImport: false,
},
}
);
next();
return;
}
const swModel = req.header("X-Software-Model");
if (IsNullishOrEmptyStr(swModel)) {
logger.debug(
`Rejected empty X-Software-Model from user ${req[SYMBOL_TACHI_API_AUTH].userID}.`
);
return res.status(400).json({
success: false,
error: `Invalid X-Software-Model.`,
});
}
try {
const { model } = ParseEA3SoftID(swModel);
if (model !== MODEL_INFINITAS_2) {
return res.status(400).json({
success: false,
error: `Refusing to perform static import from non-infinitas client. To do this anyway, enable Force Static Import.`,
});
}
} catch (err) {
logger.info(`Invalid softID from ${req[SYMBOL_TACHI_API_AUTH].userID}.`, { err });
return res.status(400).json({
success: false,
error: `Invalid X-Software-Model.`,
});
}
next();
};
const supportedExts = [EXT_HEROIC_VERSE, EXT_BISTROVER];
const ValidateModelHeader: RequestHandler = (req, res, next) => {
@@ -219,6 +159,49 @@ router.use(
ValidateCards
);
async function ShouldImportScoresFromProfileSubmit(swModel: string, userID: integer) {
const settings = await db["fer-settings"].findOne({
userID,
});
if (settings?.forceStaticImport === true) {
logger.debug(`User ${settings.userID} had forceStaticImport set, allowing request.`);
// Force static import should ideally only ever be used once. If left on, a users profile
// will get innundated with a bunch of pb imports on every game-load. This is not what
// people want.
// FSI should ideally just be used once to get unreachable scores onto Kamaitachi. Otherwise
// they're doing something wrong.
await db["fer-settings"].update(
{
userID: settings.userID,
},
{
$set: {
forceStaticImport: false,
},
}
);
return true;
}
try {
const { model } = ParseEA3SoftID(swModel);
return model === MODEL_INFINITAS_2;
} catch (err) {
logger.warn(
`Unexpected fail while parsing swModel ${swModel}, has already been validated?.`,
{ err }
);
// try some good-natured attempt to recover, since this isn't that severe of an
// issue.
return false;
}
}
/**
* Submits all of a users data to Tachi. This data is extremely minimal,
* as only a users Lamp and Score are sent. As such, this is not the prefered
@@ -227,10 +210,18 @@ router.use(
*
* @name POST /ir/fervidex/profile/submit
*/
router.post("/profile/submit", RequireInf2ModelHeaderOrForceStatic, (req, res) => {
router.post("/profile/submit", async (req, res) => {
// guaranteed to exist because of RequireInf2ModelHeader
const model = req.header("X-Software-Model")!;
const shouldImportScores = await ShouldImportScoresFromProfileSubmit(
model,
req[SYMBOL_TACHI_API_AUTH].userID!
);
const headers = {
// guaranteed to exist because of RequireInf2ModelHeader
model: req.header("X-Software-Model")!,
model,
shouldImportScores,
};
// Perform a fast return here to not allow fervidex to resend requests.
@@ -4,7 +4,7 @@
"chartID": "88eb6cc5683e2740cbd07f588a5f3db1db8d467b",
"rgcID": null,
"data": {
"aiRating": "0",
"aiLevel": "0",
"notecount": 2256,
"hashMD5": "38616b85332037cc12924f2ae2840262",
"hashSHA256": "195fe1be5c3e74fccd04dc426e05f8a9cfa8a1059c339d0a23e99f63661f0b7d",
+1 -1
View File
@@ -321,7 +321,7 @@ export const BMSGazerChart: ChartDocument<"bms:7K"> = {
chartID: "88eb6cc5683e2740cbd07f588a5f3db1db8d467b",
rgcID: null,
data: {
aiRating: "0",
aiLevel: "0",
notecount: 2256,
hashMD5: "38616b85332037cc12924f2ae2840262",
hashSHA256: "195fe1be5c3e74fccd04dc426e05f8a9cfa8a1059c339d0a23e99f63661f0b7d",