From 3df69caa97cf3e867a6194830ea5522bdc3eefb1 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Sun, 30 Jan 2022 15:29:24 +0000 Subject: [PATCH 1/2] If a banned user is making a request, all requests should 403. --- server/src/external/mongo/indexes.ts | 1 + server/src/server/middleware/auth.ts | 21 ++++++++++++++++++- .../src/server/middleware/request-logger.ts | 5 +++++ server/src/server/server.ts | 4 ++++ 4 files changed, 30 insertions(+), 1 deletion(-) diff --git a/server/src/external/mongo/indexes.ts b/server/src/external/mongo/indexes.ts index c3717106a..49d715f17 100644 --- a/server/src/external/mongo/indexes.ts +++ b/server/src/external/mongo/indexes.ts @@ -54,6 +54,7 @@ const staticIndexes: Partial> = { ], users: [ index({ id: 1 }, UNIQUE), + index({ id: 1, authLevel: 1 }, UNIQUE), index({ username: 1 }, UNIQUE), index({ usernameLowercase: 1 }, UNIQUE), ], diff --git a/server/src/server/middleware/auth.ts b/server/src/server/middleware/auth.ts index 7f82cbb0c..aa0c0b431 100644 --- a/server/src/server/middleware/auth.ts +++ b/server/src/server/middleware/auth.ts @@ -2,8 +2,9 @@ import { RequestHandler } from "express"; import db from "external/mongo/db"; import { SYMBOL_TachiAPIAuth } from "lib/constants/tachi"; import { SplitAuthorizationHeader } from "utils/misc"; -import { APITokenDocument, APIPermissions } from "tachi-common"; +import { APITokenDocument, APIPermissions, UserAuthLevels } from "tachi-common"; import CreateLogCtx from "lib/logger/logger"; +import { TachiConfig } from "lib/setup/config"; const logger = CreateLogCtx(__filename); @@ -186,3 +187,21 @@ const CreateRequireNotGuest = export const RequireNotGuest: RequestHandler = CreateRequireNotGuest("description"); export const FervidexStyleRequireNotGuest: RequestHandler = CreateRequireNotGuest("error"); + +export const RejectIfBanned: RequestHandler = async (req, res, next) => { + if (req.session?.tachi?.user.id) { + const isBanned = await db.users.findOne({ + id: req.session.tachi.user.id, + authLevel: UserAuthLevels.BANNED, + }); + + if (isBanned) { + return res.status(403).json({ + success: false, + description: `You are banned from ${TachiConfig.NAME}`, + }); + } + } + + next(); +}; diff --git a/server/src/server/middleware/request-logger.ts b/server/src/server/middleware/request-logger.ts index 6800a7883..9aee8fa0e 100644 --- a/server/src/server/middleware/request-logger.ts +++ b/server/src/server/middleware/request-logger.ts @@ -1,6 +1,7 @@ import CreateLogCtx from "lib/logger/logger"; import { RequestHandler, Response } from "express-serve-static-core"; import { SYMBOL_TachiAPIAuth } from "lib/constants/tachi"; +import { TachiConfig } from "lib/setup/config"; const logger = CreateLogCtx(__filename); @@ -60,6 +61,10 @@ export const RequestLoggerMiddleware: RequestHandler = (req, res, next) => { return; } + if (contents.body.description === `You are banned from ${TachiConfig.NAME}.`) { + return; + } + if (res.statusCode < 400 || res.statusCode === 404) { logger.verbose( `(${req.method} ${req.originalUrl}) Returned ${res.statusCode}.`, diff --git a/server/src/server/server.ts b/server/src/server/server.ts index 4f62cd32d..f29b1d47e 100644 --- a/server/src/server/server.ts +++ b/server/src/server/server.ts @@ -8,6 +8,7 @@ import { SYMBOL_TachiAPIAuth } from "lib/constants/tachi"; import CreateLogCtx from "lib/logger/logger"; import { Environment, ServerConfig, TachiConfig } from "lib/setup/config"; import { integer } from "tachi-common"; +import { RejectIfBanned } from "./middleware/auth"; import { RequestLoggerMiddleware } from "./middleware/request-logger"; import mainRouter from "./router/router"; @@ -96,6 +97,8 @@ process.on("unhandledRejection", (reason, promise) => { app.use(express.json({ limit: "4mb" })); app.use((req, res, next) => { + // Always mount an empty req body. We operate under the assumption that req.body is + // always defined. if (req.method !== "GET" && !req.body) { req.body = {}; } @@ -104,6 +107,7 @@ app.use((req, res, next) => { }); app.use(RequestLoggerMiddleware); +app.use(RejectIfBanned); app.use("/", mainRouter); From 48857eb35aa5036fcbc6484143be10a7503f8b58 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Sun, 30 Jan 2022 15:54:58 +0000 Subject: [PATCH 2/2] Enable support for banning users. --- server/src/server/middleware/auth.test.ts | 25 +++++++++++++++++++ server/src/server/middleware/auth.ts | 4 +-- .../src/server/middleware/request-logger.ts | 2 +- server/src/server/router/router.ts | 3 ++- server/src/server/server.ts | 1 - 5 files changed, 30 insertions(+), 5 deletions(-) diff --git a/server/src/server/middleware/auth.test.ts b/server/src/server/middleware/auth.test.ts index 30313e60f..910d93b85 100644 --- a/server/src/server/middleware/auth.test.ts +++ b/server/src/server/middleware/auth.test.ts @@ -2,6 +2,7 @@ import expMiddlewareMock from "express-request-mock"; import db from "external/mongo/db"; import { SYMBOL_TachiAPIAuth } from "lib/constants/tachi"; import t from "tap"; +import mockApi from "test-utils/mock-api"; import ResetDBState from "test-utils/resets"; import { AllPermissions, SetRequestPermissions } from "./auth"; @@ -119,3 +120,27 @@ t.test("#SetRequestPermissions", (t) => { t.end(); }); + +t.test("#RejectIfBanned", (t) => { + t.test("Should stop banned users from doing anything.", async (t) => { + await db.users.update({ id: 1 }, { $set: { authLevel: 0 } }); + + await db["api-tokens"].insert({ + userID: 1, + identifier: "Mock API Token", + permissions: { + customise_profile: true, + }, + token: "mock_token", + fromAPIClient: null, + }); + + const res = await mockApi.get("/api/v1/status").set("Authorization", "Bearer mock_token"); + + t.equal(res.statusCode, 403, "Should return 403 on benign endpoints."); + + t.end(); + }); + + t.end(); +}); diff --git a/server/src/server/middleware/auth.ts b/server/src/server/middleware/auth.ts index aa0c0b431..d3bc0f4aa 100644 --- a/server/src/server/middleware/auth.ts +++ b/server/src/server/middleware/auth.ts @@ -189,9 +189,9 @@ export const RequireNotGuest: RequestHandler = CreateRequireNotGuest("descriptio export const FervidexStyleRequireNotGuest: RequestHandler = CreateRequireNotGuest("error"); export const RejectIfBanned: RequestHandler = async (req, res, next) => { - if (req.session?.tachi?.user.id) { + if (req[SYMBOL_TachiAPIAuth].userID) { const isBanned = await db.users.findOne({ - id: req.session.tachi.user.id, + id: req[SYMBOL_TachiAPIAuth].userID!, authLevel: UserAuthLevels.BANNED, }); diff --git a/server/src/server/middleware/request-logger.ts b/server/src/server/middleware/request-logger.ts index 9aee8fa0e..657bf4ea5 100644 --- a/server/src/server/middleware/request-logger.ts +++ b/server/src/server/middleware/request-logger.ts @@ -61,7 +61,7 @@ export const RequestLoggerMiddleware: RequestHandler = (req, res, next) => { return; } - if (contents.body.description === `You are banned from ${TachiConfig.NAME}.`) { + if (contents.body?.description === `You are banned from ${TachiConfig.NAME}.`) { return; } diff --git a/server/src/server/router/router.ts b/server/src/server/router/router.ts index 10088693c..c9c6d04d4 100644 --- a/server/src/server/router/router.ts +++ b/server/src/server/router/router.ts @@ -1,6 +1,6 @@ import { Router } from "express"; import { UpdateLastSeen } from "server/middleware/update-last-seen"; -import { SetRequestPermissions } from "../middleware/auth"; +import { RejectIfBanned, SetRequestPermissions } from "../middleware/auth"; import { NormalRateLimitMiddleware } from "../middleware/rate-limiter"; import apiRouterV1 from "./api/v1/router"; import irRouter from "./ir/router"; @@ -13,6 +13,7 @@ router.use("/ir", NormalRateLimitMiddleware, irRouter); // but also may require custom authentication. router.use(SetRequestPermissions); router.use(UpdateLastSeen); +router.use(RejectIfBanned); router.use("/api/v1", apiRouterV1); diff --git a/server/src/server/server.ts b/server/src/server/server.ts index f29b1d47e..f951a45a2 100644 --- a/server/src/server/server.ts +++ b/server/src/server/server.ts @@ -107,7 +107,6 @@ app.use((req, res, next) => { }); app.use(RequestLoggerMiddleware); -app.use(RejectIfBanned); app.use("/", mainRouter);