diff options
| author | dakkar <dakkar@thenautilus.net> | 2024-08-15 11:35:51 +0100 |
|---|---|---|
| committer | dakkar <dakkar@thenautilus.net> | 2024-08-15 11:35:51 +0100 |
| commit | 311a31da58ebfc079653f860ea4cf4ed9a051d42 (patch) | |
| tree | 2f7b2edb8b87b46782b900d0be3ce5cdd7ad93e6 /packages/backend/src/server/api/StreamingApiServerService.ts | |
| parent | merge: fix webfinger for instances without a `/host-meta` (!593) (diff) | |
| download | sharkey-311a31da58ebfc079653f860ea4cf4ed9a051d42.tar.gz sharkey-311a31da58ebfc079653f860ea4cf4ed9a051d42.tar.bz2 sharkey-311a31da58ebfc079653f860ea4cf4ed9a051d42.zip | |
rough rate limiting for websockets
Diffstat (limited to 'packages/backend/src/server/api/StreamingApiServerService.ts')
| -rw-r--r-- | packages/backend/src/server/api/StreamingApiServerService.ts | 48 |
1 files changed, 48 insertions, 0 deletions
diff --git a/packages/backend/src/server/api/StreamingApiServerService.ts b/packages/backend/src/server/api/StreamingApiServerService.ts index b8f448477b..7ac1bcf469 100644 --- a/packages/backend/src/server/api/StreamingApiServerService.ts +++ b/packages/backend/src/server/api/StreamingApiServerService.ts @@ -19,7 +19,12 @@ import { ChannelFollowingService } from '@/core/ChannelFollowingService.js'; import { AuthenticateService, AuthenticationError } from './AuthenticateService.js'; import MainStreamConnection from './stream/Connection.js'; import { ChannelsService } from './stream/ChannelsService.js'; +import { RateLimiterService } from './RateLimiterService.js'; +import { RoleService } from '@/core/RoleService.js'; +import { getIpHash } from '@/misc/get-ip-hash.js'; +import ms from 'ms'; import type * as http from 'node:http'; +import type { IEndpointMeta } from './endpoints.js'; @Injectable() export class StreamingApiServerService { @@ -41,10 +46,33 @@ export class StreamingApiServerService { private notificationService: NotificationService, private usersService: UserService, private channelFollowingService: ChannelFollowingService, + private rateLimiterService: RateLimiterService, + private roleService: RoleService, ) { } @bindThis + private async rateLimitThis( + user: MiLocalUser | null | undefined, + requestIp: string | undefined, + limit: IEndpointMeta['limit'] & { key: NonNullable<string> }, + ) : Promise<boolean> { + let limitActor: string; + if (user) { + limitActor = user.id; + } else { + limitActor = getIpHash(requestIp || 'wtf'); + } + + const factor = user ? (await this.roleService.getUserPolicies(user.id)).rateLimitFactor : 1; + + if (factor <= 0) return false; + + // Rate limit + return await this.rateLimiterService.limit(limit, limitActor, factor).then(() => { return false }).catch(err => { return true }); + } + + @bindThis public attach(server: http.Server): void { this.#wss = new WebSocket.WebSocketServer({ noServer: true, @@ -57,6 +85,17 @@ export class StreamingApiServerService { return; } + if (await this.rateLimitThis(null, request.socket.remoteAddress, { + key: 'wsconnect', + duration: ms('1min'), + max: 20, + minInterval: ms('1sec'), + })) { + socket.write('HTTP/1.1 429 Rate Limit Exceeded\r\n\r\n'); + socket.destroy(); + return; + } + const q = new URL(request.url, `http://${request.headers.host}`).searchParams; let user: MiLocalUser | null = null; @@ -94,6 +133,14 @@ export class StreamingApiServerService { return; } + const rateLimiter = () => { + return this.rateLimitThis(user, request.socket.remoteAddress, { + key: 'wsmessage', + duration: ms('1sec'), + max: 100, + }); + }; + const stream = new MainStreamConnection( this.channelsService, this.noteReadService, @@ -101,6 +148,7 @@ export class StreamingApiServerService { this.cacheService, this.channelFollowingService, user, app, + rateLimiter, ); await stream.init(); |