fix: raw body limit (#10254)

This commit is contained in:
darkskygit
2025-02-18 11:34:58 +00:00
parent 4c7eedb920
commit 176e0a1950
5 changed files with 86 additions and 6 deletions

View File

@@ -1,8 +1,13 @@
import { Args, Mutation, Resolver } from '@nestjs/graphql';
import type { TestFn } from 'ava';
import ava from 'ava';
import GraphQLUpload, {
type FileUpload,
} from 'graphql-upload/GraphQLUpload.mjs';
import request from 'supertest';
import { buildAppModule } from '../../app.module';
import { Public } from '../../core/auth';
import { createTestingApp, TestingApp } from '../utils';
const gql = '/graphql';
@@ -11,6 +16,26 @@ const test = ava as TestFn<{
app: TestingApp;
}>;
@Resolver(() => String)
class TestResolver {
@Public()
@Mutation(() => Number)
async upload(
@Args({ name: 'body', type: () => GraphQLUpload })
body: FileUpload
): Promise<number> {
const size = await new Promise<number>((resolve, reject) => {
const stream = body.createReadStream();
let size = 0;
stream.on('data', chunk => (size += chunk.length));
stream.on('error', reject);
stream.on('end', () => resolve(size));
});
return size;
}
}
test.before('start app', async t => {
// @ts-expect-error override
AFFiNE.flavor = {
@@ -19,6 +44,7 @@ test.before('start app', async t => {
} as typeof AFFiNE.flavor;
const app = await createTestingApp({
imports: [buildAppModule()],
providers: [TestResolver],
});
t.context.app = app;
@@ -83,3 +109,29 @@ test('should not throw internal error when graphql call with invalid params', as
message: /Failed to execute gql: query { workspace\("1"\) \}, status: 400/,
});
});
test('should can send maximum size of body', async t => {
const { app } = t.context;
const body = Buffer.from('a'.repeat(10 * 1024 * 1024 - 1));
const res = await app
.POST('/graphql')
.set({ 'x-request-id': 'test', 'x-operation-name': 'test' })
.field(
'operations',
JSON.stringify({
name: 'upload',
query: `mutation upload($body: Upload!) { upload(body: $body) }`,
variables: { body: null },
})
)
.field('map', JSON.stringify({ '0': ['variables.body'] }))
.attach(
'0',
body,
`body-${Math.random().toString(16).substring(2, 10)}.data`
)
.expect(200);
t.is(Number(res.body.data.upload), body.length);
});

View File

@@ -1,6 +1,7 @@
import { mkdirSync, writeFileSync } from 'node:fs';
import path from 'node:path';
import { Controller, Post, RawBody } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
import type { TestFn } from 'ava';
import ava from 'ava';
@@ -8,6 +9,7 @@ import request from 'supertest';
import { buildAppModule } from '../../app.module';
import { Config } from '../../base';
import { Public } from '../../core/auth';
import { ServerService } from '../../core/config';
import { createTestingApp, type TestingApp } from '../utils';
@@ -36,12 +38,22 @@ function initTestStaticFiles(staticPath: string) {
}
}
@Controller('/')
export class TestResolver {
@Public()
@Post('/upload')
async upload(@RawBody() buffer: Buffer | undefined): Promise<number> {
return buffer?.length || 0;
}
}
test.before('init selfhost server', async t => {
// @ts-expect-error override
AFFiNE.isSelfhosted = true;
AFFiNE.flavor.renderer = true;
const app = await createTestingApp({
imports: [buildAppModule()],
controllers: [TestResolver],
});
t.context.app = app;
@@ -203,3 +215,16 @@ test.skip('should return web assets if visited by mobile', async t => {
t.true(res.text.includes('AFFiNE mobile'));
});
test('should can send maximum size of body', async t => {
const { app } = t.context;
const body = 'a'.repeat(1 * 1024 * 1024);
const res = await app
.POST('/upload')
.set('Content-Type', 'application/octet-stream')
.send(body)
.expect(201);
t.is(Number(res.text), body.length);
});

View File

@@ -19,6 +19,8 @@ interface TestingAppMetadata extends ModuleMetadata {
export type TestUser = Omit<User, 'password'> & { password: string };
const OneMB = 1024 * 1024;
export async function createTestingApp(
moduleDef: TestingAppMetadata = {}
): Promise<TestingApp> {
@@ -30,7 +32,7 @@ export async function createTestingApp(
rawBody: true,
});
app.useBodyParser('raw');
app.useBodyParser('raw', { limit: 1 * OneMB });
const logger = new AFFiNELogger();
@@ -40,7 +42,7 @@ export async function createTestingApp(
app.useGlobalFilters(new GlobalExceptionFilter(app.getHttpAdapter()));
app.use(
graphqlUploadExpress({
maxFileSize: 10 * 1024 * 1024,
maxFileSize: 10 * OneMB,
maxFiles: 5,
})
);

View File

@@ -14,6 +14,8 @@ import { AuthGuard } from './core/auth';
import { ENABLED_FEATURES } from './core/config/server-feature';
import { serverTimingAndCache } from './middleware/timing';
const OneMB = 1024 * 1024;
export async function createApp() {
const { AppModule } = await import('./app.module');
@@ -24,7 +26,7 @@ export async function createApp() {
bufferLogs: true,
});
app.useBodyParser('raw', { limit: '100mb' });
app.useBodyParser('raw', { limit: 100 * OneMB });
app.useLogger(app.get(AFFiNELogger));
@@ -36,8 +38,7 @@ export async function createApp() {
app.use(
graphqlUploadExpress({
// TODO(@darkskygit): dynamic limit by quota maybe?
maxFileSize: 100 * 1024 * 1024,
maxFileSize: 100 * OneMB,
maxFiles: 32,
})
);

View File

@@ -58,7 +58,7 @@ test.after.always(async t => {
await t.context.app.close();
});
test.only('should render page success', async t => {
test('should render page success', async t => {
const docId = randomUUID();
const { app, adapter, permission } = t.context;