summaryrefslogtreecommitdiff
path: root/packages/backend/src/server/api/StreamingApiServerService.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/backend/src/server/api/StreamingApiServerService.ts')
-rw-r--r--packages/backend/src/server/api/StreamingApiServerService.ts48
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();