fix: raw body limit (#10254)
This commit is contained in:
@@ -1,8 +1,13 @@
|
|||||||
|
import { Args, Mutation, Resolver } from '@nestjs/graphql';
|
||||||
import type { TestFn } from 'ava';
|
import type { TestFn } from 'ava';
|
||||||
import ava from 'ava';
|
import ava from 'ava';
|
||||||
|
import GraphQLUpload, {
|
||||||
|
type FileUpload,
|
||||||
|
} from 'graphql-upload/GraphQLUpload.mjs';
|
||||||
import request from 'supertest';
|
import request from 'supertest';
|
||||||
|
|
||||||
import { buildAppModule } from '../../app.module';
|
import { buildAppModule } from '../../app.module';
|
||||||
|
import { Public } from '../../core/auth';
|
||||||
import { createTestingApp, TestingApp } from '../utils';
|
import { createTestingApp, TestingApp } from '../utils';
|
||||||
|
|
||||||
const gql = '/graphql';
|
const gql = '/graphql';
|
||||||
@@ -11,6 +16,26 @@ const test = ava as TestFn<{
|
|||||||
app: TestingApp;
|
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 => {
|
test.before('start app', async t => {
|
||||||
// @ts-expect-error override
|
// @ts-expect-error override
|
||||||
AFFiNE.flavor = {
|
AFFiNE.flavor = {
|
||||||
@@ -19,6 +44,7 @@ test.before('start app', async t => {
|
|||||||
} as typeof AFFiNE.flavor;
|
} as typeof AFFiNE.flavor;
|
||||||
const app = await createTestingApp({
|
const app = await createTestingApp({
|
||||||
imports: [buildAppModule()],
|
imports: [buildAppModule()],
|
||||||
|
providers: [TestResolver],
|
||||||
});
|
});
|
||||||
|
|
||||||
t.context.app = app;
|
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/,
|
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);
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { mkdirSync, writeFileSync } from 'node:fs';
|
import { mkdirSync, writeFileSync } from 'node:fs';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
|
|
||||||
|
import { Controller, Post, RawBody } from '@nestjs/common';
|
||||||
import { PrismaClient } from '@prisma/client';
|
import { PrismaClient } from '@prisma/client';
|
||||||
import type { TestFn } from 'ava';
|
import type { TestFn } from 'ava';
|
||||||
import ava from 'ava';
|
import ava from 'ava';
|
||||||
@@ -8,6 +9,7 @@ import request from 'supertest';
|
|||||||
|
|
||||||
import { buildAppModule } from '../../app.module';
|
import { buildAppModule } from '../../app.module';
|
||||||
import { Config } from '../../base';
|
import { Config } from '../../base';
|
||||||
|
import { Public } from '../../core/auth';
|
||||||
import { ServerService } from '../../core/config';
|
import { ServerService } from '../../core/config';
|
||||||
import { createTestingApp, type TestingApp } from '../utils';
|
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 => {
|
test.before('init selfhost server', async t => {
|
||||||
// @ts-expect-error override
|
// @ts-expect-error override
|
||||||
AFFiNE.isSelfhosted = true;
|
AFFiNE.isSelfhosted = true;
|
||||||
AFFiNE.flavor.renderer = true;
|
AFFiNE.flavor.renderer = true;
|
||||||
const app = await createTestingApp({
|
const app = await createTestingApp({
|
||||||
imports: [buildAppModule()],
|
imports: [buildAppModule()],
|
||||||
|
controllers: [TestResolver],
|
||||||
});
|
});
|
||||||
|
|
||||||
t.context.app = app;
|
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'));
|
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);
|
||||||
|
});
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ interface TestingAppMetadata extends ModuleMetadata {
|
|||||||
|
|
||||||
export type TestUser = Omit<User, 'password'> & { password: string };
|
export type TestUser = Omit<User, 'password'> & { password: string };
|
||||||
|
|
||||||
|
const OneMB = 1024 * 1024;
|
||||||
|
|
||||||
export async function createTestingApp(
|
export async function createTestingApp(
|
||||||
moduleDef: TestingAppMetadata = {}
|
moduleDef: TestingAppMetadata = {}
|
||||||
): Promise<TestingApp> {
|
): Promise<TestingApp> {
|
||||||
@@ -30,7 +32,7 @@ export async function createTestingApp(
|
|||||||
rawBody: true,
|
rawBody: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
app.useBodyParser('raw');
|
app.useBodyParser('raw', { limit: 1 * OneMB });
|
||||||
|
|
||||||
const logger = new AFFiNELogger();
|
const logger = new AFFiNELogger();
|
||||||
|
|
||||||
@@ -40,7 +42,7 @@ export async function createTestingApp(
|
|||||||
app.useGlobalFilters(new GlobalExceptionFilter(app.getHttpAdapter()));
|
app.useGlobalFilters(new GlobalExceptionFilter(app.getHttpAdapter()));
|
||||||
app.use(
|
app.use(
|
||||||
graphqlUploadExpress({
|
graphqlUploadExpress({
|
||||||
maxFileSize: 10 * 1024 * 1024,
|
maxFileSize: 10 * OneMB,
|
||||||
maxFiles: 5,
|
maxFiles: 5,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import { AuthGuard } from './core/auth';
|
|||||||
import { ENABLED_FEATURES } from './core/config/server-feature';
|
import { ENABLED_FEATURES } from './core/config/server-feature';
|
||||||
import { serverTimingAndCache } from './middleware/timing';
|
import { serverTimingAndCache } from './middleware/timing';
|
||||||
|
|
||||||
|
const OneMB = 1024 * 1024;
|
||||||
|
|
||||||
export async function createApp() {
|
export async function createApp() {
|
||||||
const { AppModule } = await import('./app.module');
|
const { AppModule } = await import('./app.module');
|
||||||
|
|
||||||
@@ -24,7 +26,7 @@ export async function createApp() {
|
|||||||
bufferLogs: true,
|
bufferLogs: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
app.useBodyParser('raw', { limit: '100mb' });
|
app.useBodyParser('raw', { limit: 100 * OneMB });
|
||||||
|
|
||||||
app.useLogger(app.get(AFFiNELogger));
|
app.useLogger(app.get(AFFiNELogger));
|
||||||
|
|
||||||
@@ -36,8 +38,7 @@ export async function createApp() {
|
|||||||
|
|
||||||
app.use(
|
app.use(
|
||||||
graphqlUploadExpress({
|
graphqlUploadExpress({
|
||||||
// TODO(@darkskygit): dynamic limit by quota maybe?
|
maxFileSize: 100 * OneMB,
|
||||||
maxFileSize: 100 * 1024 * 1024,
|
|
||||||
maxFiles: 32,
|
maxFiles: 32,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ test.after.always(async t => {
|
|||||||
await t.context.app.close();
|
await t.context.app.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
test.only('should render page success', async t => {
|
test('should render page success', async t => {
|
||||||
const docId = randomUUID();
|
const docId = randomUUID();
|
||||||
const { app, adapter, permission } = t.context;
|
const { app, adapter, permission } = t.context;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user