Compare commits

..

17 commits

Author SHA1 Message Date
zima
3d8c3aa081 Merge 2024.9.2
Reviewed-on: https://codeberg.org/yeentown/barkey/pulls/14
2024-11-20 23:38:42 +00:00
zima
b47ebf162f Merge remote-tracking branch 'origin/update-to-2024.9.2' into update-to-2024.9.2 2024-11-20 15:08:13 -07:00
8ac6e62184 migration must happen after fixorm 2024-11-20 14:53:42 -07:00
0670359c4f improve search mfm 2024-11-20 14:53:42 -07:00
73ae0cf039 non-fucked migration script 2024-11-20 14:53:42 -07:00
3054cd4936 she migrate on my TypeORM till i (GUNSHOTS) 2024-11-20 14:53:42 -07:00
783cf3ed4a Change default settings
Someone needs to generate the TypeORM migrations for me because I can't
get the sodding thing to work on my end and it's driving me absolutely
potty.
2024-11-20 14:53:42 -07:00
bccd6b2dd8 Remove like button 2024-11-20 14:53:42 -07:00
f9dbe135e9 oops :) 2024-11-20 14:53:42 -07:00
390d99e531 Fix ORM models to match intended database schema 2024-11-20 14:53:42 -07:00
zima
da42cd8a4d Set NewRodin to swap display 2024-11-20 14:53:42 -07:00
196cb6bb8b Replaced NewRodin OTF with fixed TTF/WOFF2 version 2024-11-20 14:53:42 -07:00
zima
9e809aa3b6 Add font NewRodin Pro 2024-11-20 14:53:41 -07:00
Julia
27339e03c2 merge: Bump version (!756)
View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/756
2024-11-20 05:22:39 +00:00
Julia Johannesen
680c2a0718
Bump version 2024-11-20 00:09:56 -05:00
Julia
f258888408 merge: Prevent DoS from spammed media proxy requests (!754)
View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/754

Approved-by: Julia <julia@insertdomain.name>
2024-11-20 04:59:00 +00:00
Hazelnoot
d150e92f41 prevent DoS from spammed media proxy requests 2024-11-19 23:31:59 -05:00
2 changed files with 90 additions and 1 deletions

View file

@ -1,6 +1,6 @@
{ {
"name": "sharkey", "name": "sharkey",
"version": "2024.9.1", "version": "2024.9.2",
"codename": "shonk", "codename": "shonk",
"repository": { "repository": {
"type": "git", "type": "git",

View file

@ -28,7 +28,11 @@ import { bindThis } from '@/decorators.js';
import { isMimeImage } from '@/misc/is-mime-image.js'; import { isMimeImage } from '@/misc/is-mime-image.js';
import { correctFilename } from '@/misc/correct-filename.js'; import { correctFilename } from '@/misc/correct-filename.js';
import { handleRequestRedirectToOmitSearch } from '@/misc/fastify-hook-handlers.js'; import { handleRequestRedirectToOmitSearch } from '@/misc/fastify-hook-handlers.js';
import { RateLimiterService } from '@/server/api/RateLimiterService.js';
import { getIpHash } from '@/misc/get-ip-hash.js';
import { AuthenticateService } from '@/server/api/AuthenticateService.js';
import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify'; import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify';
import type Limiter from 'ratelimiter';
const _filename = fileURLToPath(import.meta.url); const _filename = fileURLToPath(import.meta.url);
const _dirname = dirname(_filename); const _dirname = dirname(_filename);
@ -52,6 +56,8 @@ export class FileServerService {
private videoProcessingService: VideoProcessingService, private videoProcessingService: VideoProcessingService,
private internalStorageService: InternalStorageService, private internalStorageService: InternalStorageService,
private loggerService: LoggerService, private loggerService: LoggerService,
private authenticateService: AuthenticateService,
private rateLimiterService: RateLimiterService,
) { ) {
this.logger = this.loggerService.getLogger('server', 'gray'); this.logger = this.loggerService.getLogger('server', 'gray');
@ -76,6 +82,8 @@ export class FileServerService {
}); });
fastify.get<{ Params: { key: string; } }>('/files/:key', async (request, reply) => { fastify.get<{ Params: { key: string; } }>('/files/:key', async (request, reply) => {
if (!await this.checkRateLimit(request, reply, `/files/${request.params.key}`)) return;
return await this.sendDriveFile(request, reply) return await this.sendDriveFile(request, reply)
.catch(err => this.errorHandler(request, reply, err)); .catch(err => this.errorHandler(request, reply, err));
}); });
@ -89,6 +97,20 @@ export class FileServerService {
Params: { url: string; }; Params: { url: string; };
Querystring: { url?: string; }; Querystring: { url?: string; };
}>('/proxy/:url*', async (request, reply) => { }>('/proxy/:url*', async (request, reply) => {
const url = 'url' in request.query ? request.query.url : 'https://' + request.params.url;
if (!url || !URL.canParse(url)) {
reply.code(400);
return;
}
const keyUrl = new URL(url);
keyUrl.searchParams.forEach(k => keyUrl.searchParams.delete(k));
keyUrl.hash = '';
keyUrl.username = '';
keyUrl.password = '';
if (!await this.checkRateLimit(request, reply, `/proxy/${keyUrl}`)) return;
return await this.proxyHandler(request, reply) return await this.proxyHandler(request, reply)
.catch(err => this.errorHandler(request, reply, err)); .catch(err => this.errorHandler(request, reply, err));
}); });
@ -572,4 +594,71 @@ export class FileServerService {
path, path,
}; };
} }
// Based on ApiCallService
private async checkRateLimit(
request: FastifyRequest<{
Body?: Record<string, unknown> | undefined,
Querystring?: Record<string, unknown> | undefined,
Params?: Record<string, unknown> | unknown,
}>,
reply: FastifyReply,
rateLimitKey: string,
): Promise<boolean> {
const body = request.method === 'GET'
? request.query
: request.body;
// https://datatracker.ietf.org/doc/html/rfc6750.html#section-2.1 (case sensitive)
const token = request.headers.authorization?.startsWith('Bearer ')
? request.headers.authorization.slice(7)
: body?.['i'];
if (token != null && typeof token !== 'string') {
reply.code(400);
return false;
}
// koa will automatically load the `X-Forwarded-For` header if `proxy: true` is configured in the app.
const [user] = await this.authenticateService.authenticate(token);
const actor = user?.id ?? getIpHash(request.ip);
const limit = {
// Group by resource
key: rateLimitKey,
// Maximum of 10 requests / 10 minutes
max: 10,
duration: 1000 * 60 * 10,
// Minimum of 250 ms between each request
minInterval: 250,
};
// Rate limit proxy requests
try {
await this.rateLimiterService.limit(limit, actor);
return true;
} catch (err) {
// errはLimiter.LimiterInfoであることが期待される
reply.code(429);
if (hasRateLimitInfo(err)) {
const cooldownInSeconds = Math.ceil((err.info.resetMs - Date.now()) / 1000);
// もしかするとマイナスになる可能性がなくはないのでマイナスだったら0にしておく
reply.header('Retry-After', Math.max(cooldownInSeconds, 0).toString(10));
}
reply.send({
message: 'Rate limit exceeded. Please try again later.',
code: 'RATE_LIMIT_EXCEEDED',
id: 'd5826d14-3982-4d2e-8011-b9e9f02499ef',
});
return false;
}
}
}
function hasRateLimitInfo(err: unknown): err is { info: Limiter.LimiterInfo } {
return err != null && typeof(err) === 'object' && 'info' in err;
} }