mirror of
https://iceshrimp.dev/limepotato/jormungandr-bite.git
synced 2024-11-21 17:37:29 -07:00
Apply rate limits to proxyServer and fileServer
This resolves a DoS / DDoS / request amplification attack vector that is being actively exploited. Signed-off-by: limepotato <limepot@protonmail.ch>
This commit is contained in:
parent
ebe2ff0e21
commit
d21fb75592
2 changed files with 60 additions and 2 deletions
|
@ -14,7 +14,10 @@ import { detectType } from "@/misc/get-file-info.js";
|
||||||
import { convertToWebp } from "@/services/drive/image-processor.js";
|
import { convertToWebp } from "@/services/drive/image-processor.js";
|
||||||
import { GenerateVideoThumbnail } from "@/services/drive/generate-video-thumbnail.js";
|
import { GenerateVideoThumbnail } from "@/services/drive/generate-video-thumbnail.js";
|
||||||
import { StatusError } from "@/misc/fetch.js";
|
import { StatusError } from "@/misc/fetch.js";
|
||||||
import { FILE_TYPE_BROWSERSAFE } from "@/const.js";
|
import { FILE_TYPE_BROWSERSAFE, MINUTE } from "@/const.js";
|
||||||
|
import { IEndpointMeta } from "@/server/api/endpoints.js";
|
||||||
|
import { getIpHash } from "@/misc/get-ip-hash.js";
|
||||||
|
import { limiter } from "@/server/api/limiter.js";
|
||||||
|
|
||||||
const _filename = fileURLToPath(import.meta.url);
|
const _filename = fileURLToPath(import.meta.url);
|
||||||
const _dirname = dirname(_filename);
|
const _dirname = dirname(_filename);
|
||||||
|
@ -31,6 +34,31 @@ const commonReadableHandlerGenerator =
|
||||||
export default async function (ctx: Koa.Context) {
|
export default async function (ctx: Koa.Context) {
|
||||||
const key = ctx.params.key;
|
const key = ctx.params.key;
|
||||||
|
|
||||||
|
// koa will automatically load the `X-Forwarded-For` header if `proxy: true` is configured in the app.
|
||||||
|
let limitActor: string;
|
||||||
|
limitActor = getIpHash(ctx.ip);
|
||||||
|
|
||||||
|
const limit: IEndpointMeta["limit"] = {
|
||||||
|
key: `drive-file:${key}`,
|
||||||
|
duration: MINUTE * 10,
|
||||||
|
max: 10
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rate limit
|
||||||
|
await limiter(
|
||||||
|
limit as IEndpointMeta["limit"] & { key: NonNullable<string> },
|
||||||
|
limitActor,
|
||||||
|
).catch((e) => {
|
||||||
|
const remainingTime = e.remainingTime
|
||||||
|
? `Please try again in ${e.remainingTime}.`
|
||||||
|
: "Please try again later.";
|
||||||
|
|
||||||
|
ctx.status = 429;
|
||||||
|
ctx.body = "Rate limit exceeded. " + remainingTime;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (ctx.status == 429) return;
|
||||||
|
|
||||||
// Fetch drive file
|
// Fetch drive file
|
||||||
const file = await DriveFiles.createQueryBuilder("file")
|
const file = await DriveFiles.createQueryBuilder("file")
|
||||||
.where("file.accessKey = :accessKey", { accessKey: key })
|
.where("file.accessKey = :accessKey", { accessKey: key })
|
||||||
|
|
|
@ -9,9 +9,12 @@ import { createTemp } from "@/misc/create-temp.js";
|
||||||
import { downloadUrl } from "@/misc/download-url.js";
|
import { downloadUrl } from "@/misc/download-url.js";
|
||||||
import { detectType } from "@/misc/get-file-info.js";
|
import { detectType } from "@/misc/get-file-info.js";
|
||||||
import { StatusError } from "@/misc/fetch.js";
|
import { StatusError } from "@/misc/fetch.js";
|
||||||
import { FILE_TYPE_BROWSERSAFE } from "@/const.js";
|
import { FILE_TYPE_BROWSERSAFE, MINUTE } from "@/const.js";
|
||||||
import { serverLogger } from "../index.js";
|
import { serverLogger } from "../index.js";
|
||||||
import { isMimeImage } from "@/misc/is-mime-image.js";
|
import { isMimeImage } from "@/misc/is-mime-image.js";
|
||||||
|
import { getIpHash } from "@/misc/get-ip-hash.js";
|
||||||
|
import { limiter } from "@/server/api/limiter.js";
|
||||||
|
import { IEndpointMeta } from "@/server/api/endpoints.js";
|
||||||
|
|
||||||
export async function proxyMedia(ctx: Koa.Context) {
|
export async function proxyMedia(ctx: Koa.Context) {
|
||||||
const url = "url" in ctx.query ? ctx.query.url : `https://${ctx.params.url}`;
|
const url = "url" in ctx.query ? ctx.query.url : `https://${ctx.params.url}`;
|
||||||
|
@ -21,6 +24,33 @@ export async function proxyMedia(ctx: Koa.Context) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// koa will automatically load the `X-Forwarded-For` header if `proxy: true` is configured in the app.
|
||||||
|
let limitActor: string;
|
||||||
|
limitActor = getIpHash(ctx.ip);
|
||||||
|
|
||||||
|
const parsedUrl = new URL(url);
|
||||||
|
|
||||||
|
const limit: IEndpointMeta["limit"] = {
|
||||||
|
key: `media-proxy:${parsedUrl.host}:${parsedUrl.pathname}`,
|
||||||
|
duration: MINUTE * 10,
|
||||||
|
max: 10
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rate limit
|
||||||
|
await limiter(
|
||||||
|
limit as IEndpointMeta["limit"] & { key: NonNullable<string> },
|
||||||
|
limitActor,
|
||||||
|
).catch((e) => {
|
||||||
|
const remainingTime = e.remainingTime
|
||||||
|
? `Please try again in ${e.remainingTime}.`
|
||||||
|
: "Please try again later.";
|
||||||
|
|
||||||
|
ctx.status = 429;
|
||||||
|
ctx.body = "Rate limit exceeded. " + remainingTime;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (ctx.status == 429) return;
|
||||||
|
|
||||||
const { hostname } = new URL(url);
|
const { hostname } = new URL(url);
|
||||||
let resolvedIps;
|
let resolvedIps;
|
||||||
try {
|
try {
|
||||||
|
|
Loading…
Reference in a new issue