mirror of
https://github.com/zkldi/Tachi.git
synced 2026-10-02 03:48:14 +03:00
experimental: misc ergonomic checking
This commit is contained in:
@@ -43,7 +43,7 @@ enum STATUS_CODES {
|
||||
const ValidateUSCRequest: RequestHandler = async (req, res, next) => {
|
||||
const token = req.header("Authorization");
|
||||
|
||||
if (!token) {
|
||||
if (token === undefined) {
|
||||
return res.status(200).json({
|
||||
statusCode: STATUS_CODES.BAD_REQ,
|
||||
description: "No auth token provided.",
|
||||
@@ -224,7 +224,7 @@ router.get("/charts/:chartHash/leaderboard", RetrieveChart, async (req, res) =>
|
||||
|
||||
try {
|
||||
n = AssertStrAsPositiveNonZeroInt(req.query.n, "Invalid 'N' param.");
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
return res.status(200).json({
|
||||
statusCode: STATUS_CODES.BAD_REQ,
|
||||
description: `Invalid 'n' param - expected a positive non-zero integer less than or equal to ${USCIR_MAX_LEADERBOARD_N}.`,
|
||||
@@ -265,7 +265,7 @@ router.get("/charts/:chartHash/leaderboard", RetrieveChart, async (req, res) =>
|
||||
});
|
||||
});
|
||||
|
||||
const PR_USCIRChartDoc = {
|
||||
const PR_USCIR_CHART_DOC = {
|
||||
chartHash: "string",
|
||||
artist: "string",
|
||||
title: "string",
|
||||
@@ -286,7 +286,7 @@ router.post("/scores", RequirePermissions("submit_score"), async (req, res) => {
|
||||
|
||||
const chartErr = p(
|
||||
req.body.chart,
|
||||
PR_USCIRChartDoc,
|
||||
PR_USCIR_CHART_DOC,
|
||||
{},
|
||||
{
|
||||
throwOnNonObject: false,
|
||||
@@ -333,14 +333,19 @@ router.post("/scores", RequirePermissions("submit_score"), async (req, res) => {
|
||||
// If the import failed, AND the import failure WAS NOT that the chart didnt exist
|
||||
// report that error instead.
|
||||
if (importDoc.errors[0]?.type && importDoc.errors[0].type !== "KTDataNotFound") {
|
||||
logger.info(`USC Import Failed ${importDoc.errors[0].message}`, {
|
||||
importDoc,
|
||||
userID,
|
||||
});
|
||||
logger.info(
|
||||
`USC Import Failed ${importDoc.errors[0].message ?? "with null error message?"}`,
|
||||
{
|
||||
importDoc,
|
||||
userID,
|
||||
}
|
||||
);
|
||||
|
||||
return res.status(200).json({
|
||||
statusCode: STATUS_CODES.BAD_REQ,
|
||||
description: `${importDoc.errors[0].type} ${importDoc.errors[0].message}`,
|
||||
description: `${importDoc.errors[0].type} ${
|
||||
importDoc.errors[0].message ?? "No Error Message"
|
||||
}`,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -355,7 +360,7 @@ router.post("/scores", RequirePermissions("submit_score"), async (req, res) => {
|
||||
}
|
||||
|
||||
// If the chartDoc exists, any error is a failure here.
|
||||
if (importDoc.errors[0]) {
|
||||
if (importDoc.errors.length !== 0) {
|
||||
logger.info(`USC Import Failed ${importDoc.errors[0].message}`, {
|
||||
importDoc,
|
||||
userID,
|
||||
@@ -382,7 +387,7 @@ router.post("/scores", RequirePermissions("submit_score"), async (req, res) => {
|
||||
description: "Successfully imported score.",
|
||||
body,
|
||||
});
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
return res.status(200).json({
|
||||
statusCode: STATUS_CODES.SERVER_ERROR,
|
||||
description: "An internal server error has occured.",
|
||||
|
||||
@@ -28,7 +28,9 @@ export interface USCClientScore {
|
||||
gaugeOpt: integer;
|
||||
mirror: boolean;
|
||||
random: boolean;
|
||||
autoFlags: integer; // ???
|
||||
|
||||
// ??? - Not sure what these are.
|
||||
autoFlags: integer;
|
||||
};
|
||||
windows: {
|
||||
perfect: number;
|
||||
|
||||
@@ -169,7 +169,7 @@ export async function CreatePOSTScoresResponseBody(
|
||||
|
||||
const originalScore = (await db.scores.findOne({
|
||||
scoreID,
|
||||
})) as ScoreDocument<"usc:Controller" | "usc:Keyboard">;
|
||||
})) as ScoreDocument<"usc:Controller" | "usc:Keyboard"> | null;
|
||||
|
||||
if (!originalScore) {
|
||||
logger.severe(
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import { RequestLoggerMiddleware } from "./middleware/request-logger";
|
||||
import mainRouter from "./router/router";
|
||||
import connectRedis from "connect-redis";
|
||||
import express from "express";
|
||||
import type { Express } from "express";
|
||||
import "express-async-errors";
|
||||
import expressSession from "express-session";
|
||||
import { RedisClient } from "external/redis/redis";
|
||||
import helmet from "helmet";
|
||||
import { SYMBOL_TACHI_API_AUTH } from "lib/constants/tachi";
|
||||
import CreateLogCtx from "lib/logger/logger";
|
||||
import { Environment, ServerConfig, TachiConfig } from "lib/setup/config";
|
||||
import type { Express } from "express";
|
||||
import "express-async-errors";
|
||||
import type { integer } from "tachi-common";
|
||||
import { RequestLoggerMiddleware } from "./middleware/request-logger";
|
||||
import mainRouter from "./router/router";
|
||||
import { IsNonEmptyString } from "utils/misc";
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
@@ -39,13 +40,15 @@ const userSessionMiddleware = expressSession({
|
||||
saveUninitialized: false,
|
||||
cookie: {
|
||||
secure: Environment.nodeEnv === "production" || ServerConfig.ENABLE_SERVER_HTTPS,
|
||||
sameSite: "strict", // Very important. Without this, we're vulnerable to CSRF!
|
||||
|
||||
// Very important. Without this, we're vulnerable to CSRF!
|
||||
sameSite: "strict",
|
||||
},
|
||||
});
|
||||
|
||||
const app: Express = express();
|
||||
|
||||
if (Environment.nodeEnv !== "production" && ServerConfig.CLIENT_DEV_SERVER) {
|
||||
if (Environment.nodeEnv !== "production" && IsNonEmptyString(ServerConfig.CLIENT_DEV_SERVER)) {
|
||||
logger.warn(`Enabling CORS requests from ${ServerConfig.CLIENT_DEV_SERVER}.`, {
|
||||
bootInfo: true,
|
||||
});
|
||||
@@ -63,7 +66,7 @@ if (Environment.nodeEnv !== "production" && ServerConfig.CLIENT_DEV_SERVER) {
|
||||
});
|
||||
|
||||
// hack to allow all OPTIONS requests. Remember that this setting should not be on in production!
|
||||
if (ServerConfig.OPTIONS_ALWAYS_SUCCEEDS) {
|
||||
if (ServerConfig.OPTIONS_ALWAYS_SUCCEEDS === true) {
|
||||
app.options("*", (req, res) => res.send());
|
||||
}
|
||||
} else {
|
||||
@@ -117,7 +120,7 @@ app.use("/", mainRouter);
|
||||
// In dev, this is a pain to setup, so we can just run it locally.
|
||||
if (
|
||||
ServerConfig.CDN_CONFIG.SAVE_LOCATION.TYPE === "LOCAL_FILESYSTEM" &&
|
||||
ServerConfig.CDN_CONFIG.SAVE_LOCATION.SERVE_OWN_CDN
|
||||
ServerConfig.CDN_CONFIG.SAVE_LOCATION.SERVE_OWN_CDN === true
|
||||
) {
|
||||
if (Environment.nodeEnv === "production") {
|
||||
logger.warn(
|
||||
|
||||
@@ -9,43 +9,48 @@ export function RequireAuthPerms(
|
||||
perms: APIPermissions | Array<APIPermissions>,
|
||||
method: "DELETE" | "GET" | "PATCH" | "POST" | "PUT" = "GET"
|
||||
) {
|
||||
t.test(`Testing permissions for ${method} ${url} [${perms}]`, async (t) => {
|
||||
const m = method.toLowerCase() as Lowercase<typeof method>;
|
||||
return t.test(
|
||||
`Testing permissions for ${method} ${url} [${
|
||||
Array.isArray(perms) ? perms.join(", ") : perms
|
||||
}]`,
|
||||
async (t) => {
|
||||
const m = method.toLowerCase() as Lowercase<typeof method>;
|
||||
|
||||
const res = await mockApi[m](url);
|
||||
const res = await mockApi[m](url);
|
||||
|
||||
// 401 if no auth given
|
||||
t.equal(res.statusCode, 401);
|
||||
// 401 if no auth given
|
||||
t.equal(res.statusCode, 401);
|
||||
|
||||
await db["api-tokens"].insert({
|
||||
identifier: "temp_auth_perms",
|
||||
permissions: {},
|
||||
token: "temp_auth",
|
||||
userID: 1,
|
||||
fromAPIClient: null,
|
||||
});
|
||||
await db["api-tokens"].insert({
|
||||
identifier: "temp_auth_perms",
|
||||
permissions: {},
|
||||
token: "temp_auth",
|
||||
userID: 1,
|
||||
fromAPIClient: null,
|
||||
});
|
||||
|
||||
const resAuth = await mockApi[m](url).set("Authorization", "Bearer temp_auth");
|
||||
const resAuth = await mockApi[m](url).set("Authorization", "Bearer temp_auth");
|
||||
|
||||
t.equal(resAuth.statusCode, 403);
|
||||
t.equal(resAuth.statusCode, 403);
|
||||
|
||||
const prm = Array.isArray(perms) ? perms : [perms];
|
||||
const prm = Array.isArray(perms) ? perms : [perms];
|
||||
|
||||
await db["api-tokens"].insert({
|
||||
identifier: "temp_auth_perms2",
|
||||
permissions: Object.fromEntries(prm.map((e) => [e, true])),
|
||||
token: "temp_auth2",
|
||||
userID: 1,
|
||||
fromAPIClient: null,
|
||||
});
|
||||
await db["api-tokens"].insert({
|
||||
identifier: "temp_auth_perms2",
|
||||
permissions: Object.fromEntries(prm.map((e) => [e, true])),
|
||||
token: "temp_auth2",
|
||||
userID: 1,
|
||||
fromAPIClient: null,
|
||||
});
|
||||
|
||||
const resAuthed = await mockApi[m](url).set("Authorization", "Bearer temp_auth2");
|
||||
const resAuthed = await mockApi[m](url).set("Authorization", "Bearer temp_auth2");
|
||||
|
||||
t.not(resAuthed.statusCode, 401);
|
||||
t.not(resAuthed.statusCode, 403);
|
||||
t.not(resAuthed.statusCode, 401);
|
||||
t.not(resAuthed.statusCode, 403);
|
||||
|
||||
await ResetDBState();
|
||||
await ResetDBState();
|
||||
|
||||
t.end();
|
||||
});
|
||||
t.end();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ export function isApproximately(
|
||||
|
||||
export function prAssert(
|
||||
t: Tap.Test,
|
||||
obj: Record<string, unknown> | unknown,
|
||||
obj: unknown,
|
||||
schema: PrudenceSchema,
|
||||
message = "Unnamed Prudence Assertion"
|
||||
) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { CloseRedisPubSub } from "external/redis/redis-IPC";
|
||||
import { CloseScoreImportQueue } from "lib/score-import/worker/queue";
|
||||
|
||||
export async function CleanUpAfterTests() {
|
||||
if (process.env.TAP_SNAPSHOT) {
|
||||
if (process.env.TAP_SNAPSHOT !== "" && process.env.TAP_SNAPSHOT !== undefined) {
|
||||
WriteSnapshotData();
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,16 @@ logger.verbose("Connecting to Supertest...");
|
||||
const mockApi = supertest(connection);
|
||||
|
||||
export function CloseServerConnection() {
|
||||
return connection.close();
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
connection.close((err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default mockApi;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
export function MockMulterFile(buffer: Buffer, originalname: string) {
|
||||
return {
|
||||
const mockFile: Express.Multer.File = {
|
||||
originalname,
|
||||
buffer,
|
||||
} as Express.Multer.File;
|
||||
};
|
||||
|
||||
return mockFile;
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ export async function UpdateClassIfGreater(
|
||||
});
|
||||
|
||||
if (isGreater === null) {
|
||||
EmitWebhookEvent({
|
||||
void EmitWebhookEvent({
|
||||
type: "class-update/v1",
|
||||
content: { userID, new: classVal, old: null, set: classSet, game, playtype },
|
||||
});
|
||||
@@ -88,7 +88,7 @@ export async function UpdateClassIfGreater(
|
||||
return null;
|
||||
}
|
||||
|
||||
EmitWebhookEvent({
|
||||
void EmitWebhookEvent({
|
||||
type: "class-update/v1",
|
||||
content: {
|
||||
userID,
|
||||
|
||||
@@ -195,7 +195,7 @@ export async function GetRecentlyAchievedGoals(
|
||||
goalID: { $in: goalSubs.map((e) => e.goalID) },
|
||||
});
|
||||
|
||||
if (goals.length !== goals.length) {
|
||||
if (goals.length !== goalSubs.length) {
|
||||
logger.error(
|
||||
`Found ${goals.length} goals when looking for parents of ${goalSubs.length} subscriptions. This mismatch implies a state desync.`
|
||||
);
|
||||
|
||||
@@ -11,7 +11,10 @@ export function ParseEA3SoftID(ver: string) {
|
||||
|
||||
return {
|
||||
model: a[0],
|
||||
dest: a[1], // region
|
||||
|
||||
// region
|
||||
dest: a[1],
|
||||
|
||||
spec: a[2],
|
||||
rev: a[3],
|
||||
ext: a[4],
|
||||
|
||||
@@ -135,11 +135,11 @@ export function IsValidURL(string: string) {
|
||||
}
|
||||
|
||||
export function Sleep(ms: number) {
|
||||
return new Promise<void>((resolve) =>
|
||||
return new Promise<void>((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
}, ms)
|
||||
);
|
||||
}, ms);
|
||||
});
|
||||
}
|
||||
|
||||
export function GetTimeXHoursAgo(hours: integer) {
|
||||
@@ -228,3 +228,7 @@ export function IsSupported(game: Game) {
|
||||
export function ArrayDiff<T>(left: Array<T>, right: Array<T>) {
|
||||
return right.filter((e) => !left.includes(e));
|
||||
}
|
||||
|
||||
export function IsNonEmptyString(maybeStr: string | null | undefined): maybeStr is string {
|
||||
return Boolean(maybeStr);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user