mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-01 17:50:50 +08:00
fix: enforce quota for comment attachments (#15149)
## Summary This change includes comment attachments in workspace storage usage and checks workspace storage quota before accepting a new comment attachment upload. ## Impact Comment attachments already had a per-file size limit, but they were not counted in the same workspace storage usage path as other uploaded blobs. A user with comment permission could keep adding attachments without those bytes participating in workspace storage quota calculations. ## Fix - Count comment attachment bytes in workspace storage usage reconciliation. - Check the workspace quota before storing a new comment attachment. - Return the existing comment attachment quota error when the upload would exceed limits. ## Validation - `git diff --check` - Full test/lint suite was not run locally because dependencies are not installed in this checkout. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Workspace attachment uploads now respect storage and file quota limits more accurately. * Workspace storage tracking now includes comment attachments, improving quota enforcement. * **Bug Fixes** * Attachment uploads now fail with a clear quota error when a workspace is out of space or blob capacity. * Storage usage calculations now better reflect actual workspace content, including non-deleted files. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Signed-off-by: failsafesecurity <190101117+failsafesecurity@users.noreply.github.com>
This commit is contained in:
@@ -2,6 +2,7 @@ import { Module } from '@nestjs/common';
|
|||||||
|
|
||||||
import { ServerConfigModule } from '../config';
|
import { ServerConfigModule } from '../config';
|
||||||
import { PermissionModule } from '../permission';
|
import { PermissionModule } from '../permission';
|
||||||
|
import { QuotaServiceModule } from '../quota';
|
||||||
import { StorageModule } from '../storage';
|
import { StorageModule } from '../storage';
|
||||||
import { CommentRealtimeModule } from './realtime.module';
|
import { CommentRealtimeModule } from './realtime.module';
|
||||||
import { CommentResolver } from './resolver';
|
import { CommentResolver } from './resolver';
|
||||||
@@ -9,6 +10,7 @@ import { CommentResolver } from './resolver';
|
|||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
PermissionModule,
|
PermissionModule,
|
||||||
|
QuotaServiceModule,
|
||||||
StorageModule,
|
StorageModule,
|
||||||
ServerConfigModule,
|
ServerConfigModule,
|
||||||
CommentRealtimeModule,
|
CommentRealtimeModule,
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import { Comment, DocMode, Models, Reply } from '../../models';
|
|||||||
import { CurrentUser } from '../auth/session';
|
import { CurrentUser } from '../auth/session';
|
||||||
import { ServerFeature, ServerService } from '../config';
|
import { ServerFeature, ServerService } from '../config';
|
||||||
import { DocAction, PermissionAccess } from '../permission';
|
import { DocAction, PermissionAccess } from '../permission';
|
||||||
|
import { QuotaService } from '../quota';
|
||||||
import { RealtimePublisher } from '../realtime';
|
import { RealtimePublisher } from '../realtime';
|
||||||
import { CommentAttachmentStorage } from '../storage';
|
import { CommentAttachmentStorage } from '../storage';
|
||||||
import { UserType } from '../user';
|
import { UserType } from '../user';
|
||||||
@@ -56,6 +57,7 @@ export class CommentResolver {
|
|||||||
private readonly service: CommentService,
|
private readonly service: CommentService,
|
||||||
private readonly ac: PermissionAccess,
|
private readonly ac: PermissionAccess,
|
||||||
private readonly commentAttachmentStorage: CommentAttachmentStorage,
|
private readonly commentAttachmentStorage: CommentAttachmentStorage,
|
||||||
|
private readonly quota: QuotaService,
|
||||||
private readonly queue: JobQueue,
|
private readonly queue: JobQueue,
|
||||||
private readonly models: Models,
|
private readonly models: Models,
|
||||||
private readonly server: ServerService,
|
private readonly server: ServerService,
|
||||||
@@ -354,13 +356,19 @@ export class CommentResolver {
|
|||||||
'Doc.Comments.Create'
|
'Doc.Comments.Create'
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO(@fengmk2): should check total attachment quota in the future version
|
|
||||||
const buffer = await readableToBuffer(attachment.createReadStream());
|
const buffer = await readableToBuffer(attachment.createReadStream());
|
||||||
// max attachment size is 10MB
|
// max attachment size is 10MB
|
||||||
if (buffer.length > 10 * 1024 * 1024) {
|
if (buffer.length > 10 * 1024 * 1024) {
|
||||||
throw new CommentAttachmentQuotaExceeded();
|
throw new CommentAttachmentQuotaExceeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const checkExceeded =
|
||||||
|
await this.quota.getWorkspaceQuotaCalculator(workspaceId);
|
||||||
|
const result = checkExceeded(buffer.length);
|
||||||
|
if (result?.blobQuotaExceeded || result?.storageQuotaExceeded) {
|
||||||
|
throw new CommentAttachmentQuotaExceeded();
|
||||||
|
}
|
||||||
|
|
||||||
const key = randomUUID();
|
const key = randomUUID();
|
||||||
await this.commentAttachmentStorage.put(
|
await this.commentAttachmentStorage.put(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
|
|||||||
@@ -287,17 +287,30 @@ export class QuotaStateService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async getWorkspaceStorageUsage(workspaceId: string) {
|
private async getWorkspaceStorageUsage(workspaceId: string) {
|
||||||
const sum = await this.db.blob.aggregate({
|
const [blobSum, commentAttachmentSum] = await Promise.all([
|
||||||
where: {
|
this.db.blob.aggregate({
|
||||||
workspaceId,
|
where: {
|
||||||
deletedAt: null,
|
workspaceId,
|
||||||
},
|
deletedAt: null,
|
||||||
_sum: {
|
},
|
||||||
size: true,
|
_sum: {
|
||||||
},
|
size: true,
|
||||||
});
|
},
|
||||||
|
}),
|
||||||
|
this.db.commentAttachment.aggregate({
|
||||||
|
where: {
|
||||||
|
workspaceId,
|
||||||
|
},
|
||||||
|
_sum: {
|
||||||
|
size: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
return BigInt(sum._sum.size ?? 0);
|
return (
|
||||||
|
BigInt(blobSum._sum.size ?? 0) +
|
||||||
|
BigInt(commentAttachmentSum._sum.size ?? 0)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private hasStandaloneWorkspaceQuota(plan: string) {
|
private hasStandaloneWorkspaceQuota(plan: string) {
|
||||||
|
|||||||
Reference in New Issue
Block a user