diff --git a/deno.json b/deno.json index 7e9bf6c4..2ab24847 100644 --- a/deno.json +++ b/deno.json @@ -44,6 +44,7 @@ "entities": "npm:entities@^4.5.0", "fast-stable-stringify": "npm:fast-stable-stringify@^1.0.0", "formdata-helper": "npm:formdata-helper@^0.3.0", + "hono-rate-limiter": "npm:hono-rate-limiter@^0.3.0", "iso-639-1": "npm:iso-639-1@2.1.15", "isomorphic-dompurify": "npm:isomorphic-dompurify@^2.11.0", "kysely": "npm:kysely@^0.27.3", diff --git a/deno.lock b/deno.lock index 67e4b7e7..b3a27ff0 100644 --- a/deno.lock +++ b/deno.lock @@ -1395,6 +1395,7 @@ "npm:entities@^4.5.0", "npm:fast-stable-stringify@^1.0.0", "npm:formdata-helper@^0.3.0", + "npm:hono-rate-limiter@^0.3.0", "npm:iso-639-1@2.1.15", "npm:isomorphic-dompurify@^2.11.0", "npm:kysely@^0.27.3", diff --git a/installation/ditto.conf b/installation/ditto.conf index 773573de..d74a8865 100644 --- a/installation/ditto.conf +++ b/installation/ditto.conf @@ -24,8 +24,10 @@ server { proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; - proxy_set_header Host $http_host; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; root /opt/ditto/public; diff --git a/src/app.ts b/src/app.ts index 912001ac..0c21ecf0 100644 --- a/src/app.ts +++ b/src/app.ts @@ -8,12 +8,14 @@ import Debug from '@soapbox/stickynotes/debug'; import { Conf } from '@/config.ts'; import { cron } from '@/cron.ts'; import { startFirehose } from '@/firehose.ts'; +import { Time } from '@/utils/time.ts'; import { accountController, accountLookupController, accountSearchController, accountStatusesController, + blockController, createAccountController, familiarFollowersController, favouritesController, @@ -22,6 +24,7 @@ import { followingController, muteController, relationshipsController, + unblockController, unfollowController, unmuteController, updateCredentialsController, @@ -110,11 +113,10 @@ import { nodeInfoController, nodeInfoSchemaController } from '@/controllers/well import { nostrController } from '@/controllers/well-known/nostr.ts'; import { auth98Middleware, requireProof, requireRole } from '@/middleware/auth98Middleware.ts'; import { cspMiddleware } from '@/middleware/cspMiddleware.ts'; +import { rateLimitMiddleware } from '@/middleware/rateLimitMiddleware.ts'; import { requireSigner } from '@/middleware/requireSigner.ts'; import { signerMiddleware } from '@/middleware/signerMiddleware.ts'; import { storeMiddleware } from '@/middleware/storeMiddleware.ts'; -import { blockController } from '@/controllers/api/accounts.ts'; -import { unblockController } from '@/controllers/api/accounts.ts'; import { uploaderMiddleware } from '@/middleware/uploaderMiddleware.ts'; interface AppEnv extends HonoEnv { @@ -145,6 +147,8 @@ if (Conf.cronEnabled) { cron(); } +app.use('*', rateLimitMiddleware(300, Time.minutes(5))); + app.use('/api/*', logger(debug)); app.use('/.well-known/*', logger(debug)); app.use('/users/*', logger(debug)); diff --git a/src/middleware/rateLimitMiddleware.ts b/src/middleware/rateLimitMiddleware.ts new file mode 100644 index 00000000..689f7cee --- /dev/null +++ b/src/middleware/rateLimitMiddleware.ts @@ -0,0 +1,15 @@ +import { MiddlewareHandler } from '@hono/hono'; +import { rateLimiter } from 'hono-rate-limiter'; + +/** + * Rate limit middleware for Hono, based on [`hono-rate-limiter`](https://github.com/rhinobase/hono-rate-limiter). + */ +export function rateLimitMiddleware(limit: number, windowMs: number): MiddlewareHandler { + // @ts-ignore Mismatched hono versions. + return rateLimiter({ + limit, + windowMs, + skip: (c) => !c.req.header('x-real-ip'), + keyGenerator: (c) => c.req.header('x-real-ip')!, + }); +}